Run a Chaos Engineering Lab with Chaos Mesh, k6, and Grafana
Deploy Chaos Mesh, run pod-kill and network-delay experiments during live k6 load tests, and measure the SLO impact on Grafana.
Domains & Technologies
Blueprint Walkthrough
Architecture Overview & Problem Statement
Architecture Overview
Netflix famously runs Chaos Monkey in production — a program that randomly kills production services to confirm the system can survive failures. The logic: if you never deliberately break things, you never know how they behave when they break on their own — and they will, at the worst possible time.
Chaos engineering is the discipline of deliberately injecting failure into systems in a controlled way, to discover weaknesses before they become real incidents. This project builds a complete chaos engineering practice on Kubernetes using Chaos Mesh, with k6 generating realistic load and Grafana showing the real-time impact — the same practice an SRE team at Hotstar would run before a high-traffic event like an IPL final.
[ Define steady state ](SLOs: p99 latency < 200ms, error rate < 0.5%) | v[ Form hypothesis ]("Service maintains SLO when 1 of 3 pods is killed") | v[ Inject failure with Chaos Mesh ](Kill a pod while k6 sends continuous traffic) | v[ Observe on Grafana ](Did latency spike? Did errors occur? Did it recover?) | v[ Document findings ](Hypothesis confirmed or refuted - what did we learn?) | v[ Fix the weaknesses found ](Add PDB, increase replicas, add timeouts)TipYou will run this same five-step cycle — hypothesis, inject, observe, document, fix — three separate times against three different failure types. The cycle itself is the actual skill being practiced, not any one experiment.
Problem Solved
Most teams only discover their system's real failure behavior during an actual incident — at 2am, under real user impact, with no controlled way to isolate what broke. By then it's too late to learn calmly; the priority is restoring service, not understanding the system.
Chaos engineering solves this by moving failure discovery into a controlled, scheduled, low-stakes setting. You define a steady state hypothesis — a measurable statement of what "normal" looks like, backed by real metrics, not a guess. You then inject a specific failure and observe whether the steady state holds. If your hypothesis is refuted, you found a genuine weakness on your own terms, with time to fix it, instead of during a customer-facing outage during peak order volume at a company like Swiggy.
SecurityNever run chaos experiments in production before validating the same experiment in staging. This project builds the practice safely on a local cluster — treat that discipline as non-negotiable before ever pointing these tools at a live environment.
Milestone 1: Deploy the Target Application
Concept
Chaos engineering requires something with redundancy to test against. A single-replica deployment can't teach you anything useful — killing its only pod is simply an outage, not an experiment. Three replicas is the practical minimum for a meaningful pod-kill test.
Steps
kubectl create namespace chaos-demo cat > target-app.yaml << 'EOF'apiVersion: apps/v1kind: Deploymentmetadata: name: orders-api namespace: chaos-demospec: replicas: 3 selector: matchLabels: app: orders-api template: metadata: labels: app: orders-api spec: containers: - name: api image: nginx:alpine ports: - containerPort: 80 resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m" readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5---apiVersion: v1kind: Servicemetadata: name: orders-api namespace: chaos-demospec: selector: app: orders-api ports: - port: 80 targetPort: 80EOF kubectl apply -f target-app.yamlkubectl get pods -n chaos-demoNAME READY STATUSorders-api-7d9f8c6b-2kmpq 1/1 Runningorders-api-7d9f8c6b-5xnpl 1/1 Runningorders-api-7d9f8c6b-9rvbf 1/1 RunningCommon MistakeRunning pod-kill experiments against a single-replica deployment isn't a chaos experiment — it's just an outage. Chaos engineering requires redundancy to actually test against.
Milestone 2: Install Chaos Mesh
Concept
Chaos Mesh is a CNCF project providing Kubernetes-native chaos engineering. Instead of writing ad-hoc scripts to kill processes, you apply YAML resources — PodChaos, NetworkChaos, StressChaos — that Chaos Mesh translates into real, time-bounded failures.
Steps
helm repo add chaos-mesh https://charts.chaos-mesh.orghelm repo update kubectl create namespace chaos-mesh helm install chaos-mesh chaos-mesh/chaos-mesh \ --namespace chaos-mesh \ --set chaosDaemon.runtime=containerd \ --set chaosDaemon.socketPath=/run/containerd/containerd.sock \ --wait kubectl get pods -n chaos-meshNAME READY STATUSchaos-controller-manager-abc123-xyz 3/3 Runningchaos-daemon-2hkqp 1/1 Runningchaos-daemon-5xnpl 1/1 Runningchaos-daemon-9rvbf 1/1 Runningchaos-dashboard-7d9f8c6b-klmnp 1/1 RunningAccess the dashboard:
kubectl port-forward -n chaos-mesh \ svc/chaos-dashboard 2333:2333 &Open http://localhost:2333 in your browser.
TipThe Chaos Mesh dashboard lets you build and launch experiments visually, which is a fast way to explore what's possible — but writing the YAML directly, as we do in this project, is what you'll actually check into Git for repeatable experiments.
Milestone 3: Install k6 and Write the Load Test
Concept
k6 generates realistic HTTP traffic during the experiment so you're measuring the failure's effect on real simulated load, not a theoretical availability number. The thresholds block in the script below encodes your SLOs directly — k6 will report pass/fail against them automatically.
Steps
## macOSbrew install k6 ## Linuxsudo gpg -ksudo gpg --no-default-keyring \ --keyring /usr/share/keyrings/k6-archive-keyring.gpg \ --keyserver hkp://keyserver.ubuntu.com:80 \ --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] \ https://dl.k6.io/deb stable main" | \ sudo tee /etc/apt/sources.list.d/k6.listsudo apt-get updatesudo apt-get install k6cat > load-test.js << 'EOF'import http from 'k6/http';import { check, sleep } from 'k6';import { Rate, Trend } from 'k6/metrics'; const errorRate = new Rate('errors');const p99Latency = new Trend('p99_latency'); export const options = { stages: [ { duration: '30s', target: 20 }, { duration: '5m', target: 20 }, { duration: '30s', target: 0 }, ], thresholds: { 'errors': ['rate<0.01'], 'http_req_duration': ['p(95)<200'], },}; const SERVICE_URL = 'http://localhost:8090'; export default function () { const response = http.get(`${SERVICE_URL}/`); const success = check(response, { 'status is 200': (r) => r.status === 200, 'response time < 200ms': (r) => r.timings.duration < 200, }); errorRate.add(!success); p99Latency.add(response.timings.duration); sleep(0.5);}EOFkubectl port-forward -n chaos-demo svc/orders-api 8090:80 & k6 run load-test.jsLet it run to steady state before injecting any chaos — this baseline run is what every experiment result gets compared against.
TipKeep this port-forward and k6 run active in a separate terminal tab throughout Milestones 4-6 — every chaos experiment needs continuous traffic running alongside it to be measurable at all.
Milestone 4: Experiment 1 — Pod Kill
Concept
Hypothesis: When one of three orders-api pods is killed, the remaining two pods absorb the traffic without the error rate exceeding 1% or p99 latency exceeding 200ms.
Writing the hypothesis before running the experiment matters — it forces you to commit to a measurable prediction instead of retroactively deciding what the result "meant."
Steps
cat > experiment-pod-kill.yaml << 'EOF'apiVersion: chaos-mesh.org/v1alpha1kind: PodChaosmetadata: name: pod-kill-experiment namespace: chaos-demospec: action: pod-kill selector: namespaces: - chaos-demo labelSelectors: app: orders-api mode: one duration: "2m"EOF kubectl apply -f experiment-pod-kill.yamlkubectl get pods -n chaos-demo --watchNAME READY STATUSorders-api-7d9f-9rvbf 1/1 Runningorders-api-7d9f-9rvbf 0/1 Terminatingorders-api-7d9f-9rvbf 0/1 Pendingorders-api-7d9f-9rvbf 0/1 Runningorders-api-7d9f-9rvbf 1/1 RunningWatch the k6 output during the experiment for error spikes or latency increases during the restart window.
kubectl delete -f experiment-pod-kill.yamlCommon MistakeNot watching k6's live output during the experiment window and only checking the final summary. The interesting data is the transient spike during the 10-30 second restart window — the final summary can average it away and hide exactly what you're trying to observe.
Milestone 5: Experiment 2 — Network Delay
Concept
Hypothesis: When 300ms of network delay is injected on outbound traffic from the orders-api pods, users will observe latency increases but the service will not return errors.
This experiment simulates a slow downstream dependency — the kind of thing that happens when a payment gateway or a third-party API (a real scenario for a company like Razorpay) degrades without actually going down.
Steps
cat > experiment-network-delay.yaml << 'EOF'apiVersion: chaos-mesh.org/v1alpha1kind: NetworkChaosmetadata: name: network-delay-experiment namespace: chaos-demospec: action: delay selector: namespaces: - chaos-demo labelSelectors: app: orders-api mode: all direction: to delay: latency: "300ms" jitter: "50ms" correlation: "25" duration: "3m"EOF kubectl apply -f experiment-network-delay.yamlWatch k6's p99 latency metric during this run — the question isn't whether it rises, but whether it stays under your SLO threshold.
kubectl delete -f experiment-network-delay.yamlTipTry adjusting
latencyto800msand re-running this same experiment. Finding the exact delay value where your SLO actually breaches gives you a concrete number for capacity planning conversations, instead of a vague sense that "slow downstreams are bad."
Milestone 6: Experiment 3 — CPU Stress
Concept
Hypothesis: When CPU is throttled to 50% of the container limit across all pods, the service will maintain its error rate SLO but may breach the latency SLO.
This experiment tests a different failure mode entirely — not a dependency slowing down, but the pods themselves running under resource pressure, which is common during traffic spikes like a Zerodha market-open surge.
Steps
cat > experiment-cpu-stress.yaml << 'EOF'apiVersion: chaos-mesh.org/v1alpha1kind: StressChaosmetadata: name: cpu-stress-experiment namespace: chaos-demospec: selector: namespaces: - chaos-demo labelSelectors: app: orders-api mode: all stressors: cpu: workers: 2 load: 50 duration: "2m"EOF kubectl apply -f experiment-cpu-stress.yamlThe experiment cleans itself up automatically after 2 minutes.
Common MistakeNot setting an explicit
durationon any chaos experiment. Without it, an experiment like this one persists until someone manually deletes it — a forgotten Friday-afternoon experiment can quietly degrade a system all weekend. Every experiment YAML in this project sets one deliberately.
Milestone 7: Write a Chaos Engineering Report
Concept
Documenting results is as important as running the experiment — an experiment without a written finding gets forgotten by the next incident, and the whole point of chaos engineering is to retain what you learn.
Steps
cat > chaos-report.md << 'EOF'## Chaos Engineering Report - Orders APIDate: 2026-08-14 ## Steady State* Request rate: 20 virtual users, ~40 req/sec* Baseline p99 latency: 45ms* Baseline error rate: 0%* SLO targets: error rate < 1%, p99 < 200ms --- ## Experiment 1: Pod KillHypothesis: Killing one of three pods will not cause SLO breach. What happened: Error rate spiked to 2.3% for 12 seconds during podrestart. p99 latency spiked to 380ms for 8 seconds. Recovered fullyafter 22 seconds. Result: REFUTED - error rate breached 1% during recovery. Root cause: No Pod Disruption Budget. No readiness gate holdingtraffic during pod startup - the new pod received traffic beforeit was ready. Fix: Add a PDB with minAvailable: 2. Tune readinessProbe timing. --- ## Experiment 2: Network Delay (300ms)Hypothesis: 300ms delay causes latency increase but no errors. What happened: p99 latency rose to 380ms, breaching the 200ms SLO.Error rate stayed at 0%. Result: PARTIALLY CONFIRMED - no errors, but latency SLO breached. Finding: No timeout propagation. A slow downstream call delays theentire request with no client-side timeout configured. Fix: Add request timeouts. Reconsider whether the latency SLOreflects realistic downstream call chains. --- ## Experiment 3: CPU StressHypothesis: 50% CPU throttle breaches latency but not errors. What happened: p99 latency rose to 150ms - within SLO. Error ratestayed at 0%. Fully recovered within 5 seconds of experiment ending. Result: CONFIRMED - both SLOs held under CPU stress. Finding: Current CPU limits are appropriately sized for this load. --- ## Action Items | Finding | Fix | Priority || :--- | :--- | :--- || No PDB, pod kill causes errors | Add PDB minAvailable: 2 | HIGH || No readiness gate | Tune readinessProbe | HIGH || Latency SLO breached on delay | Add timeouts, review SLO | MEDIUM |EOFTipStore
chaos-report.mdin the same Git repo as your Kubernetes manifests. Over time, a folder of these reports becomes a searchable history of every weakness your team has already found and fixed — genuinely useful during onboarding or post-incident reviews.
Milestone 8: Fix the Weakness Found
Concept
A Pod Disruption Budget tells Kubernetes the minimum number of pods that must remain available during any voluntary disruption (like a node drain or, in this case, Chaos Mesh's pod-kill action). It's the direct fix for the weakness Experiment 1 uncovered.
Steps
cat > pod-disruption-budget.yaml << 'EOF'apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: orders-api-pdb namespace: chaos-demospec: minAvailable: 2 selector: matchLabels: app: orders-apiEOF kubectl apply -f pod-disruption-budget.yamlRe-run Experiment 1 (Milestone 4) with the PDB in place. The pod kill should now cause noticeably less disruption, since Kubernetes will enforce the minimum available pod count during any voluntary disruption.
TipA PDB only protects against voluntary disruptions like this one — it does nothing against an involuntary failure like a node hardware crash. Don't mistake "passed the pod-kill experiment" for "fully resilient to all pod loss."
Validation & Testing
Verification Steps
# 1. Confirm the PDB is activekubectl get pdb -n chaos-demo# Expected: orders-api-pdb with MIN AVAILABLE 2 # 2. Re-run the pod-kill experiment with k6 runningkubectl apply -f experiment-pod-kill.yaml# Watch k6 output - error rate spike should be smaller than the first run # 3. Confirm no chaos experiments are left runningkubectl get podchaos,networkchaos,stresschaos -n chaos-demo# Expected: empty, or only experiments you're intentionally running # 4. Confirm cleanup after each experimentkubectl delete -f experiment-pod-kill.yamlTipCompare the k6 threshold pass/fail output (not just eyeballing the numbers) between the first and second pod-kill runs — k6 reports whether your defined
thresholdspassed or failed for that run, giving you an objective before/after comparison.
Common Mistakes Recap
| Mistake | Why It Breaks | Fix |
|---|---|---|
| No observability before chaos | Can't tell if the experiment did anything | Install Prometheus/Grafana first |
| Testing in prod before staging | How chaos engineering gets banned | Always validate in staging first |
No duration set |
Experiment persists indefinitely | Always set an explicit duration |
| Single-replica pod-kill test | It's an outage, not an experiment | Require redundancy before testing |
| Writing the hypothesis after the fact | Defeats the purpose of the exercise | Hypothesis first, always |
Videos & Guides
Chaos Mesh Documentation
Official reference for PodChaos, NetworkChaos, and StressChaos resource specs used across all three experiments.
k6 Documentation
Official reference for k6 test scripting, stages, and thresholds used to define and measure SLO compliance.
Principles of Chaos Engineering
The foundational reference for the steady-state hypothesis methodology this project's experiment cycle is built around.