当流量在凌晨突增三倍,你是被报警叫醒手动 kubectl scale,还是希望集群自己把 Pod 拉起来?Kubernetes HPA(Horizontal Pod Autoscaler,水平自动扩缩容)正是解决这个问题的原生方案:它根据 CPU、内存或自定义指标,自动调整 Deployment 的副本数,让服务在高峰自动扩容、低谷自动缩容。本文从原理到 yaml 配置,带你把 HPA 真正跑起来,并避开内存指标、缩容抖动等常见坑。
为什么需要 HPA
固定副本数(比如永远 3 个 Pod)要么在流量高峰扛不住、要么在深夜白白烧钱。kubectl scale 手动扩缩依赖人肉判断,既慢又容易遗漏。HPA 把”扩缩容”这件事变成声明式配置:你告诉集群”CPU 超过 60% 就加 Pod,最少 2 个、最多 10 个”,剩下的交给控制器循环。它和 Kubernetes 的 Deployment / Pod 基础 配合,是绝大多数在线服务弹性能力的基石。
HPA 工作原理:指标从哪来
HPA 控制器默认每 15 秒轮询一次 Metrics API,拿到当前 Pod 的平均利用率,再按目标值反推期望副本数:期望副本 = ceil(当前副本 × 当前指标 / 目标指标)。这里的”指标”有三个来源层级,理解它们决定了你能用 HPA 做什么:
| 指标类型 | 来源 | 典型场景 |
|---|---|---|
| Resource(CPU/内存) | metrics-server(核心指标) | Web 服务随负载扩缩 |
| Pod / Object(自定义) | Prometheus Adapter 等(Custom Metrics) | 按 QPS、队列长度扩缩 |
| External | KEDA 等(外部事件) | 按 Kafka 积压、消息数扩缩 |
最常见也最稳妥的是第一层的 Resource 指标,它依赖 metrics-server 把每个节点的 kubelet 指标聚合起来。没有它,HPA 的状态会永远卡在 unable to fetch metrics。
前置准备:部署 metrics-server
大部分托管集群(ACK、EKS、GKE)已预装 metrics-server;自建集群用下面一条命令即可:
# 标准安装(国内可换用阿里云镜像对应的 yaml)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 若 kubelet 使用自签证书,需加 --kubelet-insecure-tls 参数
kubectl edit deployment metrics-server -n kube-system
# 在 args 中追加:
# - --kubelet-insecure-tls
装好后验证指标是否就绪——能列出节点和 Pod 的实时 CPU/内存,说明采集链路通了:
kubectl top nodes
kubectl top pods -n default
# 期望输出示例:
# NAME CPU(cores) MEMORY(bytes)
# node-1 320m 1800Mi
基于 CPU 的 HPA(核心实战)
先准备一个可被压测的 Deployment,再挂上 HPA。注意:HPA 要求 Pod 必须配置 resources.requests,否则控制器算不出利用率百分比。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
spec:
containers:
- name: web
image: nginx:1.27
resources:
requests: { cpu: "100m", memory: "64Mi" }
limits: { cpu: "500m", memory: "128Mi" }
接着创建 HPA:目标平均 CPU 利用率 60%,副本区间 2–10。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容前观察 5 分钟,避免抖动
用 kubectl get hpa 观察实时状态,TARGETS 列会显示当前利用率 / 目标值:
真实压测时的曲线通常是这样的:压测开始后 30 秒,利用率从 45% 爬到 85%,HPA 在第 2 个采集周期把副本从 2 拉到 4;随着 Pod 变多,单 Pod 平均 CPU 回落到 62%,于是稳定在 4 个副本。停掉压测后,利用率跌回 20%,但因为有 300 秒缩容稳定窗口,副本不会立刻减半,而是每分钟最多退 1 个,5 分钟后才回到 2。这条”快扩慢缩”的曲线,正是 behavior 字段在起作用。
kubectl get hpa web-hpa -w
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# web-hpa Deployment/web 45%/60% 2 10 2
# 压测制造 CPU 负载(换你的 Service 地址)
hey -z 60s -c 50 http://web.default.svc.cluster.local/
kubectl get hpa web-hpa
# 几轮后 REPLICAS 会从 2 涨到 4、6……直到利用率回落到 60% 附近
基于内存的 HPA(以及为什么它很危险)
内存和 CPU 不同:内存不会随请求结束而”自然回落”,Go/Java 进程常常占用后不释放。因此用内存做扩容指标尚可,做缩容指标极易误杀正在服务的 Pod。如果一定要用,务必配合较长的缩容稳定窗口:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: cache-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: cache
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75 # 仅在内存紧张时扩容,缩容靠 behavior 兜底
经验法则:内存 HPA 只用于”扩容抗住”,缩容节奏交给 stabilizationWindowSeconds 拉长,避免 Pod 被频繁杀掉导致连接抖动。
自定义指标 HPA:用 Prometheus Adapter 接入 QPS
很多服务 CPU 没满但连接数/QPS 已经爆了,这时 Resource 指标不够用。通过 Prometheus 监控 采集指标,再用 prometheus-adapter 把 PromQL 暴露成 Custom Metrics API,HPA 就能按 QPS 扩缩:
# prometheus-adapter 的 ConfigMap 片段:把 http 请求率映射成自定义指标
rules:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources: { overrides: { namespace: {resource: "namespace"}, pod: {resource: "pod"} } }
name: { matches: "http_requests_total", as: "requests_per_second" }
metricsQuery: 'sum(rate(http_requests_total{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)'
adapter 注册成功后,HPA 直接引用这个指标名:
metrics:
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: "500" # 每个 Pod 平均承载 500 QPS
扩缩容行为精细控制(behavior 字段)
behavior 是 HPA 最被低估的字段。它能分别控制扩容(scaleUp)和缩容(scaleDown)的速度与稳定窗口,直接决定线上是否”抖动”。常用参数如下:
| 参数 | 作用 | 建议值 |
|---|---|---|
| stabilizationWindowSeconds | 缩容前观察期,避免指标瞬时回落就杀 Pod | 300(5 分钟) |
| scaleUp.selectPolicy | 多指标时取最大/最小扩容量 | Max |
| scaleDown.policies[].periodSeconds | 缩容速率,如 60s 内最多减 1 个 | 100%/60s 或 1/60s |
| maxReplicas / minReplicas | 副本上下限,防止无限扩张烧钱 | 按容量评估 |
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent # 一次最多扩 100%
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
selectPolicy: Min
policies:
- type: Pods # 5 分钟内最多缩 1 个,平稳退场
value: 1
periodSeconds: 300
常见坑与排障清单
| 现象 | 根因 | 修复 |
|---|---|---|
| HPA 状态 unknown / unable to fetch metrics | metrics-server 未装或 kubelet 证书问题 | 部署 metrics-server,加 –kubelet-insecure-tls |
| 副本数不变 | Pod 未设 resources.requests,算不出利用率 | 给容器补 requests.cpu |
| 扩容后立刻缩回 | 缩容稳定窗口太短,指标抖动 | scaleDown.stabilizationWindowSeconds 调到 300+ |
| 内存 HPA 频繁重启 Pod | 进程不释放内存,被误判可缩 | 只做内存扩容,缩容交给长窗口 |
| 自定义指标找不到 | adapter 未注册到 Custom Metrics API | kubectl get –raw /apis/custom.metrics.k8s.io 验证 |
把 HPA 接进 Grafana 监控面板,把副本数与 CPU/QPS 画在同一张图,能最直观地确认扩缩容是否按预期工作。流量入口侧配合 Nginx 反向代理做负载均衡,扩出来的 Pod 才能被均匀分摊。整套弹性发布链路也能用 GitHub Actions CI/CD 自动提交 yaml 上线。
节点层弹性:HPA 之外别忘了 Cluster Autoscaler
HPA 只解决”Pod 数量”,当副本涨到 maxReplicas 仍不够、或节点资源被占满时,新 Pod 会卡在 Pending。这时需要 Cluster Autoscaler(CA) 在节点层扩容:当调度器找不到足够资源的节点,CA 自动往集群加机器。HPA 管”横向加 Pod”,CA 管”横向加节点”,两者配合才是完整的弹性闭环。在阿里云 ACK 上,CA 通过节点池的自动伸缩开关启用,并依赖 Pod 设置了正确的 resources.requests 才能算出资源缺口。
一个常见反模式是:只配了 HPA 却忘了给节点池开 CA,流量一来 Pod 疯狂扩容却全部 Pending——利用率曲线好看,服务照样挂。上线前务必确认这两条链路都通。
上线前 Checklist
| 检查项 | 为什么 |
|---|---|
| Pod 已设 resources.requests | 否则 HPA 无法计算利用率百分比 |
| metrics-server 就绪,kubectl top 正常 | 指标采集链路的前置条件 |
| 缩容稳定窗口 ≥ 300s | 避免流量毛刺误杀正在服务的 Pod |
| maxReplicas 按容量评估设定 | 防止副本无限扩张导致成本失控 |
| 节点池 CA 已开启(高负载场景) | Pod 扩容后需要节点资源承接 |
小结
HPA 是 Kubernetes 弹性的第一道闸:先用 metrics-server + CPU 指标跑通基础扩缩,再用 behavior 字段压住缩容抖动,最后在 QPS 类场景用 Prometheus Adapter 接自定义指标。记住三件事——Pod 必须设 resources.requests、内存指标只扩不急缩、缩容稳定窗口宁长勿短。弹性能力本地的容器基座可以先用 Docker Compose 一键编排 验证,再上集群接管。




