VPA 推荐不准?不是 VPA 不行——是你 Pod 重启过
场景:VPA 上线后推荐 CPU 1.2 核,Pod 实际只用 300m 路径:坐标 → 分层 → 路径 → 定位 → 标点
以下排查基于 K8s v1.25,VPA v1.x(autoscaling.k8s.io/v1 API)。
上篇讲了 HPA 扩缩容不灵敏的 requests 分母陷阱——HPA 的 utilization 计算依赖 Pod 的 requests 做分母,requests 设大了利用率就偏低,导致该扩不扩。这篇我们来看 K8s 自动化的另一个难题:VPA 推荐值到底有多「健忘」。
VPA 上线跑了一天,开发过来说推荐不准:
kubectl get vpa 一看 target 1200m,kubectl top pod 一查只用了 300m——差了 4 倍。
不是 VPA 不准——是你的 Pod 重启过,VPA 的历史窗口清零了。
坐标
4 倍差距:target 1200m vs 实际 300m
kubectl get vpa notification-service -o yaml 的输出暴露了第一条线索:
target 显示 CPU 1200m,但同一时间 kubectl top pod 只显示 300m:

差距不是 10% 也不是 20%,是 4 倍。这不是微调能解决的问题。
但 kubectl top pod 看的是当前使用率,而 VPA Recommender 看的是历史分布——这两个时间窗口不一样。要搞清楚 VPA 到底看了哪些数据才算出 1200m,得从 VPA 的三层架构逐层排查。
分层
VPA 不是一个单独的 Pod,它由三个组件协作构成: - VPA Recommender:读 metrics-server 的历史数据,算推荐值 - VPA Updater:决定什么时候触发 Pod 重建以应用新推荐 - VPA Admission Controller:Pod 创建时通过 Webhook 注入推荐值
不搞清这三层的边界,你查 VPA 的问题就会一直在 Pod 层打转。

排查必须走完三层:先看 Recommender 怎么算的 → 再看 Updater 为什么不执行 → 最后检查集群层 HPA 是否冲突。任何一个环节都可能让推荐值和预期差 4 倍。
Layer 1 Pod 层:VPA Recommender 的 histogram 窗口
VPA Recommender 的核心数据结构是 histogram——频率分布直方图,VPA 按预设的 bucket 区间(如 100m~200m、200m~300m)统计 CPU/内存使用出现的频次分布,用于计算 P95 百分位推荐值。它不取"当前值",而是取过去一段时间的分布百分位(默认 P95)。
kubectl get vpa notification-service -o yaml 的 status.recommendation.containerRecommendations[0].cpu 字段包含了计算细节,但更关键的数据藏在推荐器的内部状态里:

要看 Recommender 实际看到了多少历史数据,两条路:
# 路径 A:log 级别提到 5 看计算过程
kubectl logs -n kube-system deployment/vpa-recommender --tail=200 | grep histogram
# 路径 B:Prometheus 查 VPA 的 metrics(如果配了监控)
vpa_recommender_histogram_bucket{container="main",pod="notification-service"}
如果日志里只有 2 行 histogram 记录,说明 Recommender 只看了一两个小时的数据。默认 8 天的观测窗口里只有 2 小时,为什么?
——因为 Pod 刚重启过。
Pod 重启后,Recommender 对新 Pod 没有历史数据积累。它的保护逻辑是:数据不够 → 先给一个保守估计,防止再次 OOM。这个保守估计就是 OOMProtectionFactor。
OOMProtectionFactor 机制:VPA 会在 histgoram 中记录 OOM 事件。一旦 Pod 的某个容器发生过 OOMKill,Recommender 会在推荐值上乘以一个保护系数(默认 1.2 + buffer),确保新推荐值显著高于 OOM 发生时的使用量。
你的 Pod 没有真的 OOM,但 Pod 重启了——Recommender 的初始推荐策略和 OOM 保护策略本质上是同一个逻辑分支:历史数据不足以判断安全边界时,给出高于实际使用量的 recommendation,防止生产环境因为 requests 太小被 OOMKill。
C2 环节的关键问题回答清楚了:VPA 的哪个组件在什么条件下触发这个行为? → VPA Recommender 的 pod_memory_oom_patching 逻辑,在容器级历史窗口不足 8 天时启动保守推荐模式。
Layer 2 控制面层:VPA Updater 为什么不执行
还有一个令人困惑的现象——既然推荐了 1200m,为什么 Pod 没被更新?
kubectl describe vpa notification-service 看看 Update 状态:

ConfigMismatching 为 True——VPA updater 发现当前 Pod 的 resources 和 VPA 配置中的 updateMode 不匹配。如果你的 updateMode 是 Initial(只在 Pod 创建时注入),updater 不会主动重建 Pod。
VPA 有三种 updateMode:
| Mode | 行为 | 适用场景 |
|---|---|---|
Auto |
自动更新 + 自动重建 Pod | 开发/测试环境 |
Initial |
只在 Pod 创建时注入 | 生产环境,希望手动控制变更时机 |
Off |
只推荐不执行 | 只看 VPA 建议,手动调整 |
C3:UpdateMode —— VPA Updater 根据这个配置决定是否主动 Evict Pod 来应用新推荐值。Auto 下 Updater 会因 recommendation 变化而驱逐旧 Pod 创建新 Pod。
大多数生产团队用的模式是 Off——只参考 VPA 的推荐值,不改 Pod。如果你的 mode 是 Off,VPA 推荐了 1200m,Pod 不会自动更新,但开发看到的画面是"VPA 推荐了 1.2 核→其实根本没动"。
Layer 3 集群层:HPA + VPA 共享 metric 时的互斥问题
如果你同时开了 HPA(HorizontalPodAutoscaler——基于 CPU/内存使用率自动调整 Pod 副本数的控制器),那还有一个更大的陷阱。
上篇我们花了整篇文章讲 HPA 的 requests 分母问题。VPA 正好反过来——它会改 requests。VPA 改 requests → HPA 的 utilization 分母改变 → HPA 判断"利用率低了" → 缩容 → VPA 发现资源变少又建议扩容 → 震荡。

这不是理论推演,是一个真实线上案例:团队同时装了 VPA Auto 和 HPA,VPA 把 requests 从 500m 改到 1.2 核后,HPA 的 CPU 利用率从 65% 掉到 27%,HPA 缩容了 2 个 Pod。VPA 检测到 Pod 变少 → 认为 CPU 压力大 → 又建议更大的 requests——无限循环。
解决方式:
- 方案 A(推荐):HPA 不用
resource.cpu,改用 custom metrics(QPS / 延迟)。这样 VPA 改 requests 不影响 HPA 的扩缩容判断 - 方案 B(长短期分离):VPA 只用
Off模式做参考,HPA 用resource.cpu。定期查看 VPA 推荐,人工调整对应的 Deployment requests - 方案 C(不推荐):同时开 VPA
Auto+ HPAresource.cpu——除非你写的 HPA target 是 custom metric
VPA 排查顺序表
| 层 | 排查内容 | 关键命令 |
|---|---|---|
| Pod 数据层 | Recommender histogram 窗口是否充足 | kubectl logs -n kube-system deployment/vpa-recommender \| grep histogram |
| 控制面更新层 | Updater 的 updateMode 和执行状态 | kubectl describe vpa <name> → Conditions |
| 集群协调层 | HPA 是否与 VPA 共享 resource.cpu | kubectl get hpa <name> -o yaml \| yq '.spec.metrics' |
路径
五步排查顺序
下次遇到"VPA 推荐值不对劲",按截图中的顺序查:

定位
最常见的误判
❌ "VPA 推荐了 1.2 核——不准啊,关掉吧"
大多数人遇到 VPA 推荐偏差后的第一反应是"关掉"。他们认为 VPA 是个自动调参工具,推出来的值经不起推敲。
✅ VPA 推荐是一个参考区间,不是最终答案
VPA 的推荐值取决于三个因素,都排查清楚再做判断:
- 历史窗口:Pod 运行多久了?<3 天的窗口,VPA 会保守推荐。用
kubectl logs看 histogram 条目数 - OOMProtectionFactor:发生过 OOMKill 吗?如果有,VPA 在保护你。没有 OOM 但窗口不够,也会触发类似逻辑
- minAllowed / maxAllowed:VPA 配置里有没有设边界?推荐值和边界重叠时,实际用的是边界
VPA 推荐不是「处方」——是「参考区间」。理解它的保守倾向,比关掉它更有用。开篇的对手不是"VPA 不准",而是"VPA 不可靠"这个结论本身。

标点
修复方案
修复 1:给 VPA 加约束边界
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: notification-service-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: notification-service
updatePolicy:
updateMode: "Off" # 只用推荐,不自动更新
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: 200m
memory: 256Mi
maxAllowed:
cpu: "1"
memory: 1Gi
minAllowed 和 maxAllowed 是 VPA 的护栏——推荐值不会低于 min、不会高于 max。哪怕 OOMProtectionFactor 把推荐推到 1.2 核,maxAllowed: 1 核能把它截住。
修复 2:mode=Off + 手动参考周期
生产环境建议用 updateMode: Off,每个月看一次 VPA 推荐,决定是否需要调整 Deployment 的 requests:
kubectl get vpa notification-service -o jsonpath='{.status.recommendation.containerRecommendations[0].target}'
这里有一条可以直接拿来用的排查清单——
Check-list
每条对应一个 kubectl 命令:
# □ 看 VPA 推荐值
kubectl describe vpa <name>
# □ 看 Recommender 日志(判断历史窗口是否充足)
kubectl logs -n kube-system deployment/vpa-recommender --tail=100 | grep histogram
# □ 看当前 Pod 实际使用量(对比检查)
kubectl top pod -l app=<app>
# □ 看 VPA 的 updateMode 和 minAllowed/maxAllowed
kubectl get vpa <name> -o yaml | yq '.spec.updatePolicy, .spec.resourcePolicy'
# □ 检查 HPA metrics(看是不是和 VPA 共享了 resource.cpu)
kubectl get hpa <name> -o yaml | yq '.spec.metrics'
C6:下篇我们聊 PodDisruptionBudget——一个设太严会导致节点维护完全卡死的"静默"配置。
Pod 重启一次,VPA 失忆一周。理解它的保守倾向,才能用好它的推荐。
"故障排查的终点不是修好了——是把排查路径写成 check-list。"
附:完整命令清单
# VPA 推荐值查看
kubectl describe vpa <name>
kubectl get vpa <name> -o yaml | yq '.status.recommendation'
# VPA Recommender 日志(窗口判断)
kubectl logs -n kube-system deployment/vpa-recommender --tail=200 | grep -E "histogram|OOM|recommendation"
# Pod 实际使用量
kubectl top pod -l app=<app>
# VPA 配置
kubectl get vpa <name> -o yaml | yq '.spec.updatePolicy'
kubectl get vpa <name> -o yaml | yq '.spec.resourcePolicy'
# HPA 指标配置(检查 VPA+HPA 冲突)
kubectl get hpa <name> -o yaml | yq '.spec.metrics'
# VPA Updater 状态
kubectl logs -n kube-system deployment/vpa-updater --tail=100
📺 公众号「Ai拆代码的曹操」 🌟 知识星球「Ai拆代码的曹操」