Featured image of post Kubernetes HPA 扩缩容不符合预期时怎么查

Kubernetes HPA 扩缩容不符合预期时怎么查

记录 HPA 排查顺序:metrics-server、CPU requests、HPA Conditions、scaleUp/scaleDown behavior、readiness 和 replicas 管理边界。

HPA 看起来配置很简单:CPU 超过多少就扩容,低于多少就缩容。但真正出问题时,经常不是 HPA 本身坏了,而是指标、requests、readiness、稳定窗口这些东西一起影响了结果。

我一般按下面的顺序查。

我遇到过最典型的误判是:业务说“CPU 都 80% 了为什么不扩容”,结果看的是节点 CPU 或容器 limit,而 HPA 算的是当前使用量相对于 requests 的 utilization。指标口径没对齐时,后面所有判断都会偏。

先看 HPA 当前状态

1
2
3
kubectl get hpa -n default

kubectl describe hpa api-server -n default

kubectl get hpa 里重点看:

  • TARGETS 有没有显示 <unknown>
  • MINPODSMAXPODS 是否符合预期。
  • REPLICAS 是当前副本数还是已经被限制住了。

describe hpa 里重点看 Conditions 和 Events。比如指标拿不到、目标对象无法 scale、已经达到 maxReplicas、缩容被稳定窗口延迟。

确认 metrics-server 正常

CPU、内存这类 resource metrics 通常依赖 metrics.k8s.io,一般由 metrics-server 提供。

1
2
3
4
5
6
kubectl get apiservice v1beta1.metrics.k8s.io

kubectl describe apiservice v1beta1.metrics.k8s.io

kubectl top node
kubectl top pod -n default

如果这里都拿不到数据,HPA 大概率也拿不到。不要一上来改 HPA YAML。

CPU utilization 依赖 requests

这是 HPA 排查里很常见的坑。

如果 HPA 用的是 CPU utilization:

1
2
3
4
5
6
7
metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60

这里的 60% 不是“宿主机 CPU 的 60%”,而是 Pod 当前 CPU 使用量相对于 container resources.requests.cpu 的比例。

换成公式大概就是:

1
2
current utilization = current CPU usage / requests.cpu
desired replicas = ceil(current replicas * current utilization / target utilization)

真实实现还会考虑缺失指标、未 Ready Pod、容忍区间和稳定窗口,但这个公式能帮人先把口径对齐。

所以 Deployment 里需要有 requests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api-server
          image: nginx:1.27
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 1000m
              memory: 512Mi

更稳一点可以用 jsonpath 看 requests:

1
2
kubectl get deploy api-server -n default \
  -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{" requests="}{.resources.requests}{"\n"}{end}'

一个完整的 HPA 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-server
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
        - type: Pods
          value: 4
          periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 30
          periodSeconds: 60
      selectPolicy: Max

maxReplicas 是硬上限。指标再高,也不会超过这个值。

多个 metrics 同时配置时,HPA 会分别计算期望副本数,然后取最大的那个。

scaleDown.stabilizationWindowSeconds: 300 表示缩容会参考过去一段时间的建议值,避免指标刚降下来就马上删 Pod。

为什么负载下来了还不缩容

常见原因:

  • 还在 scale down stabilization window 内。
  • 某个指标仍然建议较高副本数。
  • metrics 获取失败,HPA 为了保守跳过缩容。
  • minReplicas 限制住了。
  • Deployment 还在 rollout,观察值不稳定。

Kubernetes 默认的 scale down 稳定窗口是 300 秒。不要期待请求量一掉下来副本马上减少。

这其实是好事。缩容太快会把刚恢复的系统再次打抖,尤其是有连接池、缓存预热、JVM 或大模型服务这种启动成本时。很多时候我们抱怨“不缩容”,其实是 HPA 在帮你避免频繁震荡。

为什么负载上来了也不扩容

我会按这个顺序查:

1
2
3
4
5
kubectl top pod -n default -l app=api-server

kubectl describe hpa api-server -n default

kubectl get deploy api-server -n default -o jsonpath='{.status.readyReplicas}{" / "}{.status.replicas}{"\n"}'

常见原因是指标没拿到、CPU request 没配、还没超过容忍区间、Pod 还没 Ready,或者已经到 maxReplicas 了。

我会额外看一个细节:当前是否正在 rollout。新 Pod 还没稳定 Ready 时,HPA 的观测和 Deployment 的滚动发布会互相影响。这个时候不要急着同时改 HPA、改 requests、改镜像版本,否则最后很难判断到底是哪一个动作生效。

如果一个 Deployment 已经被 HPA 管理,我倾向于不要在日常 apply 的 manifest 里固定写死 spec.replicas。否则每次 apply 都可能把副本数改回 YAML 里的值,和 HPA 的调整互相打架。

排查 HPA 不要只盯着 HPA YAML。真正有用的信息通常分散在 metrics-server、Pod requests、HPA Conditions、Deployment rollout 状态和 readiness 里。

参考资料

记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计