Vertical Pod Autoscaler:从资源推荐到安全改配
一次常规发布后,订单服务的延迟突然拉高。Pod 的 CPU 使用率只有一个核左右,节点看起来也有空闲,但新副本持续 Pending;已经运行的副本间歇性出现 CPU throttling,另一些副本又因为内存峰值被 OOMKilled。团队最初把问题归因于节点数量不足,扩容节点后却只短暂缓解:Deployment 仍给每个 Pod 声明 cpu: 2、memory: 4Gi,调度器只能按这个请求值预留容量,而真实稳态远低于请求值。另一方面,夜间批任务的请求值只有 128Mi,又无法承受堆内存峰值。
这不是一个“把 requests 调大或调小”就能永久解决的故障。请求值同时进入调度、HPA 利用率分母、节点扩容模拟和成本归属;改小会提高装箱率,却可能让服务在峰值时失去保护,改大则制造空闲预留和节点碎片。Vertical Pod Autoscaler(VPA)的价值,是把历史资源使用与 OOM 信号变成有上下界的推荐,并按明确策略把推荐带到新 Pod 或现有 Pod。它不是容量的自动驾驶:样本质量、变更方式、字段所有权和业务中断预算仍由团队负责。
先把请求值、限制值和真实使用拆开
resources.requests 是调度契约。kube-scheduler 根据它判断 Pod 能否放进节点,节点扩容器也会拿它模拟未调度 Pod 需要什么节点。CPU request 不是 CPU 配额,内存 request 也不是内存保留区;它们描述的是调度与资源分配的基线。limits.cpu 通常通过 CFS 带来节流,limits.memory 被突破时更常见的结果是内核回收失败后触发 OOM。两者不能用同一个“限流”概念解释。
VPA 主要调整容器 requests,并可按策略联动 limits。它不会增加副本数,也不会直接增加节点。推荐对象里的 target 表示模型认为合适的目标请求,lowerBound 和 upperBound 表示推荐区间,uncappedTarget 则保留未受 minAllowed、maxAllowed 约束的模型结果。若 uncappedTarget 长期高于 upperBound,不是“VPA 已经保护成功”,而是容量策略正在截断真实需求。
VPA 由三条链协作:Recommender 读取 Pod 资源历史与 OOM 等信号并写入 status.recommendation;Admission Controller 在 Pod 创建时通过 mutating webhook 写入请求值;Updater 观察当前 Pod 与推荐的差异,根据更新模式选择原地调整或驱逐重建。Deployment 模板通常不会被 VPA 直接改写,因此只看 Git 中的 requests 会误判运行态。
在隔离集群安装三组件
VPA 1.7.0 使用 autoscaling.k8s.io/v1,发行说明包含 InPlace 模式、每个 VPA 的 OOM 阈值等变化;安装前应核对 VPA 1.7.0 发行说明 与目标 Kubernetes 发行线。Auto 已弃用,应显式选择 Off、Initial、Recreate、InPlaceOrRecreate 或 InPlace。VPA 1.7.0 调整的是容器级资源,不能管理 Pod 级 spec.resources;后者即使能由 Kubernetes 原地调整,也不能当成 VPA 已接管的字段。生产集群不要直接跟随仓库主分支。
本地验证可使用具备 Metrics API 的 kind、minikube 或测试集群。使用官方脚本的默认资源指标路径时,先确认 kubectl top 能返回数据,再继续诊断推荐器;如果另行配置了历史数据提供器,则应验证那条输入链,而不是把 kubectl top 当成唯一数据源。Metrics API 不是长期监控存储,相关边界可查阅 Kubernetes 资源指标流水线。
kubectl top node
kubectl top pod -A
git clone --branch vertical-pod-autoscaler-1.7.0 \
--depth 1 https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh
kubectl -n kube-system get deploy,pod | grep vpa
kubectl get crd verticalpodautoscalers.autoscaling.k8s.io
kubectl get mutatingwebhookconfiguration | grep vpa预期看到 recommender、updater、admission-controller 三个工作负载处于可用状态,CRD 存在,webhook 配置引用有效的 Service 与 CA bundle。若 kubectl top 返回 Metrics API not available,应先修复 Metrics Server、聚合 API、kubelet TLS 或控制面网络;VPA Pod 全部 Running 并不能弥补输入数据缺失。官方的安装与最小对象入口在 VPA Quick Start。
共享环境更适合把固定 tag 的清单或 Helm 封装纳入版本库,锁定镜像 digest、ServiceAccount、Pod Security、资源请求与节点放置。托管 Kubernetes 可能提供厂商集成版 VPA,启用方式、组件可见性和收费边界由平台决定;不要同时安装两套控制器管理同一 CRD。
用 Off 模式建立无中断基线
第一次接入应从 Off 开始,只观察推荐,不改变运行中的 Pod。下面的实验创建一个持续消耗 CPU 的 Deployment,并把上下界压在可观察范围内。镜像和参数应先在组织镜像仓库中完成供应链扫描;示例不代表生产资源值。
apiVersion: v1
kind: Namespace
metadata:
name: vpa-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: recommender-demo
namespace: vpa-lab
spec:
replicas: 2
selector:
matchLabels: {app: recommender-demo}
template:
metadata:
labels: {app: recommender-demo}
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65534
containers:
- name: worker
image: registry.k8s.io/ubuntu-slim:0.14
command: ["/bin/sh", "-c"]
args: ["while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"]
resources:
requests: {cpu: 50m, memory: 50Mi}
limits: {cpu: "1", memory: 256Mi}
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: recommender-demo
namespace: vpa-lab
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: recommender-demo
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: worker
controlledResources: ["cpu", "memory"]
controlledValues: RequestsOnly
minAllowed: {cpu: 25m, memory: 32Mi}
maxAllowed: {cpu: "1", memory: 256Mi}应用后不要立刻期待稳定推荐。推荐器需要样本,短时波动会让结果变化。观察对象而不是猜测固定数值:
kubectl apply -f vpa-lab.yaml
kubectl -n vpa-lab get pod -w
kubectl -n vpa-lab describe vpa recommender-demo
kubectl -n vpa-lab get vpa recommender-demo -o yaml
kubectl -n vpa-lab top pod预期证据是 status.recommendation.containerRecommendations 出现 worker,并包含 target、lowerBound、upperBound、uncappedTarget;updateMode: Off 下现有 Pod 的 requests 不应因为推荐自动变化。若 recommendation 为空,检查 VPA 的 RecommendationProvided 条件、targetRef、Pod selector、容器名、指标新鲜度和 recommender 日志。这里验证的是推荐链存在,不是证明目标值适合生产。
配置字段如何改变控制结果
targetRef 决定所有权边界。目标通常是 Deployment、StatefulSet 等有 /scale 和 Pod 模板的控制器;不要让两个 VPA 指向同一批 Pod,也不要绕过工作负载控制器直接管理裸 Pod。目标更名或迁移 namespace 后,旧 VPA 不会自动理解新对象。
updatePolicy.updateMode 决定推荐如何落地。Off 只推荐;Initial 仅在 Pod 创建时由准入控制器写入;Recreate 允许 Updater 驱逐 Pod 后重建;InPlaceOrRecreate 优先使用 Kubernetes 原地 Pod resize,失败时回退到重建;InPlace 只尝试原地调整,无法调整时等待而不驱逐。原地 resize 还受 Kubernetes 功能状态、容器 resizePolicy、节点、运行时以及 VPA Updater 对 pods/resize 子资源权限的约束,不能因为 API Server 接受了 VPA 字段就假设一定原地完成。
containerPolicies 把风险限制在容器级。containerName: "*" 是通配策略,但 sidecar、代理、日志采集器的资源模型与主容器不同,生产中通常应分别写策略。mode: Off 可排除某个容器;controlledResources 决定控制 CPU、内存或两者;controlledValues: RequestsOnly 只改 requests,RequestsAndLimits 会按现有比例联动 limits。若业务把内存 limit 当作故障隔离线,盲目联动会改变 OOM 边界。
minAllowed 是最低保护,maxAllowed 是最高预算。它们需要由启动需求、堆模型、压测峰值、节点规格和 SLO 推导。上限过低会持续截断推荐,上限过高则可能生成无法调度的大 Pod。minReplicas、驱逐速率、PDB 和 Updater 参数共同影响变更节奏;PDB 是中断保护的一部分,不是对所有更新路径的绝对成功保证。
正向实验:从推荐走到新 Pod 请求值
保持 VPA 为 Off,先等待推荐出现并记录当前 Pod UID 与 requests。然后把模式改为 Initial,主动滚动 Deployment。这样可以把“推荐生成”和“新 Pod 准入改写”分成两步,不让 Updater 自动驱逐干扰证据。
kubectl -n vpa-lab get vpa recommender-demo \
-o jsonpath='{.status.recommendation.containerRecommendations[0].target}{"\n"}'
kubectl -n vpa-lab get pod -l app=recommender-demo \
-o custom-columns=NAME:.metadata.name,UID:.metadata.uid,CPU:.spec.containers[0].resources.requests.cpu,MEM:.spec.containers[0].resources.requests.memory
kubectl -n vpa-lab patch vpa recommender-demo --type merge \
-p '{"spec":{"updatePolicy":{"updateMode":"Initial"}}}'
kubectl -n vpa-lab rollout restart deploy/recommender-demo
kubectl -n vpa-lab rollout status deploy/recommender-demo
kubectl -n vpa-lab get pod -l app=recommender-demo \
-o custom-columns=NAME:.metadata.name,CPU:.spec.containers[0].resources.requests.cpu,MEM:.spec.containers[0].resources.requests.memory
kubectl -n vpa-lab get event --sort-by=.lastTimestamp | tail -n 30预期出现新 UID,Pod 上的 requests 接近当时 VPA target 且位于上下界内,而 Deployment 模板仍保留原始值。数值不必与上一条查询逐字相同,因为推荐可能在滚动期间变化。若新 Pod 未被修改,先看 admission-controller 日志和 webhook 失败策略,再检查 Pod 是否由目标控制器创建、容器策略是否匹配。若 Pod 因推荐过大进入 Pending,Event 中应出现 Insufficient cpu 或 Insufficient memory,这说明推荐落地成功但集群容量不满足,并不等于 webhook 失败。
实验结束后将模式改回 Off,避免继续影响后续新 Pod:
kubectl -n vpa-lab patch vpa recommender-demo --type merge \
-p '{"spec":{"updatePolicy":{"updateMode":"Off"}}}'反向实验:用过窄上限暴露策略截断
一个稳定的反例应只改变一个变量。把 CPU maxAllowed 压到 100m,而容器仍持续消耗更高 CPU。等待推荐刷新后比较 target 与 uncappedTarget,再以 Initial 重建 Pod。
kubectl -n vpa-lab patch vpa recommender-demo --type json -p='[
{"op":"replace","path":"/spec/resourcePolicy/containerPolicies/0/maxAllowed/cpu","value":"100m"}
]'
kubectl -n vpa-lab get vpa recommender-demo -w
kubectl -n vpa-lab patch vpa recommender-demo --type merge \
-p '{"spec":{"updatePolicy":{"updateMode":"Initial"}}}'
kubectl -n vpa-lab rollout restart deploy/recommender-demo
kubectl -n vpa-lab rollout status deploy/recommender-demo
kubectl -n vpa-lab top pod
kubectl -n vpa-lab get vpa recommender-demo -o yaml预期 target 被限制在 100m 附近,而 uncappedTarget.cpu 可能继续高于它;Pod 的 CPU request 不超过上限,usage/request 利用率会升高。这个改动没有降低 limits.cpu: "1",因此不能把 request 变小本身解释成 CFS quota throttling;只有实际 CPU 使用触及 limit 时,才应从容器节流指标寻找该证据。具体推荐取决于样本,若短时间内没有拉开差异,应延长观察并核对负载,而不是宣称反例成立。恢复上限后继续观察 recommendation,确认截断信号消失:
kubectl -n vpa-lab patch vpa recommender-demo --type json -p='[
{"op":"replace","path":"/spec/resourcePolicy/containerPolicies/0/maxAllowed/cpu","value":"1"},
{"op":"replace","path":"/spec/updatePolicy/updateMode","value":"Off"}
]'这个实验解释了生产中的一个常见误区:VPA 对象有推荐并不代表容量正确。必须同时观察 uncapped 与 capped 推荐、Pod 实际 request、usage、throttling、OOM、Pending Event 和业务延迟。
Recreate 与 InPlace 的中断模型
Recreate 的确定性较高:Updater 驱逐旧 Pod,控制器创建新 Pod,Admission Controller 注入推荐。代价是连接重建、缓存预热、冷启动和可用副本下降。单副本服务、长事务消费者、无优雅终止的进程不应直接启用。先让 Deployment 有足够副本、readinessProbe、终止宽限和 PDB,再在发布低峰观察一次受控重建。
InPlace 把“是否中断”从 VPA 驱逐转成 Kubernetes Pod resize 状态机。当前 Kubernetes 通过 PodResizePending、PodResizeInProgress 条件及其 reason、message、observedGeneration 表达待处理和执行中状态;容器级调整还要对照 status.containerStatuses[].resources 与 Pod spec,并结合 Event、Updater 和 kubelet 日志确认最终生效。旧资料中的单一 status.resize 字段不能作为当前发行线的验收口径,只看 Pod UID 不变也不足以证明 resize 完成。内存下调尤其要谨慎:运行中占用无法瞬间释放,平台可能延迟或拒绝调整。
InPlaceOrRecreate 适合接受回退重建的服务,但名称本身不是零中断承诺。其风险在于两条路径都可能发生,演练必须同时覆盖原地成功和回退重建。对于绝不允许驱逐的工作负载,选择 InPlace 并接受“暂时不调整”;对于必须尽快纠正 requests 的工作负载,Recreate 更容易预测,但需要中断预算。
与 HPA、调度器和节点弹性的字段合同
VPA 与 HPA 可以共存,但不能同时围绕同一个 CPU 或内存事实形成互相放大的闭环。HPA 的 CPU 利用率通常以 usage/request 为分母;VPA 改 CPU request 会改变 HPA 看到的利用率,即使业务流量不变。稳妥组合是 VPA 只控制内存、HPA 使用 CPU 或业务指标,或 VPA 采用 Off 只提供推荐,由发布流程人工修改 requests。
requests 变大后,现有节点可能放不下新 Pod,Cluster Autoscaler 或 Karpenter 才会处理节点供给。节点扩容有启动时间,VPA 不会为冷启动提前保留 headroom。requests 变小能提高装箱率,却不会主动重排已经运行的 Pod;需要结合发布滚动或受控重平衡观察碎片是否真正下降。
GitOps 也要明确字段所有权。VPA 通常不改 Deployment 模板,但平台若把推荐回写 Git,就必须建立审批、样本窗口、差异阈值和回滚提交。不要让推荐机器人、人工变更和另一个 rightsizing 工具同时写同一字段。责任合同至少记录:谁拥有 VPA CR,谁批准模式从 Off 切换,谁拥有 Deployment requests,谁处理因改配触发的节点成本。
用事件、指标和日志完成分层排障
推荐为空时,从输入向输出排查:Metrics API 是否有新鲜数据,Recommender 是否能 list/watch Pod 与 VPA,targetRef 是否存在,selector 是否能选到 Pod,容器名是否命中策略。推荐存在但新 Pod 未变化时,再看 mutating webhook、Service endpoint、证书、API Server 到 webhook 的网络和 admission-controller 日志。
Pod 被反复重建时,检查 Updater 的驱逐事件、PDB、ReplicaSet 变化、推荐是否抖动,以及 OOM 信号是否持续抬高内存推荐。下面的命令形成一组最小证据:
kubectl -n vpa-lab describe vpa recommender-demo
kubectl -n vpa-lab get pod -o wide
kubectl -n vpa-lab get event --sort-by=.lastTimestamp | tail -n 50
kubectl -n kube-system logs deploy/vpa-recommender --since=20m
kubectl -n kube-system logs deploy/vpa-updater --since=20m
kubectl -n kube-system logs deploy/vpa-admission-controller --since=20m
kubectl get mutatingwebhookconfiguration -o yaml | grep -A12 -B4 vpa组件名会随安装封装变化,应先 kubectl get deploy -A | grep vpa 确认。日志进入工单前要移除 namespace、镜像仓库、节点名和业务标签等敏感信息。Prometheus 侧应记录 recommendation 与当前 request 的差异、被截断比例、Updater 驱逐量、webhook 错误、Pod resize 状态、OOM、throttling、Pending 年龄和节点碎片;指标名以实际发行包 /metrics 为准,不复制未知版本的面板。
RBAC、准入证书与敏感容量数据
Recommender 需要读取工作负载、Pod、VPA 与指标并写 VPA status;Updater 还需要驱逐 Pod;Admission Controller 通过 webhook 修改新 Pod,并管理或读取其 TLS 资产。应使用发行版自带的最小角色作为起点,按 kubectl auth can-i 验证,而不是统一绑定 cluster-admin。
kubectl auth can-i list pods --all-namespaces \
--as=system:serviceaccount:kube-system:vpa-recommender
kubectl auth can-i update verticalpodautoscalers.autoscaling.k8s.io \
--subresource=status \
--all-namespaces --as=system:serviceaccount:kube-system:vpa-recommender
kubectl auth can-i create pods --subresource=eviction --all-namespaces \
--as=system:serviceaccount:kube-system:vpa-updater
kubectl auth can-i patch pods --subresource=resize --all-namespaces \
--as=system:serviceaccount:kube-system:vpa-updaterVPA recommendation 会暴露工作负载规模、资源峰值和业务周期,属于容量敏感数据。限制跨租户读取 VPA status,导出报表时脱敏 namespace 和工作负载名。webhook 私钥只能由控制器身份读取,证书轮换必须在过期前演练;若 webhook 配置为 fail-closed,证书或网络故障可能阻塞 Pod 创建,若 fail-open 则新 Pod 可能绕过推荐。两种选择都要有告警和应急开关。
托管指标、云监控或外部历史存储还会引入云 IAM。只授予读取必要指标的身份,不把云密钥写进 VPA YAML、环境变量转储或日志。优先使用工作负载身份和短期凭证,并把撤销路径写进卸载清单。
容量收益不能只看 requests 下降
VPA 的直接收益可能是降低过度请求、减少节点数,也可能是抬高不足请求、增加节点成本来换取稳定性。评估时至少同时看四组事实:request 总量与节点可分配量、实际使用分布与峰值、Pending/OOM/throttling/延迟、节点规格与碎片率。只展示“节省了多少 CPU request”会奖励危险的下调。
推荐器本身也有成本。它需要保存和处理容器历史,工作负载数量、容器数、指标基数和历史窗口都会影响 CPU 与内存。Admission webhook 位于 Pod 创建链路,必须有高可用、副本反亲和、资源保障和延迟监控。Updater 的驱逐还会制造冷启动流量、缓存回源与额外节点 headroom。
容量评审可使用趋势不变量:连续多个业务周期后,requests 与高分位 usage 的差距收敛;OOM 与 throttling 不因下调恶化;Pending 年龄和节点碎片不单调增加;发布与故障峰值仍有可解释余量。生产阈值由 SLO、峰值模型、节点供给时延和容灾冗余决定,不能照搬示例上下限。
升级、退出与长期责任
升级前先导出 CRD、VPA 对象、控制器参数、RBAC、webhook 配置和镜像 digest,阅读目标发行说明,尤其检查 API schema、update mode、feature gate 和 Kubernetes 兼容性。先在一个非关键 namespace 使用 Off 验证 recommendation,再验证 Initial,最后才允许 Updater 改动现有 Pod。CRD 应先升级并确认 stored version,控制器采用滚动升级;不要在回滚控制器后留下它无法解析的新字段。
退出时先把所有 VPA 切到 Off,记录运行中 Pod 的实际 requests,并决定哪些值要回写 Deployment 模板。直接删除 VPA 不会自动把模板或现有 Pod 恢复到旧 requests;直接卸载 webhook 也可能留下带推荐值运行的 Pod。完成字段接管后再删除对象与组件:
kubectl get vpa -A
kubectl -n vpa-lab patch vpa recommender-demo --type merge \
-p '{"spec":{"updatePolicy":{"updateMode":"Off"}}}'
kubectl delete namespace vpa-lab
# 仅对使用官方脚本安装的隔离环境执行
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-down.sh卸载后核对 CRD、ClusterRoleBinding、webhook、ServiceAccount、Secret 和监控抓取是否残留。若共享集群仍有其他团队使用 VPA,不能删除集群级组件,只能移除自己的 VPA CR。
平台团队负责组件、CRD、证书、指标与升级;应用团队负责负载模型、启动资源、SLO 和中断语义;容量团队负责节点规格、headroom 与成本;安全团队负责跨租户读取和凭证;发布负责人批准从推荐到自动改配的阶段变化。一个 VPA 进入自动模式前,应能回答推荐依据是否新鲜、上下界为何合理、失败时谁回滚、HPA 是否受影响、节点是否有空间。自动化真正成熟的标志,不是所有工作负载都开启 Recreate,而是每次资源变化都有明确所有者、可观察证据和安全退出路径。
升级前查哪些信息
VPA 1.7.0 Release:确认新模式、修复项和依赖基线。
VPA Quick Start:核对 update mode 与最小对象结构。
Kubernetes In-place Pod Resize:判断目标集群的原地调整条件与状态字段。Kubernetes Pod Disruptions:设计 Recreate 路径的中断预算。
