1. 为什么需要HPA:从手动扩缩容到弹性伸缩
做 Kubernetes 运维的人,迟早要面对 HPA——HorizontalPodAutoscaler 这套机制。HPA 解决的问题其实很朴素:业务流量涨上去了,副本数就得跟着涨;流量下去了,多余副本也不能继续白白占着资源。过去手工kubectl scale deployment web-demo --replicas=10也能做,但凌晨三点被告警叫起来手动扩容的滋味,经历过的人都不想再有第二次。HPA 就是把这套“看指标、定副本、执行扩缩容”的判断逻辑自动化,让副本数始终贴合实际压力。
HPA 的官方名称很难念,HorizontalPodAutoscaler,它只负责“水平扩容”:多做几份副本,把流量分摊到更多 Pod 上。与之相对的是“垂直扩容”,也就是给单个 Pod 增加 CPU、内存配额,这套机制叫 VPA。两者适用场景不同,HPA 更常见,因为互联网应用绝大多数是无状态服务,都适合用多副本扛流量。HPA 能自动调整 Deployment、StatefulSet 等工作负载的副本数,也可以配合 ClusterAutoscaler 让整个节点池跟着弹性伸缩,形成从“Pod 层”到“Node 层”的完整弹性链路。
这篇文章我会从 HPA 的底层算法讲起,再给出一套可以直接抄走的 YAML 配置,然后用实际压测场景说明观测方法,最后整理我在生产环境里踩过的坑。不管你是刚接触 Kubernetes 的新手,还是已经被“副本抖动”“指标采集不到”折磨过的运维老手,都能从这篇文章里找到可以直接用的东西。文章里的所有配置我都尽量贴近官方默认行为,同时说明每个字段背后的必要性,这样你才能知道哪些可以删,哪些必须留。
2. HPA 到底怎么工作:控制回路与目标副本数算法
2.1 控制回路:谁在循环执行这件事
HPA 不是独立的 Pod,也不是单独的二进制服务,它是 kube-controller-manager 里的一个控制回路。控制器默认每隔一段时间就执行一次完整的“采集指标 -> 计算期望副本数 -> 调整副本数”流程。官方文档里把这个间隔称为 horizontal-pod-autoscaler-sync-period,默认是 15 秒。了解这个时间间隔很重要,因为 HPA 不是实时的,你要看到的扩容动作通常带有 15 秒左右的延迟。如果再加上指标数据自身的采集间隔,从流量突增到新 Pod 被创建,中间隔二三十秒是完全正常的。
控制回路读取的指标来自 metrics.k8s.io、custom.metrics.k8s.io 或 external.metrics.k8s.io 这三类 API。CPU、内存这类基础资源指标走 metrics.k8s.io,一般由 metrics-server 提供;应用自定义指标比如 QPS、队列长度、gRPC 并发连接数,走 custom.metrics.k8s.io,一般由 Prometheus Adapter 这类组件提供;外部指标比如云厂商消息队列堆积量,走 external.metrics.k8s.io。HPA 的通用逻辑是先向这些 API 发起请求,拿到当前副本数对应的 Pod 指标集合,再计算当前平均值,最后和目标值做比较。
这套机制里最容易被忽略的一点是:HPA 的目标值不是“当前总指标要小于目标值”,而是“当前每个 Pod 的平均值要靠近目标值”。以 CPU 为例,假设目标利用率 50%,如果当前 Pod 的平均利用率 80%,HPA 就会扩容;如果当前平均值只有 30%,HPA 就会缩容。它管理的粒度始终是“平均每 Pod”,而不是整个 Deployment 的总量。理解这一点,后面很多配置困惑都会迎刃而解。
2.2 期望副本数公式:所有扩容决策的数学基础
HPA 的期望副本数计算公式在官方文档里有明确说明,可以写成:
期望副本数 = ceil(当前副本数 * 当前指标平均值 / 目标指标平均值)这里的 ceil 是向上取整。假设当前有 3 个副本,每个 Pod 平均 CPU 使用量为 500m,目标平均使用量为 200m,那么根据公式:
期望副本数 = ceil(3 * 500 / 200) = ceil(7.5) = 8HPA 会把 Deployment 的副本数从 3 调整到 8。这个过程是“按比例推测”的:如果每 Pod 用量是目标的 2.5 倍,那就大约需要 2.5 倍数量的 Pod,才能把平均值压回目标线附近。
但公式并不是直接算出多少就立刻设置多少,控制器代码里还存在一个“数量级限制”。官方为了平滑伸缩,在 v1 和 v2 的实现里,每次扩容最多把副本数翻倍;如果没有现成的 Pod,首次扩容可以到 maxReplicas。缩容也有限制:每次缩容最大只会缩掉一半副本。这是为了保护系统,避免指标波动时副本数像过山车一样大起大落。尤其对于有突发流量但持续时间很短的业务,一次从 3 副本直接扩到 100 副本会造成巨大浪费,而“最多翻倍”的策略相当于给扩容加了一层节流阀。
需要注意,公式里“当前指标平均值”的采集窗口并不是实时瞬时值,而是控制器在--horizontal-pod-autoscaler-tolerance范围内做比较。默认 tolerance 是 0.1,也就是当期望副本数与当前副本数的比例为 0.9 到 1.1 之间时,HPA 认为“当前副本数基本满足需求”,不会做任何调整。这就是为什么很多场景下 CPU 已经 55% 了,HPA 却依然没有反应——因为目标 50%,偏差 10% 以内属于死区。想更敏感,可以调整 kube-controller-manager 启动参数,但生产环境建议不要轻易调小,否则极易抖动。
2.3 两种度量方式:直接值与利用率
HPA v2 的配置里,目标指标可以写成两类。第一类是“直接值”,比如 target.averageValue 或 target.value,目标是一个绝对数字。对于 Pod 内上报的自定义指标,比如“每个 Pod 当前有 20 个请求在排队”,target 就可以设为 10 或 3 这类绝对值。控制器的计算方式很简单:当前所有 Pod 的指标值求和后再除以 Pod 数,然后和 target 的平均值比较。
第二类是“利用率”,主要用于 CPU 和内存,配置为 target.averageUtilization。利用率是“当前使用量 / 当前 Pod 的资源请求量”的百分比。比如 Pod 的 CPU request 是 100m,实际使用 80m,那么当前利用率就是 80%。HPA 对这种指标计算期望副本数时,公式不变,只需要把“当前指标平均值 / 目标平均值”替换成“当前平均利用率 / 目标平均利用率”。
为什么利用率一定要基于 request 而不是 limit?因为 request 是调度器用来保证 Pod 可获得最小资源的依据,也是 Kubernetes 给 Pod 承诺的“起步配额”。limit 只是容器不能超过的上限,拿 limit 算利用率会让结果失真:一个 Pod limit 很大但实际用量很小,算出来的利用率和它真实压力完全不成正比。所以配置 HPA 的 CPU/内存指标之前,先确认你的 Deployment 里每个容器的 resources.requests 都写得清清楚楚,否则 HPA 八九不离十会报“no metrics returned”。这一点我在后面“常见问题”里还会反复强调。
3. 从零配置一个 HPA 实例
3.1 准备 metrics-server 或对应的指标采集层
在配置 HPA 之前,先确认集群里有没有安装指标采集组件。对于 CPU 和内存这类资源指标,最常用的是 metrics-server。它不是 Prometheus,不做长期存储,只提供当前节点和 Pod 的瞬时资源数据。把 metrics-server 装进集群后,API Server 才会出现 metrics.k8s.io 这个聚合 API,kubectl top nodes和kubectl top pods也才能正常使用。
验证方法很简单,执行两条命令:
kubectl top nodes kubectl top pods -A如果都能返回 CPU 和内存数据,说明 metrics-server 工作正常。如果你用的是云厂商托管 Kubernetes 集群,大部分都已经默认集成了 metrics-server,这时候可以跳过这一步。如果你的业务指标是 QPS、队列深度、gRPC 连接数这类自定义数据,就需要额外部署 Prometheus,并配置 Prometheus Adapter,让它把 Prometheus 里的指标通过 custom.metrics.k8s.io API 暴露出来。这个链路比 metrics-server 复杂很多,我后面会单独说配置要点。
3.2 创建带资源请求的 Deployment 和服务
很多新手把 HPA 配置得漂漂亮亮,但 Deployment 里的容器没有写 resources.requests,结果 HPA 一直无法获取 CPU 利用率。所以我建议你先在测试环境里把一个最简单的 Deployment 跑起来,并且把 request 写好。下面这个配置是一个标准的无状态 Web 服务:
apiVersion: apps/v1 kind: Deployment metadata: name: web-demo namespace: default spec: replicas: 1 selector: matchLabels: app: web-demo template: metadata: labels: app: web-demo spec: containers: - name: web image: registry.example.com/web-demo:latest ports: - containerPort: 8080 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi这里我特意给容器设置了requests.cpu: 100m。HPA 在计算 CPU 利用率时,会把 Pod 实际用量除以 100m 再乘 100%,得到当前的利用率百分比。如果没有任何 request,HPA 就缺少计算分母,cached 数据也找不到有效的指标来源,事件里就会出现failed to get cpu utilization或missing request for cpu这类报错。
为了便于测试,再创建一个 Service,否则你只能从集群外部或通过临时 Pod 压测。你可以用最简单的 ClusterIP 类型:
apiVersion: v1 kind: Service metadata: name: web-demo-svc spec: selector: app: web-demo ports: - port: 80 targetPort: 80803.3 创建 HPA 并理解每个字段
现在创建 HPA 对象。这里我给出的是 autoscaling/v2 版本,这是当前 HPA 功能的完整版本,支持多指标、自定义指标、行为控制等高级特性。v1 只支持 CPU,不要在新环境里使用了。
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-demo-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-demo minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50 behavior: scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 15 - type: Pods value: 4 periodSeconds: 15 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 100 periodSeconds: 60拆开解释一下:
scaleTargetRef指向要扩缩容的工作负载。我这里的kind是 Deployment,所以 HPA 只会修改这个 Deployment 的.spec.replicas字段。minReplicas和maxReplicas划定副本数边界。生产建议 minReplicas 至少 2,保证高可用;如果成本敏感,测试环境可以 1。metrics数组里的type: Resource表示这是资源指标;name: cpu指定指标名;target.type: Utilization表示按利用率算;averageUtilization: 50表示目标是所有 Pod 的平均 CPU 利用率 50%。behavior里我定义了扩容策略和缩容策略。扩容时,type: Percent, value: 100表示每次最多扩容当前副本数的 100%,比如当前 4 个副本,最多扩展到 8 个;type: Pods, value: 4表示每次最多增加 4 个 Pod。两条策略同时给出,selectPolicy: Max表示选二者中允许值更大的那次操作。stabilizationWindowSeconds是稳定窗口,缩容我保留 300 秒,也就是 5 分钟内连续满足缩容条件才会真正缩容。这个值对防止抖动很关键。
创建命令:
kubectl apply -f hpa.yaml kubectl get hpa web-demo-hpa如果能正常运行,kubectl get hpa的TARGETS列会显示类似5%/50%的信息,前面是当前值,后面是目标值。
3.4 压测与观察:确认扩容真的会发生
要验证 HPA 是否正常工作,必须人为制造压力。我更推荐用专门的压力测试工具,比如hey、wrk、ab,或者直接在集群内跑一个临时 Pod,循环请求 Service。以hey为例:
kubectl run load-generator --image=registry.example.com/hey --restart=Never -- \ -z 5m -c 20 -q 500 http://web-demo-svc这里重点不是工具本身,而是你要注意观察。用另一个终端持续查看:
kubectl get hpa web-demo-hpa -w kubectl get deploy web-demo -w kubectl top pods -l app=web-demo正常情况下,流量拉起来后,CPU 利用率会迅速超过 50%,然后 HPA 开始逐步增加副本数。因为我在 behavior 里把稳定窗口设为 0、每次扩容最多增加 4 个 Pod,你会发现副本数像台阶一样往上爬,而不是瞬间跳到最大。流量结束后,缩容需要等 5 分钟稳定窗口,这期间副本数保持不变,之后逐渐回落到 minReplicas。
3.5 kubectl 命令行方式:能省则省的手工场景
如果想快速测试或临时调整,也可以用kubectl autoscale创建 HPA:
kubectl autoscale deployment web-demo \ --min=2 \ --max=10 \ --cpu-percent=50这个命令只创建 CPU 利用率 50% 的 HPA,自动使用 autoscaling/v2 API。它生成的 metric 配置和你手写的averageUtilization: 50是一样的。命令行方式适合临时测试,但生产环境建议把 YAML 文件提交到 Git,方便 review 和回滚。我自己的习惯是:集群里的 HPA 全部由 GitOps 管理,任何人不得手工kubectl autoscale,避免线上配置漂移。
4. 核心细节与配置项拆解
4.1 autoscaling/v2 与 v1 的差异
Kubernetes 1.18 之后,HPA 的稳定版本就是 autoscaling/v2,v2beta2 已经废弃。v1 的 HorizontalPodAutoscaler 只支持 CPU 这一个指标,且不能配置 behavior。如果你在网上找到的教程还是apiVersion: autoscaling/v1、字段里写着minReplicas/maxReplicas/targetCPUUtilizationPercentage,那大概率是旧资料,别直接抄。v2 统一了指标结构,把 CPU、内存、自定义指标、外部指标都放在metrics数组里,结构更清晰。迁移也不难:把 v1 改造成 v2 的 CPU 指标写法,只需要把targetCPUUtilizationPercentage替换成 metrics 数组中的 Resource 类型。
4.2 目标指标类型:Resource、Pods、Object、External
HPA v2 的metrics数组里可以配置四种类型,每种类型对应不同的指标来源:
Resource对应 metrics.k8s.io,主要就是 CPU 和内存。target 支持Utilization和AverageValue。内存不能用 Utilization 吗?实际上averageUtilization也支持内存,但内存利用率容易出现“回收后回落”的问题,生产里用内存作为唯一扩缩容指标要格外小心。Pods对应 custom.metrics.k8s.io,指标必须来自 Pod 本身,而且每个 Pod 上报的指标是独立的。target 只支持AverageValue。比如每个 Pod 的当前任务队列长度为 10,希望平均不超过 3,就配置为target.averageValue: 3。Object对应 custom.metrics.k8s.io,它描述某个自定义对象的指标,比如 Ingress 的 QPS、Service 的请求延迟,不直接挂在 Pod 上。配置里要同时写describedObject来指定对象类型和名称。External对应 external.metrics.k8s.io,适合从集群外部读取指标,比如云厂商的消息队列堆积量、SLO 监控指标等。
它们在 API 对象上的写法差异比较大,我贴一个Object类型的示例:
metrics: - type: Object object: describedObject: apiVersion: networking.k8s.io/v1 kind: Ingress name: web-ingress metric: name: nginx_ingress_controller_requests_per_second target: type: Value value: 1000这是让 HPA 根据 Ingress 上的总 QPS 来决定副本数,目标 1000 QPS。注意这里的指标名必须是你注入到 Kubernetes 自定义指标 API 里的真实 metric 名称,不是 Prometheus 里的原始指标名。Adapter 层需要做一次映射。
4.3 多指标组合:HPA 如何决定最终副本数
一个 HPA 对象可以在metrics数组里写多个指标。比如同时配置 CPU 利用率和自定义 QPS。控制器会对每个指标分别计算“期望副本数”,最后取所有结果里的最大值。这个设计很符合业务直觉:只要其中一个指标已经“喊渴”,就应该扩容,否则单看 CPU 可能掩盖了请求积压。
举个例子,Deployment 当前副本数是 4。按 CPU 利用率计算,期望副本数是 6;按 QPS 平均值算,期望副本数是 9。那么 HPA 最终会把副本数调整到 9。缩容时也是一样,必须所有指标的期望副本数都低于当前副本数,才可能触发缩容。这可能导致某些场景下副本数“下不来”——比如 CPU 已经很低了,但自定义指标仍然偏高。遇到这种情况不能靠调 HPA 的 minReplicas 硬压,应该先查业务指标为什么居高不下。
4.4 behavior 字段:缩容保护与扩容限速的正确写法
behavior是 v2 引入的一等公民字段,它分scaleUp和scaleDown两个子块。每块都由stabilizationWindowSeconds、policies和selectPolicy组成。很多人看见这三个属性就晕,我用一句大白话总结:稳定窗口是“观望多久”,policies 是“一次最多动多少”,selectPolicy 是“多个策略同时生效时听谁的”。
稳定窗口的作用非常直观。默认缩容稳定窗口是 300 秒,扩容稳定窗口是 0 秒。为什么缩容要观望 5 分钟?因为流量波峰过去后,指标下降往往是缓慢且波动的,立刻缩容会导致副本数跟着流量一起震荡,结果就是集群资源利用率很低,但 Pod 一直在反复创建和删除。设置了稳定窗口后,控制器会记住这段时间内所有“期望副本数”,在窗口内只倾向于选择最小合理值或最大合理值。具体行为可以在官方 behavior API 文档里看到,但普通使用只需要记结果:
scaleDown.stabilizationWindowSeconds: 300表示 5 分钟内不会被马上缩到低水位。scaleUp.stabilizationWindowSeconds: 0表示指标一上来,扩容不等待。
policies是策略列表。每条策略有type和value。type 可以是Pods或Percent。periodSeconds表示“在一个周期内最多执行多少次”。比如type: Percent, value: 100, periodSeconds: 15,意思是每 15 秒允许增加 100% 当前副本数,也就是每次最多翻番。如果你把value: 4, type: Pods写成一个 Pods 策略,它和 Percent 策略并存,HPA 会按selectPolicy决定。selectPolicy有三个候选值:Max选较大操作、Min选较小操作、Disabled表示这种方向上的伸缩被完全禁止。Disabled很不常用,但有些团队为了“只扩容不缩容”会这么配,我不建议,因为那是掩耳盗铃,还不如直接把 minReplicas 调到期望值。
4.5 和其他控制器协同:PDB、ClusterAutoscaler 与资源配额
HPA 不是 Kubernetes 里唯一的伸缩机制,它需要和几个“邻居”配合好。
PodDisruptionBudget(PDB)用于控制自愿驱逐时最多能中断多少 Pod。当你缩容时,HPA 不会直接删除 Pod,而是修改 Deployment 的 replicas,随后 ReplicaSet 控制器按一定顺序终止多余的 Pod。如果集群里有节点维护、Spot 实例回收、蓝绿发布这类操作,PDB 会约束“最多允许几个 Pod 同时不可用”。假如 HPA 想把副本从 10 缩到 2,但 PDB 限制最多只能有 1 个 Pod 同时中断,那么缩容过程会变得很慢,甚至长时间停在中间副本数。这不是故障,而是保护机制在起作用。生产上你要预期到这种互相折衷。
ClusterAutoscaler 是节点层弹性的关键。HPA 扩容出来的 Pod 如果当前节点资源不足,会卡在 Pending 状态。ClusterAutoscaler 会观察 Pending Pod,触发节点池扩容。反过来说,HPA 缩容之后节点负载下降,ClusterAutoscaler 也可能缩容节点。二者配合得好,整个集群就是“应用压测涨 Pod,节点不够涨机器”的完整弹性链路;配合得不好,会出现“HPA 想扩容,但节点资源不够,Pod 一直Pending,越挤压越严重”的尴尬。我见过很多私有云环境没装好 ClusterAutoscaler,HPA 扩容到接近资源上限后就卡死,所有新 Pod 都在 Pending,这时候先排查节点可用资源,而不是怀疑 HPA 配置有误。
资源配额(ResourceQuota)也能拦住 HPA 的扩容。如果 namespace 里设置了 CPU request 总配额,HPA 把 replicas 调上去,但 Pod 创建时配额不足,ReplicaSet 会反复创建失败。这种问题非常隐蔽,因为 HPA 的状态还是正常的,副本数也显示已更新,但实际可用副本数根本不够。排查时一定要看 ReplicaSet 的事件或者kubectl get events里的FailedCreate记录。
5. 实战遇到的坑和排查技巧实录
5.1 指标没数据:HPA 报 no metrics returned
我在测试环境最常遇见的 HPA 问题是kubectl describe hpa的 Events 里出现failed to get cpu utilization: unable to get metrics for resource cpu: no metrics returned from the metrics API。这几乎都是因为 metrics-server 没正常工作,或者容器没有 resources.requests。
先按这个顺序排查:
kubectl get apiservice v1beta1.metrics.k8s.io kubectl top nodes kubectl top pods如果kubectl top nodes正常,说明 metrics-server 向 API Server 注册成功,数据链路没问题。接着看 Deployment 里的 Pod 是否所有容器都写了 resources.requests。注意,如果一个 Pod 里有多个容器,HPA 的 CPU 利用率会按所有容器 request 之和作为分母,所有容器当前用量之和作为分子。有一个容器没写 request,都会导致整个 Pod 的利用率无法计算。
5.2 指标值波动导致副本抖动
HPA 的默认 tolerance 是 0.1,也就是目标值和当前值差距在 10% 以内时不动。但即使有 tolerance,指标峰谷交替明显时,copy 还是可能出现“扩 2 个、缩 1 个、再扩 2 个”的抖动。这个问题的解法不是把 sync-period 调大,而是把缩容稳定窗口调大。我给生产环境定的经验值是scaleDown.stabilizationWindowSeconds: 600,也就是流量下降后至少观望 10 分钟再缩容。如果你是 CPAU 类型的高延迟应用,还可以把扩容稳定窗口也设置成 60 秒,避免一次性扩容太过激进。
5.3 单副本无法扩容
有一种现场:Deployment 副本数是 1,HPA 的目标是 CPU 50%,当前 CPU 已经 90%,但 HPA 就是不扩容,TARGETS 里显示<unknown>/50%或90%/50%,事件里也没有任何 action。原因可能是 HPA 控制器第一次同步的时候拿不到 Pod 的指标,Pod 处于启动过程中,而 controller-manager 有--horizontal-pod-autoscaler-initial-readiness-delay参数,默认 30 秒。在这 30 秒内,Pod 即使已经 Ready,HPA 也不会用它的指标作为计算依据。另外还有一个常见原因:你的 Deployment 的 Pod 被某种控制器(比如另外一个人工kubectl scale操作)改过副本数,导致 HPA 认为当前副本数已经不是它期望的数值,暂时收敛到新的基线。这个问题不容易一眼看出,但只要等待下一次 sync 周期,HPA 会自己修正。
5.4 利用率计算与你预期不一致
我见过很多次:业务方说 CPU 用了 200%,但 HPA 显示 50%。这类问题几乎都出在 request 设置上。HPA 是拿“当前 CPU 使用量 / 当前 CPU request”算利用率的。如果你的容器 request 是 1 核,实际使用 2 核,利用率就是 200%。如果你的容器 request 是 2 核,实际使用 2 核,利用率就是 100%。业务觉得“CPU 很高”,是因为只看了容器实际使用量和 limit 的关系,而 HPA 看的是 request。想对齐认知,要么统一以 request 作为资源成本口径,要么干脆改用target.averageValue而不是averageUtilization。用 averageValue 的话,公式直接变成“当前每 Pod 平均使用量 / 目标平均使用量”,不再受 request 影响。比如你想让每 Pod 平均 CPU 不超过 250m,就可以写:
metrics: - type: Resource resource: name: cpu target: type: AverageValue averageValue: 250m这招非常实用,尤其适合团队里对“利用率”概念理解不一致的场景。
5.5 与资源 limit 的关系:limit 不参与计算,但会影响调度
上面的公式我说了很多次 request 是分母,接下来要说 limit 的作用。limit 虽然不参与 HPA 计算,但影响运行稳定性。如果 Pod 里没设置 limit,或者 limit 设得很大,而业务压力又一直上涨,HPA 扩容速度可能跟不上 CPU 爆掉的速度。尤其很多 Java 类应用,内存压力上来后开始 Full GC,CPU 飙升,HPA 还没来得及扩容,Pod 已经被 OOMKilled 或宿主机压垮。所以我的建议是:
- request 尽量贴近真实日常使用,不要拍脑袋。
- limit 至少留 30% 到 50% 冗余。
- 针对 CPU 密集型业务,最好同时配置 CPU 和内存两类 HPA 指标,避免只看 CPU 忽略了内存维度。
5.6 快速定位命令合集
如果你手里的 HPA 表现诡异,我推荐按下面顺序执行:
kubectl get hpa -A kubectl describe hpa web-demo-hpa kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" | head kubectl get --raw "/apis/autoscaling/v2/namespaces/default/horizontalpodautoscalers/web-demo-hpa/status" | jq . kubectl get events -n default --sort-by='.lastTimestamp' | grep -i hpa kubectl logs -n kube-system -l k8s-app=metrics-server --tail=200第一条看状态,第二条看事件,第三条确认 Pod 级 metrics API 有没有返回数据,第四条直接看 HPA 的 status 子资源里保存的最近一次计算记录,第五条找集群事件,最后一条看 metrics-server 自己的日志。这套命令在大多数疑难杂症里都能定位到七七八八。
5.7 常见问题速查表
| 现象 | 常见原因 | 快速解法 |
|---|---|---|
HPA 显示<unknown>/50% | metrics-server 不可用 | kubectl top nodes验证,检查 apiservice |
| 一直无法扩容 | Deployment 容器缺 resources.requests | 补全 requests.cpu |
| 扩容过慢 | 扩容策略限速 | 调整 behavior.scaleUp.policies |
| 缩容过快导致抖动 | 稳定窗口太小 | 设置 scaleDown.stabilizationWindowSeconds |
| 副本停在中间数不缩 | PDB 限制中断数量 | 查看 PDB 状态和 maxUnavailable |
| 新 Pod 一直 Pending | 节点资源不足或配额限制 | kubectl describe pod看事件 |
| HPA 状态但无生效副本数 | ReplicaSet 创建失败 | kubectl get rs -o yaml看条件 |
这张表没办法覆盖所有情况,但能覆盖我实际运维里 80% 的 HPA 问题。剩下那 20%,大多和你的自定义指标采集链路有关,得从 Prometheus Adapter 的日志和 custom.metrics API 返回结果里一层层往下查。
做 Kubernetes 弹性伸缩这几年,我自己最大的体会是:HPA 本身不是一个“配置完就万事大吉”的组件,它像是一个精密的控制系统,你给它什么数据,它就给你什么行为。指标不准确、request 设置不合理、稳定窗口配置过短,都会让它“看起来不太聪明的样子”。如果你能先把 CPU 指标跑通,理解期望副本数公式,再一步步加上自定义指标和 behavior 控制,HPA 就会变成一个非常可靠的自动运维帮手。最后再分享一个小建议:任何 HPA 改动上线前,都要像改业务代码一样做一次小流量压测,毕竟副本数背后都是真金白银的资源账单。