适用场景

部署在 Kubernetes 中的 Web、API 或消费服务在高峰期 CPU 已长期接近上限,但 HorizontalPodAutoscaler(HPA)始终保持原副本数;也可能在 kubectl get hpa 中看到 CPU 显示为 <unknown>。本文以基于 CPU 利用率的 HPA 为例,给出一条从工作负载到聚合指标 API 的排查路径。

现象与影响

典型表现包括:

  • HPA 的 TARGETS<unknown>/70%,或当前值一直是 0%
  • Pod 已出现排队、5xx 或延迟升高,副本数却没有增加;
  • kubectl top pod 也无法返回资源使用率;
  • HPA Events 出现 FailedGetResourceMetricfailed to get cpu utilization

不要先把 maxReplicas 调大。HPA 只有拿到“实时使用量”和“Pod 请求量”后才能计算利用率;指标链路断开时,扩大上限不会产生扩容动作。

HPA 计算依赖

CPU 利用率模式下,HPA 的核心计算是:

期望副本数 = ceil(当前副本数 × 当前平均 CPU 利用率 / 目标利用率)

其中“利用率”不是节点 CPU 百分比,而是容器实际 CPU 使用量除以该容器 resources.requests.cpu。因此至少需要同时满足:

  1. metrics-server 能从每个 kubelet 获取 /metrics/resource
  2. API aggregation 层的 metrics.k8s.io 可用;
  3. 被 HPA 选择到的 Pod 容器声明了 CPU request;
  4. HPA 的 selector 与 Deployment 的标签一致,且 Pod 已 Ready。

排查步骤

1. 从 HPA 状态和事件开始

kubectl -n production get hpa api -o wide
kubectl -n production describe hpa api

重点看 MetricsConditions 和末尾 Events。例如 unable to get metrics for resource cpu: no metrics returned from resource metrics API 说明问题在指标链路;missing request for cpu 则直接指向工作负载资源声明。

2. 对比 metrics API 与 HPA 视角

kubectl top nodes
kubectl -n production top pods -l app=api --containers
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | head
kubectl get apiservice v1beta1.metrics.k8s.io

kubectl top 和 raw API 都失败时,不必在应用内继续找原因。APIServiceAvailable 必须为 True;若为 False,执行下面命令取得精确原因:

kubectl describe apiservice v1beta1.metrics.k8s.io
kubectl -n kube-system get pods -l k8s-app=metrics-server -o wide
kubectl -n kube-system logs deploy/metrics-server --tail=200

日志中常见的 x509: cannot validate certificate 是 kubelet 服务端证书的 SAN 不包含节点 InternalIP;context deadline exceeded 常见于控制面到节点 10250 端口的网络策略或防火墙不通;403 则要检查 kubelet authentication/authorization 或 metrics-server 的 RBAC。

3. 核对 Pod 的 CPU request

kubectl -n production get deploy api \
  -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\trequest="}{.resources.requests.cpu}{"\n"}{end}'

输出为空的容器会使基于 CPU utilization 的 HPA 无法计算该 Pod。一个容易漏掉的点是 sidecar:只要 HPA 选择到的 Pod 中有相关容器未设置 request,指标可能被跳过。补齐所有业务容器和 sidecar 的 request,例如:

resources:
  requests:
    cpu: 200m
    memory: 256Mi
  limits:
    cpu: "1"
    memory: 512Mi

200m 表示 0.2 个 CPU 核,不是 200MB。request 应以稳定负载的 P95 为起点,并预留突发空间;盲目设置得过小会让利用率虚高,造成频繁扩缩容。

一次定位与修复示例

某 API 服务的 HPA 配置为目标 CPU 70%、最小 3 副本。高峰时每个 Pod 实际使用约 350m CPU,理论利用率应为 350m / 200m = 175%,应该迅速扩容。实际输出却是:

NAME   REFERENCE        TARGETS        MINPODS   MAXPODS   REPLICAS
api    Deployment/api   <unknown>/70%  3         12        3

排查发现 metrics-server 日志报 kubelet 证书校验失败。测试环境节点使用自签名 kubelet 证书,且证书未包含 InternalIP。短期恢复措施是在 metrics-server 参数中增加 --kubelet-insecure-tls,并确保按 InternalIP 选取节点地址:

args:
  - --kubelet-preferred-address-types=InternalIP,Hostname,ExternalIP
  - --kubelet-insecure-tls

修改后等待 metrics-server 滚动完成,再依次验证:

kubectl -n kube-system rollout status deploy/metrics-server
kubectl top pod -n production -l app=api
kubectl -n production describe hpa api

生产环境不应把 --kubelet-insecure-tls 当作长期方案。根治方式是为 kubelet 服务端证书签发包含节点 InternalIP/DNS 的证书,并在 metrics-server 中恢复 TLS 校验。这样既避免 HPA 失效,也避免指标采集链路被中间人伪造。

让 HPA 行为更稳定

指标恢复后,建议显式配置缩容稳定窗口,减少瞬时流量回落造成的抖动:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  minReplicas: 3
  maxReplicas: 12
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 60

扩容每分钟最多翻倍,缩容则以五分钟窗口和每分钟 25% 限速进行。具体数值应匹配应用启动时间、连接预热时间和下游承受能力。对于主要受队列长度或 RPS 驱动的服务,也应考虑通过 Prometheus Adapter 提供业务指标,而不是只看 CPU。

预防清单

  • v1beta1.metrics.k8s.io APIService 的 Available 状态建立告警;
  • 在发布检查中执行 kubectl top pod,将指标链路作为集群基础能力验收;
  • 用准入策略要求受 HPA 管理的容器声明 CPU/内存 request;
  • 为 HPA 的 ScalingActive=False、副本数到达上限和持续高延迟分别告警;
  • 演练指标服务重启、节点证书轮换和网络策略变更,避免故障只在流量高峰暴露。

总结

HPA 不扩容时,先判断是“没有指标”还是“计算结果不需要扩容”。依次验证 HPA 事件、metrics.k8s.io、metrics-server 到 kubelet 的连接,以及容器 CPU request,通常能很快收敛问题。恢复后再通过扩缩容行为策略和监控闭环,才能让弹性能力在高峰真正可用。