基于 KWOK 的 Kubernetes VPA 组件性能基准测试:以 Vertical Pod Autoscaler 为例的完整实战指南

原创2026-09-15 10:15:11533 阅读
文章标签:弹性伸缩云原生容器编排

基于 KWOK 的 Kubernetes VPA 组件性能基准测试:以 Vertical Pod Autoscaler 为例的完整实战指南

导读:本文围绕 Kubernetes 官方 autoscaler 仓库中 vertical-pod-autoscaler 自带的性能基准测试方案展开,讲解如何利用 KWOK(Kubernetes WithOut Kubelet)在不消耗真实资源的前提下,大规模模拟 Pod 与 VPA 对象,量化 vpa-updater 各执行阶段(ListVPAs、ListPods、FilterPods、AdmissionInit、EvictPods)的端到端延迟。读完本文,你将掌握一套可一键复现的本地基准测试流程,能够用同一台机器、同一组 profile 参数对比代码改动前后 VPA 的性能差异,为 VPA 的调优与回归验证提供数据支撑。

一、基准测试的背景:为什么用 KWOK 测 VPA

Vertical Pod Autoscaler(VPA)在真实集群中的性能瓶颈通常出现在大规模场景下:当集群中存在成百上千个 VPA 对象与关联 Pod 时,updater 每一轮循环需要完成的 API 调用、过滤与驱逐操作会显著拉长执行时间。要稳定、低成本地复现这种压力,传统做法需要真实工作节点和真实容器,成本高且难以控制变量。

本仓库的基准测试方案(vertical-pod-autoscaler/test/benchmark/README.md)采用了 KWOK(Kubernetes WithOut Kubelet)来模拟 Pod:KWOK 可以在没有 kubelet 的节点上"假装"Pod 处于 Running 状态,从而在没有真实资源消耗的情况下快速创建数千个 Pod 对象。这样测试环境的规模只受 API Server 与客户端吞吐限制,而不受真实计算资源限制。

需要特别说明的是(README 明确标注):当前版本只采集 updater 的指标,recommender 指标仍在规划中。因此本文聚焦于 updater 的延迟测量。

二、环境准备(Prerequisites)

运行基准测试需要以下工具:

工具 用途
Go 1.21+ 编译基准测试程序 vpa-benchmark
kubectl 与 Kind 集群交互
Kind 本地搭建 Kubernetes 集群
Helm 安装 VPA 与 KWOK(含 stage-fast chart)
Docker Kind 集群运行的基础(full-benchmark.sh 脚本注释中列出)

三、快速开始(Quick Start,本地一键执行)

hack/full-benchmark.sh 脚本将整个流程封装为一条命令:创建 Kind 集群 → 部署 VPA → 安装 KWOK → 配置 VPA 基准参数 → 编译并运行基准测试。

cd vertical-pod-autoscaler
./test/benchmark/hack/full-benchmark.sh --profile=small --output=results.csv

也可以直接把任意基准测试 flag 透传给脚本:

./test/benchmark/hack/full-benchmark.sh --profile=small,medium,large --runs=3 --output=results.csv

从 full-benchmark.sh 的源码可以确认其完整的四步流程:

  1. Step 1:创建 Kind 集群——若集群已存在则跳过;否则复制仓库根目录的 .github/kind-config.yaml(1 个 control-plane + 2 个 worker,镜像 kindest/node:v1.36.1),并追加 kubeadmConfigPatches 提高 API Server 与 controller-manager 的吞吐上限;
  2. Step 2:部署 VPA——调用 deploy-for-e2e-locally.sh 安装 full-vpa,并通过 EXTRA_HELM_VALUES 环境变量注入基准专用的 Helm values;
  3. Step 3:安装 KWOK 并创建 fake node——执行 install-kwok.sh;
  4. Step 4:编译并运行——go build -C benchmark -o ../bin/vpa-benchmark . 后运行二进制,把命令行参数原样透传。

四、手动分步搭建(Manual Setup)

如果集群已经存在,或希望逐步排查问题,可以按以下步骤单独执行(注意:手动搭建时需自行处理 full-benchmark.sh 自动追加的 kubeadmConfigPatches 优化,可用基础配置或自建配置创建集群):

# 1. 创建 Kind 集群
kind create cluster --config=.github/kind-config.yaml

# 2. 部署 VPA(注入基准专用 Helm values)
EXTRA_HELM_VALUES=./benchmark/hack/values.yaml ./hack/deploy-for-e2e-locally.sh full-vpa

# 3. 安装 KWOK 并创建 fake node
./benchmark/hack/install-kwok.sh

# 4. 编译并运行基准测试
go build -C benchmark -o ../bin/vpa-benchmark .
./bin/vpa-benchmark --profile=small --output=results.csv

4.1 基准专用 VPA 配置(values.yaml)

benchmark/hack/values.yaml 通过 Helm 为三个 VPA 组件注入基准专用参数:

组件 参数 值 作用
recommender --kube-api-qps 100 提高 API 客户端 QPS
recommender --kube-api-burst 200 提高 API 客户端突发上限
recommender --memory-saver true 启用内存节省模式
updater --kube-api-qps 100 提高 API 客户端 QPS
updater --kube-api-burst 200 提高 API 客户端突发上限
updater --updater-interval 2m 拉长更新循环间隔(默认 60s)
admissionController --kube-api-qps 100 提高 API 客户端 QPS
admissionController --kube-api-burst 200 提高 API 客户端突发上限

提高 QPS/Burst 的目的是避免客户端侧限流成为测量的瓶颈;把 updater 间隔设为 2 分钟则是为了配合"等待首轮循环"的时序设计(详见下文注意事项)。

4.2 KWOK 安装细节(install-kwok.sh)

install-kwok.sh 完成三件事:

  1. 通过 Helm 安装 kwok/kwok chart(--set hostNetwork=true)并等待就绪;
  2. 安装 kwok/stage-fast chart,让 Pod 快速进入 Running 状态;
  3. 创建名为 kwok-node 的 fake Node(打上 kwok.x-k8s.io/node: fake 注解与 NoSchedule 污点),供后续 Pod 直接绑定。

五、基准测试程序内部流程(What It Does)

基准测试程序入口为 main.go,它假定集群已就绪(VPA、KWOK、fake node 均已部署),然后对每个 profile 依次执行一轮完整迭代:

  1. 缩容 VPA 组件:把 vpa-updater、vpa-recommender、vpa-admission-controller 三个 Deployment 全部缩到 0,并等待 Pod 全部消失(源码 scaleDownVPAComponents),确保缓存不成为影响因素;
  2. 清理上一轮资源:删除全部 VPA Checkpoint、benchmark 命名空间下的 VPA 与 ReplicaSet(源码 cleanupBenchmarkResources);
  3. 创建受管 ReplicaSet:并行(并发上限 50)创建指定数量的 ReplicaSet,每个 RS 2 个 Pod,Pod 通过 nodeName: kwok-node 直接指定绑定到 KWOK 节点,绕过调度器以加速创建;同时容忍 KWOK 节点污点(源码 makeReplicaSet);
  4. 创建噪声 ReplicaSet(--noise-percentage > 0 时):额外创建一批不关联任何 VPA 的 RS,用于考察无关对象带来的干扰成本(源码 main.go#L243-L267);
  5. 创建 VPA:并行创建同样数量的 VPA 对象,TargetRef 指向对应 ReplicaSet(apps/v1),更新模式固定为 Recreate(源码 makeVPA);
  6. 等待 Pod 全部 Running:轮询直到预期 Pod 数量全部就绪(超时 5 分钟);
  7. 扩容 recommender 与 admission-controller,等待 VPA 全部产生 Recommendation(超时 2 分钟);
  8. 扩容 updater,等待其就绪;
  9. 等待 2 分钟,直到 updater 首轮循环结束;
  10. 轮询采集指标:通过 kubectl port-forward 访问 updater Pod 的 8943 端口,抓取 /metrics 端点中的 vpa_updater_execution_latency_seconds_sum 指标(源码 waitForAndScrapeMetrics),直到出现 total 标签值(由 updater RunOnce() 中的 defer timer.ObserveTotal() 产生)即认为该轮循环完成。

一轮完整的迭代即产生一组各步骤延迟数据;--runs 大于 1 时会对多次运行取平均值,并额外输出逐次运行明细表。

5.1 结果输出示例

README 给出的示例(执行 bin/vpa-benchmark --profile=small,large,xxlarge):

========== Results ==========
┌───────────────┬───────────────┬────────────────┬───────────────────┐
│     STEP      │ SMALL  ( 25 ) │ LARGE  ( 250 ) │ XXLARGE  ( 1000 ) │
├───────────────┼───────────────┼────────────────┼───────────────────┤
│ AdmissionInit │ 0.0000s       │ 0.0001s        │ 0.0004s           │
│ EvictPods     │ 2.4239s       │ 24.5535s       │ 98.6963s          │
│ FilterPods    │ 0.0002s       │ 0.0020s        │ 0.0925s           │
│ ListPods      │ 0.0001s       │ 0.0006s        │ 0.0025s           │
│ ListVPAs      │ 0.0024s       │ 0.0030s        │ 0.0027s           │
│ total         │ 2.4267s       │ 24.5592s       │ 98.7945s          │
└───────────────┴───────────────┴────────────────┴───────────────────┘

观察该表可以发现:随着 VPA/Pod 数量从 25 增长到 1000,EvictPods 几乎线性增长并主导了总耗时(约占总循环时间的 99%),而 ListVPAs 等查询类操作基本保持稳定。这就是本基准测试希望量化对比的核心结论形态。

对比方法:将代码改动后的测试结果与 main 分支的结果对比;理想情况下应在同一台机器(或配置相近的机器)、使用相同的 profile 与 runs 设置下进行,以控制变量。

六、测试画像(Profiles)

main.go 中内置了 5 档 profile(main.go#L64-L70),每个 profile 定义的是 ReplicaSet 数量,每个 RS 固定 2 个 Pod:

Profile VPAs ReplicaSets Pods
small 25 25 50
medium 100 100 200
large 250 250 500
xlarge 500 500 1000
xxlarge 1000 1000 2000

当设置 --noise-percentage=P 时,每个 profile 还会额外创建 P% 的噪声 ReplicaSet(不关联任何 VPA)。例如 --profile=medium --noise-percentage=50 会创建 100 个受管 RS(200 Pod)+ 50 个噪声 RS(100 Pod),共 300 Pod。噪声 Pod 不增加 VPA 数量,但会抬高 FilterPods 与 ListPods 的成本。

七、命令行参数(Flags)

Flag 默认值 说明
--profile small 逗号分隔的 profile 列表,可一次运行多个(如 --profile=small,medium);合法值包括 small/medium/large/xlarge/xxlarge,未知值会直接 fatal 退出(源码 main.go#L89-L95)
--runs 1 每个 profile 的迭代次数,用于多次运行取平均
--output "" 结果表格输出文件路径(CSV 格式);结果始终同时打印到 stdout
--kubeconfig "" kubeconfig 路径;未设置时使用 KUBECONFIG 环境变量或 ~/.kube/config
--noise-percentage 0% 相对于受管 ReplicaSet 数量的额外噪声(不受管)ReplicaSet 百分比;0% 表示无噪声;负值会被拒绝(源码 main.go#L97-L99)

此外,基准程序内部会把客户端 QPS 强制设为 200、Burst 设为 400(源码 main.go#L111-L113),避免客户端限流干扰测量;创建/更新资源时内置了指数退避重试(5 步、初始 100ms、因子 2.0、抖动 0.1),对 Conflict/ServerTimeout/TooManyRequests/ServiceUnavailable 类瞬时错误自动重试。

八、采集的指标(Metrics Collected)

Updater Metrics

指标名为 vpa_updater_execution_latency_seconds(直方图,标签 step),程序通过正则从 /metrics 中抓取其 _sum 序列(源码 parseMetrics)。

指标(step) 说明
ListVPAs 列出 VPA 对象
ListPods 列出匹配 VPA 目标的 Pod
FilterPods 过滤可驱逐的 Pod
AdmissionInit 校验 admission controller 状态
EvictPods 驱逐需要更新的 Pod
total 整轮循环总耗时

指标背后的源码实现

这些 step 与 pkg/updater/logic/updater.go 中的 ExecutionTimer 调用一一对应:RunOnce() 开头创建计时器并 defer timer.ObserveTotal()(updater.go#L194-L197),随后在 ListVPAs(updater.go#L216)、ListPods(updater.go#L263)、FilterPods(updater.go#L280)、AdmissionInit(updater.go#L285)、EvictPods(updater.go#L464)处分别调用 timer.ObserveStep(...)。

计时器的实现位于 pkg/utils/metrics/metrics.go:ObserveStep 测量自上一次调用以来的耗时,ObserveTotal 测量自计时器创建以来的总耗时;直方图桶从 0.01s 一直延伸到 300s(CreateExecutionTimeMetric,metrics.go#L69-L78),可覆盖从数百个到数千个 Pod 的宽动态范围。基准程序正是利用"出现 total 标签即表示整轮循环已走完"这一特性来判断采集时机。

九、辅助脚本与环境变量(Scripts)

脚本 用途
hack/full-benchmark.sh 完整本地工作流(Kind + VPA + KWOK + 配置 + 基准测试)
hack/install-kwok.sh 安装 KWOK controller 并创建 fake node
hack/values.yaml 基准专用的 VPA Helm values 配置

脚本接受的环境变量:

变量 默认值 使用者
KWOK_VERSION v0.7.0 install-kwok.sh
KWOK_NAMESPACE kube-system install-kwok.sh
KWOK_NODE_NAME kwok-node install-kwok.sh
VPA_NAMESPACE kube-system configure-vpa.sh
KIND_CLUSTER_NAME kind full-benchmark.sh

十、清理(Cleanup)

基准测试结束后,删除整个 Kind 集群即可:

kind delete cluster

十一、性能优化措施(Performance Optimizations)

本基准测试为保证"测的是 VPA 而非基础设施瓶颈",内置了多项优化:

  • values.yaml 通过 Helm 配置 VPA 组件:
    • 三个组件均设置 --kube-api-qps=100、--kube-api-burst=200;
    • updater 设置 --updater-interval=2m(默认 60s);
    • recommender 设置 --memory-saver=true;
  • Pod 直接绑定 KWOK 节点:通过 nodeName 绕过调度器,加速 Pod 创建(源码 makeReplicaSet 中的 NodeName: kwokNodeName);
  • 提升 API Server 与 controller-manager 吞吐:full-benchmark.sh 在基础 .github/kind-config.yaml 上追加 kubeadmConfigPatches,将 max-requests-inflight 提到 2000、max-mutating-requests-inflight 提到 1000,并将 controller-manager 的 concurrent-replicaset-syncs 设为 500、kube-api-qps 设为 500、kube-api-burst 设为 1000,以支撑大规模 API 调用;
  • 使用 ReplicaSet 而非 Deployment:跳过 Deployment controller 这一层,加快 Pod 创建速度,同时仍保留 VPA 所需的 targetRef。

十二、注意事项与已知限制(Caveats)

  • 首轮间隔等待:updater 使用 time.Tick,首个 tick 要等满整个间隔才触发。由于基准配置的 --updater-interval=2m,程序在扩容 updater 后需先睡眠 2 分钟再开始轮询指标(对应源码 main.go#L326-L330 的注释说明);
  • 更新模式限制:基准测试使用 Recreate 更新模式,因为 KWOK 的 Pod 不支持 in-place 扩缩容;
  • 缓存隔离:每轮运行开始时都会把所有 VPA 组件缩容到 0,确保各轮之间任何缓存都不会成为影响因素;
  • 当前只覆盖 updater:recommender 指标尚未采集,属于未来的规划项。

十三、扩展阅读与源码索引

本文介绍的方法适用于任何基于 VPA 的性能回归场景:修改 updater 的过滤逻辑、驱逐速率控制、缓存策略或 API 调用方式后,只需在同一环境下以相同 profile/runs 复跑本基准,即可得到量化的延迟对比,作为合入前的客观依据。

登录后查看全文
autoscaler