Kubernetes 自动伸缩:HPA、VPA 与 Cluster Autoscaler
流量来了 Pod 扛不住、流量去了 Pod 浪费。K8s 的三级伸缩(HPA 水平/VPA 垂直/CA 节点)解决不同问题。本文讲清 HPA(按 CPU/自定义指标加减 Pod)、VPA(调资源 request)、Cluster Autoscaler(加减节点),以及冷启动与预测性伸缩的实战。
本文要点
- 主题聚焦:DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Kubernetes 自动伸缩:HPA、VPA 与 Cluster Autoscaler
先说一个我见过无数次的翻车现场:某团队上线一个 API,按"峰值流量 × 3"静态配了 20 个 Pod,白天跑得挺欢,晚上没人了 20 个 Pod 还在那儿烧钱;后来听说有 HPA,挂上去按 CPU 伸缩,结果大促当天 CPU 没怎么涨(瓶颈在 DB 连接数),Pod 根本没扩,直接雪崩。
这两个极端,正是 K8s 自动伸缩要解决的。但很多人对它的认知停留在"HPA = 按 CPU 加 Pod",没搞清楚K8s 其实有三层伸缩机制,各自解决不同维度的问题:
- HPA(水平伸缩):加减 Pod 数量。扛流量的主力。
- VPA(垂直伸缩):调 单个 Pod 的资源(request/limit)。解决"给多少"的问题。
- Cluster Autoscaler(集群伸缩):加减 节点(Node)。Pod 没地方落了,它去要机器。
这三者不是三选一,而是组合拳。本文把它们逐个拆透,再讲那个最让团队吃亏的冷启动问题,最后给一套能直接抄的实战配置(HPA + 自定义指标 + PDB)。
一、为什么需要三级伸缩:三种维度,三个问题
先把心智模型立住。伸缩这件事,本质是回答**「当负载变化时,改什么」**:
┌─────────────────────────────────────────────────────────┐
│ 负载来了 / 负载走了 │
└───────────────────────┬─────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌───────────┐
│ HPA │ │ VPA │ │ CA │
│ 水平 │ │ 垂直 │ │ 节点 │
├─────────┤ ├─────────┤ ├───────────┤
│ 加减 │ │ 调 │ │ 加减 │
│ Pod 副本│ │ Pod资源 │ │ Node │
└─────────┘ └─────────┘ └───────────┘
"多几个人干活" "给每个人更大工位" "多租一层办公楼"
这三层的关系是自底向上的触发链:
- HPA 发现 Pod CPU 高 → 把副本数从 3 加到 10;
- 但集群节点塞不下 10 个 Pod → 新 Pod 进入
Pending状态(无节点可调度); - Cluster Autoscaler 发现
PendingPod → 向云厂商要新节点; - 新节点就绪 → Pending Pod 调度上去 → 流量被分摊。
而 VPA 在这条链之外,它解决的是另一个问题:你这 Pod 一开始申请的资源就给错了。比如一个服务实际只用 100m CPU,你 request 写了 1000m,那调度器会按 1000m 给它预留节点空间——10 个这样的 Pod 就浪费了 9 核。VPA 通过观察实际用量,告诉你(或自动改成)正确值。
关键认知:HPA 和 VPA 不能同时基于同一个资源维度(比如都按 CPU)工作——因为 HPA 加副本会让每个 Pod 的 CPU 用量下降,VPA 看到用量低就调小 request,形成互相打架的反馈循环。后面会讲怎么组合。
二、HPA:水平伸缩,扛流量的主力
HPA(HorizontalPodAutoscaler)是绝大多数团队第一个上、也是用得最多的伸缩机制。它的工作循环很简单:周期性查指标 → 算出期望副本数 → 调 Deployment 的 replicas。
1. 核心算法:一个公式决定一切
HPA 的扩缩容决策,背后就是这个公式:
期望副本数 = ceil( 当前副本数 × ( 当前指标值 / 目标指标值 ) )
举个例子:当前 3 个副本,HPA 目标 CPU 50%,实际 CPU 90%:
期望副本数 = ceil( 3 × (90 / 50) ) = ceil(5.4) = 6
所以从 3 扩到 6。如果配了多个指标(CPU + 自定义 QPS),HPA 会分别算出每个指标对应的期望副本数,取最大值——保守策略,谁喊救命听谁的。
一个必须记住的细节:容忍度(tolerance)。默认 ±10% 的波动不触发任何动作。也就是说目标 50% 时,实际值在 45%~55% 之间,HPA 当没看见。这是为了防止抖动导致副本数反复横跳(flapping)。如果你发现 HPA"反应迟钝",很可能就是这 10% 的死区在起作用。
2. CPU 指标:是「相对于 request 的百分比」
这是新手踩的第一个大坑。HPA 里的 CPU 利用率,是相对 Pod 的 request 算的,不是绝对值。
resources:
requests:
cpu: 200m # 0.2 核
配 HPA 目标 CPU 50%,意思是:每个 Pod 用到 100m(200m 的 50%)就达标。如果你把 request 写成 2000m,同样是 50% 目标,意味着每个 Pod 要用到 1000m 才会触发扩容——扩容阈值被悄悄抬高了 10 倍。
这就是为什么 request 设错了,HPA 的行为会完全错乱。很多团队"HPA 不工作"的根因,是 request 设得离谱(比如图省事全填 1 核 2G),导致 CPU 百分比永远在低位,HPA 永远不扩。
3. 最基础的 HPA:按 CPU 和内存
先看一个最常见、能覆盖大部分 Web 服务的配置:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3 # 兜底,再闲也别低于 3
maxReplicas: 50 # 上限,防止失控扩容把集群打爆
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # 目标 CPU 60%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70 # 目标内存 70%
注意几点:
minReplicas别设成 1。生产服务至少给 2~3 个兜底,缩到 1 个时一旦那个 Pod 挂了,服务直接中断;maxReplicas是你的预算护栏。设一个你能承受的上限,否则一个指标异常能把副本数拉到几百,直接把节点资源耗光;autoscaling/v2是当前稳定版本(v2beta2已废弃),支持多指标和行为配置。
4. 行为调优:scaleDown 是真正烧脑的地方
很多人 HPA 扩容很顺,但缩容时出事:流量一降,HPA 立刻把 Pod 砍掉,结果下一波流量来时来不及起,又雪崩。behavior 字段就是来解决这个的:
spec:
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # 扩容立刻执行,不等
policies:
- type: Percent
value: 100 # 每次最多翻倍
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300 # 缩容前先观察 5 分钟
policies:
- type: Pods
value: 2 # 每 60s 最多缩 2 个
periodSeconds: 60
- type: Percent
value: 25 # 或最多缩 25%
periodSeconds: 60
selectPolicy: Min # 取更保守的那个
这里的实战经验:
- 扩容要快,缩容要慢。
scaleUp的 stabilization 设 0(立刻动),scaleDown至少给 5~10 分钟稳定窗口。流量往往是尖刺型,扩晚了出事,缩快了下波又出事; - 缩容用
selectPolicy: Min,同时配"绝对值(Pods)"和"百分比"两条策略,取更小的,避免一次砍太多; - 扩容用
selectPolicy: Max,让 Pod 数尽快追上流量。
5. 自定义指标:这才是 HPA 的精华
按 CPU 扩容有个致命假设:CPU 是瓶颈。但现实里很多服务瓶颈是"队列深度""DB 连接数""在线连接数""QPS"——这些时候 CPU 根本不高,HPA 按 CPU 算就是聋子。这时候需要自定义指标。
HPA 支持四类指标,自定义指标走后两类:
| 类型 | 数据来源 | 典型场景 |
|---|---|---|
Resource | metrics-server(Pod 内置 CPU/内存) | 通用 |
Pods | 自定义指标 API(每个 Pod 一个值) | 每副本 QPS、队列消费速率 |
Object | 自定义指标 API(关联某 K8s 对象) | Ingress QPS、CRD 队列长度 |
External | 外部指标 API(非 K8s 资源) | SQS 队列长度、Redis 连接数 |
要用自定义指标,得先部署一个 Metrics API 聚合器,最主流的是 Prometheus Adapter 或 KEDA。它把 Prometheus 里的指标暴露成 K8s Metrics API,HPA 才查得到。
一个基于 Ingress QPS 的 HPA 例子:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
# 关键:按每副本 QPS 扩容,这才是真实负载信号
- type: Pods
pods:
metric:
name: http_requests_per_second # 来自 Prometheus Adapter
target:
type: AverageValue
averageValue: "100" # 每个 Pod 扛 100 QPS 就扩
Prometheus Adapter 的规则(把 PromQL 翻译成 HPA 能查的指标)长这样:
# adapter config (rules)
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
我的强烈建议:生产服务别只按 CPU 扩,至少配一个业务语义指标(QPS、队列深度、延迟分位)。CPU 是结果,业务指标是原因——按原因扩容永远比按结果扩容快一步。如果觉得 Prometheus Adapter 配置繁琐,直接上 KEDA(后面冷启动部分会讲),它把"按 Prometheus/SQS/Kafka 等数据源伸缩"做成了 ScaledObject,配置直观得多。
三、VPA:垂直伸缩,给 Pod 调"工位大小"
VPA(VerticalPodAutoscaler)处理的是另一个问题:这个 Pod 该申请多少资源才合适。它不加减副本,而是改 Pod 的 resources.requests。
1. VPA 解决的真实痛点
每个团队都干过这种事:新服务上线,不知道吃多少资源,先拍脑袋填 cpu: 500m, memory: 512Mi。跑两周后一看监控,实际 CPU 只用了 30m,内存 80Mi——你白白占了 16 倍的资源配额。更糟的是反过来的情况:request 填小了,Pod 被限流(CPU throttling)或 OOMKilled,你还在排查"为啥这服务这么慢"。
VPA 的工作流:持续观察 Pod 的真实用量 → 用滑动窗口(默认 8 天历史 + 实时)统计 P50/P95/P99 分位 → 给出推荐值(target、lowerBound、upperBound)→ 根据模式决定要不要应用。
查推荐值:
kubectl describe vpa api-server-vpa
# 输出里会有:
# Recommendation:
# Target: cpu 78m memory 312Mi
# ...
2. 三种模式:Off / Initial / Auto
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto" # 关键字段
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 50m
memory: 64Mi
maxAllowed:
cpu: 1
memory: 1Gi
三种 updateMode:
Off:只观察、只给推荐,不改动任何东西。强烈建议第一周就这么跑,先把推荐数据看明白;Initial:只在 Pod 新建时套用推荐值,不碰运行中的 Pod。这是最安全的"半自动"模式——下次滚动更新时自然生效,不重启现有 Pod;Auto:会主动驱逐并重建 Pod 来套用新 request。最激进。
注意 minAllowed / maxAllowed 是护栏:防止 VPA 因为某个抖动把 request 调到离谱的值。生产环境一定要设。
3. VPA 的硬伤:改 request 要重启 Pod
这是 VPA 推广不开的根本原因。K8s 里运行中的 Pod 的资源 request 是不可变的(除非开了 InPlacePodVerticalScaling 特性,1.33 转正,目前较新)。所以 VPA 的 Auto 模式要"改 request",只能把 Pod 杀掉重建。
这带来一堆副作用:
- 重建期间该副本下线,如果没配 PDB 或副本数太少,可能影响可用性;
- VPA 不会一次性杀全部(它走驱逐逻辑,遵守 PDB),但频繁调资源 = 频繁重启;
- JVM/预热型服务每次重启都要重新 warmup,代价不小。
实战建议:
- 生产用
Initial模式最稳——观察推荐值,滚动发布时自然套用,零额外重启; - 想自动调,先确认服务能频繁优雅重启,并配好 PDB;
- 别忘了
Off模式本身就是宝:把 VPA 当"资源调优顾问",用它给的推荐值去人工调整 Deployment,比拍脑袋准 10 倍。
4. HPA 和 VPA 怎么共存
记住一条铁律:HPA 和 VPA 绝不能基于同一个资源维度。
- ❌ HPA 按 CPU 扩容 + VPA 调 CPU request:VPA 调大 request → 每个 Pod CPU 利用率下降 → HPA 觉得"不用那么多副本了"开始缩容 → 两个控制器互相打架,副本数和 request 反复横跳;
- ✅ HPA 按自定义指标(QPS)扩容 + VPA 调 CPU/memory request:两者各管一摊,QPS 决定副本数,CPU/memory 决定每个副本的工位,互不干扰。
社区甚至推荐:先用 VPA(Off 模式)跑两周摸清真实资源画像 → 把 request 定准 → 再上 HPA。先把地基打好,再谈水平扩展。
四、Cluster Autoscaler:节点层面的伸缩
前面两层都在 Pod 里打转,但 Pod 总得跑在 Node 上。当 HPA 扩容出 10 个新 Pod,而集群已经塞满了,这 10 个 Pod 会卡在 Pending 状态——这时就该 Cluster Autoscaler(CA) 上场了。
1. CA 怎么工作:盯 Pending Pod + 盯空闲节点
┌──────────────────────────────────────────────────────┐
│ 1. HPA 扩容 → 新 Pod 进入 Pending(无节点可调度) │
└──────────────────────┬───────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 2. CA 监测到 Pending Pod,模拟调度器评估 │
│ "如果加一台 m5.xlarge,这些 Pod 能不能落地?" │
└──────────────────────┬───────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 3. 调用云厂商 API,开新节点(AWS/GCP/Azure) │
│ 节点就绪并注册 → Pod 被调度上去 │
└──────────────────────────────────────────────────────┘
▲
│
┌──────────────────────┴───────────────────────────────┐
│ 4. 缩容:某节点利用率 < 阈值(默认50%)且持续 10 分钟 │
│ → CA 把上面 Pod 迁走 → 节点下线 │
└──────────────────────────────────────────────────────┘
CA 的扩容逻辑相对直接:看到 Pending Pod,找最合适的节点规格去开。缩容逻辑更微妙:它会找利用率长期低于阈值(默认 50%)的节点,把上面的 Pod 驁到别处,再关机。但有几类 Pod 不会被迁:带本地存储的、不被驱逐的、DaemonSet 的等。
2. CA 的"慢"是结构性的
CA 从"Pod Pending"到"节点就绪",在云上通常要 1~5 分钟(开机 + kubelet 注册 + 镜像拉取)。这个延迟是冷启动问题的核心来源之一,后面专门讲。
配置上,CA 本身一般以 Deployment 形式跑在集群里,关键参数:
# Cluster Autoscaler 启动参数(节选)
- --scale-down-unneeded-time=10m # 节点空闲多久才缩
- --scale-down-utilization-threshold=0.5 # 利用率低于多少算"空"
- --max-node-provision-time=15m # 开节点最多等多久
- --balance-similar-node-groups=true # 跨可用区均衡
- --expander=least-waste # 选节点规格的策略
expander 决定"开哪种节点",常见策略:random(随机)、most-pods(能塞最多 Pending Pod 的)、least-waste(浪费最少的,推荐)、priority(按预设优先级表,适合 spot+on-demand 混部)。
3. CA vs Karpenter:这个赛道在变
传统的 Cluster Autoscaler 是节点组(Node Group / ASG / MIG)驱动的——你得先定义好几组固定规格的节点池,CA 在这些池里加减机器。问题是:规格是预先固定的,Pod 形状千变万化,经常塞不进去导致碎片。
AWS 的 Karpenter(后来捐给社区,跨云支持在推进)走的是另一条路:没有节点组,直接按 Pod 需求实时挑最合适的实例规格。它把 Pending Pod 的需求(CPU/内存/GPU/拓扑/容忍度)翻译成一次"买哪台机器最划算"的决策,几秒内就能起节点。在 AWS 上,Karpenter 几乎已经是新集群的默认选择。
选型判断:
- 在 AWS 上、节点规格多样、想用 Spot 省钱 → Karpenter,体验降维打击;
- GCP/Azure 或需要严格节点组治理 → Cluster Autoscaler,成熟稳定;
- 两者的共同前提:Pod 资源 request 要设准——CA/Karpenter 都按 request 调度,request 虚高 = 节点虚开 = 浪费。
五、冷启动:伸缩最难的从来不是"算出来",而是"来得及"
讲到这里,你会发现所有伸缩机制都有个共同硬伤:它们是反应式的。流量已经来了 → 指标才涨上去 → HPA 才算出要扩 → 新 Pod 才起 → 新 Pod 才 ready → 才能接流量。这条链路加起来,经常是 2~5 分钟。
可流量尖刺往往 30 秒就把现有 Pod 打挂了。这就是冷启动问题:伸缩永远慢半拍。
1. 把延迟拆开,逐段优化
先看扩容的完整时间线:
[流量到达] t=0
[指标采集窗口,通常 30~60s] +30~60s ← 段①:指标延迟
[HPA 计算并改 replicas] +几秒 ← 段②:控制循环
[新 Pod 调度 + 拉镜像 + 启动] +20~90s ← 段③:Pod 启动
[就绪探针通过] +5~30s ← 段④:首次 ready
[如果节点不够,CA 开机] +60~300s ← 段⑤:节点冷启动
───────────────────────────────────────────
最坏情况:5 分钟才扛上流量
逐段打:
- 段①指标延迟:缩短采集窗口(比如 PromQL 从
[2m]改[1m]),但太短会抖动; - 段②控制循环:HPA 默认 15s 一次(
--horizontal-pod-autoscaler-sync-period),可调到 10s,边际收益不大; - 段③Pod 启动:镜像要小(用 distroless/alpine,去掉调试工具)、用更快的拉取(镜像预热到节点、或用 lazydocker/soci 加速大镜像分层拉取)。JVM 类应用启动慢,可改用 GraalVM 原生镜像或 CRaC;
- 段④就绪探针:
readinessProbe的initialDelaySeconds别设太大(很多人图省事填 30s,白白浪费 30s),改用startupProbe处理慢启动; - 段⑤节点冷启动:这是最硬的一段,靠下面两招。
2. 过量配置(Pod 过量配置 / Pause Pod):用钱换时间
最朴素有效的招:常备一些"占位 Pod",节点早就开好了,新 Pod 直接调度。
原理是用一个低优先级的 Pause Pod占着节点上的资源。真业务 Pod 来了,因为优先级高,调度器立刻把 Pause Pod 踢掉,业务 Pod 顶上——节点不用新开,省掉了段⑤那几分钟。
# 1. 定义低优先级 PriorityClass
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: overprovisioning
value: -1 # 负值,比所有业务 Pod 都低
globalDefault: false
description: "用于占位扩容的 Pause Pod"
---
# 2. 占位 Pod(什么都不干,纯占资源)
apiVersion: apps/v1
kind: Deployment
metadata:
name: overprovisioning
spec:
replicas: 3 # 占 3 个"虚拟槽位"
selector:
matchLabels:
app: overprovisioning
template:
metadata:
labels:
app: overprovisioning
spec:
priorityClassName: overprovisioning
containers:
- name: reserve
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: 1
memory: 2Gi # 占位大小 = 一个业务 Pod 的量
这相当于用 3 个 Pod 的钱,预先买了"随时可用的扩容槽位"。流量来了,业务 Pod 秒级顶上,Pause Pod 被挤走。等流量退了,Pause Pod 又回来继续占位。简单粗暴但极其有效,是几乎所有对延迟敏感的大厂都会用的招。
3. 预测性伸缩:用时间表打败反应式延迟
如果你的负载有规律(每天高峰、每周高峰、大促),别等指标涨了再扩,提前扩。
招式一:CronHPA / 定时伸缩。Kubernetes 社区有 CronHPA(华为贡献),或用 KEDA 的 cron scaler。比如每天 10:00 扩到 20 副本,22:00 缩回 5:
# KEDA 的 cron trigger
triggers:
- type: cron
metadata:
timezone: Asia/Shanghai
start: "0 9 * * *" # 早9点前扩好
end: "0 23 * * *" # 晚11点后缩
desiredReplicas: "20"
招式二:基于历史指标预测。更进阶,用历史 7 天同时段指标预测今天的负载曲线,提前 N 分钟扩容。这块目前没有官方一等公民方案,常见做法:
- 自建脚本/Operator,查 Prometheus 历史,调 HPA 的
minReplicas抬高; - 用云厂商的预测伸缩(如 AWS Predictive Scaling for EKS 节点);
- 部分商业平台(Datadog Adaptive Scaling 等)提供托管方案。
我的判断:90% 的团队先把「过量配置 + 业务指标 HPA」做好,收益就远大于折腾预测算法。预测性伸缩是为规律性极强、或对延迟极其敏感的场景准备的,别一上来就追这个。
六、实战配置:一套能直接抄的组合拳
把前面的东西揉成一套生产可用的配置。场景:一个 HTTP API 服务,希望按 QPS + CPU 扩容,缩容要稳,缩容时不被一锅端。
1. Deployment:资源设准是前提
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: prod
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: registry.example.com/api-server:v1.2.3
ports:
- containerPort: 8080
resources:
requests:
cpu: 250m # 设准!这是 HPA 算 CPU% 的分母
memory: 256Mi
limits:
memory: 512Mi # CPU 不设 limit,避免 throttling 误判
readinessProbe: # 就绪探针:能不能接流量
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
startupProbe: # 启动探针:慢启动专用,不挡 readiness
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 5 # 给最多 150s 启动时间
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"] # 优雅退出,等连接排空
terminationGracePeriodSeconds: 60
几个关键决策:
- CPU 不设 limit(只设 request):避免 CPU throttling 把延迟拉高,同时 HPA 看到的利用率更线性。设了 limit 一旦接近上限,cgroup 限流但利用率曲线会被压平,反而误导 HPA;
- startupProbe 单独配:慢启动服务(JVM、模型加载)用
startupProbe给足启动时间,成功后再交给readinessProbe,这样initialDelaySeconds可以设很小; - preStop sleep:给 kube-proxy 一点时间把 Pod 从 Service endpoints 里摘掉,避免流量打到正在退出的 Pod。
2. HPA:双指标 + 稳健缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 30
metrics:
# 指标1:CPU 兜底
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
# 指标2:每副本 QPS(业务真实信号,来自 Prometheus Adapter)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "150"
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 2
periodSeconds: 60
selectPolicy: Min
3. PDB:缩容/驱逐时的保命索
很多人忘配 PDB(PodDisruptionBudget),结果一次节点缩容或维护把所有 Pod 一锅端。PDB 限制"自愿驱逐"(节点缩容、kubectl drain 等)能同时干掉多少 Pod:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: prod
spec:
minAvailable: 2 # 任何时候至少留 2 个
# 或者用 maxUnavailable: 1
selector:
matchLabels:
app: api-server
注意 PDB 只防自愿驱逐(CA 缩容、节点 drain、VPA 驱逐),不防 Pod 崩溃这种非自愿驱逐——那得靠副本数本身兜底。所以 minReplicas + PDB.minAvailable 要一起算,保证"最坏情况下还剩够用的副本"。
4. 把这套串起来验证
部署后,用这几条命令验证伸缩是否健康:
# 看 HPA 当前决策和指标
kubectl get hpa api-server-hpa -n prod
# 看 TARGETS 列:实际值/目标值,实际远超目标却不扩 → 查指标源
# 看 HPA 的决策事件(扩了几次、为什么)
kubectl describe hpa api-server-hpa -n prod | grep -A20 Events
# 看 CA 决策(有没有 Pending Pod 触发扩节点)
kubectl logs -n kube-system deploy/cluster-autoscaler | grep "scale up"
# 压测模拟流量,观察扩容链路
kubectl get pods -n prod -w # -w 持续看副本变化
一个常见排错:kubectl get hpa 的 TARGETS 列显示 <unknown>——说明指标查不到。CPU 显示 unknown 是 metrics-server 没装好;自定义指标 unknown 是 Prometheus Adapter 规则没匹配上。先把这个搞通,再谈调优。
小结
- 三级伸缩各管一摊:HPA 加减 Pod 副本(扛流量)、VPA 调 Pod 的 request/limit(给资源)、Cluster Autoscaler 加减 Node(给 Pod 落脚点)。三者是自底向上的触发链,不是三选一,生产里通常组合使用。
- HPA 按 CPU 是入门,按业务指标才是精华。CPU 是结果,QPS/队列深度/延迟才是原因。记住 HPA 的 CPU 利用率是相对 request 的百分比——request 设错,HPA 行为全错。扩容要快、缩容要慢(
behavior字段),缩容前给 5 分钟稳定窗口。 - VPA 最有价值的用法是
Off模式当"资源顾问"。Auto模式会重启 Pod,生产慎用;Initial模式最稳。HPA 和 VPA 绝不能基于同一资源维度,否则互相打架。 - Cluster Autoscaler 是反应式且慢的(分钟级),选型上 AWS 可考虑 Karpenter(秒级、按需挑规格)。无论哪个,前提都是 request 设准——它们都按 request 调度。
- 冷启动是伸缩最难的部分:把延迟拆成指标/控制循环/Pod 启动/就绪/节点开机五段逐个打。最有效的两招——过量配置(Pause Pod 占位)用钱换时间,定时/预测伸缩用先验打败反应式。PDB 别忘配,否则一次缩容可能把你一锅端。
金句:K8s 的自动伸缩,本质是用「反应式延迟」换「资源效率」。HPA/VPA/CA 都是「指标先涨、我才扩」的事后机制,永远慢半拍——真正决定你扛不扛得住尖刺的,不是 HPA 算得多准,而是你愿不愿意为那几分钟延迟预先付一点过量的钱、配一点占位的 Pod。伸缩调优做到最后,工程师都是在「省钱」和「睡得着觉」之间找那条线。
Related
相关推荐
文章
Docker 部署 Next.js 社区项目的注意事项
围绕 DevOps、Next.js 的技术趋势、工程实践和独立开发视角。
文章
OpenTelemetry 如何帮助你观察 AI 应用?
围绕 DevOps、AI 的技术趋势、工程实践和独立开发视角。
文章
长运行工具调用:Operation、Poll 与 Cancel 的终态协议
初始请求超时后,工具可能仍在服务端运行;发出取消也不等于副作用已停止。本文为单次长运行工具调用建立 operation 身份、轮询水位、取消竞态和唯一终态接受点。
文章
Agent 记忆删除传播:从用户遗忘请求到可验证清除
删除主表一行不会清掉向量、摘要、缓存和恢复后的备份。本文用删除状态机、代际墓碑、派生血缘与端到端 canary 验证真正遗忘。