Overview
Per chaos-principles (opens in new window), chaos engineering is "the discipline of experimenting on a system in order to build confidence in the system's capability to withstand turbulent conditions in production." This skill walks the team through authoring an experiment that honors the five principles - steady-state hypothesis, real-world events, running in production, continuous automation, and minimized blast radius - each applied in the step below that uses it.
When to use
Step 1 - Define the steady-state hypothesis
Per chaos-principles (opens in new window) principle 1: focus on measurable output. The hypothesis must be a number, not a feeling:
# experiments/checkout-network-latency.yaml
hypothesis:
steady_state:
metric: checkout_completion_rate
threshold: ">= 95%"
measured_over: "5 minutes"
source: "datadog dashboard 'checkout-success'"Bad hypotheses:
Good hypotheses:
Step 2 - Pick a real-world event to inject
Per chaos-principles (opens in new window) principle 2: vary real-world events. Don't inject "anything"; inject what could plausibly happen - events the team has already seen in real incidents or realistically expects. The catalog of event classes (network, compute, storage, time, region/zone, dependency, configuration) with concrete examples is in references/experiment-authoring.md.
Step 3 - Set the blast radius
Per chaos-principles (opens in new window) principle 5: minimize blast radius.
blast_radius:
scope: "1% of pods in the staging namespace"
duration: "5 minutes"
abort_conditions:
- "Sentry error rate exceeds 2%"
- "PagerDuty incident raised"
- "Manual abort signal"Start small; expand as confidence grows.
Step 4 - Pick the chaos tool
Default: chaos-mesh for Kubernetes stacks - CNCF-graduated, broadest fault catalog (network / pod / IO / time / stress), declarative CRDs that compose with the experiment YAML in Step 1. Use LitmusChaos (references/litmus.md) when the team wants a ChaosCenter web UI and ChaosHub catalog; Gremlin (references/gremlin.md, deep operations in references/gremlin-advanced-operations.md) for commercial multi-platform support outside Kubernetes; toxiproxy-chaos when the failure surface is purely TCP-level.
The tool's syntax (CRD, attack config, etc.) goes alongside the experiment YAML.
Step 5 - Automate
Per chaos-principles (opens in new window) principle 4: automate continuously.
# .github/workflows/chaos-monthly.yml
on:
schedule:
- cron: '0 4 1 * *' # 1st of every month, 4am UTC
jobs:
chaos:
runs-on: ubuntu-latest
steps:
- run: |
kubectl apply -f experiments/checkout-network-latency.yaml
# Wait for completion
kubectl wait --for=condition=Complete chaosengine/checkout-network-latency --timeout=10m
# Check verdict
kubectl get chaosengine/checkout-network-latency -o jsonpath='{.status.experimentStatus.verdict}'Schedule per the team's appetite - monthly for new experiments, weekly for established ones, on-demand for incident reproduction.
Step 6 - Run in production?
Per chaos-principles (opens in new window) principle 3: experiments in production are the gold standard. But:
| Stage | Use |
|---|---|
| Pre-prod (staging) | Initial experiment runs; confidence-building. |
| Canary (5% traffic) | Once steady-state holds in staging. |
| Production (full) | Mature experiments; team has playbook for abort. |
Most teams should start in staging. Move to production after the team has confidence and abort procedures.
Step 7 - Verdict + report
Emit a per-experiment verdict: the steady-state hypothesis, a pre / during / post metric table, observations, action items, and the next iteration. The full report template is in references/experiment-authoring.md.
Steady-state hypothesis validation (pre-flight)
An authored experiment is not ready to run until its hypothesis survives five pre-flight checks. A hypothesis that cannot be measured, has no baseline, or would not move under the injected fault produces a verdict that means nothing. Per chaos-principles (opens in new window) Principle 1: "Focus on the measurable output of a system, rather than internal attributes of the system. Measurements of that output over a short period of time constitute a proxy for the system's steady state." Run these checks while the cost of fixing the hypothesis is still low.
The Chaos Toolkit steady-state-hypothesis block (required title + probes, each with a provider and a tolerance gate; a failed pre-method check bails the experiment) and its eight tolerance forms are in references/chaostoolkit-tolerance.md, per chaostoolkit.org/reference/api/experiment/ (opens in new window) and chaostoolkit.org/reference/concepts/ (opens in new window).
| # | Check | Passes when | Fails when |
|---|---|---|---|
| 1 | Measurable and observable | The team can run the probe in isolation right now and get a numeric or boolean return (Prometheus query, Datadog API, health endpoint, exit code) | The provider is a dashboard URL read by eye, a metric no service emits yet, or needs credentials absent from the run environment |
| 2 | A recent baseline exists | A dashboard, runbook, or monitoring record shows the metric's typical value over the past 7-30 days of normal traffic | The threshold is a round-number guess, the baseline predates the last deployment or is older than 30 days, or was taken during an incident |
| 3 | Tolerance is SLI-backed | The threshold maps to a published SLO, error-budget line, or documented user-impact threshold (e.g. the on-call alert value) | The tolerance accepts total degradation (>= 0%), sits inside the metric's noise band, or no SLO/SLI document backs it |
| 4 | Measurement window defined | The probe aggregates over an explicit window of at least 1 minute (avg_over_time(...[5m]), a rollup with a stated range) | A single-sample point-in-time value (one HTTP 200) stands in for sustained health, or measured_over is 0 / missing |
| 5 | Metric moves under the fault | The fault's propagation path from injection point to the metric's data source is traceable, with at least one step directly affecting the metric | Fault and probe share no call-graph path, a global aggregate averages a regional fault away, or a fallback masks the fault entirely |
Check 5 is the most important: a probe decoupled from the fault produces a vacuous "held" result. Ask: if this experiment "held", would that mean the system is resilient, or just that the metric is unrelated?
Hard-reject conditions
These block execution outright; do not run the experiment until resolved.
| Hard reject | Maps to | Why it is fatal |
|---|---|---|
| Probe returns a constant (an LB liveness check or single HTTP 200 that passes even with all backends down) | Checks 1, 4, 5 | The probe cannot register degradation; a "held" verdict is vacuous |
Boolean tolerance: true whose only false path is total unavailability | Check 3 | Tests catastrophe, not resilience |
| No baseline measurement cited in the experiment or runbook | Check 2 | The tolerance was chosen without measurement |
| Metric is an internal attribute (thread-pool queue depth, JVM heap) that is not also a published SLI | Checks 1, 3 | Per chaos-principles (opens in new window) Principle 1, internal state is not a valid steady-state output |
| Fault and probe share no call-graph path, or a global aggregate masks a regional fault | Check 5 | A "held" result means the metric is unrelated, not that the system is resilient |
Pre-flight verdict format
Emit one row per probe, then a summary:
Probe: <probe name>
Check 1 (measurable): PASS / FAIL - <reason>
Check 2 (baseline): PASS / FAIL - <reason>
Check 3 (SLI-backed): PASS / FAIL - <reason>
Check 4 (window): PASS / FAIL - <reason>
Check 5 (moves): PASS / FAIL - <reason>
Verdict: SOUND / UNSOUND
Hard-reject triggered: yes / no
Recommended action: <proceed | revise probe | replace metric | add baseline>The validation reads the hypothesis specification, not the live system: instrumentation gaps (Check 1) and stale baselines (Check 2) are confirmed by running the probe manually, and Check 5 is a reasoning exercise over the dependency graph, not an automated trace.
Anti-patterns
The seven authoring anti-patterns, each with why it fails and the fixing step, are in references/experiment-authoring.md: hypothesis-as-feeling, inject-anything, production-first, no abort conditions, manual-only runs, one-off-then-forget, and skipping the verdict report.
Limitations
References
Chaos Toolkit steady-state-hypothesis block and tolerance forms
View source (opens in a new tab)Reference for the steady-state hypothesis validation section of chaos-experiment-author. The five pre-flight checks there validate a hypothesis expressed in this schema; this file is the schema and tolerance detail they assume.
The steady-state-hypothesis object
Per chaostoolkit.org/reference/api/experiment/ (opens in new window), the steady-state-hypothesis object requires:
Tolerance forms supported
Per chaostoolkit.org/reference/api/experiment/ (opens in new window):
| Tolerance form | Syntax example | Evaluation |
|---|---|---|
| Scalar equality | "tolerance": 200 | probe return == 200 |
| Boolean equality | "tolerance": true | probe return == true |
| String equality | "tolerance": "OK" | probe return == "OK" |
| Inclusive range | "tolerance": [95, 100] | 95 <= value <= 100 |
| Membership | "tolerance": [200, 201, 204] | value in list |
| Regex | "tolerance": {"type": "regex", "pattern": "^healthy$"} | regex match |
| JSONPath | "tolerance": {"type": "jsonpath", "path": "$.status", "expect": "up"} | JSONPath extract + compare |
| Range object | "tolerance": {"type": "range", "range": [95.0, 100.0]} | numeric bounds |
Execution flow
Per chaostoolkit.org/reference/concepts/ (opens in new window): probes run once before the method (baseline check) and once after (deviation check). A probe that fails before the method means the system is already outside its acceptable state; the experiment must not run. A probe that fails after the method means the chaos activity caused the system to leave its steady state.
Gremlin advanced operations - attacks, Scenarios, Reliability Score, CI, compliance
View source (opens in a new tab)Deep reference for gremlin.md (opens in new window). Consult when picking a specific attack, chaining attacks into a Scenario, wiring Gremlin into CI, reading the Reliability Score, or satisfying an audit / compliance requirement.
Per gremlin-home (opens in new window):
Full attack table
Per gremlin-home (opens in new window) and the broader Gremlin docs, the four attack classes expand into these individual attacks:
| Class | Attack | Effect |
|---|---|---|
| Resource | CPU | Spike CPU usage |
| Resource | Memory | Spike memory |
| Resource | Disk I/O | Spike disk I/O |
| Resource | Disk space | Fill disk |
| Network | Latency | Inject latency |
| Network | Packet loss | Drop packets |
| Network | DNS | DNS resolution failure |
| Network | Blackhole | Drop all packets to/from a target |
| State | Shutdown | Reboot the host |
| State | Process killer | Kill a specific process |
| State | Time travel | Skew the system clock |
| Request | Request injection | Modify HTTP requests in flight |
Running an attack via the web UI
Web UI workflow:
The UI provides safety: blast-radius scoping, abort button, notifications.
Authoring a Scenario
A Scenario chains multiple attacks:
# Pseudo-Scenario config (Gremlin's UI exports JSON; this approximates)
scenario:
name: "Checkout resilience test"
attacks:
- type: latency
target: { service: checkout }
length: 5min
latency: 500ms
- type: packet-loss
target: { service: payment }
length: 5min
loss-percent: 10
delay-after-previous: 1min
abort_conditions:
- "Sentry error rate > 2%"
- "Manual abort"Scenarios match per the chaos-experiment-author "vary real-world events" principle - combinations approximate real incidents.
Reliability Score
Per gremlin-home (opens in new window), Gremlin's differentiator is the "Reliability Score" - "individual services" get scores "based on dependency mapping, risk detection, and failure testing."
Score components (per Gremlin docs):
A service moving from "untested" to "score 80" via passing attacks creates an objective improvement signal.
API + CI automation
Trigger an attack directly from the API:
curl -X POST "https://api.gremlin.com/v1/attacks/new" \
-H "Authorization: Key $GREMLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"command": {
"type": "latency",
"args": ["-l", "300", "-m", "500", "-c", "5", "-h", "^api\\.example\\.com$"]
},
"target": {
"type": "Random",
"containers": { "labels": { "app": "checkout" } }
}
}'The API enables CI integration - trigger a saved Scenario, wait for it to run, then evaluate a monitoring-driven verdict:
- name: Trigger Gremlin scenario
run: |
curl -X POST "https://api.gremlin.com/v1/scenarios/${{ vars.SCENARIO_ID }}/runs" \
-H "Authorization: Key ${{ secrets.GREMLIN_API_KEY }}"
- name: Wait + verdict
run: sleep 600 && ./scripts/datadog-verdict.shCompliance + audit
Gremlin's enterprise tier (per gremlin-home (opens in new window)'s positioning) provides:
Important for regulated industries where audit is non-negotiable.
References
Deep dive for chaos-experiment-author Step 4. Configures Gremlin (commercial) for cross-platform chaos engineering - agent install on Linux / Windows / Kubernetes, the four attack classes, Scenarios, and the Reliability Score. Use when the platform spans multiple environments (bare metal + cloud
Overview
Gremlin is a commercial reliability platform for fault injection across bare metal, on-prem, multi-cloud, and serverless. It assigns each service a forward-looking Reliability Score from repeated resilience tests, so teams fix likely failure points before an incident (per gremlin-home (opens in new window)).
When to use
If the team is K8s-only and OSS-preferred, see litmus.md (opens in new window) or chaos-mesh.
How to use
Install
Linux:
sudo apt install -y gremlin
sudo gremlin auth login --org-id <org-id> --user-id <user-id> --api-token <token>Kubernetes:
helm repo add gremlin https://helm.gremlin.com
helm install gremlin gremlin/gremlin \
--namespace gremlin --create-namespace \
--set gremlin.secret.create=true \
--set gremlin.secret.teamID=<team-id> \
--set gremlin.secret.clusterID=<cluster-id> \
--set gremlin.secret.teamSecret=<secret>The agent connects to the Gremlin Control Plane (cloud); attacks trigger via web UI or API.
Attack types
Gremlin groups fault injections into four classes (per gremlin-home (opens in new window) and the Gremlin docs):
| Class | Representative attacks | Effect |
|---|---|---|
| Resource | CPU, Memory, Disk I/O, Disk space | Starve or saturate a host resource |
| Network | Latency, Packet loss, DNS, Blackhole | Degrade or sever connectivity |
| State | Shutdown, Process killer, Time travel | Disrupt host / process state |
| Request | Request injection | Modify HTTP requests in flight |
The full per-attack table (all twelve attacks with their exact effect) lives in gremlin-advanced-operations.md (opens in new window).
Worked example
A single end-to-end experiment: inject 500ms latency into the checkout service, scoped to one container for five minutes.
curl -X POST "https://api.gremlin.com/v1/attacks/new" \
-H "Authorization: Key $GREMLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"command": {
"type": "latency",
"args": ["-l", "300", "-m", "500", "-c", "1", "-h", "^checkout\\..*$"]
},
"target": {
"type": "Random",
"percent": 10,
"containers": { "labels": { "app": "checkout" } }
}
}'Scenarios (chaining this latency attack with a downstream packet-loss attack), the Reliability Score model, the full CI workflow, and the compliance / audit posture are in gremlin-advanced-operations.md (opens in new window).
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Manual UI-only attacks | Doesn't scale; per chaos principle 4 must automate. | API-driven Scenarios (gremlin-advanced-operations.md). |
| Skipping abort conditions | Attack runs past safety threshold. | Define abort signals on every Scenario (gremlin-advanced-operations.md). |
| Treating Reliability Score as the only signal | Score is service-level; per-attack verdicts matter too. | Both Score (trend) + per-attack verdicts (detail). |
| One-shot installation; team forgets | License paid; not used. | Schedule attacks; build into release process. |
| Production attacks without playbook | Real incident if attack escalates. | Per chaos-experiment-author: blast radius + abort. |
Limitations
References
Deep dive for chaos-experiment-author Step 4. Configures LitmusChaos for Kubernetes-native chaos engineering. Prefer over chaos-mesh when the team wants a ChaosCenter web UI for workflow scheduling and ChaosHub catalog browsing; use chaos-mesh for fine-grained network-fault policies via its own CRD family.
Overview
Per litmus-home (opens in new window):
"LitmusChaos is a CNCF-hosted, open-source Chaos Engineering platform that helps teams identify infrastructure weaknesses through safe, controlled chaos tests."
"Kubernetes developers & SREs use Litmus to manage chaos in a declarative manner." (litmus-home (opens in new window))
The architecture: Litmus runs as a Kubernetes operator; experiments are CRDs; results export to Prometheus.
When to use
Step 1 - Install
Per litmus-home (opens in new window):
helm repo add litmuschaos https://litmuschaos.github.io/litmus-helm/
helm install litmuschaos litmuschaos/litmus -n litmus --create-namespaceThe Litmus operator + ChaosCenter (web UI) deploy.
Step 2 - Pick a ChaosExperiment from the Hub
Per litmus-home (opens in new window), the ChaosHub is "a repository hosting most of the chaos experiments that are needed for a quick start in Chaos Engineering." Common experiments:
| ChaosExperiment | Effect |
|---|---|
pod-delete | Kill random pods |
pod-network-latency | Inject network latency on the pod |
pod-network-loss | Drop a percentage of packets |
pod-cpu-hog | Spike CPU on the pod |
pod-memory-hog | Spike memory on the pod |
node-cpu-hog | Spike CPU on the node |
node-drain | Drain a node |
disk-fill | Fill the pod's writable disk |
kubelet-service-kill | Kill kubelet on a node |
Install per-experiment:
kubectl apply -f https://hub.litmuschaos.io/api/chaos/2.14.0?file=charts/generic/pod-delete/experiment.yamlStep 3 - Author a ChaosEngine
The ChaosEngine CR runs an experiment against a target:
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
name: checkout-pod-delete
namespace: app
spec:
appinfo:
appns: app
applabel: 'app=checkout'
appkind: deployment
chaosServiceAccount: pod-delete-sa
experiments:
- name: pod-delete
spec:
components:
env:
- name: TOTAL_CHAOS_DURATION
value: '60' # seconds
- name: CHAOS_INTERVAL
value: '20' # seconds
- name: PODS_AFFECTED_PERCENTAGE
value: '50'
probe:
- name: 'check-checkout-availability'
type: httpProbe
httpProbe/inputs:
url: 'http://checkout.app.svc:8080/health'
insecureSkipVerify: false
method:
get:
criteria: '=='
responseCode: '200'
mode: 'Continuous'
runProperties:
probeTimeout: 5
interval: 2
retry: 3
probePollingInterval: 1Per litmus-home (opens in new window), probes "create complete chaos scenarios close to the real application experience upon failure." The probe is the steady-state check per the chaos principles.
Step 4 - Run
kubectl apply -f checkout-pod-delete.yamlLitmus runs the experiment for TOTAL_CHAOS_DURATION seconds, checking the probe continuously. The verdict (Pass / Fail) lands in chaosengine.status.experimentStatus.verdict.
Step 5 - Read the verdict
kubectl get chaosengine checkout-pod-delete -o jsonpath='{.status.experimentStatus.verdict}'
# Output: Pass | FailStep 6 - Probe types
| Probe type | Use |
|---|---|
httpProbe | HTTP endpoint health + status code |
cmdProbe | Run a shell command; check exit code |
k8sProbe | Check Kubernetes resource state |
promProbe | Query Prometheus metric; assert threshold |
Probes can run in different modes: SOT (start of test), EOT (end of test), Edge (both), Continuous (every N seconds during the experiment).
Step 7 - Observability
Per litmus-home (opens in new window): "chaos observability by exporting Prometheus metrics that highlight and quantify the impact of chaos on the applications or infrastructure in real time."
Key metrics:
Wire to Grafana for dashboards.
Step 8 - CI integration
- name: Run chaos experiment
run: |
kubectl apply -f experiments/checkout-pod-delete.yaml
kubectl wait --for=condition=Complete chaosengine/checkout-pod-delete --timeout=10m
VERDICT=$(kubectl get chaosengine checkout-pod-delete -o jsonpath='{.status.experimentStatus.verdict}')
echo "Verdict: $VERDICT"
[ "$VERDICT" = "Pass" ]Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Running ChaosEngine without probe | No steady-state check; verdict meaningless. | Always include httpProbe / promProbe (Step 3). |
PODS_AFFECTED_PERCENTAGE: 100 | Kills all pods; service down. | Start at 25-50%; increase per blast-radius principle. |
| Running in default namespace | Could affect cluster components. | Dedicated app namespace target. |
| One-shot experiment; never re-run | Per chaos principle 4: automate continuously. | Schedule via CronJob (Step 8 in cron form). |
Skipping chaosServiceAccount | RBAC blocks experiment execution. | Define ServiceAccount with appropriate permissions. |