KEDA 怎么接入 Kubernetes 实现弹性扩缩,以及和 HPA、阿里云 AHPA 的区别
在一个跑 GPU 推理的 Kubernetes 集群里,所有工作负载都是手工固定副本数:忙时副本不够、请求排队,闲时一堆 GPU 空转。GPU 又是整个集群最贵的资源,这种"按峰值常备"的浪费很扎眼。
要解决它,绕不开三个名字:原生 HPA、事件驱动的 KEDA、以及云厂商的预测式弹性(以阿里云 AHPA 为例)。这三者不是平级的竞品,而是层层叠加的关系。这篇把它们的定位、KEDA 的具体接入方法、以及"什么时候该上哪一个"讲清楚,顺带把 GPU 推理服务这种"扩容慢、资源贵"的场景该怎么配也写了。
先给结论:
- 只按 CPU/内存扩 → 原生 HPA 就够,不用引入任何东西。
- 要按队列积压、GPU 利用率、自定义指标扩,或者想缩到 0 → 上 KEDA,它架在 HPA 之上、跨云通用。
- 流量有明显日周期、且扩容本身很慢(GPU 大镜像 + 模型加载) → 在支持的云上叠加预测式弹性(AHPA),提前预热,补 KEDA"阈值触发=事后才扩"的短板。
文中所有集群名、命名空间、服务名、IP 均为占位示例(IP 统一用
192.168.1.1、地域用<REGION>),kubectl一律标准写法,替换成你自己的即可。
一、为什么固定副本不够,以及三种扩缩的定位
固定副本的问题不用多说:你只能按峰值配,峰值之外的算力全是浪费;一旦真超过峰值,又只能人工 kubectl scale。自动扩缩要解决的就是"让副本数跟着负载走"。
但"负载"怎么衡量,决定了你该用哪种方案:
| 你的扩缩信号 | 该用什么 |
|---|---|
| Pod 的 CPU / 内存利用率 | 原生 HPA(配 metrics-server) |
| 消息队列堆积、GPU 利用率、QPS、自定义业务指标 | KEDA(或 HPA + 自定义指标 adapter) |
| 想在无负载时缩到 0 副本 | KEDA(原生 HPA 做不到) |
| 负载有周期规律,且想在高峰到来前就扩好 | 预测式弹性 / AHPA(云厂商提供) |
关键认知:它们是叠加的,不是二选一。KEDA 底层仍然生成并托管一个 HPA;AHPA 也复用 external metrics 这套机制。所以问题从来不是"KEDA 还是 HPA",而是"原生 HPA 够不够,要不要再往上叠 KEDA / AHPA 这层"。
二、KEDA 是什么,和原生 HPA 什么关系
KEDA(Kubernetes-based Event Driven Autoscaling)是一个 CNCF 项目。它干两件事:
- 当作一个 Kubernetes Metrics Server:把外部事件源(队列长度、Prometheus 指标、Cron……)暴露成 Kubernetes 的 external metrics。
- 用一个叫
ScaledObject的自定义资源(CRD)声明扩缩规则,包住你的 Deployment。
它的工作方式是:
- 副本数在 1↔N 区间时,KEDA 自动创建并托管一个 HPA,通过
external.metrics.k8s.io把指标喂给它——也就是说真正调副本的还是 HPA,KEDA 只是给 HPA 喂"它自己拿不到的指标"。 - 0↔1 这一段 HPA 原生做不到(HPA 的
minReplicas最小是 1),由 KEDA 自己接管,实现 scale-to-zero。
所以一句话:KEDA 不是 HPA 的替代品,而是给 HPA 加了"50+ 种事件源"和"缩到 0"两个能力的外挂。 它无外部依赖、原生集成 HPA、云上和边缘都能跑。

上图按 ①→⑤ 的顺序:①事件/指标源(队列长度、Prometheus 里的 GPU 利用率)被 ②KEDA Operator 轮询,算出期望副本数;③通过 KEDA 自带的 metrics adapter 暴露成 external metric;④喂给 KEDA 自动托管的 HPA,由 HPA 在 1↔N 区间真正调副本;⑤目标 Deployment 扩出 GPU 副本。而 0↔1 缩到零这一段(红色虚线)是 HPA 做不到的,由 KEDA 自己接管。
KEDA vs 原生 HPA 的差异:
| 维度 | 原生 HPA | KEDA |
|---|---|---|
| 关系 | Kubernetes 内置控制器 | 底层仍用 HPA,额外管 scale-to-0 |
| 开箱指标 | 仅 CPU / 内存(metrics-server) | CPU/内存 + 50+ scaler(Prometheus、Redis、Kafka、RabbitMQ、Cron…) |
| 队列 / GPU 利用率扩缩 | 不开箱,要装 prometheus-adapter 手写指标映射 |
开箱,scaler 里直接写 PromQL / 队列名 |
| 缩到 0 | 原生不行(minReplicas≥1) | 原生支持 |
| 事件驱动 | 无,纯指标阈值 | 有激活阈值、按事件激活 |
| 配置心智 | 要懂 metrics pipeline、APIService 注册 | 一个 ScaledObject 声明式 |
三、KEDA 接入实操
3.1 部署 KEDA
先看 Kubernetes 版本。KEDA 和 k8s 是强绑定的 support matrix,装之前务必用 kubectl version 对一下 keda.sh 官方 support matrix,别无脑上最新版——新版常要求较新的 k8s,在老集群上 apiserver 会不兼容。
两种装法:
# 方式 A —— Helm(推荐)
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace --version <选定版本>
# 方式 B —— 纯 YAML manifest(机器上没 helm 时)
kubectl apply --server-side -f https://github.com/kedacore/keda/releases/download/v<版本>/keda-<版本>.yaml
用
--server-side,因为 KEDA 的 CRD 体积较大,client-side apply 会超过注解大小上限。
三个上线前要收紧的点(最小权限 + 小爆炸半径):
watchNamespace收敛到一个命名空间。KEDA 默认监听全集群;先灰度时把它限定到你的业务命名空间,只在这一个 ns 里生效,爆炸半径最小。scaledRefKinds只填你真要扩的资源种类。KEDA 装上会建 ClusterRole、拿到对工作负载scale子资源的权限。如果你只扩 Deployment,就只填Deployment,别留默认全开,更别用通配符*。要扩 StatefulSet 再加。- 镜像换成可达的源。社区 chart 默认拉
ghcr.io/kedacore/*,国内节点经常拉不动;装前把镜像参数指到你的私有 registry 或国内 mirror,否则 operator 起不来。
装完验收:
kubectl -n keda get pods # operator / metrics-apiserver / webhooks 都 Running
kubectl get crd | grep keda.sh # scaledobjects.keda.sh 等就位
kubectl get apiservice v1beta1.external.metrics.k8s.io # KEDA 的 external metrics adapter 注册成功
kubectl get clusterrole | grep keda # 顺手审一遍它拿了哪些权限,确认没有 *
KEDA 注册的是
external.metrics.k8s.io,和 metrics-server 的metrics.k8s.io(resource metrics)是两套,不冲突。装完仍建议kubectl top pods复查 metrics-server 没受影响。
3.2 普通 CPU 服务:用 CPU trigger
最简单、风险最低,先拿一个无状态 CPU 服务练手:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: my-cpu-service
namespace: demo
spec:
scaleTargetRef:
name: my-cpu-service # 默认就是 apps/v1 Deployment
minReplicaCount: 1 # CPU 类不缩到 0
maxReplicaCount: 4
pollingInterval: 30
cooldownPeriod: 300
triggers:
- type: cpu
metricType: Utilization
metadata:
value: "70" # 平均 CPU 利用率目标 70%
前提:目标容器要设了 CPU
requests(Utilization 以 request 为分母)。kubectl apply后,kubectl get hpa -n demo能看到 KEDA 自动建出来的 HPA。
3.3 GPU 推理服务:用 Prometheus(GPU 利用率)trigger
GPU 推理 worker 的特点是:CPU 几乎一直空闲(就几个毫核),真正的负载体现在 GPU 利用率或任务队列积压上。所以对 GPU 服务,用 CPU trigger 毫无意义,要用 Prometheus trigger 去查 GPU 指标。
前提:集群里有 Prometheus,并且已经在抓 DCGM exporter(NVIDIA 的 GPU 指标 exporter,暴露 DCGM_FI_DEV_GPU_UTIL 等)。很多带 GPU 的 k8s 监控栈默认就装了。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: gpu-infer
namespace: demo
spec:
scaleTargetRef:
name: gpu-infer
minReplicaCount: 1 # GPU 镜像大、模型加载慢,不缩到 0,避免冷启动长尾
maxReplicaCount: 8 # 配额护栏:最多 8 个副本 = 最多 8 张 GPU
pollingInterval: 30
cooldownPeriod: 600 # GPU 贵,缩容更稳重
fallback: # Prometheus 挂了时兜底,避免误缩
failureThreshold: 3
replicas: 2
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 600
policies:
- type: Pods
value: 1 # 每次最多缩 1 个 GPU 副本,防抖
periodSeconds: 120
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc.cluster.local:9090
# label 名(pod / exported_pod)取决于 dcgm-exporter 的 relabel 配置,
# 落地前先在 Prometheus 里跑一次确认
query: |
avg(DCGM_FI_DEV_GPU_UTIL{exported_namespace="demo", exported_pod=~"gpu-infer-.*"})
threshold: "60" # 平均 GPU 利用率目标 60%
activationThreshold: "5" # 利用率 <5% 视为"无活"
一个易踩的坑:
DCGM_FI_DEV_GPU_UTIL上的 pod 标签到底叫pod还是exported_pod,取决于 exporter 的 relabel 规则。先kubectl -n monitoring port-forward svc/prometheus 9090:9090,在 Prometheus 里把这个指标查出来看一眼真实 label 名,再写进 query,否则匹配不到任何 pod,扩缩永远不触发。
3.4 更进一步:队列长度 trigger
GPU 利用率是个滞后信号——等 GPU 都烧满了才扩,任务其实已经在排队了。如果你的 GPU worker 是"从队列取任务"的模式(典型的生产-消费),直接盯队列积压会更超前:
triggers:
- type: redis
metadata:
address: redis.demo.svc.cluster.local:6379
listName: task_queue # 你的任务队列 key
listLength: "5" # 每积压 5 条任务扩 1 个副本
# 队列有密码就配 TriggerAuthentication 引用 Secret
Kafka、RabbitMQ、RocketMQ 等也都有对应 scaler,写法类似。多个 trigger 可以挂在同一个 ScaledObject 上,KEDA 取各 trigger 期望副本的最大值——常见组合是"队列长度(超前)+ GPU 利用率(兜底)"。
整个"积压→扩容→消费→缩容"的数据流向是这样一圈:

①队列积压涨到 50 → ②KEDA 轮询发现超阈值 → ③HPA 把期望副本从 1 提到 6 → ④GPU 副本扩到 6 张开始消费 → ⑤积压被清空 → ⑥再按防抖策略一步步缩回去。GPU 利用率信号是"等烧满才扩"(滞后),队列信号是"一积压就扩"(超前),这也是为什么 GPU worker 优先用队列长度做 trigger。
3.5 GPU 场景的几条硬约束
把 KEDA 用在贵且慢的 GPU 上,有几条不能省:
- 配额护栏(最重要):KEDA 会照 trigger 一路扩到
maxReplicaCount。每个ScaledObject都要把 max 卡死;再给命名空间加ResourceQuota限制requests.nvidia.com/gpu总量,防止多个服务同时扩容把 GPU 抢爆、挤掉线上关键服务。 - 冷启动:GPU 镜像动辄几个 G、加上模型加载,新副本要几十秒到几分钟才 Ready。对策:
minReplicaCount≥1、优先用超前的队列信号、cooldownPeriod给足。 - 缩到 0 要挑服务:核心付费链路保
min≥1保延迟;低优 / 离线 / 夜间无流量的服务才用min: 0+ 队列 trigger,把 GPU 在夜间释放出来。 - 防抖:GPU 抖动代价高,用
behavior.scaleDown拉长稳定窗口、限制每周期缩的副本数。 - 灰度与回滚:先在一个命名空间挑一个服务接,观察一两天扩缩曲线再推广。回滚就
kubectl delete scaledobject——它会连带删掉自己建的 HPA,但副本数会停在当前值,记得手工kubectl scale回原固定值。所有ScaledObject进 Git 管理。
四、云厂商的增量:ack-keda 与 AHPA 预测式弹性
如果你在阿里云 ACK 上,有两层和上面相关的东西。
第一层,ack-keda。 阿里云把社区 KEDA 包装成了 ack-keda,安装入口在「集群 → 应用 → Helm 应用目录」里搜 keda,不在「组件管理」里——这点很多人会找错。它本质就是社区 KEDA,ScaledObject 写法完全通用,额外和云上的 RocketMQ / RabbitMQ 等消息产品做了堆积量集成。装的时候同样遵循最小权限:别用 apiGroups:"*" / resources:"*",scaledRefKinds 只列实际要扩的种类。
第二层,AHPA(预测式弹性),这才是云的真正增量。 前面 KEDA 也好 HPA 也好,本质都是"阈值触发"——指标越过线了才开始扩,对扩容很慢的 GPU 服务来说,等扩完高峰可能已经过去一半了。AHPA 换了个思路:
- 用
alibaba-cloud-metrics-adapter把 Prometheus 的 GPU 利用率 / 显存等指标转成 external metrics; - AHPA 组件学习历史负载(要求 Prometheus 至少积累 7 天数据),识别出周期规律,预测未来的资源需求,在高峰到来前就把副本扩好、把资源预热。
- 关键字段:
metric.resource.name: gpu、target.averageUtilization、prediction.quantile: 95(预测分位)、scaleStrategy: observer(只观察不动手,先验证准不准)/auto(真扩缩)。 - 限制:需要 ≥7 天历史数据,且只对有周期规律的负载有效,突发无规律的流量它预测不了。
所以三者的完整定位:
| 能力 | 原生 HPA | KEDA | AHPA(云) |
|---|---|---|---|
| 触发方式 | 阈值(事后) | 事件 / 阈值(事后) | 预测(事前) |
| 指标来源 | CPU/内存 | 50+ 事件源 | 历史指标 + 预测 |
| 缩到 0 | ✗ | ✓ | — |
| GPU 利用率扩缩 | 要自己装 adapter | ✓ | ✓ |
| 解决冷启动滞后 | ✗ | ✗(事后扩) | ✓(提前预热) |
| 跨云通用 | ✓ | ✓ | ✗(云专有) |
腾讯云 TKE 同样支持把 KEDA 作为插件安装,并且自带一个 custom-metrics adapter(
custom.metrics.k8s.io),用 HPA 接自定义指标比 ACK 少装一步;但预测式那一层是各云自己的产品。
落到 GPU 推理这个场景的取舍:
- 跨云通用、想立刻就能用 → KEDA(队列 + GPU 利用率多 trigger),三个云写法一致,零绑定。
- 流量有日周期、且受不了冷启动滞后 → 在 ACK 上叠 AHPA 做预测预热,补 KEDA 的事后短板。两者可以并存:KEDA 管事件/突发,AHPA 管周期预测。
快速参考
选型一句话:
- 只按 CPU/内存 → 原生 HPA。
- 要队列 / GPU 利用率 / 自定义指标 / 缩到 0 → KEDA(跨云通用)。
- 周期性负载 + 扩容慢 → 叠加 AHPA 预测预热(云专有)。
装 KEDA 的三个收紧项:
watchNamespace = 你的业务 ns # 别全集群
scaledRefKinds = Deployment # 别全开,别用 *
image.registry = 国内 mirror # 别裸拉 ghcr.io
GPU 服务 ScaledObject 的关键字段:
minReplicaCount: 1 # 不缩 0,避免冷启动长尾(低优服务才设 0)
maxReplicaCount: N # 配额护栏,配合 ResourceQuota 限 GPU 总量
cooldownPeriod: 600 # GPU 贵,缩容稳重
fallback: {...} # Prometheus 挂了不误缩
behavior.scaleDown: # 每周期最多缩 1 个,防抖
trigger: prometheus(DCGM_FI_DEV_GPU_UTIL) + redis(队列长度)取最大值
几条铁律:
- 装前用
kubectl version对 KEDA 的 k8s support matrix,别上最新版砸老集群。 - DCGM 指标的 pod label(
podvsexported_pod)先 port-forward 进 Prometheus 查准,再写 query。 - GPU 命名空间必加
ResourceQuota限 GPU 总量,防自动扩容抢爆。 - 回滚
delete scaledobject后副本停在当前值,记得手工kubectl scale回原值。 - AHPA 要 ≥7 天历史、只对周期性负载有效,先用
observer模式验证准不准再切auto。

浙公网安备 33010602011771号