上个月底搞活动,运维把 K8s 集群从 3 台节点扩到 8 台,业务侧给 Deployment 配了 HPA,规则写得很漂亮:CPU 超过 60% 就扩,最多 20 个副本。监控大屏上流量曲线一路往上爬,Pod 数量却像被钉死一样全天停在 3 个。
那天下午我们排查了近四个小时,最后发现根本不是集群算力不够,是 YAML 里三个很小的地方写错了:digvps(全球云服务器) 的测评数据可以先拿来做扩容前的节点选型,机器买回来之前把配置和线路摸清楚,比出事后再补救省事得多。

先确认现象:HPA 到底有没有在算
很多人一看到不扩容就直接去翻 Deployment 和 ReplicaSet,其实第一步是看 HPA 自己的状态。命令行里敲 kubectl get hpa,重点看两列:
TARGETS 这一列如果显示成 <unknown> 或者 0%/60%,说明 metrics-server 根本没把指标喂给 HPA,控制器连"当前 CPU 是多少"都不知道,自然不会动作。如果是 85%/60% 却依旧不扩,那就说明指标是通的,是后面的规则在卡它。
kubectl describe hpa 里 Events 段落信息量最大。正常触发扩容会看到 Scaling Up 的记录并写明当前副本数和目标副本数;如果是 FailedGetResourceMetric,那就是 metrics-server 的 RBAC 或者网络链路问题;如果是 FailedComputeMetricsReplicas,通常是 requests 没配。

坑一:Target CPU 写成了 0.8,单位错了整整 100 倍
这是我们踩的第一个坑,也是最容易犯的一个。HPA 的 targetCPUUtilizationPercentage 是一个"百分比数字",填 60 表示 60%,不是 0.6。我们那个 YAML 里写的是 targetCPUUtilizationPercentage: 0.8,本意是"利用率到 80% 就扩",实际含义变成了"利用率到 0.8% 就扩"。
更阴的是前几分钟它看起来还挺正常集群刚重启,容器还在冷启动,CPU 利用率低得离谱,0.8% 这个阈值瞬间就被越过了,HPA 应该疯狂扩副本才对。可实际现象恰恰相反,一个都没扩。
原因在于 utilization 的计算分母是 requests.cpu,不是 limit。冷启动阶段请求量还没起来,比值虽然数值上大于 0.8%,但因为 requests 也小,比值本身一直卡在很低的百分比;更关键的是 scale-up 的稳定窗口还没过,控制器连着几个采样周期都判定"不需要扩"。等到流量真正涌进来,采样窗口已经被"无变化"的记录填满,看起来就像 HPA 死了。
正确的写法是 targetCPUUtilizationPercentage: 60,整数,带不带百分号由 YAML 数值类型决定,不要写成字符串 "60%",不同版本解析行为不一致。

坑二:requests 没写,utilization 永远算不出来
第二个坑更隐蔽。我们的 Go 服务 Deployment 里 resources 只写了 limits 没写 requests,HPA 也就跟着没配 metric。整个上午 kubectl top pod 都能正常显示 CPU 百分比,团队里有人说"指标明明有",但那 top 命令读的是 metrics-server 直接采的原始使用率,跟 HPA 的算法完全是两回事。
HPA 算的是 当前使用量 ÷ requests 值。requests 缺失时,Kubernetes 会用节点可分配量做兜底分母,算出来可能只有百分之零点几,永远达不到 60% 的阈值。describe hpa 里会明确写 FailedComputeMetricsReplicas。
补 requests 的原则很简单:按压测出来的 P95 值再加 30% 到 50% 余量。写太小了会一直触发扩容,写太大了会一直缩容,两个方向都难受。
坑三:maxReplicas 写死 3
第三个坑其实是个乌龙,最初那份 YAML 是照着示例抄的,maxReplicas: 3 没改。前面两个原因修好之后,HPA 开始扩了,从 3 扩到 3,然后就不动了因为上限本来就是 3。
这类"配置从示例来"的错误没法靠逻辑推理发现,只能逐项核对。我的习惯是上线前把 HPA 的四个数(min、max、target、behavior)单独抄到一张表里,贴在变更单旁边。

顺带说说 behavior 那块容易被忽略的延迟
修完上面三个问题后我们又遇到一个新情况:流量是脉冲式的,来 10 分钟涨 3 倍然后回落,HPA 扩上去要 3 分钟才能拉起新 Pod,缩容倒是不动,最后节点一直空着。后来在 behavior 里加了 scaleDown 的 stabilizationWindowSeconds: 300,让缩容慢一点、扩容快一点,才符合业务节奏。
顺带说一句,这套集群跑在几台自己采购的物理机上,走的是裸金属加虚拟化的自建路线。如果你也打算在自有硬件上搭一套而不是用托管的 interserver(云与裸机基础设施,硬件托管) 这类现成方案,那节点层面的资源预留要留得更宽裕,因为没有云厂商的弹性调度帮你兜底。
顺便提醒一句:behavior 里的 policies 写的是"在什么条件下允许缩到几个副本",不是"缩容速度"。
我现在的排查顺序
这套顺序是踩完坑之后定下来的,四步基本两分钟内能定位:
一,kubectl get hpa 看 TARGETS 是 unknown 还是具体数值;二,kubectl describe hpa 看 Events 里有没有报错;三,对照 Deployment 确认 requests 有没有配、Target CPU 的单位对不对、maxReplicas 是不是还停在示例值;四,如果都对还不动,就去节点上看 kubectl top node 的 Allocatable,确认集群真的一点余量都没有了。
需要临时手动拉一把验证规则是否生效的时候,kubectl scale deployment/xxx --replicas=5 很快,但别忘了把 HPA 停掉再改 replicas,不然两边会互相打架,改完的 replicas 会被 HPA 立刻改回去。这条我也踩过。
整套问题复盘下来,最深的感受是:弹性伸缩这件事,出问题的时候 90% 概率不在集群能力上,而在 YAML 的单位和细节上。写完之后记得用 kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods 确认一次指标通道是通的,这个接口不通,上面所有配置都是空谈。


喜欢
顶
难过
囧
围观
无聊





