HPA 已经要求 4 个副本,为什么只有 1 个可用?
Last modified:
kubectl get hpa 已经显示 DESIRED=4,业务监控却只有一个实例能接收流量。是 HPA 失效了吗?
不一定。在一次慢启动实验中,HPA 很快写入 4,新增的 3 个 Pod 也进入 Running,但约一分钟后才全部 Ready。
HPA 已经要求 4 个副本,为什么仍可能只有 1 个可用?
本文沿着控制链路逐层回答。它是系列第四篇,默认已经理解指标如何变成期望副本。
用一条链路代替猜测
|
|
排障时依次记录:
|
|
自定义指标还要分别查询 Prometheus 与指标 API,避免把「没采到」「没映射」「HPA 没采用」混成一个问题。
第一步:确认原始建议不是碰巧撞到上限
队列每副本目标为 10,依次把总量改成 15、25、35,再降到 25、15、5:
| 队列总量 | ceil(Q/10) |
Ready 副本 |
|---|---|---|
| 15 | 2 | 2 |
| 25 | 3 | 3 |
| 35 | 4 | 4 |
| 25 | 3 | 3 |
| 15 | 2 | 2 |
| 5 | 1 | 1 |
完整走过 1 → 2 → 3 → 4 → 3 → 2 → 1,说明中间副本建议也符合公式;只验证 1 → 4 → 1,可能只是每次都撞到 maxReplicas。
重跑中 Q=35、4 个副本时,HPA 的 current.averageValue 是 8750m,即 35 / 4 = 8.75:
|
|
第二步:看建议是否被稳定窗口保留
实验将缩容稳定窗口改为 60 秒:先用 Q=35 保持 4 个副本,把队列降到 0 约 25 秒,再恢复到 35。整个低值脉冲期间,期望副本始终是 4;最后持续置零才缩回 1。
|
|
稳定窗口保存的是副本建议,不是指标平均值,也不是每次指标变化后重启的定时器。实现见 Kubernetes v1.35.0 的 stabilizeRecommendationWithBehaviors。
第三步:看变化速度是否被限制
下面的策略关闭稳定等待,只观察每 30 秒最多改变一个 Pod:
|
|
与 Percent: 100 对照,并在方向切换前等待上一轮历史离开窗口:
| 策略 | 扩容序列 | 缩容序列 |
|---|---|---|
Pods: 1 / 30s |
1 → 2 → 3 → 4 |
4 → 3 → 2 → 1 |
Percent: 100 / 30s |
1 → 2 → 4 |
4 → 1 |
periodSeconds 是统计近期副本变化的窗口,不是控制器严格每 30 秒执行一次。方向切换时,窗口内的历史变化仍会进入基数计算;第一次未隔离历史的探索结果因此没有用作上表结论。原始探索记录仍保存在 evidence 目录。
第四步:多指标不是投票
CPU 与队列可以放进同一个 HPA。控制器分别计算副本建议,再选择较大的值,不是取平均,也不要求两项都超过阈值。
| CPU / 队列状态 | 本次结果 |
|---|---|
| CPU 约 1%,Q=35 | 队列建议占优,保持 4 |
| CPU 约 1%,队列指标失败 | 拒绝按低 CPU 缩容,保持 4 |
| CPU 高负载,队列指标失败 | 有效 CPU 建议触发 1 → 4 |
| CPU 高负载,Q=0 | CPU 独立触发 1 → 4 |
部分指标失败时,HPA 可以使用仍然有效的扩容建议,但不会仅凭其他指标的低值冒险缩容。因此 ScalingActive=True 也不等于每一条指标链路都健康;要同时查看 status.currentMetrics 和事件。
第五步:指标在哪一层消失
实验在 Q=35、4 个副本时分别暂停 exporter、Prometheus 和 Adapter:
| 暂停组件 | Prometheus 查询 | custom metrics API | worker |
|---|---|---|---|
| queue exporter | 先读到旧值,后为空向量 | 先返回 35,后 NotFound |
4 |
| Prometheus | 无可用 endpoint | InternalError |
4 |
| Adapter | 仍返回 35 | ServiceUnavailable |
4 |
对应的关键错误日志为:
|
|
恢复组件并明确让应用返回 Q=0 后,worker 才缩回 1。这三个状态不能互换:
|
|
Prometheus 有值而指标 API 没值时,检查 Adapter 的 seriesQuery、资源标签和映射;指标 API 有值但副本不变时,再检查目标、condition、稳定窗口和限速。
第六步:desired=4 后,Pod 卡在哪里
调度失败
独立实验把每个 Pod 的 CPU request 写成 1000,即 1000 核,而不是 1000m。HPA 正确写入 4,但 4 个 Pod 全部 Pending:
|
|
这是调度容量不足,不是 HPA 没有计算。实验没有启用节点自动扩容。
启动慢
另一个实验给 web 增加 60 秒 readiness 延迟:
| 时间(UTC) | desired | Running | Ready |
|---|---|---|---|
| 14:50:49 | 4 | 4 | 1 |
| 14:51:56 | 4 | 4 | 4 |
这约 67 秒里,指标从 35 变为按 4 个副本展示的 8750m,但 Ready 一直为 1。差值包含容器启动、探针和采样间隔,不能全部算成 HPA 延迟。HPA、Deployment、调度器、kubelet 和 readiness 是不同的调谐过程。
复现实验
配置、脚本和证据固定在 k8sdev c48f140。按第一篇创建集群后执行:
|
|
脚本只使用 Python 标准库。每个场景只有在全部条件满足后才输出 PASS;失败会非零退出。JSONL 约每 5 秒记录动作、HPA 状态、期望副本、Ready 副本和相关 API 查询。不要在真实队列消费实验运行时暂停指标组件。
排障速查
| 现象 | 先检查 |
|---|---|
CPU 是 <unknown> |
metrics.k8s.io、Metrics Server、CPU request、Pod readiness |
| Prometheus 没有序列 | /metrics、target up、服务发现与标签 |
| API 没发现自定义指标 | APIService、Adapter 日志、seriesQuery 与资源映射 |
| API 有值,副本不变 | 当前值/目标、condition、min/max、稳定窗口、限速 |
| desired 增加,Pod Pending | Pod Events、资源 request、节点容量 |
| Pod Running,Ready 未增加 | readiness、启动日志、依赖服务 |
回答开头的问题
HPA 显示 DESIRED=4,只证明副本建议已经通过指标计算与行为策略,并成功写入 Deployment。实际只有 1 个可用,可能是其他 Pod 尚未被调度、容器还未启动,或 readiness 尚未通过。
所以,不要用一个 HPA condition 判断整条扩缩容链路。先定位指标、建议、策略、期望副本、调度、启动和就绪中的哪一层没有前进。 本次慢启动实验的答案是:HPA 没有失效,新增 Pod 已经 Running,只是约一分钟后才 Ready。