K8s HPA 实战:用 Horizontal Pod Autoscaler 实现自动扩缩容

当流量在凌晨突然翻倍、或大促瞬间涌入数万请求时,靠人工敲 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
副本数永远是 minReplicasMetrics 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——那正是自动化的意义。

上一篇 程序员如何高效阅读开源项目源码:从入门到吃透的实战方法
下一篇 pnpm 实战:用内容寻址优化 Node 依赖管理
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]