HorizontalPodAutoscaler 把配置的指标转换为副本建议,因此选择指标与聚合口径本身就是应用设计的一部分。增加副本应该能够降低所测压力,或提升真正有用的吞吐量,否则控制器可能只增加费用,却无法缓解瓶颈。应设置明确的最小与最大副本数,但成本上限不等于性能保障,更不代表到达上限之后仍能处理任何规模的请求。

模型从四个副本开始,取 max(7,12)=12。队列来源失败时,CPU 建议仍可扩到七个,但不能缩到四个以下。CPU 利用率以 requests 为分母;每副本平均积压不等于队列总积压。
展示原文算术模型,不是实时观测。历史 Python 模型通过范围不变。模型未包含真实控制器的容差、就绪、稳定窗口与速率策略;不证明集群行为或服务可用。

区分测量与控制

HPA 通常周期性协调,控制器默认同步间隔为十五秒。对于按 Pod 平均的目标,基本计算是当前副本数乘以当前指标与目标指标的比值,再向上取整。CPU 利用率相对于资源 requests 计算,不是相对于 limits,也不是整台节点的总容量。把分母理解错,会让看起来合理的百分比对应完全不同的扩容时机。

跨 Pod 求平均,与对时间序列做移动平均是两回事。Metrics Server 的 CPU 用量来自一个测量窗口,自定义适配器则有自己的查询与取样语义。HPA 并不会统一替每种输入做一个可配置时间窗口的平滑。容差、就绪处理、稳定窗口与速率策略属于控制层,是围绕指标建议的不同机制,不能全部称作“先平滑再决策”。

先确定单位,再讨论目标数字。按 Pod 的自定义指标使用 AverageValue;对象和外部指标可使用 Value 或 AverageValue,扩缩意义不同。整个队列的总积压与每副本平均积压不是可以随意互换的输入。资源利用率还要求提供相应资源请求。即使接口返回正常数字,如果它衡量的不是实际瓶颈,控制器依旧可能做出没有业务价值的动作。

选择增加副本能够改善的信号

CPU 常适合计算密集型服务,但必须先有可用的资源指标接口,并非所有集群天然具备。工作者等待数据库时,可能 CPU 很低、延迟很高;增加更多工作者反而加重数据库压力。依赖自动扩缩之前,应验证目标值是否对应每副本能够持续承担的真实工作,而不是只确认图表会随着请求变化。

请求速率、在途工作与消费积压都需要明确范围。按 Pod 的指标应保留必要的逐 Pod 观测,不能一概预聚合成每工作负载一条序列;对象或外部指标则要聚合到指定工作负载或队列,排除无关流量。避免不必要的用户级、请求级标签,防止查询成本膨胀和选择条件变得难以解释。聚合不是越少越好,而是必须与指标类型的契约一致。

副本字段要有明确负责人

HPA 会读取目标的 scale 信息,并修改副本数。Deployment 初始 replicas 不是 HPA 下限,真正配置的地板是 minReplicas。若部署流程反复提交一个固定副本数,便可能与 HPA 争抢同一字段。应明确配置所有权,并提前安排取消自动扩缩之后使用什么静态规模;不要把最初的副本值说成控制器永远不读取、也完全无关的数字。

坏掉的样子

自定义指标不可用时,不会自动转去读取 CPU。配置多个指标后,HPA 选择可用建议中的最大值;若某一路失败,而其他可用建议要求缩容,则跳过这次缩容。若有效指标要求比当前更多的副本,仍可能扩容。因此显式加入 CPU 是增加一条输入,并不是在两个方向上都无条件接管的故障切换机制。

还要区分整个指标来源失败,与部分 Pod 缺失测量。后者也会在资源算法里触发保守调整,但不是同一种问题。应检查 conditions 与 events,不要断言任何单个样本缺失都会让 ScalingActive=False。有效读数、失败来源与副本上下限可能同时存在,单看某个显示为未知的数值无法还原整个决策过程。

适配器数据过旧,会让控制器在队列已清空之后才扩容。调目标值前,先检查时间戳、查询范围、缓存与采集延迟。接口报错与“读数有效但选错了指标”也不同:把内存当作不合适的延迟代理,不会仅因为相关性差就把读数变成 Unknown。先判断是取不到数据,还是数据根本无法指导有效动作。

快速验证

保存为 hpa_metric_sim.py,执行 python3 hpa_metric_sim.py。这个标准库模型使用已经计算好的每副本平均值,初始副本位于上下限内,展示多指标取最大值和来源失败时的缩容保护。它并没有实现每一种 HPA 指标类型,更没有访问真实集群。

import math
import sys


def desired_for(current, value, target):
    assert current > 0 and target > 0
    if value is None:
        return None
    assert value >= 0
    return math.ceil(current * value / target)


def decide(current, metrics, min_replicas, max_replicas):
    assert 0 < min_replicas <= current <= max_replicas
    recommendations = [desired_for(current, value, target)
                       for _, value, target in metrics]
    available = [want for want in recommendations if want is not None]
    if not available:
        return current
    want = max(available)
    if None in recommendations and want < current:
        return current
    return max(min_replicas, min(max_replicas, want))


def check(actual, expected, message):
    assert actual == expected, (message, actual, expected)
    print(f"OK: {message} -> {actual}")


def main():
    current = 4
    cpu = ("cpu", 80, 50)
    queue = ("average_queue", 900, 300)
    missing_queue = ("average_queue", None, 300)
    cool_cpu = ("cpu", 10, 50)
    check(decide(current, [cpu, queue], 1, 20), 12, "maximum wins")
    check(decide(current, [cpu, missing_queue], 1, 20), 7, "missing allows scale-up")
    check(decide(current, [cool_cpu, missing_queue], 1, 20), 4, "missing blocks scale-down")
    check(decide(current, [("cpu", None, 50), missing_queue], 1, 20), 4, "all missing holds")
    check(decide(current, [cpu, queue], 1, 10), 10, "upper bound")
    check(decide(current, [cool_cpu, ("average_queue", 30, 300)], 1, 20), 1, "valid low metrics shrink")
    check(decide(current, [cool_cpu], 2, 20), 2, "lower bound")
    print("PASS: 7 model scenarios; no Kubernetes controller was exercised")
    print(sys.version.split()[0])


if __name__ == "__main__":
    main()

七条断言分别检查热指标提出十二与七副本、失败来源伴随缩容建议时保持四个、全部来源失败时也保持四个,以及两个边界和完整有效的缩容到一个。两种语言中的代码副本都在本轮通过 Python 3.12.14 本地运行。相同代码确保公式和保护条件一致,输出中的数字可以逐项与正文解释对应。

实验没有运行集群控制器、指标适配器、容差、就绪调整、稳定窗口或扩缩速率策略。学习目标是代码中明确写出的失败来源保护条件。要证明生产自动扩缩可用,仍需另行核对真实时间戳、状态条件和伸缩事件;算术断言成功不能替代这条端到端证据。

延伸阅读

来源