K8s HPA扩缩容震荡复盘:CPU阈值配错后,服务在凌晨被扩缩了47次
K8s HPA扩缩容震荡复盘CPU阈值配错后服务在凌晨被扩缩了47次凌晨3点被告警电话叫醒打开监控面板差点没背过气去Deployment的Pod数量在10分钟里从3个飙到47个又缩回3个来回震荡了5轮。CPU使用率曲线像心电图Pod重建次数直接刷屏。事情的罪魁祸首居然是HPA里两个阈值的配法。背景一个看似正常的HPA配置我们有一个核心的订单处理服务平时3个Pod就能扛住流量。为了应对早晚高峰我给它配了HPAHorizontal Pod Autoscaler目标是在CPU使用率超过70%时自动扩容低于30%时缩容。当时的配置看起来挺合理的apiVersion:autoscaling/v2kind:HorizontalPodAutoscalermetadata:name:order-service-hpaspec:scaleTargetRef:apiVersion:apps/v1kind:Deploymentname:order-serviceminReplicas:3maxReplicas:50metrics:-type:Resourceresource:name:cputarget:type:UtilizationaverageUtilization:70behavior:scaleDown:stabilizationWindowSeconds:60policies:-type:Percentvalue:50periodSeconds:60scaleUp:stabilizationWindowSeconds:0policies:-type:Percentvalue:100periodSeconds:15问题出在哪扩容和缩容的阈值差距太大了。70%扩30%缩中间差了40个百分点。当Pod从3个扩容到6个后CPU使用率会立刻掉一半如果负载没变的话直接从70%掉到35%左右。然后HPA一看35%低于30%不还没到。但如果负载稍微波动一下掉到28%缩容触发Pod从6个缩回3个。CPU使用率又飙回70%扩容再次触发。就这样我们在凌晨3点见证了一场完美的扩缩容震荡。排查时间线20分钟定位根因3:12 告警触发Prometheus告警规则kube_deployment_status_replicas_unavailable 0连续3分钟触发。同时收到k8s_hpa_desired_replicas 20的告警。打开Grafana看到 Pod 数量曲线3:05 → 3 replicas 3:07 → 6 replicas 3:08 → 12 replicas 3:09 → 24 replicas 3:10 → 47 replicas达到maxReplicas上限 3:11 → 24 replicas 3:12 → 12 replicas ...震荡继续3:15 确认不是流量攻击先排查是不是突发流量。看 ingress-nginx 的 QPS 面板凌晨3点流量平稳比平时还低。排除流量突增导致。3:18 定位HPA行为异常执行kubectl describe hpa order-service-hpa看到事件日志刷屏Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulRescale 10s horizontal-pod-autoscaler New size: 6; reason: cpu resource utilization (percentage) above target Normal SuccessfulRescale 15s horizontal-pod-autoscaler New size: 12; reason: cpu resource utilization (percentage) above target Normal SuccessfulRescale 20s horizontal-pod-autoscaler New size: 24; reason: cpu resource utilization (percentage) above target Normal SuccessfulRescale 30s horizontal-pod-autoscaler New size: 3; reason: All metrics below target Normal SuccessfulRescale 35s horizontal-pod-autoscaler New size: 6; reason: cpu resource utilization (percentage) above targetAll metrics below target和above target交替出现间隔只有5秒。这就是典型的扩缩容震荡。3:22 检查HPA配置发现阈值问题用kubectl get hpa order-service-hpa -o yaml导出配置一眼看到问题averageUtilization:70# 扩容阈值缩容默认行为没有显式配置目标值但默认是50%不K8s HPA的缩容并没有单独的阈值。它用的是同一个目标值70%。也就是说当使用率低于70%时理论上都可能触发缩容。但HPA的缩容有冷却窗口stabilizationWindowSeconds默认是5分钟。等等我配置了scaleDown.stabilizationWindowSeconds: 60只有1分钟。这意味着扩容后只要CPU在1分钟内低于目标值就会缩容。而CPU在扩容后通常会骤降加上 Pod 启动和就绪探针的延迟实际使用率计算时会出现短暂低于目标的情况。根本原因是扩缩容阈值重叠 缩容冷却窗口太短 没有设置缩容的 targetUtilization 下限。修复方案三层加固第一层调整目标值增加缓冲区核心修复把HPA的CPU目标从70%改为85%同时引入自定义指标请求延迟作为辅助判断避免单一指标导致震荡。apiVersion:autoscaling/v2kind:HorizontalPodAutoscalermetadata:name:order-service-hpaspec:scaleTargetRef:apiVersion:apps/v1kind:Deploymentname:order-serviceminReplicas:3maxReplicas:50metrics:-type:Resourceresource:name:cputarget:type:UtilizationaverageUtilization:85# 提高扩容阈值给缓冲区-type:Podspods:metric:name:http_request_duration_seconds_p99target:type:AverageValueaverageValue:500m# P99延迟超过500ms才扩容behavior:scaleDown:stabilizationWindowSeconds:300# 缩容冷却窗口延长到5分钟policies:-type:Percentvalue:10# 每次缩容最多10%periodSeconds:60scaleUp:stabilizationWindowSeconds:0policies:-type:Percentvalue:100periodSeconds:15-type:Podsvalue:4periodSeconds:15selectPolicy:Max关键改动CPU目标从70%提到85%减少扩容的敏感度避免轻微波动就触发扩容。缩容冷却窗口从60秒延长到300秒确保扩容后的稳定期足够长让Pod充分承载负载。缩容策略改为每次最多10%避免一次性缩回太多Pod导致CPU反弹。引入P99延迟作为辅助指标CPU可能受JVM GC、定时任务等影响波动但延迟更能反映真实服务质量。第二层给Pod加上启动期和就绪期保护Pod刚启动时CPU使用率会短暂飙升JVM预热、连接池初始化这会导致HPA误判。加上startupProbe和适当的initialDelaySecondsapiVersion:apps/v1kind:Deploymentmetadata:name:order-servicespec:template:spec:containers:-name:order-serviceimage:order-service:v2.3.1resources:requests:cpu:500mmemory:1Gilimits:cpu:1000mmemory:2GistartupProbe:httpGet:path:/actuator/healthport:8080initialDelaySeconds:10periodSeconds:5failureThreshold:12# 最多60秒启动时间readinessProbe:httpGet:path:/actuator/health/readinessport:8080initialDelaySeconds:5periodSeconds:5failureThreshold:3livenessProbe:httpGet:path:/actuator/health/livenessport:8080initialDelaySeconds:30periodSeconds:10failureThreshold:3划重点HPA计算CPU使用率时只统计Ready状态的Pod。如果Pod还在Startup阶段不会被纳入计算。所以合理的启动探针能避免未就绪Pod拖累平均值的问题。第三层监控告警和应急预案加了两个Prometheus告警规则专门监控HPA震荡-alert:HPAFlappingDetectedexpr:|( count by (namespace, horizontalpodautoscaler) ( kube_horizontalpodautoscaler_status_condition{statusTrue, conditionScalingLimited} 1 ) 0 and count by (namespace, horizontalpodautoscaler) ( changes(kube_horizontalpodautoscaler_status_current_replicas[10m]) 5 ) 0 )for:2mlabels:severity:criticalannotations:summary:HPA {{ $labels.horizontalpodautoscaler }} 震荡检测description:10分钟内副本数变化超过5次疑似扩缩容震荡-alert:HPAScaleToMaxexpr:|kube_horizontalpodautoscaler_status_current_replicas / kube_horizontalpodautoscaler_spec_max_replicas 1for:5mlabels:severity:warningannotations:summary:HPA {{ $labels.horizontalpodautoscaler }} 已达到最大副本数description:当前副本数等于maxReplicas可能需要扩容上限或排查异常应急预案如果HPA震荡发生快速执行kubectl scale deployment order-service --replicas5固定副本数然后手动调整HPA配置。踩坑记录HPA的4个血泪教训1. 不要用默认的scaleDown配置K8s HPA的默认缩容冷却窗口是5分钟300秒但默认缩容策略是每分钟缩容当前副本数的100%——这意味着只要条件满足它可以在1分钟内把所有Pod都缩掉。生产环境务必显式配置behavior.scaleDown。2. CPU目标值不是越低越好很多人觉得CPU使用率超过50%就该扩容但其实要考虑服务的负载特征。如果服务有定期任务比如每5分钟跑一次统计CPU会周期性波动。目标值设太低这些正常波动也会触发扩容。对于Web服务CPU目标在70%-85%通常比较合理。3. HPA的CPU计算包含所有Ready Pod但metric-server采样有延迟metric-server默认每15秒采集一次CPU使用率而HPA每15秒评估一次。如果Pod刚启动CPU采样可能还没稳定。加上启动探针和适当的initialDelay能避免新Pod拉高平均值或新Pod还没数据被跳过的问题。4. 单一指标很容易翻车只依赖CPU的HPA在遇到GC、定时任务、批处理作业等场景时很容易误判。建议至少搭配一个业务指标比如QPS、延迟、队列深度或者自定义的external metrics。写在最后这次凌晨3点的故障表面上是HPA配置的问题本质上是对自动扩缩容的理解还不够深。K8s HPA看似简单但target utilization、stabilization window、behavior policies这几个参数组合起来能在生产环境玩出各种花样。修复后监控了一周HPA行为稳定。早高峰从3个Pod扩容到8个耗时约90秒晚高峰结束后缩容到5个Pod不是直接缩回3个然后平稳过渡到3个。再也没有出现过震荡。如果你也在用HPA建议检查一下你的缩容冷却窗口是不是太短了你的CPU目标值有没有给服务留够缓冲区有没有只靠CPU一个指标在做扩缩容决策希望这篇复盘能帮你少走点弯路。有问题欢迎评论区交流。