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 项目。它干两件事:

  1. 当作一个 Kubernetes Metrics Server:把外部事件源(队列长度、Prometheus 指标、Cron……)暴露成 Kubernetes 的 external metrics。
  2. 用一个叫 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、云上和边缘都能跑。

KEDA 在 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 会超过注解大小上限。

三个上线前要收紧的点(最小权限 + 小爆炸半径):

  1. watchNamespace 收敛到一个命名空间。KEDA 默认监听全集群;先灰度时把它限定到你的业务命名空间,只在这一个 ns 里生效,爆炸半径最小。
  2. scaledRefKinds 只填你真要扩的资源种类。KEDA 装上会建 ClusterRole、拿到对工作负载 scale 子资源的权限。如果你只扩 Deployment,就只填 Deployment,别留默认全开,更别用通配符 *。要扩 StatefulSet 再加。
  3. 镜像换成可达的源。社区 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 利用率(兜底)"。

整个"积压→扩容→消费→缩容"的数据流向是这样一圈:

KEDA 弹性扩缩数据流向

①队列积压涨到 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: gputarget.averageUtilizationprediction.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(队列长度)取最大值

几条铁律:

  1. 装前用 kubectl version 对 KEDA 的 k8s support matrix,别上最新版砸老集群。
  2. DCGM 指标的 pod label(pod vs exported_pod)先 port-forward 进 Prometheus 查准,再写 query。
  3. GPU 命名空间必加 ResourceQuota 限 GPU 总量,防自动扩容抢爆。
  4. 回滚 delete scaledobject 后副本停在当前值,记得手工 kubectl scale 回原值。
  5. AHPA 要 ≥7 天历史、只对周期性负载有效,先用 observer 模式验证准不准再切 auto
posted @ 2026-06-26 20:45  Hello_worlds  阅读(8)  评论(0)    收藏  举报