当流量像潮汐一样起伏时,K8s HPA(Horizontal Pod Autoscaler,水平 Pod 自动伸缩)能让 Deployment 根据负载自动增减副本,而亲和性调度又决定了这些副本落在集群的什么位置。两者配合,才能既”弹性”又”高可用”。本文用可复制的 YAML,拆解从指标采集到弹性伸缩、再到拓扑感知的完整链路。
一、为什么需要 HPA 与亲和性调度
固定副本数(比如始终 3 个 Pod)在真实业务里两头不讨好:流量低时资源空转烧钱,流量尖峰时单 Pod 被打满、请求排队超时。HPA 解决的是”副本数随负载自动变化”的问题;但它只管数量,不管落点——如果 10 个副本因为调度策略全挤在同一台节点上,节点一挂就是全灭。这时候就需要亲和性与反亲和性来把副本”打散”到不同宿主机、可用区,实现拓扑感知。
一句话总结分工:HPA 决定”有几个”,亲和性决定”在哪几个”。下文先讲伸缩原理,再讲调度,最后讲协同。
举个电商场景:大促零点流量瞬间涨 5 倍,若靠人工改 Deployment 的 replicas,等响应过来早已超时崩盘;而 HPA 在 15 秒内就能把副本从 3 拉到 15。但如果没有反亲和性,这 15 个副本很可能被调度器塞进仅有的 2 台空闲节点,任意一台宕机就损失近半容量。把”自动伸缩”和”打散容灾”同时做对,才是生产级用法。
二、HPA 核心原理:指标 → 计算 → 调副本
HPA 控制器以固定周期(默认 15 秒)从 Metrics 接口拉取目标指标,按公式算出期望副本数:
期望副本数 = ceil( 当前副本度量值 / 目标度量值 )
# 例:当前 4 个 Pod 平均 CPU=240m,目标=60m/副本 → 期望 = ceil(4 * 240 / 60) = 16
# 但受 minReplicas / maxReplicas 夹逼,最终取 [2, 10] 区间内的 10
指标来源分两类:Resource(CPU/内存,由 metrics-server 提供,开箱即用)和自定义/外部指标(QPS、队列长度、Prometheus 指标,需额外适配器)。先用最简单也最常用的 CPU 指标跑通流程:
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: 60
部署后立刻观察,重点关注 TARGETS 是否从 <unknown> 变成具体百分比——如果是 unknown,多半是 metrics-server 没装或权限没给:
# 查看当前伸缩状态
kubectl get hpa web-hpa -n default
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
# web-hpa Deployment/web 45%/60% 2 10 3 5m
# 看详细事件与计算依据
kubectl describe hpa web-hpa
# 导出当前配置核对
kubectl get hpa web-hpa -o yaml
三、基于 CPU/内存的 HPA 配置实战
Resource 类指标最稳,适合”计算密集型”服务。注意 CPU 的目标一般用利用率百分比;内存不建议用利用率(内存回收慢、抖动大),若要用请用平均使用量(AverageValue)而非百分比。下面这张表梳理四种指标来源的差异:
| 指标类型 | 数据来源 | 适用场景 | 额外组件 |
|---|---|---|---|
| Resource | metrics-server | CPU/内存这类容器原生指标 | 无需 |
| Pods | 自定义指标适配器 | 每个 Pod 自定义指标(如每秒请求数) | 需 adapter |
| Object | 自定义指标适配器 | 某个 K8s 对象指标(如 Ingress 流量) | 需 adapter |
| External | 自定义指标适配器 | 集群外指标(如消息队列积压、云监控) | 需 adapter |
内存用 AverageValue 的写法:
metrics:
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 512Mi
四、基于自定义指标的 HPA:以 QPS 为例
真正业务里,CPU 常常”扛得住但响应慢”——瓶颈在下游依赖而非算力。此时让”每秒请求数(QPS)”驱动伸缩更贴合。这依赖 Prometheus Adapter 把 Prometheus 指标暴露成 K8s 自定义指标 API。先定义一条映射规则:
apiVersion: v1
kind: ConfigMap
metadata:
name: adapter-config
namespace: monitoring
data:
config.yaml: |
rules:
- seriesQuery: 'http_requests_total'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
metricsQuery: 'sum(rate(http_requests_total{namespace="default"}[2m])) by (pod)'
规则生效后,HPA 就可以引用这个外部指标:
metrics:
- type: External
external:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
这样每个 Pod 平均 QPS 超过 100 就扩容,低于就缩容,比单纯看 CPU 更贴近”用户体感”。延伸阅读:Prometheus + Grafana 监控面板正是这套指标体系的底座。
五、亲和性与反亲和性:让副本”打散”
默认调度器只保证”资源够放”,不保证”别放一起”。要让同一服务的多个副本分散到不同节点,用 podAntiAffinity(Pod 反亲和性)。下面这张表区分三种亲和性:
| 类型 | 作用 | 典型用途 |
|---|---|---|
| nodeAffinity | 把 Pod 调度到特定节点 | SSD 节点跑数据库、GPU 节点跑推理 |
| podAffinity | 把 Pod 调度到”已有同类 Pod”的节点 | 同服务就近部署降低延迟 |
| podAntiAffinity | 避免 Pod 与同类 Pod 同节点 | 打散副本提升容灾 |
最常用的”反亲和性打散”写法,preferred 表示尽量满足但不强求(避免资源不足时无法调度):
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["web"]
topologyKey: kubernetes.io/hostname
topologyKey: kubernetes.io/hostname 表示以”节点”为拓扑域——同一节点不算数就不算打散。若要跨可用区,可换成自定义的 zone 标签(如 topology.kubernetes.io/zone)。
六、HPA + 亲和性协同:弹性与高可用兼得
把两者写进同一个 Deployment,就能同时获得”自动扩缩”和”副本分散”:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["web"]
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx:1.27
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
配合第二节的 HPA,流量来时副本从 2 涨到 10,且新副本自动避开已有 web Pod 的节点。关于 Pod 调度与故障排查的底层机制,可回顾 Kubernetes Pod 调度与故障排查实战。
七、常见踩坑与调优
坑 1:缩容太猛引发抖动。HPA 默认扩容快、缩容慢,但节流窗口可以显式拉长,避免指标毛刺导致频繁伸缩:
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容前稳定观察 5 分钟
policies:
- type: Percent
value: 50
periodSeconds: 60 # 每分钟最多缩 50%
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
坑 2:指标 unknown。先确认 metrics-server 就绪,再确认 HPA 的 scaleTargetRef 指向的 Deployment 名字、namespace 完全正确,Resource 指标要求目标有 requests.cpu/memory 声明——没设 requests,HPA 算不出利用率。
坑 3:反亲和性导致无法调度。required(硬性)反亲和性在节点数不足时会让 Pod 一直 Pending;生产环境优先用 preferred,或在缩容下限预留足够节点。集群工具链如 k9s 能实时看到 Pending 原因,Helm 则便于把这些模板参数化复用。
八、监控与可观测
HPA 不是”设完就忘”。建议在 Grafana 上把”副本数、CPU/QPS、目标值”三条线叠在一起,观察伸缩是否跟得上流量;并结合 GitHub Actions 在 CI 里做清单 diff 评审。典型告警:副本长时间贴着 maxReplicas(说明上限该抬高)、缩容长期不触发(说明阈值或指标有问题)。
另一个常被忽视的点:HPA 伸缩有延迟,从指标超标到新 Pod 就绪通常有数十秒到几分钟(受稳定窗口、镜像拉取、就绪探针影响)。对延迟极度敏感的服务,建议在压测里实测”扩容到位时间”,并在入口层(如 Nginx 反向代理)保留一定连接缓冲,避免扩容空窗期直接拒绝请求。监控看板最好同时展示”期望副本数”与”就绪副本数”的差值,差值长期大于 0 即说明扩容跟不上。
九、小结
HPA 让副本数随负载呼吸,亲和性让副本在集群里均匀分布,二者是”弹性伸缩 + 高可用”的黄金组合。落地顺序建议:先用 Resource(CPU)指标跑通 HPA → 接入 Prometheus Adapter 用业务指标(QPS)驱动 → 加 preferred 反亲和性打散 → 用 behavior 稳缩容 → 上 Grafana 看板持续调参。从 Kubernetes 入门到本文的弹性与调度,K8s 生产化的拼图就基本齐了。




