基于 KWOK 的 Kubernetes VPA 组件性能基准测试:以 Vertical Pod Autoscaler 为例的完整实战指南
基于 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 的源码可以确认其完整的四步流程:
- Step 1:创建 Kind 集群——若集群已存在则跳过;否则复制仓库根目录的 .github/kind-config.yaml(1 个 control-plane + 2 个 worker,镜像
kindest/node:v1.36.1),并追加kubeadmConfigPatches提高 API Server 与 controller-manager 的吞吐上限; - Step 2:部署 VPA——调用 deploy-for-e2e-locally.sh 安装
full-vpa,并通过EXTRA_HELM_VALUES环境变量注入基准专用的 Helm values; - Step 3:安装 KWOK 并创建 fake node——执行
install-kwok.sh; - 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 完成三件事:
- 通过 Helm 安装
kwok/kwokchart(--set hostNetwork=true)并等待就绪; - 安装
kwok/stage-fastchart,让 Pod 快速进入 Running 状态; - 创建名为
kwok-node的 fake Node(打上kwok.x-k8s.io/node: fake注解与NoSchedule污点),供后续 Pod 直接绑定。
五、基准测试程序内部流程(What It Does)
基准测试程序入口为 main.go,它假定集群已就绪(VPA、KWOK、fake node 均已部署),然后对每个 profile 依次执行一轮完整迭代:
- 缩容 VPA 组件:把
vpa-updater、vpa-recommender、vpa-admission-controller三个 Deployment 全部缩到 0,并等待 Pod 全部消失(源码 scaleDownVPAComponents),确保缓存不成为影响因素; - 清理上一轮资源:删除全部 VPA Checkpoint、
benchmark命名空间下的 VPA 与 ReplicaSet(源码 cleanupBenchmarkResources); - 创建受管 ReplicaSet:并行(并发上限 50)创建指定数量的 ReplicaSet,每个 RS 2 个 Pod,Pod 通过
nodeName: kwok-node直接指定绑定到 KWOK 节点,绕过调度器以加速创建;同时容忍 KWOK 节点污点(源码 makeReplicaSet); - 创建噪声 ReplicaSet(
--noise-percentage > 0时):额外创建一批不关联任何 VPA 的 RS,用于考察无关对象带来的干扰成本(源码 main.go#L243-L267); - 创建 VPA:并行创建同样数量的 VPA 对象,TargetRef 指向对应 ReplicaSet(
apps/v1),更新模式固定为Recreate(源码 makeVPA); - 等待 Pod 全部 Running:轮询直到预期 Pod 数量全部就绪(超时 5 分钟);
- 扩容 recommender 与 admission-controller,等待 VPA 全部产生 Recommendation(超时 2 分钟);
- 扩容 updater,等待其就绪;
- 等待 2 分钟,直到 updater 首轮循环结束;
- 轮询采集指标:通过
kubectl port-forward访问 updater Pod 的 8943 端口,抓取/metrics端点中的vpa_updater_execution_latency_seconds_sum指标(源码 waitForAndScrapeMetrics),直到出现total标签值(由 updaterRunOnce()中的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 指标尚未采集,属于未来的规划项。
十三、扩展阅读与源码索引
- 基准测试官方文档:vertical-pod-autoscaler/test/benchmark/README.md
- 基准测试主程序:vertical-pod-autoscaler/test/benchmark/main.go
- 一键脚本:vertical-pod-autoscaler/test/benchmark/hack/full-benchmark.sh
- KWOK 安装脚本:vertical-pod-autoscaler/test/benchmark/hack/install-kwok.sh
- 基准专用 Helm values:vertical-pod-autoscaler/test/benchmark/hack/values.yaml
- updater 主循环与指标埋点:vertical-pod-autoscaler/pkg/updater/logic/updater.go
- 计时器与直方图实现:vertical-pod-autoscaler/pkg/utils/metrics/metrics.go
- VPA 本地部署脚本:vertical-pod-autoscaler/hack/deploy-for-e2e-locally.sh
- Kind 基础集群配置:.github/kind-config.yaml
本文介绍的方法适用于任何基于 VPA 的性能回归场景:修改 updater 的过滤逻辑、驱逐速率控制、缓存策略或 API 调用方式后,只需在同一环境下以相同 profile/runs 复跑本基准,即可得到量化的延迟对比,作为合入前的客观依据。