bigywater.tech
Kubernetes · 56

HPA 的算法:期望副本数怎么算

HPA 到底怎么决定副本数?为什么负载一下去它不立刻缩?高一点点就不动是 bug 吗?这篇用 busybox 死循环和 Python 占空比负载在真集群里把公式、稳定窗口、容忍区和缺少 requests 的报错都实际走一遍。

ceil(1×200÷50) = 4
利用率 200%、目标 50% 时第一次扩容的副本数
89 秒
负载消失到副本数回到 1 所用时间(窗口设成 30 秒)
1.04 不动,1.4 就扩
利用率比目标高 4% 与高 40% 的不同结果

扩容:一次一个公式

实测 · 本机执行输出
  HPA app(目标利用率 50%,min 1、max 6)创建后,(当前副本→期望副本,利用率)的变化:(副本 1→0, 利用率 None%)@0s → (副本 1→4, 利用率 200%)@31s → (副本 4→4, 利用率 201%)@46s → (副本 4→6, 利用率 200%)@77s → (副本 6→6, 利用率 200%)@92s
    事件 SuccessfulRescale:New size: 4; reason: cpu resource utilization (percentage of request) above target
    事件 SuccessfulRescale:New size: 6; reason: cpu resource utilization (percentage of request) above target
  算式:第一次决定:当前 1 个副本、利用率 200%、目标 50% → ceil(1 × 200 ÷ 50) = ceil(4.00) = 4(实测期望副本 4);第二次:当前 4 个、利用率仍是 200% → ceil(4 × 200 ÷ 50) = 16,超过 maxReplicas=6,所以只到 6

HPA 每 15 秒从 metrics API 取一次 Pod 的 CPU 用量,算 desired = ceil(当前副本数 × 当前值 ÷ 目标值)。每个 Pod 总是用满 200m 的 limit、requests 是 100m,所以利用率恒为 200%、目标 50%:第一次有数据(创建后 31 秒)是 ceil(1×200÷50)=4,副本数 1→4。新副本利用率还是 200%,下一次 ceil(4×200÷50)=16,超过 maxReplicas=6,只到 6。

注意“利用率”是用量占 requests 的百分比,不是占 limits 的。

缩容:要等稳定窗口

实测 · 本机执行输出
  给所有 Pod 创建 /tmp/stop(死循环改成 sleep,CPU 降到接近 0)之后,(当前副本→期望副本,利用率)的变化:(副本 6→6, 利用率 200%)@0s → (副本 6→6, 利用率 195%)@12s → (副本 6→6, 利用率 80%)@28s → (副本 6→6, 利用率 12%)@43s → (副本 6→2, 利用率 1%)@58s → (副本 2→1, 利用率 1%)@73s → (副本 1→1, 利用率 1%)@89s
    事件 SuccessfulRescale:New size: 2; reason: All metrics below target
    事件 SuccessfulRescale:New size: 1; reason: All metrics below target
  从负载消失到缩到 1 个副本用了约 89 秒(指标采集周期 15 秒 + HPA 同步周期 15 秒 + 稳定窗口 30 秒)

给所有 Pod 创建 /tmp/stop 让负载消失,利用率 200% → 195% → 80% → 12% → 1% 逐步落下(metrics 本身有采集周期,不会瞬间反映)。副本数 6 一直不动到 58 秒才降到 2,73 秒才到 1,整个过程约 89 秒。

利用率 12% 时的期望副本数是 ceil(6×12÷50)=2,与 6→2 一致:缩容取稳定窗口(实验里设成 30 秒;默认是 300 秒)内最大的建议,窗口里还留着之前较高的建议,所以先落到 2,窗口滑过之后才到 1。扩容没有这种等待,所以扩得快、缩得慢。

容忍区

实测 · 本机执行输出
  tol-in(利用率约 52%,比目标 50% 高 4%(比值 1.04,在默认 ±10% 的容忍区内)):100 秒后 当前副本 2 期望 2 利用率 52%;SuccessfulRescale 事件:(没有)
  tol-out(利用率约 70%,比目标高 40%(比值 1.4)):100 秒后 当前副本 5 期望 5 利用率 70%;SuccessfulRescale 事件:['New size: 3; reason: cpu resource utilization (percentage of request) above target', 'New size: 5; reason: cpu resource utilization (percentage of request) above target']

两个 2 副本、requests 100m 的 Deployment,占空比负载把利用率控制在 52% 和 70%。tol-in 的比值是 1.04:按公式 ceil(2×52÷50)=3 本应扩容,100 秒里却一直是 2 个副本、没有任何 SuccessfulRescale 事件——比值落在默认 ±10% 的容忍区(0.9~1.1)内,HPA 不动作。tol-out 的比值是 1.4:先 2→3,再 ceil(3×70÷50)=5 到 5。

所以“利用率比目标高一点点却没扩”不是故障,是容忍区在避免抖动。

缺少 requests

实测 · 本机执行输出
  Deployment 没有 requests.cpu:HPA conditions:{"AbleToScale": ["True", "SucceededGetScale", "the HPA controller was able to get the target's current scale"], "ScalingActive": ["False", "FailedGetResourceMetric", "the HPA was unable to compute the replica count: failed to get cpu utilization: missing request for cpu in container c of Pod noreq-75647b8894-pjwf6"]}
  kubectl get hpa:noreq   Deployment/noreq   cpu: <unknown>/50%   1         4         1          31s

Deployment 没有 requests.cpu 时,HPA 的 ScalingActive 条件是 False,reason 是 FailedGetResourceMetric,message 是 failed to get cpu utilization: missing request for cpu in container c of Pod …;kubectl get hpa 的 TARGETS 一列显示 cpu: <unknown>/50%,副本数停在 1。

排查 HPA 不工作,先看 TARGETS 是不是 <unknown>,再看 kubectl describe hpa 里的 Conditions。

分步演示

HPA
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0),装了 metrics-server v0.7.2(采集周期 15 秒,kubelet 用 --kubelet-insecure-tls);HPA 用 autoscaling/v2,稳定窗口为缩容 30 秒(默认 300 秒);扩缩容的时间点是每 1~2 秒轮询 HPA 状态得到,相对创建 HPA/停止负载的时刻,精度约 ±15 秒;第三节的占空比负载是 Python 每 100 毫秒忙等一段时间(5.2 ms / 7 ms),利用率是 metrics 实测值(52% / 70%);每种情形各一次
    • 没有验证:自定义指标与外部指标(Pods/Object/External);内存指标;behavior 里的 scaleUp/scaleDown policies 与速率限制;scale-up 的稳定窗口;多指标取最大;新 Pod 未就绪、刚启动的 CPU 样本被忽略(initial readiness delay);容忍区的具体取值可通过 kube-controller-manager 参数调整;AverageValue 型目标不需要 requests
    京ICP备2026067502号-1京公网安备11010502063487号