KEDA:事件驱动扩缩容、缩零与容量保护
促销流量结束后,队列消费者被缩到零,节点也随之回收。下一批订单进入消息系统时,监控先看到积压增长,却迟迟没有 Pod;Pod 出现后又经历镜像拉取、连接建链和缓存预热,最老消息年龄已经越过业务预算。团队把 pollingInterval 从 30 秒改成 5 秒,结果消息系统的查询请求暴涨,凭证限流与网络抖动让 scaler 间歇报错,副本数开始在 0、1 和高水位之间震荡。
另一个服务没有缩零,却同样失控。一个 Deployment 同时被手写 HPA、GitOps 的 replicas 字段和事件扩缩容器修改,三个写者互相覆盖。值班人员看到 ScaledObject 为 Ready,便以为 KEDA 已接管;实际生成的 HPA 因外部指标不可读而保留当前副本,GitOps 又把副本拉回固定值。故障并不在某个 YAML 拼写,而在控制器所有权、指标新鲜度、冷启动时延和失败策略没有被设计成一条闭环。
先理解 KEDA 接管了哪一段
KEDA 把队列长度、最老消息年龄、流式平台 lag、Prometheus 查询结果、定时窗口等外部信号转换成 Kubernetes 可消费的指标。ScaledObject 描述要扩缩哪个工作负载、多久轮询、空闲时降到多少、活动时最多到多少,以及使用哪些 trigger;KEDA 为它创建 HPA,并通过外部指标 API 提供数值。
控制过程分成两段。副本为零时,KEDA Operator 按 pollingInterval 查询事件源;trigger 活跃后,即使 minReplicaCount 为 0,也会先把目标激活到 1,若最小副本配置得更高则拉到该下限。副本在 1 到 N 之间时,HPA 按自身同步周期读取 KEDA 指标并计算期望副本。官方 ScaledObject 规范 明确区分了这两个轮询周期。因此,0→1 慢要查 Operator、事件源和冷启动,1→N 慢还要查 HPA 行为与 kube-controller-manager。
KEDA 不消费业务消息,不保证消息处理一次,也不替代队列的重试、死信、分区和顺序设计。它只根据事件事实改变 /scale 子资源。若一个消费者处理一条消息需要很久,简单用“队列长度 / 每 Pod 目标长度”计算副本,可能仍无法满足最老消息年龄 SLO。
固定发行线并安装控制组件
KEDA 2.20.1 是当前稳定修复版本,要求 Kubernetes 1.30+,CRD 仍使用 keda.sh/v1alpha1。升级自更早发行线时要先阅读 KEDA 2.20.1 发行说明 与其中链接的升级说明。验证集群应先有可用 DNS、聚合 API、出站网络和一个允许创建 CRD、webhook、ClusterRole 的安装身份。
Helm 适合团队环境,因为 values、镜像和升级记录容易进入版本库:
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm upgrade --install keda kedacore/keda \
--namespace keda --create-namespace \
--version 2.20.1 \
--wait
kubectl -n keda get deploy,pod,service
kubectl get crd | grep keda.sh
kubectl get apiservice | grep external.metrics
kubectl api-resources --api-group=keda.sh预期 Operator、Metrics API Server 和 admission webhook 工作负载可用,CRD 可发现,外部指标 APIService 为 Available。官方 KEDA 部署文档 还提供 release YAML 和 Operator Hub 入口。直接应用 YAML 时应下载固定 v2.20.1 资产、校验摘要并用 server-side apply;不要引用随时间变化的 latest URL。
本地 kind 或 minikube 适合验证 CRD、HPA 和一个无凭证 scaler,不适合证明云消息服务 IAM、私网 DNS、生产限流与冷启动已满足。托管集群应额外核对控制面能否访问 KEDA webhook、Operator 能否访问外部事件源,以及 NetworkPolicy、代理和自定义 CA 是否允许 TLS 建链。
用 Kubernetes 工作负载做最小正向实验
为了让实验可复制且不需要外部账号,可使用 kubernetes-workload scaler 观察同一 namespace 中匹配标签且未终止的 Pod。该 scaler 的 value 是“匹配 Pod 数 / 被扩缩工作负载 Pod 数”的目标关系,不是队列长度阈值;这里用一个信号 Pod 只验证 KEDA 的 CRD、Operator、外部指标和 HPA 链路,不能证明 RabbitMQ、Kafka 或云队列认证。先创建一个可缩放的 worker,再创建一个默认不存在的“信号 Pod”。
apiVersion: v1
kind: Namespace
metadata:
name: keda-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-worker
namespace: keda-lab
spec:
replicas: 0
selector:
matchLabels: {app: event-worker}
template:
metadata:
labels: {app: event-worker}
spec:
containers:
- name: worker
image: registry.k8s.io/pause:3.10
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {cpu: 50m, memory: 32Mi}
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: event-worker
namespace: keda-lab
spec:
scaleTargetRef:
name: event-worker
pollingInterval: 10
cooldownPeriod: 30
minReplicaCount: 0
maxReplicaCount: 4
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 30
triggers:
- type: kubernetes-workload
metadata:
podSelector: 'autoscaling.example.com/signal=active'
value: "1"
activationValue: "0"kubectl apply -f keda-lab.yaml
kubectl -n keda-lab get scaledobject,hpa,deploy,pod
kubectl -n keda-lab describe scaledobject event-worker预期 KEDA 创建名为 keda-hpa-event-worker 的 HPA,ScaledObject 条件中可看到 Ready;没有信号 Pod 时 Deployment 最终保持零副本。接着创建信号:
kubectl -n keda-lab run signal \
--image=registry.k8s.io/pause:3.10 \
--labels='autoscaling.example.com/signal=active'
kubectl -n keda-lab get deploy event-worker -w
kubectl -n keda-lab get hpa keda-hpa-event-worker -w
kubectl -n keda-lab get event --sort-by=.lastTimestamp | tail -n 30在轮询与调和发生后,预期 worker 从 0 增到至少 1,HPA 能显示外部指标而不是 <unknown>,Event 中出现扩容动作。这个实验只会产生一个 worker;删除信号 Pod 后,trigger 失活并经过 cooldownPeriod,KEDA 才把 1 降到 0。若目标原本高于 1,则先由 HPA 的 scaleDown 行为降到活动下限,再由 KEDA 完成最后的 1 到 0:
kubectl -n keda-lab delete pod signal
kubectl -n keda-lab get deploy event-worker -w观察时间不是固定 SLA,受 pollingInterval、HPA 同步周期、调度、镜像拉取和控制器队列影响。实验需要把相对时间点与对象变化记录下来,不能只截取最终副本数。
配置字段决定延迟、成本与抖动
scaleTargetRef 指向同 namespace 的 Deployment、StatefulSet 或提供 /scale 的自定义资源。name 必填,非 Deployment 目标还要声明 apiVersion 与 kind。KEDA、手写 HPA 和其他扩缩控制器不能同时管理同一目标;GitOps 也不应持续回写 spec.replicas,否则控制器会互相覆盖。
pollingInterval 默认 30 秒,主要决定零副本阶段的事件发现延迟。调小会更快唤醒,也会增加事件源 API、网络、认证和 KEDA CPU 成本。启用指标缓存可以减少同一窗口内重复查询,但缓存也扩大陈旧数据窗口。需要根据消息到达频率、查询价格、限流和冷启动预算计算,不应把 5 秒当成通用最优值。
initialCooldownPeriod 延迟创建后第一次进入冷却判断,cooldownPeriod 控制 KEDA 在 trigger 不活跃后从活跃副本降向零的等待。1 到 N 的缩容节奏由 HPA behavior.scaleDown 决定,因此只调 cooldownPeriod 不能解决所有震荡。
minReplicaCount 默认 0,意味着允许缩零;maxReplicaCount 默认 100,并传递到生成的 HPA。默认值不是容量承诺。最大副本数应同时受队列分区数、下游连接池、数据库 QPS、第三方限流、namespace 配额和节点供给约束。idleReplicaCount 必须小于 minReplicaCount,HPA 的限制使它通常只适合使用 0。
fallback.failureThreshold 和 fallback.replicas 定义 scaler 连续取数失败达到阈值后的保护副本。它不是对所有 scaler 都适用:CPU、内存 trigger 不支持,ScaledJob 也不使用这套 fallback。静态 fallback 过低会扩大积压,过高会压垮下游;应以事件源故障期间仍可接受的吞吐和成本推导。
advanced.restoreToOriginalReplicaCount 默认 false。删除 ScaledObject 时,目标通常保持删除瞬间的副本数;设为 true 才恢复创建 ScaledObject 时记录的副本数。退出前必须明确选择,否则“删掉 KEDA 就恢复原样”可能完全错误。
把队列事实换算成副本需求
真实队列 scaler 通常有一个目标值,例如每个 Pod 期望处理的消息数。粗略关系是 期望副本 = ceil(当前积压 / 每副本目标积压),再受最小、最大副本和 HPA behavior 约束。但它隐含每个 Pod 吞吐稳定、消息成本相近、分区可并行、下游无瓶颈等假设。
架构师应先测单 Pod 在稳态和峰值下的处理速率 r,业务允许的清空时间为 T,到达速率为 λ,积压为 B。最低处理能力要覆盖 λ + B/T,副本需求约为 ceil((λ + B/T) / r)。然后再用分区数、连接数、配额和节点容量截断。若 Kafka topic 只有 6 个可并行分区,设置 maxReplicaCount: 50 不会得到 50 份有效吞吐,反而制造空闲连接和调度压力。
队列长度也可能不是最好的信号。长短任务混合时,最老消息年龄更接近用户等待;重试洪峰可能让长度增长但有效吞吐下降;批处理窗口则可能更适合 cron 与队列信号组合。多个 trigger 默认由 HPA 选择更高的副本需求,使用 scaling modifiers 时要明确公式、单位、空值和 fallback,避免两个相关指标重复放大。
反向实验:让外部指标稳定失败
将实验 ScaledObject 的 trigger 改成指向一个不存在的 Prometheus 服务,并配置 fallback。这个反例只破坏取数链,不破坏 KEDA 自身,也不会需要真实凭证。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: event-worker
namespace: keda-lab
spec:
scaleTargetRef:
name: event-worker
pollingInterval: 10
minReplicaCount: 1
maxReplicaCount: 6
fallback:
failureThreshold: 3
replicas: 2
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.invalid.svc:9090
metricName: synthetic_backlog
query: sum(synthetic_backlog)
threshold: "10"kubectl apply -f keda-failure.yaml
kubectl -n keda-lab describe scaledobject event-worker
kubectl -n keda-lab get hpa keda-hpa-event-worker -w
kubectl -n keda logs deploy/keda-operator --since=10m
kubectl -n keda-lab get event --sort-by=.lastTimestamp | tail -n 40预期先出现 scaler 取数错误,HPA 外部指标可能暂时为 <unknown>;连续失败达到阈值后,支持该 metric type 的 fallback 应把目标引向 2 个副本。具体调和时间取决于轮询和 HPA 周期。若副本没有进入 fallback,要核对 trigger 是否支持、错误是否连续、ScaledObject condition 和 Operator 日志,不能只因为 YAML 被接受就判断保护生效。
恢复时先应用原来的 kubernetes-workload 配置,再确认错误停止、fallback condition 恢复、HPA 指标可读。不要直接删除 ScaledObject 来“修复”,因为默认会把当前副本数留给 Deployment,并丢失故障现场。
kubectl apply -f keda-lab.yaml
kubectl -n keda-lab describe scaledobject event-worker
kubectl -n keda-lab describe hpa keda-hpa-event-worker
kubectl -n keda logs deploy/keda-operator --since=5m恢复证据不是 kubectl apply 成功,而是 Prometheus 取数错误停止、ScaledObject 不再报告 fallback、生成的 HPA 重新暴露 kubernetes-workload 指标。此时保持信号 Pod 不存在,目标在冷却后回到零是原配置的预期行为。
冷启动决定缩零是否值得
缩零节省空闲 Pod 与节点成本,却把唤醒成本放到第一批事件上。总恢复时间至少包含事件发现、控制器调和、节点供给、调度、镜像拉取、容器启动、readiness、连接建链与缓存预热。pollingInterval 只覆盖第一段,不能修复慢镜像或无节点容量。
先测量从事件到 Ready 的分段时间,再决定 minReplicaCount。对交互式请求、严格延迟 SLO 或昂贵初始化服务,保留 1 个热副本通常比缩零更合理;对低频异步任务,允许几十秒启动则可以缩零。若节点也缩到零,要把节点扩容时延纳入预算,并准备适合工作负载 requests、架构和污点的节点池。
避免抖动需要同时设置 activation threshold、KEDA cooldown、HPA stabilization 与扩缩速率。激活阈值应高于事件源的噪声底,缩容窗口应长于一次正常处理波动。若每次缩容都导致连接风暴、缓存回源和重新分配分区,表面节省的 Pod 成本可能转移为下游延迟和额外流量。
认证、云权限与敏感数据
很多 scaler 需要连接串、令牌、证书或云身份。不要把密码直接写进 trigger metadata。KEDA 提供 TriggerAuthentication 复用 namespace 内身份,ClusterTriggerAuthentication 提供集群级复用;后者扩大爆炸半径,应由平台团队集中管理。官方 认证模型 支持 Secret、环境变量、工作负载身份、Vault、绑定 ServiceAccount token 等路径。
一个 namespace 内的 Secret 引用示意如下:
apiVersion: v1
kind: Secret
metadata:
name: queue-reader
namespace: keda-lab
type: Opaque
stringData:
connection: "<secret>"
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: queue-reader
namespace: keda-lab
spec:
secretTargetRef:
- parameter: connection
name: queue-reader
key: connection实际生产优先使用云工作负载身份和短期令牌,让 scaler 身份只能读取队列长度或监控指标,不能消费、删除消息或管理 topic。KEDA Operator 需要读取引用的认证对象,这意味着平台身份可能跨多个业务读取 Secret;应检查 chart 的权限模型、namespace 隔离和审计事件。
连接串、broker 地址、队列名、订阅 ID、PromQL label 和错误日志都可能泄露业务拓扑。日志采集应脱敏,不把完整 Secret、Authorization header 或签名 URL 放进工单。私网事件源还需要 DNS、NetworkPolicy、代理、CA 与 TLS SNI;connection refused、x509 和 403 分别代表不同层,不应统一归类成“指标不可用”。
从 ScaledObject 追到 HPA 和事件源
排障先判定哪一段停止。ScaledObject 不 Ready 时查看 condition、generation、trigger 参数与 admission webhook;Ready 但没有生成 HPA 时看 Operator RBAC 与 reconciliation;HPA 有 <unknown> 指标时查询 external metrics API 和 Metrics Server Adapter;指标正常但副本不变时看 HPA conditions、最大副本、behavior 和目标 /scale;Pod 已创建但不 Ready 时转向调度、镜像、Secret 和应用启动。
kubectl -n keda-lab get scaledobject event-worker -o yaml
kubectl -n keda-lab describe hpa keda-hpa-event-worker
kubectl -n keda-lab get deploy event-worker -o jsonpath='{.spec.replicas}{"\n"}'
kubectl get --raw '/apis/external.metrics.k8s.io/v1beta1' | head
kubectl -n keda logs deploy/keda-operator --since=20m
kubectl -n keda logs deploy/keda-operator-metrics-apiserver --since=20m
kubectl -n keda-lab get event --sort-by=.lastTimestamp | tail -n 60组件名可能随 chart 变化,应先列出 Deployment。Event 中的 ScaledObjectReady 只说明配置被控制器接受,不证明事件源持续可读;HPA AbleToScale=True 也不证明业务积压正在下降。端到端证据应把事件源的队列长度/消息年龄、KEDA scaler 错误、external metric、HPA desired/current replicas、Pod Ready、单 Pod 吞吐和下游错误放在同一相对时间线上。
生产告警至少覆盖:ScaledObject not ready、scaler 连续错误、fallback 激活、外部指标陈旧、HPA 达到 max 但积压继续增长、从事件到首个 Ready 超预算、缩放频率异常、消息年龄不降和目标工作负载无可用副本。指标名称以实际 2.20.1 端点为准,先抓取 /metrics 再建面板。
容量成本要算到下游
KEDA 组件自身消耗 CPU、内存和 API 请求;ScaledObject 数量、trigger 数量、轮询频率、事件源延迟和指标缓存共同决定控制面负载。大规模集群不能把数千个对象都设为短轮询而不做压测和限流。Operator 与 Metrics Server Adapter 应有资源保障、多个副本、拓扑分散和可观察的队列延迟。
工作负载成本不只由最大 Pod 数决定。每个副本会占用 requests、连接池、文件句柄、分区消费者、许可证席位和第三方 API 配额。突然从 1 扩到 100 可能把数据库打挂,即使 Kubernetes 容量充足。maxReplicaCount 应取 Kubernetes、消息系统和下游三者能承受的最小值,并为节点供给保留 headroom。
缩零节省的是空闲运行成本,增加的是冷启动延迟、镜像流量和节点抖动。fallback 则在事件源不可见时购买一份保险容量。容量评审应比较完整周期:空闲成本下降、峰值清空时间、消息年龄、失败重试、下游限流、节点扩缩次数与控制面查询费用,而不是只看副本均值。
架构选型:什么时候不用 KEDA
普通 HPA 适合 CPU、内存或已经稳定暴露为 Kubernetes 指标的连续负载;KEDA 更适合事件源原生语义、需要缩零、认证适配或大量现成 scaler 的场景。若已有成熟的自定义指标适配器和 HPA 平台,增加 KEDA 会多一层控制器与权限,应由运维复杂度和 scaler 生态收益决定。
定时且资源可预估的批任务可用 CronJob;每个事件创建独立短任务、需要并发作业语义时考虑 ScaledJob,但要处理任务幂等、历史 Job、并发与失败重试。长驻消费者更适合 ScaledObject。服务直接接收同步 HTTP 请求时,队列长度可能不存在,应采用请求并发、网关指标或保留热副本,不能为了缩零硬造一个滞后的间接信号。
KEDA 也不是调度器或节点供给器。异构 GPU、拓扑、优先级和抢占由 Pod spec 与 scheduler 决定,节点缺口由 Cluster Autoscaler 或 Karpenter 等组件处理。一个完整弹性架构要给每层唯一所有者:KEDA/HPA 改副本,VPA 或发布流程改 requests,调度器放置 Pod,节点控制器提供机器。
升级、回滚与安全退出
升级前导出 CRD、ScaledObject、ScaledJob、TriggerAuthentication、ClusterTriggerAuthentication、生成的 HPA、Helm values、RBAC 和镜像 digest。先阅读跨版本升级说明,检查被弃用 scaler、字段默认值、CRD schema、webhook 和 Kubernetes 最低版本。先升级 CRD,再滚动控制组件,并在隔离对象上验证 0→1、1→N、fallback 与认证。
回滚不能只执行 helm rollback。若新版本已经存储旧控制器无法理解的字段,必须先移除或转换对象;若 external metrics API 暂时不可用,HPA 的行为也要纳入窗口。升级期间冻结 ScaledObject 变更,保留固定 minReplicaCount 或人工副本作为保护,确认新旧 controller 不会同时调和。
退出单个对象时,先决定目标副本数由谁接管。若希望恢复创建 ScaledObject 前的副本,可事先设置并验证 restoreToOriginalReplicaCount: true;更可控的方式是暂停变更、记录当前与期望副本、删除 ScaledObject、确认生成的 HPA 消失,再由 GitOps 写入固定 replicas。
kubectl -n keda-lab get scaledobject,hpa,deploy
kubectl -n keda-lab delete scaledobject event-worker
kubectl -n keda-lab scale deploy event-worker --replicas=1
kubectl -n keda-lab delete secret queue-reader --ignore-not-found
kubectl delete namespace keda-lab
# 集群中不存在其他 KEDA 使用者时才卸载
helm uninstall keda -n keda
kubectl delete namespace kedaHelm 卸载后仍要核对 CRD、ClusterRoleBinding、APIService、webhook、Secret 和监控对象。删除 CRD 会删除所有同类自定义对象,是集群级破坏操作,必须先确认没有租户使用并备份对象。云端还要撤销工作负载身份、监控读取角色、消息服务 ACL 和临时网络入口。
用责任合同保持控制环可解释
平台团队负责 KEDA 版本、CRD、webhook、外部指标 API、集群级认证和升级;应用团队负责每事件成本、幂等、并发、冷启动与 SLO;消息平台负责积压、分区、限流和只读指标权限;容量团队负责节点 headroom 与下游保护;安全团队负责 Secret、云身份和跨 namespace 边界。
每个 ScaledObject 应进入代码评审,并记录 trigger 的业务含义、单位、查询成本、激活阈值、最大副本推导、fallback、下游上限、冷启动预算和退出动作。生产变更先在 minReplicaCount > 0 下验证 1 到 N,再演练缩零与唤醒,最后才允许节点层跟随缩零。故障关闭的标准不是副本最终回来了,而是事件源、外部指标、HPA 决策、Pod Ready、积压清空和下游健康能够互相解释。
为事件源选择可维护的实现
KEDA 2.20 部署:Helm、YAML、Operator Hub、卸载和平台入口。ScaledObject 规范:轮询、冷却、副本边界、fallback 与 HPA behavior。
KEDA 认证:Secret、TriggerAuthentication 与工作负载身份。KEDA Scalers:逐个核对事件源字段、metric type、认证和触发语义。
Kubernetes HPA:理解 1 到 N 的计算、容差、稳定窗口和失败行为。
