为何HPA在滚动时扩容出了多余的Pod?

1.背景

  在给线上环境的Pod增加HPA时,HPA通过云效的YAML模板自动部署,因此每次更新时,Pod会自动获得HPA能力。

  线上如以下配置:

  • 最小副本设置为1,最大副本为10;
  • 扩容阈值设置为CPU和内存的75%;
  • request CPU为1C、内存为1G;

  但在发布过程中,Pod 副本从 1 突然增加到 2,尽管此时 CPU 使用率不到 0.1%,内存使用率稳定在 35% 左右。我们对以下可能的原因进行了排查,均未发现异常:

1.内存指标基准值:已确认未混淆请求(Request)与限制(Limit)。

2.指标类型配置:未误用 AverageUtilization 与 AverageValue,配置正确。

3.发布时资源波动:发布过程中未出现瞬时资源峰值,未触发扩容阈值。

4.历史资源使用情况:历史监控数据显示使用率始终未超过 40%。

  综上,目前尚未发现引起异常扩容的直接原因。

 

2.问题解决

  该问题此前未被重点关注,经阿里云排查后确认,属 HPA 在缺乏监控数据时采用保守补零策略,在滚动发布过程中误判负载,从而过度扩容(多弹)

  

  解决方案说明:

  我们选择**“工作负载维度配置”**的方式来规避问题,而非调整全局参数(便于通过云效模版控制,避免影响其他服务)。

  方法一:HPARollingUpdateSkipped: "true"

  • 作用:在滚动更新期间临时暂停 HPA 判断逻辑
  • 行为:HPA 不进行任何扩缩容操作
  • 效果:等待发布完成后,HPA 再恢复正常工作,避免“多弹”现象

  方法二:HPAScaleUpDelay: "3m"

  • 作用:为新 Pod 设置预热时间
  • 行为:在前 3 分钟内,不将新 Pod 的指标纳入 HPA 判断
  • 背景问题:
    • 新 Pod 启动初期尚无可用指标
    • 控制器将其指标视为“0%”,导致平均值被拉低
    • HPA 误判负载高,从而提前扩容(多弹)

  可以两种方法可配合使用 —— 在滚动过程中暂停判断(HPARollingUpdateSkipped),并为新 Pod 设置合理的预热窗口(HPAScaleUpDelay),以避免因指标缺失导致的错误扩容。

 

3.具体原理

3.1 什么是“没有监控数据”?

  在滚动发布(rolling update) 的过程中,新版本的 Pod 会逐个替换旧版本的 Pod。而新启动的 Pod 由于刚启动不久,还没有进入就绪状态或者 Metrics Server 还未采集到它的资源使用数据(比如 CPU、内存)。这时候,这个 Pod 被认为是“没有监控数据”。

 

3.2 为什么要“补零”?

  这是 HPA 的一种保守策略。在无法获取监控数据的情况下,HPA 面临两种选择:

1.跳过这个 Pod,不参与平均值计算:这样做可能会导致误判,比如系统实际负载升高了,但因为新 Pod 没数据而被忽略。

2.假设这个 Pod 使用为 0(补零):即将这个 Pod 视为“资源完全空闲”,也就是认为它的 CPU 或内存使用率是 0%。

  Kubernetes 社区的 HPA 控制器采用的是第 2 种方式(补零),原因如下:

  • 避免错过扩容:如果真实负载很高,新 Pod 没有数据被跳过了,HPA 可能会低估整体使用率,不扩容,从而服务性能可能受到影响。
  • 安全优先:宁可多扩几个 Pod,也不要资源不够导致服务崩溃。

 

3.3 补零的副作用:为什么会“多弹”?

  这种“补零”机制虽然出于安全考虑,但也带来副作用:

  • 新 Pod 没数据 → 被当成 0% 使用率 → 拉低整个 Deployment 的平均使用率。
  • HPA 认为整体平均值 < 目标值(例如低于 75%) → 判定为“该扩容” → 扩容新 Pod。
  • 然后这些新 Pod 也因为刚启动没数据,又被补零 → 再次触发扩容……

  于是形成了“连锁反应”,导致短时间内扩容了多个 Pod,这就叫“多弹现象”。

 

  举个例子更清楚:

  假设你有一个 Deployment,当前有 4 个 Pod,CPU 利用率都在 75% 附近。现在触发滚动更新,系统逐个替换 Pod。

  滚动更新时,一些新 Pod 还没准备好,这些 Pod 会:

  • 没有 metrics;
  • 被算作 0% 使用率。

  于是:

  • 当前 Pod 数 = 4;
  • 有两个还没准备好的新 Pod;
  • 实际参与平均计算的就变成了:
(75 + 75 + 0 + 0) / 4 = 37.5%

  这比目标 75% 要低太多了,HPA 认为负载不够,就触发扩容,于是就“多弹”出了几个 Pod。

 

3.4 如何解决这个问题?

  有些平台或云厂商(比如阿里云)提供了解决方案,比如:

  • 设置 Annotation 临时暂停 HPA 判断;(本文档解决方案使用的就是这种)
  • 延迟 HPA 对新 Pod 的评估(通过设置预热时间);
  • 关闭补零机制(如果平台支持)。

  但是其他云们目前还未调研,但是搜索资料得到以下信息:

 

3.4.1 其他云(Amazon EKS / GKE / AKS)

  这些平台基本使用 Kubernetes 原生 HPA 控制器,也存在“补零”导致的多弹问题。

  解决手段一般为:

  • 使用 KEDA 替代原生 HPA,支持更细粒度的触发逻辑;
  • 搭配使用 --horizontal-pod-autoscaler-initial-readiness-delay 和 --horizontal-pod-autoscaler-cpu-initialization-period;
  • 限制 HPA 行为的频率:配置 behavior.scaleUp.stabilizationWindowSeconds 和 behavior.scaleDown.stabilizationWindowSeconds。

  但没有像阿里云那样的滚动发布期间的 Annotation 暂停能力。

  

  总结

  目前仅阿里云 ACK 提供了工作负载级别的 annotation 方式,解决滚动更新导致的“多弹”问题

  其他云厂商基本只能通过全局 controller 参数调整,修改成本高,风险较大

  若你在使用阿里云 ACK,优先推荐使用 annotation 控制策略,既灵活又安全。

 

3.4.2 KEDA(可选替代方案)

  • 事件驱动自动扩缩容框架
  • 支持更多维度的扩缩容:如 Kafka、消息队列、数据库队列长度等;
  • 更灵活,可自定义触发条件、冷却时间、缩放行为;
  • 避免了 HPA 传统 CPU/内存模型下的补零问题。

 

3.5 总结一句话

  “补零”是为了在没有监控数据时保守估算资源使用情况,确保服务不因负载过高而不可用,但这种机制也可能引起不必要的扩容(多弹),尤其是在滚动发布过程中。

 

3.6 补充参数(可无视)

 

4.参考链接

k8s工作负载伸缩FAQ及对应的解决方案_容器服务 Kubernetes 版 ACK(ACK)-阿里云帮助中心 (aliyun.com)

Pod 水平自动扩缩 | Kubernetes

posted @ 2025-04-09 21:38  小家电维修  阅读(80)  评论(2)    收藏  举报