Kubernetes 基础体系 · 第 59/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes HPA 与 VPA:指标、算法、稳定窗口、冲突和容量
Kubernetes 的自动扩缩容不是一个单独的“把资源调大或调小”的功能,而是多个控制器根据观测指标持续修改 Kubernetes 对象:
- HPA(Horizontal Pod Autoscaler,水平 Pod 自动扩缩容):主要修改工作负载的副本数。
- VPA(Vertical Pod Autoscaler,垂直 Pod 自动扩缩容):主要调整 Pod 容器的 CPU、内存等资源请求与限制。
- 节点自动扩缩容器:通常由云厂商或 Cluster Autoscaler 修改节点组规模,使 Pending Pod 获得可调度的节点,或删除空闲节点。
HPA 和 VPA 都是控制回路。它们读取当前状态,计算目标状态,再通过 API Server 修改对象。理解它们的关键,不是记住几个 YAML 字段,而是弄清楚:
- 指标从哪里来,代表什么;
- 目标值如何转换为副本数或资源请求;
- 控制器如何避免频繁抖动;
- 多个控制器同时修改同一系统时,反馈回路如何相互影响;
- Pod 的资源请求变化最终如何影响调度、节点容量和成本。
本文以当前稳定的 Kubernetes API 为范围。HPA 的主流稳定 API 是 autoscaling/v2;VPA 不是 Kubernetes 核心组件内置的标准控制器,而是 Kubernetes Autoscaler 项目的独立组件,通常安装其 CRD 和控制器后使用 autoscaling.k8s.io/v1。VPA 的具体推荐算法、默认参数和安装方式可能随项目版本变化,不能把它们都视为 Kubernetes API 的规范保证。
一、先建立统一模型:自动扩缩容到底在控制什么
一个 Kubernetes 工作负载至少涉及三种不同的容量:
1. Pod 副本容量
副本数记为:
HPA 控制的主要就是 。例如,Deployment 当前有 3 个副本,HPA 可能将其改成 6 个。
副本数增加通常意味着:
- 更多并行处理能力;
- 更多连接、缓存和后台任务实例;
- 更高的总资源请求;
- 可能需要更多节点。
但副本数并不等于吞吐量。应用可能受数据库连接数、分区数、锁竞争或下游服务限制,增加副本后并不会线性提高吞吐。
2. 单个容器的资源请求和限制
对某个容器,CPU 和内存通常分别有:
requests.cpu、requests.memory:调度和资源计量使用的请求值;limits.cpu、limits.memory:运行时资源上限。
请求值不是“应用一定会用掉的资源”,而是 Kubernetes 为调度和资源保证采用的声明值。对于节点上的可调度容量,最重要的是请求值:
VPA 主要修改容器的请求和限制,从而改变每个 Pod 的资源画像。
3. 节点容量
节点数量记为:
节点自动扩缩容器根据 Pending Pod 的请求、节点组约束和可调度性决定是否增加节点;缩容则需要判断节点上的 Pod 能否被驱逐并重新调度。
因此三个控制层次可以表示为:
flowchart LR
A[应用负载] --> B[指标采集]
B --> C[HPA]
B --> D[VPA Recommender]
C --> E[修改 Deployment 副本数]
D --> F[VPA Recommendation]
F --> G[VPA Admission / Updater]
E --> H[创建或删除 Pod]
G --> I[创建新 Pod 时注入资源]
I --> J[调度器根据 requests 调度]
H --> J
J --> K{是否有可调度节点}
K -- 是 --> L[Pod Running]
K -- 否 --> M[Pending Pod]
M --> N[节点自动扩缩容器]
N --> O[增加或删除节点]
O --> J
这张图中有一个容易被忽略的事实:HPA 和 VPA 通常不直接修改同一个字段,但会通过指标和调度间接影响彼此。HPA 修改副本数,VPA 修改资源请求;而资源请求又可能是 HPA 计算利用率时的分母。
二、HPA 的指标、数据流和控制对象
2.1 HPA 不负责采集指标
HPA Controller 运行在 kube-controller-manager 中,它通过 Kubernetes API 读取指标,并通过目标资源的 Scale 子资源修改副本数。
常见指标来源包括:
| 指标类型 | 典型来源 | 适合表达 |
|---|---|---|
| Resource metrics | Metrics Server | CPU、内存使用量或利用率 |
| Custom metrics | Custom Metrics API 适配器 | Pod 或其他 Kubernetes 对象的业务指标 |
| External metrics | External Metrics API 适配器 | Kubernetes 集群外部指标,例如队列长度 |
仅安装 HPA 对象不会自动产生指标。使用 CPU、内存指标时,集群通常需要 Metrics Server;使用 Prometheus 等系统时,通常还需要适配器把指标暴露为 Kubernetes Custom Metrics API 或 External Metrics API。
先检查资源指标是否可用:
kubectl top pods -n demo
kubectl top nodes
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | head
可能的结果:
error: Metrics API not available
这表示 HPA 依赖的资源指标 API 不可用,而不是“当前没有负载”。此时 HPA 通常无法得到完整的当前指标,扩缩容会停止或进入错误状态。
检查 HPA:
kubectl get hpa -n demo
kubectl describe hpa web -n demo
重点查看:
AbleToScaleScalingActiveScalingLimitedConditionsEvents- 当前指标和目标指标
2.2 HPA 的稳定 API 示例
下面的 Deployment 使用 CPU 请求作为 HPA 利用率的分母:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: 250m
memory: 128Mi
limits:
cpu: "1"
memory: 256Mi
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: demo
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:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
selectPolicy: Max
前置条件:
kubectl create namespace demo
kubectl apply -f web-hpa.yaml
kubectl get deployment,hpa -n demo
resources.requests.cpu: 250m 必须存在。因为这里使用了 averageUtilization,HPA 需要知道每个容器的 CPU 请求,否则无法计算该 Pod 的 CPU 利用率。250m 表示 0.25 个 CPU 核,而不是 250% CPU。
三、HPA 的核心算法:从利用率推导副本数
3.1 单指标的基本公式
对于资源利用率指标,HPA 的常见目标公式是:
其中:
- :当前副本数;
- :当前平均指标值;
- :HPA 配置的目标值;
- :向上取整;
- :理论上希望的副本数。
当使用 CPU 利用率时:
如果每个 Pod 的请求相同,也可以理解为先计算每个 Pod 的利用率,再求平均;在请求不同时,按总使用量除以总请求量更准确。
完整数值例子
当前有 3 个 Pod,每个 Pod 的 CPU 请求为 500m:
| Pod | CPU 使用量 | CPU 请求 | 利用率 |
|---|---|---|---|
| A | 400m | 500m | 80% |
| B | 300m | 500m | 60% |
| C | 500m | 500m | 100% |
总使用量为:
总请求量为:
当前利用率:
目标利用率为 60%,因此:
HPA 会把期望副本数计算为 4,然后还要经过最小副本数、最大副本数、稳定窗口和缩放策略限制。
3.2 为什么“利用率”不是“CPU 使用率”
如果一个 Pod 的 CPU 请求是 100m,实际使用 80m,则利用率为:
如果把请求改成 500m,实际仍使用 80m,利用率变成:
应用实际使用的 CPU 没有变化,但 HPA 看到的利用率大幅下降。这就是 VPA 与基于资源利用率的 HPA 可能产生反馈冲突的根本原因。
3.3 AverageValue 与 Utilization 的区别
autoscaling/v2 中,资源指标可以使用不同目标类型。
Utilization
target:
type: Utilization
averageUtilization: 60
它表示相对于资源请求的百分比。它要求相关容器有对应的资源请求。
AverageValue
target:
type: AverageValue
averageValue: 500m
它表示每个 Pod 的平均绝对值,例如每 Pod 平均 500m CPU:
metrics:
- type: Resource
resource:
name: cpu
target:
type: AverageValue
averageValue: 500m
此时目标不是“请求的 60%”,而是每个 Pod 的平均 CPU 使用量约为 500m。它仍然依赖指标采集,但不会使用 CPU 请求作为目标计算的分母。
3.4 多指标如何合并
HPA 可以配置多个指标,例如 CPU、内存和队列长度。对每个指标分别计算一个期望副本数:
HPA 通常选择它们的最大值:
这是保守策略:任一指标表明容量不足,都不能被另一个“较低”的指标抵消。
例如:
- CPU 推导 4 个副本;
- 内存推导 3 个副本;
- 队列长度推导 8 个副本;
最终期望副本数是 8,而不是平均值 5。
如果某个指标无法获取,且其他指标明确要求缩容,HPA 对缩容通常会更加谨慎;具体行为还取决于指标错误、当前副本状态和控制器实现。生产诊断时应以 kubectl describe hpa 中的条件和事件为准,不能只观察 desiredReplicas。
四、HPA 的容忍度、稳定窗口和缩放策略
4.1 容忍度:避免无意义的整数变化
如果当前指标和目标指标非常接近,直接按比例计算会导致副本数在边界附近频繁上下变化。因此 HPA 使用容忍度判断是否需要缩放。
常见实现中,默认容忍度约为 10%,但这是控制器参数和实现行为,不应当当作 HPA API 对所有发行版的绝对保证。
设偏差为:
当:
其中 是容忍度,控制器可以保持当前副本数。
例如当前利用率为 63%,目标为 60%,比例为:
如果容忍度为 10%,5% 的偏差不足以触发扩容。
这不是把目标改成 66%,而是控制器决定暂时不采取动作。目标值仍然是 60%。
4.2 稳定窗口不是采样窗口
两个概念经常被混淆:
- 同步周期:控制器多久重新计算一次,常见默认值约为 15 秒,但由控制器参数决定;
- 稳定窗口:在一段时间内观察多个历史期望值,避免选择导致振荡的值。
scaleDown.stabilizationWindowSeconds: 300 并不表示“每 300 秒才检查一次”。它通常表示:过去 300 秒内出现过较高的缩容期望值时,先保留较高值,避免刚扩容就立即缩回去。
可以把缩容稳定窗口抽象成:
其中:
- :当前时刻;
- :缩容稳定窗口;
- :历史计算出的期望副本数。
时间线例子
假设当前副本数为 10,缩容稳定窗口为 300 秒:
| 时间 | 当前指标 | 原始期望副本数 |
|---|---|---|
| 高负载 | 10 | |
| 低负载 | 6 | |
| 低负载 | 5 | |
| 低负载 | 4 | |
| 低负载 | 4 |
过去 300 秒中的最大期望值是 10,因此稳定窗口会阻止立即缩到 4。只有旧的高值逐渐离开窗口后,副本数才会下降。
扩容通常不使用同样长的默认稳定窗口,因为容量不足的代价可能是请求延迟和错误率上升。实际是否立即扩容,还会受到扩容策略、指标采集延迟、Deployment 创建 Pod 的速度和调度能力影响。
4.3 behavior 的作用
behavior 控制的是扩缩容动作,而不是指标采集。
例如:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
selectPolicy: Max
含义是:
- 最近 300 秒的缩容期望值用于稳定处理;
- 每 60 秒最多按某个策略缩容;
Percent: 25表示最多减少当前副本数的 25%;- 若配置多个策略,
selectPolicy: Max选择允许动作更大的策略。
Pods 和 Percent 的区别:
policies:
- type: Pods
value: 2
periodSeconds: 60
- type: Percent
value: 50
periodSeconds: 60
当当前有 10 个副本时:
Pods允许减少 2 个;Percent允许减少 5 个;Max选择减少 5 个;Min则选择减少 2 个。
这些限制不能突破 minReplicas 和 maxReplicas。
4.4 HPA 修改的是 Scale 子资源
HPA 不直接把 Deployment 的完整对象重写一遍,而是通过目标资源的 Scale 子资源修改期望副本数。Deployment Controller 再根据 spec.replicas 创建或删除 ReplicaSet/Pod。
这意味着:
- HPA 管的是目标工作负载的副本数;
- Deployment 的滚动发布控制器仍然负责发布;
- 如果另一个系统不断修改 Deployment 的
spec.replicas,就会和 HPA 争夺控制权。
常见错误是同时使用:
kubectl scale deployment web --replicas=3
和 HPA 自动控制副本数。手工设置可能很快被 HPA 覆盖;如果某个 GitOps 系统持续把 spec.replicas: 3 同步回来,也会与 HPA 形成写入竞争。
五、VPA 的组成、生命周期和资源推荐
5.1 VPA 不是 Kubernetes 核心内置控制器
VPA 通常由以下组件组成:
- Recommender:根据历史资源使用情况生成推荐;
- Updater:寻找资源明显不足或明显过量的 Pod,并决定是否驱逐;
- Admission Controller:Pod 创建时根据推荐修改容器资源配置;
- VPA CRD:保存 VPA 对象和推荐结果。
因此,安装 Kubernetes 集群并不会自动获得 VPA。需要按照 VPA 项目的版本说明安装其 CRD、RBAC、Webhook 和控制器。VPA 对指标历史、Webhook、证书和驱逐策略都有额外运维要求。
5.2 VPA 的输入与输出
典型数据流如下:
flowchart LR
A[Pod 容器实际 CPU/内存使用] --> B[指标 API]
B --> C[VPA Recommender]
C --> D[VPARecommendation]
D --> E[Admission Controller]
D --> F[VPA Updater]
E --> G[新建 Pod]
G --> H[注入 requests/limits]
F --> I[选择需调整的旧 Pod]
I --> J[遵守 PDB 后驱逐]
J --> K[工作负载重建 Pod]
K --> E
VPA 的最终效果通常发生在 Pod 创建时:
- Admission Controller 拦截 Pod 创建请求;
- 根据 VPA 推荐修改容器的
resources.requests和可能的resources.limits; - 调度器看到修改后的请求;
- Pod 按新资源配置运行。
对于已经运行的 Pod,仅仅更新 VPA 推荐值并不一定会修改该 Pod 的 cgroup 资源。是否重建或驱逐 Pod,取决于 updatePolicy.updateMode 和 VPA 版本实现。
5.3 VPA 示例
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web
namespace: demo
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: web
controlledResources:
- cpu
- memory
minAllowed:
cpu: 100m
memory: 64Mi
maxAllowed:
cpu: "2"
memory: 2Gi
Off 模式适合先观察推荐值:
kubectl apply -f web-vpa.yaml
kubectl get vpa web -n demo -o yaml
典型状态中会看到类似以下结构:
status:
recommendation:
containerRecommendations:
- containerName: web
target:
cpu: 300m
memory: 256Mi
lowerBound:
cpu: 200m
memory: 192Mi
upperBound:
cpu: 600m
memory: 512Mi
字段含义:
target:VPA 认为较合适的目标请求;lowerBound:低于该范围可能资源不足;upperBound:高于该范围可能资源过量;- 这些推荐不是实时保证,也不是应用必须使用的精确容量。
推荐结果可能因历史窗口、样本数量、异常流量、启动阶段和资源上下限而变化。VPA 的具体算法并不是 Kubernetes API 规范的一部分。
5.4 VPA 的更新模式
常见模式包括:
Off:只生成推荐,不自动修改 Pod;Initial:只在新 Pod 创建时注入推荐资源,不主动更新已有 Pod;Recreate:需要时驱逐旧 Pod,让新 Pod 使用推荐值;Auto:历史上用于自动更新,当前实现中通常等价于重新创建式更新;其语义和未来状态应以所安装的 VPA 版本为准。
生产环境不能只看“推荐值变好了”,还要评估重建影响。驱逐一个 Pod 可能触发:
- 连接断开;
- 缓存丢失;
- 消费者重新均衡;
- PDB 阻塞;
- 节点容量不足导致新 Pod Pending。
VPA Updater 通常会考虑 PodDisruptionBudget,但 PDB 只限制自愿中断,并不保证更新一定成功。如果 PDB 要求至少保留全部副本,VPA 可能无法驱逐任何 Pod。
六、VPA 推荐算法:能推导什么,不能过度承诺什么
VPA 的推荐不是简单的:
如果这样做,短时尖峰会导致请求过大,低谷又会导致请求过小。VPA 通常会基于历史样本构建使用量分布,并结合目标分位数、波动性、启动阶段、上下限和安全余量生成推荐。
对某个容器的 CPU 使用样本:
可以先按时间窗口收集样本,再形成直方图或分位数估计。若目标分位数为 ,则目标资源可抽象为:
其中:
- :样本的第 分位数;
- :安全余量或算法调整项;
- :建议的资源请求。
但这里的 、时间权重、直方图实现和安全余量并不是 VPA API 保证的固定公式。不同 VPA 版本可能调整实现。工程上应把 target 看作“基于历史观测的推荐”,而不是容量规划的数学真值。
一个反例:只看平均值会掩盖尖峰
假设一个容器 10 分钟内的 CPU 使用量多数为 100m,但每分钟有一次 900m 的短时尖峰:
100m, 100m, 100m, 900m, 100m, 100m, 100m, 900m, ...
平均值可能并不高,但如果尖峰对应请求延迟或超时,单纯按平均值设置请求会造成:
- CPU throttling;
- 调度时请求不足;
- HPA 利用率异常升高;
- 负载上升时扩容晚于预期。
另一方面,如果尖峰只是没有用户影响的后台任务,按高分位数配置又可能造成节点资源浪费。资源推荐必须结合 SLO、延迟、错误率和业务负载,而不能只看一个 CPU 数字。
内存尤其不能按短时平均值处理
CPU 使用量在短期内可以下降,内存中的缓存、堆、碎片和泄漏则可能长期保留。若容器有一个持续增长的内存泄漏:
512Mi -> 600Mi -> 700Mi -> 800Mi -> ...
历史分位数可能仍然低于未来峰值。VPA 可能推荐不足,最终由内核触发 OOMKill。内存请求和限制还涉及节点可调度容量,因此内存异常通常需要配合:
kubectl describe pod <pod-name> -n demo
kubectl get pod <pod-name> -n demo \
-o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
七、HPA 与 VPA 的冲突:同一资源上的反馈回路
7.1 CPU 利用率 HPA 与 VPA 的直接冲突
考虑如下配置:
- 当前 4 个 Pod;
- 每个 Pod CPU 请求
250m; - 每个 Pod 实际使用
200m; - HPA 目标 CPU 利用率 60%。
当前利用率:
理论副本数:
HPA 倾向于扩容到 6 个。
此时 VPA 观察到容器长期使用约 200m,把请求推荐为 500m。新 Pod 使用 200m 时:
HPA 的理论副本数变为:
于是可能出现:
- 请求小,利用率高,HPA 扩容;
- VPA 提高请求;
- 利用率下降,HPA 缩容;
- 负载升高,HPA 再扩容。
这不是 HPA 或 VPA 单独“算错了”,而是二者共同改变了被测系统:VPA 改变了 HPA 利用率的分母。
7.2 资源使用量 HPA 能否避免该冲突
如果 HPA 使用 CPU 的 AverageValue:
metrics:
- type: Resource
resource:
name: cpu
target:
type: AverageValue
averageValue: 200m
则目标是每个 Pod 的绝对 CPU 使用量,VPA 修改 CPU 请求不会直接改变这个目标的分母。
但这并不意味着两者完全独立:
- VPA 仍然会改变调度请求;
- 请求改变后可能导致 Pending;
- CPU 限制改变后可能影响 throttling;
- Pod 重建会改变短期指标;
- HPA 副本数改变会改变每 Pod 分摊的负载;
- 应用的吞吐和延迟可能随资源配置变化。
因此,AverageValue 只减少了一个特定冲突,不是通用的冲突消除方案。
7.3 更适合 HPA 的业务指标
对于 Web 服务,HPA 经常更适合使用业务负载指标,例如:
- 每 Pod 请求数;
- 活跃连接数;
- 消息队列长度;
- 每秒待处理任务数;
- 请求延迟或错误率的派生指标。
例如队列长度可以抽象为:
其中:
- :当前队列中待处理任务总数;
- :每个 Pod 期望承担的任务数。
这种指标直接描述“需要多少副本”,通常比 CPU 利用率更接近扩容意图。但业务指标采集链路更长,可能包括 Prometheus、适配器、权限和指标名称映射,故障点也更多。
7.4 HPA 与 VPA 的边界建议
以下组合通常风险较高:
HPA:CPU Utilization
VPA:自动修改 CPU requests
因为二者共同改变利用率计算关系。
相对清晰的组合包括:
HPA:队列长度、请求率等业务指标
VPA:根据 CPU/内存历史调整 requests
或者:
HPA:CPU/内存 AverageValue
VPA:调整 requests
但仍需验证重建、调度和节点容量行为。
对于同一个 Deployment,不建议同时让多个独立系统管理 spec.replicas。例如 HPA、GitOps、发布脚本和手工运维都写副本数,会导致声明状态与实际控制状态不断互相覆盖。
八、VPA 如何影响调度、Pending Pod 和节点自动扩缩容
8.1 请求变大不等于节点会自动增加
假设集群节点的可分配 CPU 为 4,当前已有两个 Pod:
Pod A request: 1500m
Pod B request: 1500m
剩余可分配: 1000m
VPA 将另一个 Pod 的 CPU 请求从 500m 调整为 1500m。如果没有任何节点能容纳它,该 Pod 会进入 Pending:
kubectl get pods -n demo
kubectl describe pod <pending-pod> -n demo
describe 中可能看到:
0/3 nodes are available:
2 Insufficient cpu,
1 node(s) had untolerated taint
只有当节点自动扩缩容器识别到这个 Pending Pod,并且 Node Group 有可扩容空间时,才可能增加节点。VPA 不负责创建节点。
8.2 节点自动扩缩容器看的是请求和可调度性
节点扩容通常关注:
- Pending Pod 的资源请求;
- 节点标签、污点、亲和性和拓扑约束;
- Node Group 的最小、最大节点数;
- Pod 是否属于某个节点组可以承载的范围。
它不应根据“节点当前 CPU 使用率很低”就判断 Pending Pod 一定可以调度。调度器首先依据 requests 和约束检查可行性。
因此,以下组合可能形成延迟链:
VPA 更新推荐
-> 驱逐旧 Pod
-> 新 Pod requests 变大
-> 新 Pod Pending
-> 节点自动扩容
-> 新节点加入并完成调度
如果节点扩容耗时较长,VPA 的自动更新可能在短期内降低可用容量,而不是提高可用容量。
8.3 请求过大也会造成碎片
节点容量规划不能只做总量加法。假设节点剩余 CPU 总量为 2000m,但分散在两个节点:
节点 1 剩余 1000m
节点 2 剩余 1000m
一个 VPA 调整后的 Pod 请求为 1500m,它仍然无法调度,尽管集群总剩余量是 2000m。这就是资源碎片。
所以必须同时观察:
- 总请求与总可分配容量;
- 单节点剩余容量;
- Pod 的单实例最大请求;
- 拓扑、亲和性、污点和 DaemonSet 占用;
- 故障余量。
VPA 推荐出的单 Pod 请求越大,对节点类型和碎片越敏感。
九、容量计算:HPA、VPA 和故障余量如何合并
9.1 基本容量公式
设:
- :副本数;
- :每个 Pod 的资源请求;
- :节点数;
- :每个节点的可分配资源;
- :DaemonSet、系统组件等固定占用;
- :故障余量。
工作负载请求约为:
节点侧可用于工作负载的容量约为:
若要求保留故障余量,则需要满足:
如果还要求一个节点故障后仍能容纳工作负载,则对于节点数为 的集群,至少要检查:
其中 表示剩余节点上的系统占用。实际故障余量还可能按多个节点、可用区和 Pod 分布计算,不能只套一个固定百分比。
9.2 完整算例
假设:
- HPA 最大副本数 ;
- VPA 推荐每 Pod 请求为
700mCPU; - 每个节点可分配 CPU 为
3500m; - 每个节点系统和 DaemonSet 已占
500m,所以工作负载可用约3000m; - 要允许一个节点故障。
最大工作负载请求:
单节点可容纳的完整 Pod 数:
仅从总量看,至少需要:
但 5 个节点在一个节点故障后只剩 4 个节点,最多约能提供:
无法承载 14000m 的请求。因此至少需要 6 个节点:
- 正常状态:
6 × 3000m = 18000m; - 一个节点故障后:
5 × 3000m = 15000m; - 可以容纳
14000m,但余量只有1000m,还要验证 Pod 分布和碎片。
这说明 HPA 的 maxReplicas 与 VPA 的最大请求共同决定节点容量上界。只配置 HPA 的最大副本数而不计算 VPA 可能推荐的最大请求,会使“理论上允许扩容到 20 个副本”变成大量 Pending Pod。
9.3 requests 与 limits 的容量含义不同
调度器主要依据 requests 调度;limits 更像运行时上限。一个 Pod 配置:
resources:
requests:
cpu: 100m
limits:
cpu: "2"
可以被调度到只剩 100m 可分配 CPU 的节点,但运行时可能争用到更高 CPU,具体取决于节点负载和 CFS 限制。
这会带来两个边界:
- 请求过低:调度过于乐观,节点容易超卖,HPA 利用率可能很高;
- 请求过高:调度保守,Pending 和节点成本增加。
VPA 不是用来替代容量压测的。请求应表达调度和稳定运行所需的资源,而不是随意把 limits 的上限复制成 requests。
十、常见误解和对应的失败表现
误解一:HPA 目标 CPU 60% 表示节点 CPU 保持 60%
不对。averageUtilization: 60 通常表示 Pod 容器 CPU 使用量相对于 CPU request 的平均比例,不是节点整体利用率。
节点可能只有 40% 利用率,但某个 Deployment 的 Pod 已达到 90% request;HPA 仍可能扩容。反过来,节点整体很忙,也不代表每个目标 Pod 都需要扩容。
误解二:HPA 一定会立即扩容
HPA 计算出新副本数后,还要经过:
- 指标采集延迟;
- 控制器同步周期;
- 容忍度;
behavior策略;- Deployment 创建 Pod;
- 调度器找到节点;
- 容器启动并通过就绪探针;
- 服务发现和负载均衡更新。
任何一个环节慢,用户看到的扩容效果都会延后。
误解三:VPA 推荐值就是正确的资源规格
VPA 只根据它能观察到的历史和算法生成推荐。以下情况可能使推荐失真:
- 工作负载刚部署,历史样本不足;
- 夜间低负载覆盖了白天峰值;
- 指标采集缺失;
- 容器频繁重启,使用量样本不完整;
- 内存泄漏尚未达到历史高点;
- VPA 的上下限人为截断了推荐;
- 业务指标而不是 CPU 才是实际瓶颈。
误解四:修改 VPA YAML 后,运行中的 Pod 会立即变大
updateMode: Off 不会主动修改运行中的 Pod。Initial 也只影响新建 Pod。自动更新模式可能通过驱逐并重建 Pod 生效,而不是在线修改所有容器资源。
验证方式:
kubectl get vpa web -n demo -o yaml
kubectl get pod -n demo -o custom-columns=NAME:.metadata.name,CPU_REQ:.spec.containers[*].resources.requests.cpu,MEM_REQ:.spec.containers[*].resources.requests.memory
要区分:
- VPA 的
status.recommendation; - Pod 实际
spec.containers[].resources; - 容器当前运行状态。
误解五:Pod Pending 就是节点 CPU 使用率太高
Pending 的真实原因可能是:
- CPU 或内存请求无法满足;
- 污点和容忍度不匹配;
- 节点选择器或亲和性无匹配节点;
- 拓扑分布约束无法满足;
- PVC 无法绑定;
- 镜像拉取或权限问题;
- Pod 数量上限或云厂商配额限制。
应先执行:
kubectl describe pod <pending-pod> -n demo
kubectl get nodes --show-labels
kubectl describe node <node-name>
不要只看:
kubectl top nodes
kubectl top 反映的是观测到的使用量,而调度失败首先通常与 requests 和调度约束有关。
十一、诊断一条完整的扩缩容链路
11.1 HPA 不扩容
依次检查:
kubectl get hpa web -n demo -o yaml
kubectl describe hpa web -n demo
kubectl top pods -n demo
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/demo/pods"
重点判断:
- HPA 的
scaleTargetRef是否指向正确 Deployment; - Deployment 是否存在并有可用的 Scale 子资源;
- Pod 是否声明了使用
Utilization所需的 resource request; - Metrics API 是否有数据;
- 当前指标是否只是落在容忍度内;
maxReplicas是否已经达到;- 是否存在指标解析或权限错误;
- Pod 是否已经扩容但仍未 Ready。
如果 HPA 显示:
desiredReplicas: 10
currentReplicas: 10
不能立刻推断“扩容没有生效”。可能是 maxReplicas: 10 限制了它,也可能 Deployment 已经创建了 10 个 Pod,但其中部分仍处于 Pending 或未通过就绪探针。
11.2 HPA 频繁抖动
观察:
kubectl get hpa web -n demo -w
kubectl get deployment web -n demo -w
kubectl get events -n demo --sort-by=.lastTimestamp
如果副本数在两个值之间变化,检查:
- 指标是否在目标边界附近波动;
- 缩容稳定窗口是否过短;
- 扩缩容策略是否过于激进;
- Pod 启动时间是否长于指标反馈周期;
- VPA 是否修改了资源请求;
- 新 Pod 是否因冷缓存导致 CPU 暂时升高;
- 业务负载是否本身具有周期性。
只增加稳定窗口不一定能解决问题。如果根因是 Pod 启动后资源请求变化、应用预热或 HPA 指标不适合,稳定窗口只能隐藏一部分症状。
11.3 VPA 不生成推荐
检查:
kubectl get vpa -n demo
kubectl describe vpa web -n demo
kubectl get pods -A | grep -i vpa
kubectl logs -n kube-system deploy/vpa-recommender
实际命名空间和 Deployment 名称取决于安装清单。需要确认:
- VPA CRD 是否存在;
- Recommender 是否运行;
- Recommender 是否能访问指标 API;
targetRef是否匹配目标工作负载;- 目标 Pod 是否确实存在;
- 指标历史是否足够;
- resource policy 是否把目标资源排除了;
- VPA 版本与 CRD 字段是否匹配。
11.4 VPA 更新后 Pod Pending
执行:
kubectl get pods -n demo -o wide
kubectl describe pod <pending-pod> -n demo
kubectl get nodes
kubectl get events -n demo --sort-by=.lastTimestamp
然后把 Pending Pod 的请求与节点可分配容量对比:
kubectl get pod <pending-pod> -n demo \
-o jsonpath='{.spec.containers[*].resources.requests}'
kubectl describe node <node-name>
恢复路径通常包括:
- 降低 VPA 的
maxAllowed; - 暂时切换为
Off或Initial; - 增加或放宽节点组容量;
- 修正节点标签、污点和拓扑约束;
- 回滚工作负载资源配置;
- 等待旧 Pod 恢复,或在确认服务容量后手动调整副本数。
如果更新模式会持续驱逐 Pod,直接修改 VPA 配置后还应观察是否有新的驱逐动作和 Pod 重建。
十二、生产取舍:如何把两个控制器放进同一容量模型
12.1 先明确控制目标
一个可靠的设计通常先回答两个问题:
-
副本数由什么决定?
- 请求率;
- 队列长度;
- 并发连接;
- CPU 或内存;
- 其他业务负载指标。
-
单个 Pod 需要多少资源?
- 稳态 CPU;
- 启动和预热峰值;
- 内存工作集;
- Sidecar 和 Agent 开销;
- 故障后重新分配负载时的余量。
如果 HPA 通过业务指标控制副本数,VPA 通过资源历史调整请求,两个控制目标较为清晰。如果 HPA 通过 CPU 利用率控制副本数,同时 VPA 频繁改变 CPU request,就必须把分母变化纳入测试。
12.2 使用 VPA 前先用 Off
先部署:
updatePolicy:
updateMode: "Off"
持续观察:
kubectl get vpa web -n demo -o yaml
kubectl top pod -n demo
kubectl get pod -n demo -o yaml
把推荐值与以下数据比较:
- 压测时的 P95/P99 CPU;
- 内存峰值和 OOM 记录;
- Pod 启动时长;
- 节点可分配容量;
- HPA 在不同请求值下的副本曲线;
- 一个或多个节点故障后的可用容量。
只有推荐值经过压测和容量校验,才考虑 Initial 或自动更新模式。
12.3 用上限保护节点调度
VPA 的 minAllowed 和 maxAllowed 可以限制推荐范围,但它们不是容量规划的替代品。上限过低可能导致 OOM 或 CPU throttling,上限过高可能让单个 Pod 无法调度。
例如:
resourcePolicy:
containerPolicies:
- containerName: web
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: "1"
memory: 1Gi
这段配置的作用是约束 VPA 推荐,不代表 Pod 一定能在当前节点上运行,也不代表 1Gi 内存足以覆盖所有业务峰值。
12.4 用 PDB 和副本数控制 VPA 的中断边界
自动更新模式下,VPA 可能需要重建 Pod。PDB 可以限制自愿中断,但它不会创造容量,也不会保证所有更新都能完成。
例如,3 个副本的服务可以定义:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web
namespace: demo
spec:
minAvailable: 2
selector:
matchLabels:
app: web
它表示自愿中断时至少保留 2 个匹配 Pod。若只有 1 个副本,配置 minAvailable: 1 会使驱逐基本无法进行;这不是 PDB 故障,而是策略本身禁止中断。
十三、API、版本和实现边界
HPA
- 推荐使用
autoscaling/v2; - 旧的
autoscaling/v1能力较少,通常只适合简单 CPU 利用率场景; autoscaling/v2支持多指标和behavior;- HPA 的控制循环周期、默认容忍度、部分错误处理行为受控制器参数和 Kubernetes 版本影响;
- Metrics Server、Custom Metrics API 适配器和 External Metrics API 适配器不是 HPA API 本身。
VPA
- VPA 是独立项目,不是 Kubernetes 核心内置能力;
- 使用前必须确认安装的 VPA 版本、CRD 版本、Admission Webhook 和组件参数;
- 推荐算法不是 Kubernetes API 规范保证;
Auto的历史语义存在版本演进,生产环境应查看所安装版本的文档和源码配置;- 自动更新通常意味着 Pod 驱逐和重建,而不是对所有运行中容器进行无中断资源修改。
云厂商和节点自动扩缩容
节点自动扩缩容的行为取决于:
- 云厂商节点组;
- Cluster Autoscaler 或其他节点控制器;
- 可用区、实例类型和配额;
- 节点启动时间;
- 云厂商弹性伸缩限制;
- 预留实例、Spot/抢占式实例和成本策略。
因此,“HPA 扩容后节点会自动增加”只能算常见部署组合,不是 Kubernetes HPA 的规范保证。
结语:把 HPA、VPA 和节点容量看成一个闭环
HPA 控制的是副本数:
VPA 控制的是单 Pod 资源请求:
节点自动扩缩容器控制的是节点容量:
工作负载能否稳定运行,至少要满足:
而当 HPA 使用资源利用率时,利用率又依赖:
这使得 VPA 修改 后会改变 HPA 的观测结果。若再加上 Pod 启动延迟、指标延迟、稳定窗口、PDB、节点碎片和故障余量,自动扩缩容就不再是一个简单的比例控制问题。
正确的容量验证应同时测试:
- 低负载到高负载的 HPA 扩容;
- 高负载回落后的稳定缩容;
- VPA 推荐值在稳态和峰值下的差异;
- VPA 重建期间的服务可用性;
- 新请求导致的 Pending Pod;
- 节点自动扩容是否能识别并消化 Pending Pod;
- 一个节点或一个可用区故障后的剩余容量;
- HPA 和 VPA 同时启用时的反馈是否振荡。
只有把指标含义、算法分母、稳定窗口、资源请求和节点容量放在同一模型中,HPA 与 VPA 才能从两个独立的自动化功能,变成可验证的交付与运维系统。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:PodDisruptionBudget 与可用性:自愿中断、Eviction、滚动和误区
- 下一篇:Kubernetes 节点自动扩缩容:Pending Pod、Node Group、缩容和成本
- 延伸:Kubernetes 容量规划:Request、利用率、碎片、故障余量和压测
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论