当前位置: 首页 > news >正文

K8s HPA扩缩容震荡复盘:CPU阈值配错后,服务在凌晨被扩缩了47次

K8s HPA扩缩容震荡复盘:CPU阈值配错后,服务在凌晨被扩缩了47次

凌晨3点被告警电话叫醒,打开监控面板差点没背过气去:Deployment的Pod数量在10分钟里从3个飙到47个,又缩回3个,来回震荡了5轮。CPU使用率曲线像心电图,Pod重建次数直接刷屏。

事情的罪魁祸首,居然是HPA里两个阈值的配法。


背景:一个看似正常的HPA配置

我们有一个核心的订单处理服务,平时3个Pod就能扛住流量。为了应对早晚高峰,我给它配了HPA(Horizontal 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 target

All metrics below targetabove 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和适当的initialDelaySeconds

apiVersion:apps/v1kind:Deploymentmetadata:name:order-servicespec:template:spec:containers:-name:order-serviceimage:order-service:v2.3.1resources:requests:cpu:"500m"memory:"1Gi"limits:cpu:"1000m"memory:"2Gi"startupProbe: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{status="True", condition="ScalingLimited"} == 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 --replicas=5固定副本数,然后手动调整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 utilizationstabilization windowbehavior policies这几个参数组合起来,能在生产环境玩出各种花样。

修复后监控了一周,HPA行为稳定。早高峰从3个Pod扩容到8个,耗时约90秒;晚高峰结束后,缩容到5个Pod(不是直接缩回3个),然后平稳过渡到3个。再也没有出现过震荡。

如果你也在用HPA,建议检查一下:

  1. 你的缩容冷却窗口是不是太短了?
  2. 你的CPU目标值有没有给服务留够缓冲区?
  3. 有没有只靠CPU一个指标在做扩缩容决策?

希望这篇复盘能帮你少走点弯路。有问题欢迎评论区交流。

http://www.jsqmd.com/news/1154712/

相关文章:

  • 绍兴黄金回收实测:三大品牌门店对比与避坑指南 - 黄金回收66
  • MaxKB二次开发
  • 蓝牙5.4 LE Audio嵌入式开发实战
  • BBDown终极指南:免费下载B站视频的完整教程
  • 锂电池主动均衡方案:MP2672A与PIC18F87K22实现高效能量转移
  • QMCDecode:一键解锁QQ音乐加密文件的终极指南
  • TB67H480FNG与STM32F417ZG在工业控制中的高效组合方案
  • FModel技术解析:虚幻引擎游戏资产探索工具的价值定位与实现机制
  • 高效解决M3U8视频下载难题的智能工具:m3u8-downloader使用指南
  • L9958与MKV42F128VLH16的电机驱动方案设计与优化
  • 上海万国中国官方售后服务中心|官网认证电话及地址权威公示(2026年7月最新) - 万国中国服务中心
  • 森麦等品牌是否提供无稀释原浆感精酿啤酒
  • NS-USBloader完整指南:Switch游戏安装的一站式解决方案
  • 3CTEST大功率高压电性能测试系统:应用解析与核心技术介绍
  • 如何在浏览器中免安装使用微信?终极解决方案来了!
  • TLA2518 ADC芯片与R7FA6M3AH3CFC MCU的工业信号链设计
  • 计算机毕业设计之基于SSM医院挂号平台小程序
  • STM32F411RE与TLA2518 ADC的高精度信号采集系统设计
  • 如何用SketchUp STL插件轻松实现3D打印:终极免费解决方案
  • 仿小红书源码多图短视频上传中的资源表设计与清理机制
  • 基于MAX77654与STM32的低功耗物联网电源管理方案
  • 如何快速搭建个人游戏库:FitGirl游戏启动器完整指南
  • 5步终极指南:用免费开源工具让普通鼠标在macOS上超越苹果触控板!
  • NAU8224 Class-D音频放大器与R7FA4M3 MCU的高效音频系统设计
  • 基于MA12070与STM32F413RH的高保真音频系统设计
  • 清华PPT模板终极指南:2025年学术演示的完整解决方案
  • rpmdepsearch配置详解:10个关键步骤搭建高效依赖查询环境
  • 从ChatGPT到AI Agent:4周内掌握智能体核心技术,成为2026年爆款人才!
  • Claude code使用笔记
  • 纽扣电池增强方案NBM5100A与PIC18F45K22协同设计