Kubernetes弹性伸缩体系:HPA、VPA、KEDA与节点扩容实战
弹性伸缩不是“CPU到80%就加Pod”。真正的生产体系需要把业务指标、Pod副本、资源请求、节点容量、启动时间和下游承载能力连接起来。HPA负责横向副本,VPA负责资源规格,KEDA把事件源接入伸缩链路,节点伸缩器为待调度Pod补充底层容量;其中任何一层设计错误,都可能出现抖动、排队、资源浪费或雪崩。
本文面向使用 Kubernetes 承载在线服务、异步消费者或周期性任务的团队。示例参数必须结合真实负载测试调整,不应直接复制到生产。
一、先画清四层控制回路
| 层级 | 观察对象 | 调整对象 | 典型实现 |
|---|---|---|---|
| 业务弹性 | QPS、并发、队列长度、延迟 | Pod副本 | HPA、KEDA |
| 资源校准 | CPU、内存、OOM和历史用量 | requests/limits | VPA |
| 集群容量 | Pending Pod、调度约束 | 节点数量和规格 | Cluster Autoscaler、Karpenter或云实现 |
| 业务保护 | 错误率、下游容量、预算 | 上下界、限流和停止条件 | 发布平台、网关、应用策略 |
伸缩链路通常是:负载上升→指标变化→增加Pod→Pod进入Pending→节点扩容→镜像拉取→Pod就绪。最终生效时间可能是几十秒到数分钟,所以只看控制器轮询周期会严重低估响应时间。
二、伸缩前先完成资源基线
HPA按CPU利用率伸缩时,利用率是“实际使用量/requests”。若没有 requests,相关Pod可能无法参与计算;requests严重偏低会过度扩容,偏高则可能迟迟不扩容。先采集至少一个完整业务周期的CPU、工作集内存、GC、吞吐和延迟,再给出初始值。
建议区分:
- 可压缩资源CPU:过载通常表现为节流和延迟;
- 不可压缩资源内存:超限通常导致OOMKill;
- 启动资源峰值:JVM预热、模型加载、缓存恢复;
- 单Pod安全吞吐:压测得到的持续承载量,而非瞬时峰值。
弹性不能替代容量底线。关键服务的 minReplicas 至少覆盖一次故障域失效、滚动发布和PDB约束。
三、HPA v2的基本配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: checkout
namespace: shop
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout
minReplicas: 4
maxReplicas: 40
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 60
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
maxReplicas 应由下游容量、节点上限和成本预算共同决定。扩容可相对积极,缩容通常需要稳定窗口,防止短暂低谷后立刻删除刚预热完成的实例。
四、选择能代表瓶颈的指标
CPU只适合CPU与吞吐近似线性的服务。以下场景应考虑自定义或外部指标:
- Web服务:每Pod并发、请求速率、排队长度或高分位延迟;
- 消费者:待处理消息数、最老消息年龄、Kafka lag;
- 批处理:剩余任务数和任务截止时间;
- 连接型服务:活跃连接数和连接池占用;
- GPU工作负载:队列深度、显存与批次等待时间。
高分位延迟可能在下游故障时升高,此时盲目扩容会放大压力。指标必须配合错误率、限流和熔断。若配置多个指标,HPA通常选择建议副本数最大的结果,应确认不会由噪声指标长期顶到上限。
五、控制抖动和指标缺失
常见抖动来源包括指标延迟、冷启动、突发流量、Pod尚未Ready和缩容过快。治理方法:
- 为启动探针和就绪探针设置真实预热边界;
- 让扩容指标领先于SLO恶化,例如队列长度早于超时;
- 为缩容设置更长稳定窗口和较小比例;
- 配置合理的容忍度,不追逐细小波动;
- 指标系统不可用时保留安全副本,不把“无数据”解释为零;
- 记录期望副本、当前副本、指标时间戳和伸缩原因。
不要让发布系统持续写 spec.replicas 与HPA争夺字段所有权。使用GitOps时,应按工具建议忽略或转移该字段的管理权。
六、VPA先从推荐模式开始
VPA由推荐器、更新器和准入组件协作。生产接入第一阶段宜使用 Off,只读取建议:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: checkout-vpa
namespace: shop
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: app
minAllowed:
cpu: 200m
memory: 256Mi
maxAllowed:
cpu: "4"
memory: 4Gi
controlledValues: RequestsOnly
至少覆盖工作日、周末、批处理和发布波峰,再决定是否自动应用。自动模式可能驱逐Pod或尝试原地调整;具体能力与Kubernetes、VPA版本和特性开关相关。关键业务要验证PDB、最小副本和单故障域容量。
七、HPA与VPA的组合边界
若HPA按CPU利用率伸缩,而VPA同时修改CPU requests,两者会相互影响。常见安全组合:
- HPA使用业务或外部指标,VPA管理CPU和内存requests;
- HPA使用CPU,VPA仅管理内存;
- VPA只输出建议,由容量评审或流水线定期调整;
- 对不可水平扩展的单实例服务使用VPA,但保留维护窗口和回退规格。
不要让VPA自动修改DaemonSet资源后又期待节点伸缩器准确预测新节点开销。系统组件应单独评估。
八、用KEDA连接事件源并缩到零
KEDA通常负责从0到1的激活,并创建或管理HPA完成1到N的伸缩。下面按Kafka积压伸缩消费者:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-consumer
namespace: shop
spec:
scaleTargetRef:
name: order-consumer
pollingInterval: 15
cooldownPeriod: 300
minReplicaCount: 1
maxReplicaCount: 30
fallback:
failureThreshold: 3
replicas: 4
triggers:
- type: kafka
metadata:
bootstrapServers: kafka.messaging.svc:9092
consumerGroup: order-worker
topic: orders
lagThreshold: "200"
activationLagThreshold: "10"
凭据应通过认证引用或工作负载身份提供,不写进清单。并非所有服务都适合缩到零:需要常驻连接、低延迟、较长启动时间或无法可靠唤醒的服务,应保留最小副本。
九、队列消费者的副本上限不是越大越好
Kafka同一消费组的有效并行度受分区数限制;20个分区配置100个消费者,额外副本可能处于空闲。RabbitMQ还受consumer prefetch、单消息处理时间和下游写入能力影响。
可用近似式估算所需并发:
所需消费者数 ≈ 峰值到达速率 × 单消息处理秒数 ÷ 单消费者并行度
再通过压测修正。伸缩指标应同时考虑总积压与最老消息年龄,避免低速大消息或毒消息被平均值掩盖。若下游数据库最多承受200个连接,就应通过连接池和最大副本限制把并发控制在安全范围。
十、节点伸缩要接住Pending Pod
节点伸缩器主要根据不可调度Pod的requests和调度约束决策,并不直接根据Pod真实使用量扩容。以下配置会影响结果:
- nodeSelector、亲和/反亲和、拓扑分布;
- taint与toleration;
- 本地盘、GPU、架构和可用区要求;
- DaemonSet预留和系统保留;
- 节点组上限、云配额、库存和启动时间。
必须监控从“Pod Pending”到“节点Ready”和“应用Ready”的总时长。对突发业务可保留缓冲节点、占位Pod或定时预扩容;仅依赖临时创建节点往往赶不上秒级流量峰值。
十一、冷启动与就绪控制
容器启动完成不等于可承载流量。应拆分:
- startupProbe:允许慢启动,但限定最大时间;
- readinessProbe:缓存、依赖和监听端口均准备好才接流量;
- livenessProbe:只判断是否需要重启,避免依赖抖动导致连环重启;
- preStop和优雅终止:缩容时停止接新请求并完成在途任务。
镜像预热、JIT、模型加载和连接池建立都应计入伸缩延迟。通过分阶段压测确认新增副本何时真正降低队列或延迟。
十二、容量保护与失败回退
为每个伸缩对象定义:
- 最小副本:满足高可用和常态负载;
- 最大副本:受下游、安全预算和节点容量限制;
- 指标失效回退副本;
- 伸缩冻结开关和人工固定方法;
- 扩容未改善时的限流、降级和熔断;
- 节点无法交付时的告警和业务优先级。
当延迟持续升高而副本已达上限,应判断是容量不足、下游瓶颈、锁竞争还是单Pod效率下降。继续提高上限可能只会加速故障。
十三、观测与告警
至少建立以下面板:
- 当前、期望、最小和最大副本;
- 原始指标值、目标值、采集时间和缺失状态;
- HPA/KEDA条件、事件和失败次数;
- Pending Pod及调度失败原因;
- 节点申请、注册、Ready和整合时长;
- 新Pod启动、就绪、首个请求和稳定吞吐时间;
- 扩容前后SLO、错误率、队列年龄和下游负载;
- VPA建议与当前requests的偏差。
只在“副本变化”时告警会产生噪声。更有价值的是:长期顶到上限、指标不可用、持续Pending、扩容后SLO未恢复和频繁反向伸缩。
十四、故障排查顺序
kubectl describe hpa -n shop checkout
kubectl get hpa -n shop checkout -o yaml
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes
kubectl get pods -n shop -l app=checkout -o wide
kubectl get events -n shop --sort-by=.lastTimestamp
依次确认:指标是否新鲜→目标对象和selector是否正确→requests是否存在→是否已到上下限→behavior是否限制变化→Pod是否Pending→节点伸缩是否受配额或调度约束阻挡→新增Pod是否真正Ready。
十五、上线演练
- 用历史流量回放验证指标与负载相关;
- 阶梯加压,记录每次扩容和SLO恢复时间;
- 模拟指标系统中断,确认安全回退;
- 模拟节点库存不足和镜像拉取慢;
- 注入下游限速,验证不会无界扩容;
- 执行缩容,确认连接排空和任务不丢失;
- 回滚HPA/KEDA/VPA配置并验证固定副本模式。
十六、生产检查清单
- [ ] requests来自真实观测和压测,而非随意填写;
- [ ] 指标代表瓶颈并有缺失处理;
- [ ] 最小副本覆盖发布、故障域和PDB;
- [ ] 最大副本受下游容量、分区数和成本约束;
- [ ] 扩容、节点交付、启动和就绪总时长已测量;
- [ ] HPA与VPA不争夺同一控制信号;
- [ ] KEDA认证信息未明文写入配置;
- [ ] 缩容具备稳定窗口和优雅终止;
- [ ] 指标故障、节点不足和下游故障均有回退;
- [ ] 每季度用真实流量模型复验参数。
十七、官方参考
- Kubernetes Horizontal Pod Autoscaling
- Kubernetes Vertical Pod Autoscaling
- Kubernetes Node Autoscaling
- KEDA Scaling Deployments, StatefulSets and Custom Resources
- KEDA Scalers
成熟的弹性体系不追求副本变化得最频繁,而是用最少的控制动作在目标时间内恢复SLO,并且不突破下游容量与成本边界。