一、问题背景
我们有一套跑在自建 Kubernetes(v1.27)上的电商中台服务,日常流量呈典型的双峰形态:午间与晚间各一波高峰。为了扛住波峰,核心的 order-api Deployment 早就配了 HorizontalPodAutoscaler(HPA),按 CPU 利用率 60% 自动在 4~20 个副本之间伸缩,稳定运行了大半年,从没出过岔子。
变更是从一次例行的证书轮换开始的。运维同学按季度给集群组件更新了一批内部 CA 签发的证书,包括 metrics-server 用的服务证书,操作当天一切正常,监控大盘也没报警。谁也没想到,这颗雷会在三天后的晚高峰被引爆——流量上来了,副本却一个都没多起来。
二、故障现象
晚上 20:10 左右,order-api 的 P99 延迟开始飙升,从平时的 80ms 一路冲到 3s 以上,网关侧大量 504。值班同学第一反应是扩容顶不住了,但打开监控发现更诡异的事情:Pod 数量始终停在 4 个(HPA 的最小副本数),完全没有随流量增长而扩容。
手动敲命令确认:
|
|
关键信号出现了:TARGETS 那一列本该是类似 85%/60% 的实时利用率,此刻却是 <unknown>/60%。也就是说 HPA 根本读不到当前的 CPU 指标,自然无从判断该不该扩容,只能维持在最小副本数干瞪眼。
再看几个横向证据:
kubectl top pod直接报错:error: Metrics API not available;kubectl top node同样失败;- 应用本身、节点资源都没问题,Pod 是活的、能处理请求,只是数量不够。
问题很清晰了:不是应用挂了,而是扩容的"眼睛"瞎了。
三、排查过程
1. 先看 HPA 到底为什么读不到指标
describe 是最快的入口:
|
|
ScalingActive=False、FailedGetResourceMetric,指向的是 resource metrics API(也就是 metrics.k8s.io)没有返回数据。HPA 控制器本身没毛病,问题在它依赖的指标源。
2. 确认 Metrics API 这条路是否通
kubectl top 和 HPA 的 CPU 指标,走的都是聚合层暴露出来的 metrics.k8s.io API。直接问 apiserver 这个 API 组还在不在:
|
|
AVAILABLE 是 False,原因 FailedDiscoveryCheck。说明 apiserver 通过聚合层(Aggregation Layer)去访问 metrics-server 后端时,握手/发现这一步失败了。这是整条链路断裂的核心证据。
顺手看看 APIService 的详细状态:
|
|
x509: certificate signed by unknown authority——问题一下子聚焦了。apiserver 去调 metrics-server 时,校验对方证书失败,说这张证书不是可信 CA 签的。
3. 定位 metrics-server 自身状态
|
|
两个关键信息叠加:
- Pod 是
Running但READY 0/1——readiness 探针(/readyz,依赖metric-storage-ready)因为抓不到任何指标而一直不就绪,于是它被从 Service 的 Endpoints 摘掉,apiserver 聚合层自然连不上后端,这解释了上面的FailedDiscoveryCheck; - metrics-server 去抓各节点 kubelet 的
:10250/metrics/resource时,同样报x509: certificate signed by unknown authority。
到这里链路彻底清楚:三天前证书轮换时更新了 kubelet 的服务证书,改用了新的内部 CA 签发;但 metrics-server 的启动参数里还配着信任旧 CA(或压根没配 --kubelet-insecure-tls 也没挂新 CA),于是它拿新证书校验失败,抓不到任何节点指标 → 自己 readiness 不就绪 → 被摘出 Endpoints → apiserver 聚合层 discovery 失败 → metrics.k8s.io 不可用 → HPA 拿不到 CPU → 高峰不扩容。
4. 为什么三天前改证书、今天才爆
这也是最迷惑的一点。查了下 metrics-server 的重启时间——它并没有随证书轮换重启,一直用着内存里旧的、还能验证通过的连接缓存和已抓到的历史指标数据在"续命"。真正的转折点是当天早上一次 kubelet 配置滚动生效触发了各节点 kubelet 重启、启用新证书,metrics-server 的抓取从那一刻起才全面失败。而白天流量平缓,4 个副本足够扛,HPA 即便读不到指标也没暴露问题;直到晚高峰需要扩容,“眼睛瞎了"的后果才第一次被真正需要,于是集中爆发。
四、解决方案
止血(分钟级恢复业务):既然自动扩容瘫痪,先手动把副本顶上去,给排查争取时间:
|
|
副本拉起后延迟迅速回落,504 消失。注意:只要 HPA 还在且 metrics 不可用,它虽然扩不上去,但也不会把手动扩上来的副本缩回去(AbleToScale 逻辑在指标缺失时不会误缩),所以手动扩容是安全的临时手段。
根治(修复指标链路):修 metrics-server 的证书信任。让它信任集群 CA 来校验 kubelet 证书,编辑 Deployment 的容器参数:
|
|
并把新 CA 通过 hostPath/Secret 正确挂进容器。改完滚动重启:
|
|
验证链路恢复:
|
|
TARGETS 恢复成真实数值,HPA 重新开始按负载伸缩。确认稳定后把手动扩容的固定副本数交还给 HPA 管理即可。
五、根因分析
根本原因是证书轮换只更新了链路的一端(kubelet 服务证书),却漏掉了依赖方(metrics-server)对新 CA 的信任配置,导致一条隐蔽的指标依赖链在证书生效后静默断裂。
链路的脆弱性被三个因素放大:其一,metrics-server 是内存态、无历史落盘的组件,故障后没有任何本地告警,只会安静地"不就绪”;其二,故障发生(早上 kubelet 重启)与暴露(晚高峰需扩容)之间存在时间差,掩盖了因果关系;其三,HPA 对指标缺失的降级是"冻结当前副本"而非报错,从业务视角看就是"扩容不生效",很容易被误判为 HPA 配置或应用问题,而非上游指标源故障。
六、预防措施
- 给 Metrics API 本身加监控:直接探测
kubectl get apiservice v1beta1.metrics.k8s.io的Available状态,或 Prometheus 采集apiserver_aggregator_unavailable_apiservice;这是最直接的哨兵,比等 HPA 出事早得多。 - 给 HPA 加告警:对
kube_horizontalpodautoscaler_status_condition{condition="ScalingActive",status="false"}报警,一旦 HPA 无法计算副本立即通知。 - 证书轮换纳入变更清单:凡涉及 kubelet/CA 证书变更,检查清单里必须包含"所有校验 kubelet 证书的组件(metrics-server、Prometheus node 抓取等)的 CA 信任配置同步更新",并在变更后主动跑一遍
kubectl top node。 - 压测扩容能力:定期在预发环境注入负载,验证 HPA 真能扩上去,而不是只看配置存在。
- 关键组件就绪探测纳入巡检:把
metrics-server等基础组件的READY状态纳入日常巡检,避免"Running 但 0/1"这类半死状态长期潜伏。
七、总结
这是一次典型的"配置变更埋雷、需求触发引爆"的故障:证书轮换的一个疏漏,让 metrics-server 静默失明,进而让 HPA 在最需要扩容的晚高峰彻底失灵。排查的关键路径其实很短——从 TARGETS 的 <unknown> 一眼看出指标缺失,顺着 describe hpa → apiservices → metrics-server 日志三步就能锁定 x509 证书信任问题。
真正值得记取的教训有两点:一是指标链路和监控链路一样是生产的一等公民,它一旦断裂,自动化能力(扩容、调度决策)就会静默降级,必须为它单独设哨兵;二是变更影响面要顺着依赖链推演到底,改一端证书,要问清楚"谁在校验这张证书",否则埋下的雷只会在最不合适的时刻炸响。