Skip to content

Commit 001261e

Browse files
committed
Creates exemplary/representative pod fuzzer tool
1 parent da60396 commit 001261e

File tree

7 files changed

+3412
-0
lines changed

7 files changed

+3412
-0
lines changed

fuzzer/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Exemplary Pod Fuzzer
2+
3+
The Exemplary Pod Fuzzer is a high-fidelity synthetic pod generator designed to stress-test the Kubernetes control plane. It uses a **"Sanitize & Clone"** model: it takes a real production pod manifest as a "base", scrubs all PII/sensitive data, and preserves the exact structural complexity (volume mounts, env vars, managed fields) for benchmarking.
4+
5+
## Key Features
6+
7+
- **Direct Sanitization**: Automatically scrubs Names, Namespaces, UIDs, and OwnerReferences from a real pod.
8+
- **Spec Randomization**: Randomizes all environment variable keys and values while maintaining the original count and structure.
9+
- **ManagedFields Cloning**: Clones the exact history and field ownership of the base pod, but randomizes the internal JSON paths (supporting both `f:` and `k:` prefixes).
10+
- **Safety by Default**: Forces pods to be unschedulable (Pending state) using non-existent nodeSelectors and schedulerNames.
11+
- **Performance Optimized**: Uses high-performance `client-go` settings (500 QPS / 1000 Burst) and precomputes "Fuzzed Prototypes" to ensure string interning memory optimizations are correctly triggered.
12+
13+
## Benchmarking with Kind
14+
15+
To perform a realistic 50,000 pod stress test on a local `kind` cluster, follow these steps:
16+
17+
### 1. Create a Large-Scale Kind Cluster
18+
Standard `kind` clusters have a default `etcd` quota that is too small for 50k heavy pods. Use this configuration to increase the quota to 8GB:
19+
20+
```bash
21+
cat <<EOF > kind-config.yaml
22+
kind: Cluster
23+
apiVersion: kind.x-k8s.io/v1alpha4
24+
nodes:
25+
- role: control-plane
26+
kubeadmConfigPatches:
27+
- |
28+
kind: ClusterConfiguration
29+
etcd:
30+
local:
31+
extraArgs:
32+
quota-backend-bytes: "8589934592"
33+
EOF
34+
35+
kind create cluster --config kind-config.yaml --name fuzzer-test
36+
```
37+
38+
### 2. Prepare the Namespace and Dependencies
39+
Real production pods (like the templates in this repo) often have dependencies like specific **Namespaces** or **ServiceAccounts**. The fuzzer preserves these references. You must create them before injecting pods:
40+
41+
```bash
42+
# Example: Using the complex-daemonset.yaml base
43+
kubectl create namespace fuzz-test
44+
kubectl create serviceaccount cilium -n fuzz-test
45+
```
46+
47+
### 3. Run the Fuzzer
48+
Inject the pods using high concurrency.
49+
50+
```bash
51+
go run test/utils/fuzzer/cmd/main.go \
52+
--base-pod test/utils/fuzzer/templates/complex-daemonset.yaml \
53+
--namespace fuzz-test \
54+
--name-prefix representative-pod \
55+
--count 50000 \
56+
--concurrency 100
57+
```
58+
59+
## Usage (General)
60+
61+
### Generate Pod Manifests to Disk
62+
To generate fuzzed manifests for manual inspection:
63+
```bash
64+
go run test/utils/fuzzer/cmd/main.go \
65+
--base-pod path/to/real-pod.yaml \
66+
--name-prefix representative-pod \
67+
--namespace my-test-ns \
68+
--count 1000 \
69+
--out-dir ./generated-pods
70+
```
71+
72+
## Flags
73+
74+
- `--base-pod`: Path to the real `v1.Pod` YAML manifest used as a structural source.
75+
- `--name-prefix`: Prefix for the generated pod names (default: `fuzzed-pod`).
76+
- `--namespace`: Target namespace for the generated pods (default: `fuzz-test`).
77+
- `--count`: Number of pods to generate.
78+
- `--offset`: Starting index for naming (useful for incremental runs).
79+
- `--concurrency`: Number of concurrent workers (default: 50).
80+
- `--kubeconfig`: Path to the kubeconfig file. Defaults to `$HOME/.kube/config`.
81+
- `--out-dir`: If specified, write YAMLs to this directory instead of injecting into a cluster.
82+
83+
## Verification & Metrics
84+
85+
To quickly verify the number of pods in the API server storage without timing out:
86+
```bash
87+
kubectl get --raw /metrics | grep 'apiserver_storage_objects{resource="pods"}'
88+
```
89+
90+
To measure control plane memory usage:
91+
```bash
92+
docker exec fuzzer-test-control-plane ps aux | grep -E "kube-apiserver|etcd|kube-scheduler"
93+
```

fuzzer/cmd/main.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
Copyright The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"flag"
22+
"fmt"
23+
"log"
24+
"os"
25+
"time"
26+
27+
v1 "k8s.io/api/core/v1"
28+
"k8s.io/client-go/kubernetes"
29+
"k8s.io/client-go/tools/clientcmd"
30+
"k8s.io/kubernetes/test/utils/fuzzer"
31+
"sigs.k8s.io/yaml"
32+
)
33+
34+
func main() {
35+
count := flag.Int("count", 1000, "Number of pods to generate")
36+
offset := flag.Int("offset", 0, "Starting index for pod naming")
37+
basePodPath := flag.String("base-pod", "test/utils/fuzzer/templates/complex-daemonset.yaml", "Path to the real pod YAML to sanitize and clone")
38+
namespace := flag.String("namespace", "fuzz-test", "Target namespace for fuzzed pods")
39+
namePrefix := flag.String("name-prefix", "fuzzed-pod", "Prefix for generated pod names")
40+
outDir := flag.String("out-dir", "", "Directory to write YAMLs (if specified, no cluster injection)")
41+
concurrency := flag.Int("concurrency", 50, "Number of concurrent workers")
42+
kubeconfig := flag.String("kubeconfig", "", "Path to the kubeconfig file for direct injection")
43+
44+
flag.Parse()
45+
46+
// Load Base Pod
47+
basePodData, err := os.ReadFile(*basePodPath)
48+
if err != nil {
49+
log.Fatalf("Failed to read base pod file: %v", err)
50+
}
51+
var basePod v1.Pod
52+
if err := yaml.Unmarshal(basePodData, &basePod); err != nil {
53+
log.Fatalf("Failed to unmarshal base pod: %v", err)
54+
}
55+
56+
var clientset *kubernetes.Clientset
57+
if *outDir == "" {
58+
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
59+
if *kubeconfig != "" {
60+
loadingRules.ExplicitPath = *kubeconfig
61+
}
62+
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}).ClientConfig()
63+
if err != nil {
64+
log.Fatalf("Failed to load kubeconfig: %v", err)
65+
}
66+
67+
// Increase QPS and Burst for high-performance injection
68+
config.QPS = 500
69+
config.Burst = 1000
70+
clientset, err = kubernetes.NewForConfig(config)
71+
if err != nil {
72+
log.Fatalf("Failed to create clientset: %v", err)
73+
}
74+
}
75+
76+
creator := fuzzer.NewExemplaryPodCreator(clientset, time.Now().UnixNano(), *namePrefix, *namespace)
77+
78+
progress := func(current, total int) {
79+
fmt.Printf("\rProgress: %d/%d pods (%.1f%%)", current, total, float64(current)/float64(total)*100)
80+
if current == total {
81+
fmt.Println()
82+
}
83+
}
84+
85+
start := time.Now()
86+
if *outDir != "" {
87+
fmt.Printf("Writing %d fuzzed pod manifests to %s (base: %s)...\n", *count, *outDir, *basePodPath)
88+
dir, err := creator.WriteExemplaryPodsToDir(context.Background(), &basePod, *count, *offset, *concurrency, *outDir, progress)
89+
if err != nil {
90+
log.Fatalf("\nFailed to write pods: %v", err)
91+
}
92+
fmt.Printf("Successfully created %d pod manifests in: %s\n", *count, dir)
93+
} else {
94+
fmt.Printf("Injecting %d fuzzed pods into cluster (base: %s)...\n", *count, *basePodPath)
95+
err := creator.CreateExemplaryPods(context.Background(), &basePod, *count, *offset, *concurrency, progress)
96+
if err != nil {
97+
log.Fatalf("\nFailed to inject pods: %v", err)
98+
}
99+
fmt.Printf("Successfully injected %d pods.\n", *count)
100+
}
101+
102+
duration := time.Since(start)
103+
fmt.Printf("Time taken: %v\n", duration)
104+
}

0 commit comments

Comments
 (0)