ᕕ( ᐛ )ᕗ Jimyag's Blog

HPA 已经要求 4 个副本,为什么只有 1 个可用?

Last modified:

kubectl get hpa 已经显示 DESIRED=4,业务监控却只有一个实例能接收流量。是 HPA 失效了吗?

不一定。在一次慢启动实验中,HPA 很快写入 4,新增的 3 个 Pod 也进入 Running,但约一分钟后才全部 Ready。

HPA 已经要求 4 个副本,为什么仍可能只有 1 个可用?

本文沿着控制链路逐层回答。它是系列第四篇,默认已经理解指标如何变成期望副本

用一条链路代替猜测

1
2
3
4
5
6
7
8
9
指标有值?
原始副本建议是多少?
是否受稳定窗口、限速、min/max 或多指标约束?
Deployment 期望副本是否更新?
Pod 能否调度、启动并通过 readiness?

排障时依次记录:

1
2
3
4
kubectl --context kind-hpa-lab -n hpa-demo get hpa
kubectl --context kind-hpa-lab -n hpa-demo describe hpa worker
kubectl --context kind-hpa-lab -n hpa-demo get deployment,pod
kubectl --context kind-hpa-lab -n hpa-demo get events --sort-by=.lastTimestamp

自定义指标还要分别查询 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.averageValue8750m,即 35 / 4 = 8.75

1
2
3
4
5
metric:          queue_depth
averageValue:    8750m
target:          10
desiredReplicas: 4
Ready:           4

第二步:看建议是否被稳定窗口保留

实验将缩容稳定窗口改为 60 秒:先用 Q=35 保持 4 个副本,把队列降到 0 约 25 秒,再恢复到 35。整个低值脉冲期间,期望副本始终是 4;最后持续置零才缩回 1。

1
2
最近 60 秒的原始建议:4, 4, 1, 1, 4
缩容采用的约束:max(...) = 4

稳定窗口保存的是副本建议,不是指标平均值,也不是每次指标变化后重启的定时器。实现见 Kubernetes v1.35.0 的 stabilizeRecommendationWithBehaviors

第三步:看变化速度是否被限制

下面的策略关闭稳定等待,只观察每 30 秒最多改变一个 Pod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 只验证速度限制,不引入稳定窗口。
behavior:
  scaleUp:
    stabilizationWindowSeconds: 0
    policies:
      - type: Pods # 使用绝对 Pod 数限制变化
        value: 1
        periodSeconds: 30
  scaleDown:
    stabilizationWindowSeconds: 0
    policies:
      - type: Pods
        value: 1
        periodSeconds: 30

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

对应的关键错误日志为:

1
2
3
4
5
6
exporter 停止: Prometheus result=[]
               custom metrics API: NotFound: could not find metric queue_depth
Prometheus 停止: ServiceUnavailable: no endpoints available for service "prometheus"
                 InternalError: unable to fetch metrics
Adapter 停止: Prometheus value=35
              custom metrics API: ServiceUnavailable

恢复组件并明确让应用返回 Q=0 后,worker 才缩回 1。这三个状态不能互换:

1
旧样本 ≠ 没有样本 ≠ 指标 API 不可用 ≠ 应用明确报告 0

Prometheus 有值而指标 API 没值时,检查 Adapter 的 seriesQuery、资源标签和映射;指标 API 有值但副本不变时,再检查目标、condition、稳定窗口和限速。

第六步:desired=4 后,Pod 卡在哪里

调度失败

独立实验把每个 Pod 的 CPU request 写成 1000,即 1000 核,而不是 1000m。HPA 正确写入 4,但 4 个 Pod 全部 Pending:

1
0/1 nodes are available: 1 Insufficient cpu

这是调度容量不足,不是 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。按第一篇创建集群后执行:

1
2
3
4
5
6
cd ../advanced
./setup.sh
HPA_OUTPUT=$(mktemp -d "${TMPDIR:-/tmp}/hpa-debug.XXXXXX")
for case in steps policies multi failures scheduling; do
  python3 run.py "$case" --output "$HPA_OUTPUT" || break
done

脚本只使用 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。

#Kubernetes #HPA #Troubleshooting #Kind