即时扩容,犹豫缩容的自动扩缩器 — HPA的非对称哲学

“扩容0秒,缩容5分钟。”

我们将深入探讨Kubernetes HPA为何在缩容操作中故意设置延迟。

本文涵盖内容

  • HPA为何将扩容(Scale Up)和缩容(Scale Down)设计为非对称
  • 不设置延迟时间(Stabilization Window)会导致的实际问题 — 抖动、冷启动、残留请求
  • 如何使用K8s HPA v2的behavior字段进行实际调优
  • AWS Auto Scaling Group、GCP MIG等其他云服务也遵循相同理念的原因

引言 — “流量减少了,但Pod却没有减少?”

这可能是任何首次运营HPA的人都曾经历过的场景。

当流量瞬间飙升时,HPA会像闪电般增加Pod。不到10秒,新的Pod就会启动。然而,当流量平息后,Pod却长时间不减少。”CPU使用率已经下降了,为什么这家伙还不动?” 这样的疑问自然而然地浮现出来。

直接说结论,这不是一个bug。 Kubernetes的设计者们有意地给缩容速度踩了刹车。相反,这种“缓慢缩减”是保护服务免受众多故障影响的防线。


核心概念 — 非对称扩缩容 (Asymmetric Scaling)

自动扩缩容基本遵循“扩容要快,缩容要慢”的非对称原则。这并非Kubernetes独有。AWS Auto Scaling Group的冷却时间(cooldown)、GCP MIG的稳定期(stabilization period)、Azure VMSS的冷却时间都基于相同的理念。

扩容判断与缩容判断的权重

两种判断失败的成本截然不同。

  • 扩容判断错误时: 多启动了几个Pod → 少量成本浪费
  • 缩容判断错误时: 减少Pod后流量立即涌入 → 服务故障、客户流失、SLA违规

前者是钱包受损,后者是业务崩溃。这种非对称的风险正是非对称扩缩容的依据。

Kubernetes的默认设置

  • –horizontal-pod-autoscaler-downscale-stabilization: 默认值 5分钟 (300秒)
  • Scale Up stabilization window: 默认值 0秒 (立即)

足足300倍的差距。这个数字并非随意设定,而是从无数故障案例中积累的经验值。


为何设置延迟 — 5个结构性原因

1️⃣ 防止抖动 (Flapping)

流量像波浪一样波动。服务每隔30秒就出现请求高峰和低谷的情况很常见。如果缩容是即时的,就会发生以下情况:

  1. 流量减少 → 缩减2个Pod
  2. 30秒后流量再次增加 → 扩增3个Pod
  3. 再次减少 → 缩减…

这种无限循环被称为抖动(Flapping)颠簸(Thrashing)。它不仅浪费集群资源,还会因为Pod的不断创建/删除而同时引发后续的“残留请求”问题。

2️⃣ 处理残留请求 (Graceful Shutdown)

下线Pod与删除文件不同。该Pod此刻仍在处理请求

  • 完成正在进行的HTTP请求响应
  • 等待DB事务提交
  • 接收外部API调用结果
  • 从服务端点中移除该Pod的传播延迟

Kubernetes通过terminationGracePeriodSeconds(默认30秒)来保证这段时间。但是,如果HPA每5秒就决定缩容,Pod将始终在Terminating状态中结束生命。用户将看到5xx错误。

3️⃣ 冷启动 (Cold Start) 成本

你可能会想“启动一个Pod能花多少时间?”,但现实中却相当长。

  • 镜像拉取(Pull):数秒至数十秒(取决于镜像大小)
  • 容器启动:1-5秒
  • 应用程序初始化:基于JVM的应用常见10秒以上
  • Readiness Probe通过:额外数秒
  • 总计:短则15秒,长则1分钟以上

要重新启动一个已下线的Pod,就必须支付这整个时间。在此期间,剩余的Pod能否承受住流量?冷启动期间,响应延迟很容易飙升或发生超时。

4️⃣ 保持预热状态

这是一个经常被忽视的重点。一个活着的Pod不仅仅是“内存中有一个进程”。

  • JVM JIT编译:频繁调用的方法已被编译成本地代码
  • DB连接池:已建立的TCP连接得到保障
  • OS页面缓存:频繁读取的文件已加载到内存中
  • 应用程序内部缓存:会话信息、查询结果等

下线Pod会丢失所有这些预热资产。新启动的Pod将在一段时间内以较慢的状态接收请求,这直接导致p99延迟飙升。

5️⃣ 过滤指标噪声

CPU使用率或请求数量很容易瞬间飙升。一次垃圾回收、一次批处理作业就能让数字大幅波动。

如果仅凭5秒的指标下降就立即缩容,实际上是在响应噪声。设置5分钟的观察窗口可以平均化这种瞬时波动,从而判断真实的流量下降趋势。


实际配置 — HPA v2 behavior 调优

从Kubernetes HPA v2开始,可以使用behavior字段精细调整这种非对称性。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0      # 立即扩容
      policies:
        - type: Percent
          value: 100                      # 一次最多允许增加100%
          periodSeconds: 15
    scaleDown:
      stabilizationWindowSeconds: 300    # 观察5分钟后缩容
      policies:
        - type: Percent
          value: 10                       # 一次最多减少10%
          periodSeconds: 60

我们来解读一下核心要点。

  • scaleUp.stabilizationWindowSeconds: 0 — 扩容时不犹豫
  • scaleDown.stabilizationWindowSeconds: 300 — 缩容时观察5分钟
  • scaleDown.policies.value: 10 — 一次绝不超过10%的缩减

对于流量波动大的实时服务(如流媒体、游戏、广告竞价等),将stabilizationWindowSeconds增加到10分钟以上是常见的实际选择。


⚠️ 注意事项 / 常见错误

  • 不要仅仅为了成本而缩短稳定窗口(stabilization window)。 一次故障的成本往往超过一周的实例费用。
  • 不要忘记优雅停机(Graceful Shutdown)设置。 如果terminationGracePeriodSeconds和preStop钩子不合适,无论缩容多慢,请求丢失都会发生。
  • 缩容策略基于百分比比基于Pod绝对值更安全。 当Pod数量较少时,基于百分比的策略会更保守地运行,从而保护最小可用性。
  • KEDA、Karpenter等也遵循相同理念。 这不是特定工具的特性,而是“自动扩缩容的原理”。Cluster Autoscaler在缩容时也会等待默认10分钟的非使用时间。
  • 与PodDisruptionBudget一起使用。 HPA打算缩减的Pod与PDB打算保护的Pod之间,策略必须保持一致,以避免冲突。

✅ 总结

扩容 (Scale Up) 缩容 (Scale Down)
速度 快速 (0秒) 缓慢 (300秒+)
判断标准 敏感 保守
失败代价 成本 故障
基本理念 先活下来再说 只有确定时才缩减

自动扩缩器的非对称设计不仅仅是技术选择,更是反映了失败的非对称性的工程哲学。扩容的错误可以被原谅,但缩容的错误可能会导致整个服务崩溃,这是从惨痛经验中得出的设计。

下一步,建议您了解Cluster Autoscaler / Karpenter与HPA协同工作的方式,以及如何通过PodDisruptionBudget在缩容期间保证最小可用性的技术。

“快速扩展和谨慎收缩” — 这一句话是守护云运营稳定性的无形防波堤。


Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注