Skip to content

Commit 1988ae0

Browse files
committed
ci(perf): add VPA performance benchmarking
This commit adds VPA benchmarking to the project. It adds code for a benchmark binary which measures the internal latency of the VPA updater loop. This allows users to test changes and how it will affect the performance of the VPA. The code and documentation lives in the vertical-pod-autoscaler/benchmark directory. Signed-off-by: Max Cao <macao@redhat.com>
1 parent 9c5be1b commit 1988ae0

File tree

5 files changed

+1242
-0
lines changed

5 files changed

+1242
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# VPA Performance Benchmark
2+
3+
Measures VPA component latencies using KWOK (Kubernetes WithOut Kubelet) to simulate pods without real resource consumption.
4+
5+
> **Note:** Currently only updater metrics are collected. Recommender metrics are planned for the future.
6+
7+
<!-- toc -->
8+
- [Prerequisites](#prerequisites)
9+
- [Quick Start](#quick-start)
10+
- [What It Does](#what-it-does)
11+
- [Profiles](#profiles)
12+
- [Flags](#flags)
13+
- [Metrics Collected](#metrics-collected)
14+
- [Updater Metrics](#updater-metrics)
15+
- [Cleanup](#cleanup)
16+
- [Notes](#notes)
17+
- [Performance Optimizations](#performance-optimizations)
18+
- [Caveats](#caveats)
19+
<!-- /toc -->
20+
21+
## Prerequisites
22+
23+
- Go 1.21+
24+
- kubectl
25+
- Kind
26+
- yq
27+
28+
## Quick Start
29+
30+
```bash
31+
# Create a Kind cluster with tuned settings
32+
kind create cluster --config=benchmark/kind-config.yaml
33+
34+
# Deploy VPA
35+
./hack/deploy-for-e2e-locally.sh full-vpa
36+
37+
# Build and run
38+
go build -C benchmark -o ../bin/vpa-benchmark .
39+
# requires --kubeconfig flag, KUBECONFIG env var, or ~/.kube/config
40+
bin/vpa-benchmark --profile=small --output=results.txt
41+
```
42+
43+
## What It Does
44+
45+
1. Installs KWOK and creates a fake node
46+
2. Configures VPA deployments with higher QPS/burst limits
47+
3. For each profile run:
48+
- Scales down VPA components
49+
- Cleans up previous benchmark resources
50+
- Creates ReplicaSets with fake pods assigned directly to KWOK node (bypasses scheduler)
51+
- Creates VPAs targeting those ReplicaSets
52+
- Scales up recommender, waits for recommendations
53+
- Scales up updater, waits for its loop to complete
54+
- Scrapes `vpa_updater_execution_latency_seconds_sum` metrics
55+
4. Outputs results to stdout and/or a CSV file if specified
56+
57+
e.g., of output using this command: `bin/vpa-benchmark --profile=small,large,xxlarge`
58+
59+
```bash
60+
========== Results ==========
61+
┌───────────────┬───────────────┬────────────────┬───────────────────┐
62+
│ STEP │ SMALL ( 25 ) │ LARGE ( 250 ) │ XXLARGE ( 1000 ) │
63+
├───────────────┼───────────────┼────────────────┼───────────────────┤
64+
│ AdmissionInit │ 0.0000s │ 0.0001s │ 0.0004s │
65+
│ EvictPods │ 2.4239s │ 24.5535s │ 98.6963s │
66+
│ FilterPods │ 0.0002s │ 0.0020s │ 0.0925s │
67+
│ ListPods │ 0.0001s │ 0.0006s │ 0.0025s │
68+
│ ListVPAs │ 0.0024s │ 0.0030s │ 0.0027s │
69+
│ total │ 2.4267s │ 24.5592s │ 98.7945s │
70+
└───────────────┴───────────────┴────────────────┴───────────────────┘
71+
```
72+
73+
We can then compare the results of a code change with the results of the main branch.
74+
Ideally the benchmark would be done on the same machine (or a similar one), with the same benchmark settings (profiles and runs).
75+
76+
## Profiles
77+
78+
| Profile | VPAs | ReplicaSets | Pods |
79+
| ------- | ---- | ----------- | ---- |
80+
| small | 25 | 25 | 50 |
81+
| medium | 100 | 100 | 200 |
82+
| large | 250 | 250 | 500 |
83+
| xlarge | 500 | 500 | 1000 |
84+
| xxlarge | 1000 | 1000 | 2000 |
85+
86+
## Flags
87+
88+
| Flag | Default | Description |
89+
| ---- | ------- | ----------- |
90+
| `--profile` | small | Comma-separated profiles to run. You can run multiple profiles at once. (e.g., `--profile=small,medium`) |
91+
| `--runs` | 1 | Iterations per profile. This is used for averaging multiple runs. |
92+
| `--output` | "" | Path to output file for results table (CSV format). Output will always be printed to stdout. |
93+
| `--kubeconfig` | "" | Path to kubeconfig. Required if not using KUBECONFIG env var or ~/.kube/config. |
94+
95+
## Metrics Collected
96+
97+
### Updater Metrics
98+
99+
| Metric | Description |
100+
| ------ | ----------- |
101+
| `ListVPAs` | List VPA objects |
102+
| `ListPods` | List pods matching VPA targets |
103+
| `FilterPods` | Filter evictable pods |
104+
| `AdmissionInit` | Verify admission controller status |
105+
| `EvictPods` | Evict pods needing updates |
106+
| `total` | Total loop time |
107+
108+
## Cleanup
109+
110+
```bash
111+
kind delete cluster
112+
```
113+
114+
## Notes
115+
116+
### Performance Optimizations
117+
118+
The benchmark includes several performance optimizations:
119+
120+
- Modifies VPA deployments at startup using `yq`:
121+
- Sets `--kube-api-qps=100` and `--kube-api-burst=200` on all three components
122+
- Sets `--updater-interval=2m` on the updater (default is 60s)
123+
- Pods are assigned directly to the KWOK node via `nodeName`, bypassing the scheduler for faster creation
124+
- The `kind-config.yaml` increases API server limits (`max-requests-inflight`, `max-mutating-requests-inflight`) and kube-controller-manager client QPS to handle the large number of API calls
125+
- Uses ReplicaSets instead of Deployments to skip the Deployment controller layer and speed up pod creation, but keep a targetRef for VPA
126+
127+
### Caveats
128+
129+
- The updater uses `time.Tick` which waits the full interval before the first tick, so the benchmark sleeps 2 minutes before polling for metrics
130+
- The benchmark uses Recreate update mode. In-place scaling is not supported on KWOK pods.
131+
- The benchmark scales down all VPA components at the start of each run, so that any caching is not a factor.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
module k8s.io/autoscaler/vertical-pod-autoscaler/benchmark
2+
3+
go 1.25.3
4+
5+
require (
6+
github.com/olekukonko/tablewriter v1.1.3
7+
golang.org/x/sync v0.19.0
8+
k8s.io/api v0.35.0
9+
k8s.io/apimachinery v0.35.0
10+
k8s.io/autoscaler/vertical-pod-autoscaler v1.5.1
11+
k8s.io/client-go v0.35.0
12+
k8s.io/klog/v2 v2.130.1
13+
)
14+
15+
require (
16+
github.com/clipperhouse/displaywidth v0.6.2 // indirect
17+
github.com/clipperhouse/stringish v0.1.1 // indirect
18+
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
19+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
20+
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
21+
github.com/fatih/color v1.18.0 // indirect
22+
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
23+
github.com/go-logr/logr v1.4.3 // indirect
24+
github.com/go-openapi/jsonpointer v0.22.4 // indirect
25+
github.com/go-openapi/jsonreference v0.21.4 // indirect
26+
github.com/go-openapi/swag v0.25.4 // indirect
27+
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
28+
github.com/go-openapi/swag/conv v0.25.4 // indirect
29+
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
30+
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
31+
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
32+
github.com/go-openapi/swag/loading v0.25.4 // indirect
33+
github.com/go-openapi/swag/mangling v0.25.4 // indirect
34+
github.com/go-openapi/swag/netutils v0.25.4 // indirect
35+
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
36+
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
37+
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
38+
github.com/google/gnostic-models v0.7.1 // indirect
39+
github.com/google/uuid v1.6.0 // indirect
40+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
41+
github.com/json-iterator/go v1.1.12 // indirect
42+
github.com/kr/text v0.2.0 // indirect
43+
github.com/mattn/go-colorable v0.1.14 // indirect
44+
github.com/mattn/go-isatty v0.0.20 // indirect
45+
github.com/mattn/go-runewidth v0.0.19 // indirect
46+
github.com/moby/spdystream v0.5.0 // indirect
47+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
48+
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
49+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
50+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
51+
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
52+
github.com/olekukonko/errors v1.1.0 // indirect
53+
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 // indirect
54+
github.com/spf13/pflag v1.0.10 // indirect
55+
github.com/x448/float16 v0.8.4 // indirect
56+
go.yaml.in/yaml/v2 v2.4.3 // indirect
57+
go.yaml.in/yaml/v3 v3.0.4 // indirect
58+
golang.org/x/net v0.48.0 // indirect
59+
golang.org/x/oauth2 v0.34.0 // indirect
60+
golang.org/x/sys v0.39.0 // indirect
61+
golang.org/x/term v0.38.0 // indirect
62+
golang.org/x/text v0.32.0 // indirect
63+
golang.org/x/time v0.14.0 // indirect
64+
google.golang.org/protobuf v1.36.11 // indirect
65+
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
66+
gopkg.in/inf.v0 v0.9.1 // indirect
67+
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect
68+
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
69+
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
70+
sigs.k8s.io/randfill v1.0.0 // indirect
71+
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
72+
sigs.k8s.io/yaml v1.6.0 // indirect
73+
)
74+
75+
replace k8s.io/autoscaler/vertical-pod-autoscaler => ../

0 commit comments

Comments
 (0)