当流量在凌晨突然翻倍、或大促瞬间涌入数万请求时,靠人工敲 kubectl scale 永远慢半拍。K8s HPA(Horizontal Pod Autoscaler,水平 Pod 自动扩缩容)就是为解决这个痛点而生的:它根据 CPU、内存或自定义指标,自动增减 Deployment 的副本数,让集群既扛得住峰值、又不在低谷浪费资源。本文用真实 YAML 与压测步骤,带你从零把 HPA 跑通,并避开生产环境最常见的几个坑。
一、为什么需要 HPA:手动扩缩的三处痛点
在引入 HPA 之前,多数团队靠三种方式应对流量波动:白天固定副本数、半夜被报警叫醒手动扩容、或干脆长期开着高配副本“以防万一”。这三种做法分别带来响应慢、人肉苦、成本高的问题。Kubernetes 本身已经把“期望副本数”抽象成了声明式对象,HPA 只是把这个数字交给控制器按指标自动维护——你定义规则,集群自己执行。
从架构演进看,HPA 也补齐了传统调度器(如 Nomad 轻量级调度)在“无状态服务弹性”上的短板:Nomad 更擅长同时编排容器与批处理任务,而 HPA 专注在 K8s 内把一个 Deployment 的副本数随指标连续微调,两者适用面不同但思路一脉相承——把人的判断沉淀成可执行的策略。
更关键的是,HPA 是水平扩展:通过增加 Pod 实例分摊压力,而不是给单个 Pod 加内存(那是 VPA 的活)。水平扩展天然契合无状态服务的架构,也是云原生弹性最成熟的一条路径。结合我们已有的 Kubernetes 集群基础 一起看,HPA 相当于给工作负载装上了一个“会自动喘气”的肺。
二、HPA 工作原理:控制器每 15 秒算一次
HPA 控制器运行在 kube-controller-manager 中,默认每 15s 通过 metrics.k8s.io API 拉取当前指标,套用下面的核心公式算出目标副本数:
desiredReplicas = ceil(currentReplicas * (currentMetric / desiredMetric))
# 例:当前 2 个副本,CPU 实测 80%,目标 50%
# desired = ceil(2 * (80 / 50)) = ceil(3.2) = 4 个副本
公式里有两个前提:第一,指标必须真实可达,所以集群里要先装好 Metrics Server(很多排障失败都卡在这一步);第二,Pod 必须声明 resources.requests,否则 HPA 算不出利用率百分比,会永久停在 <unknown> 状态。关于集群底层存储与 etcd 的健康,可参考 etcd 高可用运维,指标异常时先排除控制面本身的问题。
三、快速上手:基于 CPU 的 HPA
先确认 Metrics Server 已就绪。新版本集群(1.24+)通常用下面的命令一键安装:
# 安装 metrics-server(官方清单)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 验证指标已流动
kubectl top nodes
kubectl top pods -n default
给一个 Deployment 配置“CPU 平均利用率 50% 时自动在 2~10 副本间伸缩”,yaml 如下(注意 autoscaling/v2 是 1.23+ 推荐的 API 版本):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容冷却 5 分钟,避免抖动
应用后观察状态:
kubectl apply -f web-hpa.yaml
kubectl get hpa web-hpa -w
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
# web-hpa Deployment/web 45%/50% 2 10 3 2m
四、压测验证:让副本真的动起来
没有压测的 HPA 等于没验证。在另一个终端用 hey 或 wrk 打流量,观察副本数是否随 CPU 上升:
# 装一个压测工具
go install github.com/rakyll/hey@latest
# 持续 3 分钟、50 并发打 web 服务
hey -z 3m -c 50 http://web.default.svc.cluster.local/
# 另开窗口看 HPA 变化
kubectl get hpa web-hpa -w # REPLICAS 应从 3 涨到 6、8…
监控侧建议同时接 Prometheus + Grafana 看副本数与 QPS 曲线,确认扩缩节奏符合预期,而不是只在报警时才知道“原来早该扩了”。
五、基于内存的 HPA:慎用但有用
内存和 CPU 最大的区别是:内存不可压缩。CPU 飙了限制一下就降,内存涨上去往往不会自动回落,靠 HPA 缩容并不能立刻释放已占内存。所以内存型 HPA 只适合“内存随请求数线性增长且请求结束后能 GC 掉”的场景(如批处理 Worker)。配置与 CPU 几乎一致:
metrics:
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
六、进阶:用 Prometheus Adapter 做自定义指标
CPU/内存只是资源视角,业务真正关心的是“每秒请求数(QPS)”“消息积压长度”“P99 延迟”。这些需要把 Prometheus 指标暴露给 K8s,桥梁就是 Prometheus Adapter。先在 Adapter 的 ConfigMap 里注册一条规则:
# prometheus-adapter ConfigMap 片段
rules:
- seriesQuery: 'http_requests_per_second{pod!=""}'
resources:
overrides:
pod: {resource: "pod"}
name:
matches: "http_requests_per_second"
as: "requests_per_second"
metricsQuery: 'sum(rate(http_requests_total{namespace="default"}[2m])) by (pod)'
然后 HPA 就能基于 QPS 伸缩——这在“单请求很轻、但并发一高就雪崩”的接口上比 CPU 灵敏得多:
metrics:
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: "500" # 每个 Pod 平均 500 QPS 时扩容
七、behavior 字段:控制扩缩的“脾气”
默认 HPA 扩容快、缩容也快,但缩容太快会造成“抖动扩缩”(thrashing)。生产环境一定要用 behavior 给缩容加稳定窗口,并给扩容设单步上限:
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100 # 单次最多翻倍
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300 # 缩容前观察 5 分钟
policies:
- type: Pods
value: 2 # 单次最多减 2 个
periodSeconds: 60
八、常见坑与排障清单
下面这张表覆盖 90% 的 HPA “不动”现场,按出现频率排序:
| 现象 | 根因 | 解法 |
|---|---|---|
| TARGETS 显示 <unknown> | Pod 未声明 resources.requests | 给容器补上 cpu/memory 的 requests |
| 副本数永远是 minReplicas | Metrics Server 没装 / 指标 API 不通 | kubectl top 验证,检查 apiservice 可用 |
| 扩容了但不生效 | 节点资源耗尽,新 Pod Pending | 检查节点可分配资源与 ResourceQuota |
| 频繁抖动扩缩 | 缩容窗口太短 | 调大 scaleDown.stabilizationWindowSeconds |
| 自定义指标不被识别 | Adapter 规则未注册或命名不匹配 | kubectl get –raw /apis/custom.metrics.k8s.io 核对 |
排障时一条命令看到底:
kubectl describe hpa web-hpa
# 关注 Conditions / Events:
# AbleToScale / ScalingActive / ScalingLimited
# 若 ScalingActive=False,多半是 metrics 取不到,顺藤摸瓜查 Adapter。
九、生产落地建议
1)resource.requests 必填:没有 requests,HPA 无从算起,这是头号坑。2)配合就绪探针:扩容后新 Pod 未 Ready 前不要接流量,避免雪崩,可参考 Kyverno 策略守护 做准入校验。3)HPA + VPA 分工:HPA 管副本数、VPA 管单副本规格,二者同时改 requests 容易打架,建议 VPA 只设为 Off/Initial 模式。4)纳入 GitOps:把 HPA yaml 和 Deployment 一起进 Git,用 Argo CD 管理,避免手动改了又回滚。5)和 CI 联动:压测脚本可以挂到 GitHub Actions 里,每次发版后自动验证扩缩行为。
十、HPA 的边界:什么时候它不够用
HPA 不是万能弹性。第一,冷启动慢的服务(如 JVM 大应用、大模型推理 Pod)扩容后新副本要几十秒才能就绪,突发流量下靠 HPA 来不及,需要配合预热、PDB 或预留副本。第二,有状态/单例服务(含主从数据库的写节点)不能简单加副本,HPA 只对无状态 Workload 有意义。第三,指标有滞后:从请求涨到指标上报、再到控制器算完,通常有 30~60 秒延迟,秒级尖峰会丢。
对可预测的周期性流量(如每天 9 点开市、每晚报表),更稳的做法是定时扩缩:用 crontab 或 KEDA Cron 触发器在高峰前主动拉起副本,高峰后交给 HPA 收尾。把“预测式”和“反应式”结合,才是生产里真正扛揍的方案。此外,若扩缩涉及跨节点调度,还要保证集群有足够的节点弹性,必要时接入节点自动伸缩(Cluster Autoscaler)形成“Pod 层 + 节点层”双层弹性。
十一、小结
HPA 是 Kubernetes 弹性能力的“入门必装项”:从 Metrics Server 起步,先用 CPU 跑通闭环,再用 Prometheus Adapter 接入业务指标,最后用 behavior 把扩缩节奏调稳。它不解决所有弹性问题(长任务、有状态、冷启动另说),但能把 80% 的无状态服务从“半夜手动扩容”解放出来。真正吃透之后,你会发现自己越来越少碰 kubectl scale——那正是自动化的意义。




