Kubernetes HPA 为何在流量高峰不再自动扩容?一次 metrics-server 指标链路中断的排查记录

晚高峰 QPS 翻倍但 HPA 副本纹丝不动,kubectl get hpa 的 TARGETS 显示 unknown,根因是 metrics-server 指标链路中断。

一、问题背景

我们有一套跑在自建 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 的最小副本数),完全没有随流量增长而扩容

手动敲命令确认:

1
2
3
$ kubectl get hpa order-api
NAME        REFERENCE              TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
order-api   Deployment/order-api   <unknown>/60%   4         20        4          214d

关键信号出现了:TARGETS 那一列本该是类似 85%/60% 的实时利用率,此刻却是 <unknown>/60%。也就是说 HPA 根本读不到当前的 CPU 指标,自然无从判断该不该扩容,只能维持在最小副本数干瞪眼。

再看几个横向证据:

  • kubectl top pod 直接报错:error: Metrics API not available
  • kubectl top node 同样失败;
  • 应用本身、节点资源都没问题,Pod 是活的、能处理请求,只是数量不够。

问题很清晰了:不是应用挂了,而是扩容的"眼睛"瞎了

三、排查过程

1. 先看 HPA 到底为什么读不到指标

describe 是最快的入口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ kubectl describe hpa order-api
...
Conditions:
  Type            Status  Reason                   Message
  ----            ------  ------                   -------
  AbleToScale     True    SucceededGetScale        the HPA controller was able to get the target's current scale
  ScalingActive   False   FailedGetResourceMetric  the HPA was unable to compute the replica count: failed to
                          get cpu utilization: unable to get metrics for resource cpu: no metrics returned from
                          resource metrics API
Events:
  Warning  FailedGetResourceMetric       ... failed to get cpu utilization: unable to get metrics for resource cpu
  Warning  FailedComputeMetricsReplicas  ... invalid metrics (1 invalid out of 1)

ScalingActive=FalseFailedGetResourceMetric,指向的是 resource metrics API(也就是 metrics.k8s.io)没有返回数据。HPA 控制器本身没毛病,问题在它依赖的指标源。

2. 确认 Metrics API 这条路是否通

kubectl top 和 HPA 的 CPU 指标,走的都是聚合层暴露出来的 metrics.k8s.io API。直接问 apiserver 这个 API 组还在不在:

1
2
$ kubectl get apiservices | grep metrics
v1beta1.metrics.k8s.io   kube-system/metrics-server   False (FailedDiscoveryCheck)   214d

AVAILABLEFalse,原因 FailedDiscoveryCheck。说明 apiserver 通过聚合层(Aggregation Layer)去访问 metrics-server 后端时,握手/发现这一步失败了。这是整条链路断裂的核心证据。

顺手看看 APIService 的详细状态:

1
2
3
4
5
6
7
8
9
$ kubectl describe apiservice v1beta1.metrics.k8s.io
...
Status:
  Conditions:
    Message: failing or missing response from https://10.96.xx.xx:443/apis/metrics.k8s.io/v1beta1:
             Get "https://10.96.xx.xx:443/...": x509: certificate signed by unknown authority
    Reason:  FailedDiscoveryCheck
    Status:  False
    Type:    Available

x509: certificate signed by unknown authority——问题一下子聚焦了。apiserver 去调 metrics-server 时,校验对方证书失败,说这张证书不是可信 CA 签的。

3. 定位 metrics-server 自身状态

1
2
3
4
5
6
7
8
9
$ kubectl -n kube-system get pod -l k8s-app=metrics-server
NAME                              READY   STATUS    RESTARTS      AGE
metrics-server-7d9c8f6b5-abcde    0/1     Running   0             3d

$ kubectl -n kube-system logs metrics-server-7d9c8f6b5-abcde | tail -n 20
...
"Failed to scrape node" node="node-3" err="Get \"https://10.0.1.13:10250/metrics/resource\":
  x509: certificate signed by unknown authority"
"Failed probe" probe="metric-storage-ready" err="no metrics to serve"

两个关键信息叠加:

  1. Pod 是 RunningREADY 0/1——readiness 探针(/readyz,依赖 metric-storage-ready)因为抓不到任何指标而一直不就绪,于是它被从 Service 的 Endpoints 摘掉,apiserver 聚合层自然连不上后端,这解释了上面的 FailedDiscoveryCheck
  2. 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 即便读不到指标也没暴露问题;直到晚高峰需要扩容,“眼睛瞎了"的后果才第一次被真正需要,于是集中爆发。

四、解决方案

止血(分钟级恢复业务):既然自动扩容瘫痪,先手动把副本顶上去,给排查争取时间:

1
kubectl scale deployment order-api --replicas=16

副本拉起后延迟迅速回落,504 消失。注意:只要 HPA 还在且 metrics 不可用,它虽然扩不上去,但也不会把手动扩上来的副本缩回去AbleToScale 逻辑在指标缺失时不会误缩),所以手动扩容是安全的临时手段。

根治(修复指标链路):修 metrics-server 的证书信任。让它信任集群 CA 来校验 kubelet 证书,编辑 Deployment 的容器参数:

1
2
3
4
5
6
7
8
args:
  - --cert-dir=/tmp
  - --secure-port=10250
  - --kubelet-preferred-address-types=InternalIP
  - --kubelet-use-node-status-port
  # 关键:指定信任的 CA,用它校验 kubelet 服务证书
  - --kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt
  - --metric-resolution=15s

并把新 CA 通过 hostPath/Secret 正确挂进容器。改完滚动重启:

1
2
kubectl -n kube-system rollout restart deploy/metrics-server
kubectl -n kube-system rollout status deploy/metrics-server

验证链路恢复:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ kubectl get apiservices | grep metrics
v1beta1.metrics.k8s.io   kube-system/metrics-server   True    216d

$ kubectl top pod -n order
NAME                         CPU(cores)   MEMORY(bytes)
order-api-xxx                420m         512Mi

$ kubectl get hpa order-api
NAME        REFERENCE              TARGETS     MINPODS   MAXPODS   REPLICAS
order-api   Deployment/order-api   78%/60%     4         20        13

TARGETS 恢复成真实数值,HPA 重新开始按负载伸缩。确认稳定后把手动扩容的固定副本数交还给 HPA 管理即可。

五、根因分析

根本原因是证书轮换只更新了链路的一端(kubelet 服务证书),却漏掉了依赖方(metrics-server)对新 CA 的信任配置,导致一条隐蔽的指标依赖链在证书生效后静默断裂。

链路的脆弱性被三个因素放大:其一,metrics-server 是内存态、无历史落盘的组件,故障后没有任何本地告警,只会安静地"不就绪”;其二,故障发生(早上 kubelet 重启)与暴露(晚高峰需扩容)之间存在时间差,掩盖了因果关系;其三,HPA 对指标缺失的降级是"冻结当前副本"而非报错,从业务视角看就是"扩容不生效",很容易被误判为 HPA 配置或应用问题,而非上游指标源故障。

六、预防措施

  1. 给 Metrics API 本身加监控:直接探测 kubectl get apiservice v1beta1.metrics.k8s.ioAvailable 状态,或 Prometheus 采集 apiserver_aggregator_unavailable_apiservice;这是最直接的哨兵,比等 HPA 出事早得多。
  2. 给 HPA 加告警:对 kube_horizontalpodautoscaler_status_condition{condition="ScalingActive",status="false"} 报警,一旦 HPA 无法计算副本立即通知。
  3. 证书轮换纳入变更清单:凡涉及 kubelet/CA 证书变更,检查清单里必须包含"所有校验 kubelet 证书的组件(metrics-server、Prometheus node 抓取等)的 CA 信任配置同步更新",并在变更后主动跑一遍 kubectl top node
  4. 压测扩容能力:定期在预发环境注入负载,验证 HPA 真能扩上去,而不是只看配置存在。
  5. 关键组件就绪探测纳入巡检:把 metrics-server 等基础组件的 READY 状态纳入日常巡检,避免"Running 但 0/1"这类半死状态长期潜伏。

七、总结

这是一次典型的"配置变更埋雷、需求触发引爆"的故障:证书轮换的一个疏漏,让 metrics-server 静默失明,进而让 HPA 在最需要扩容的晚高峰彻底失灵。排查的关键路径其实很短——从 TARGETS<unknown> 一眼看出指标缺失,顺着 describe hpaapiservices → metrics-server 日志三步就能锁定 x509 证书信任问题。

真正值得记取的教训有两点:一是指标链路和监控链路一样是生产的一等公民,它一旦断裂,自动化能力(扩容、调度决策)就会静默降级,必须为它单独设哨兵;二是变更影响面要顺着依赖链推演到底,改一端证书,要问清楚"谁在校验这张证书",否则埋下的雷只会在最不合适的时刻炸响。

使用 Hugo 构建
主题 StackJimmy 设计