Chaos Engineering is the discipline of experimenting on a distributed system to build confidence in its capability to withstand turbulent conditions in production. Instead of waiting for failures to happen, chaos engineers proactively inject failures to test system resilience.
What is Chaos Engineering?
Chaos Engineering is not about breaking things randomly. It's a controlled, hypothesis-driven approach to discovering weaknesses in your system before they cause outages.
Core Principles
- 1Steady State: Define what "normal" looks like
- 2Hypothesis: Believe that system will remain in steady state
- 3Inject Failure: Introduce real-world events like server failure
- 4Observe: Compare steady state to system after failure
- 5Learn: Improve system based on observations
Getting Started with Chaos Engineering
Identify the System
// Example E-commerce system
const system = {
services: ['web', 'api', 'database', 'cache', 'payment-gateway'],
metrics: ['response_time', 'error_rate', 'throughput'],
steady_state: {
response_time_ms: 200,
error_rate_percent: 0.1,
requests_per_second: 1000
}
};Define Hypothesis
Hypothesis: The system will maintain 99.9% availability even if:
- One database replica fails
- Cache becomes unavailable
- API service experiences 50% latency increaseChaos Tools
Gremlin
Gremlin is a popular chaos engineering platform that lets you run attacks on your infrastructure.
Installation
# Install Gremlin CLI
curl -O https://dl.gremlin.com/release/gremlin/darwin/gremlin
chmod +x gremlin
sudo mv gremlin /usr/local/bin/
# Authenticate
gremlin loginRunning Attacks
# Shutdown attack - terminate a container
gremlin attack container shutdown --container-id $(docker ps -q | head -n 1) --duration 60s
# CPU attack - stress CPU resources
gremlin attack cpu 100 --duration 60s --cluster-targets kubelet,container
# Latency attack - add network delay
gremlin attack network latency --interface eth0 --delay 1000 --duration 60sChaos Mesh
Chaos Mesh is an open-source chaos engineering platform for Kubernetes.
# chaos-mesh-experiment.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: pod-kill-experiment
namespace: default
spec:
action: pod-kill
mode: one
selector:
namespaces:
- default
labelSelectors:
app: my-app
scheduler:
cron: "@every 10m"Chaos Experiments
Experiment 1: Database Failure
// Simulate database failure
const chaosExperiment = {
name: 'database-failure-simulation',
steady_state: {
metric: 'successful_orders_per_minute',
baseline: 50
},
hypothesis: 'System maintains >80% of baseline during DB failover',
method: 'Terminate primary database replica',
duration: '5 minutes',
rollback: 'Promote standby replica to primary'
};
// Monitoring script
async function monitorDatabaseFailure() {
const before = await getMetric('successful_orders_per_minute');
// Inject failure
await terminateDatabaseReplica('db-primary-01');
// Wait for failover
await sleep(30000);
const during = await getMetric('successful_orders_per_minute');
const recovery = (during / before) * 100;
console.log(`Recovery: ${recovery}%`);
if (recovery < 80) {
console.error('FAIL: System didn't recover adequately');
} else {
console.log('PASS: System maintained acceptable performance');
}
// Cleanup: Restore database
await restoreDatabase('db-primary-01');
}Experiment 2: Cache Failure
// Test system resilience when cache fails
async function cacheFailureExperiment() {
const config = {
steady_state: {
response_time_ms: 200,
error_rate_percent: 0.1
},
hypothesis: 'Response time stays under 1s without cache'
};
// Monitor baseline
const baseline = await measureMetrics();
// Stop cache service
await stopService('redis-01');
// Measure impact
const withoutCache = await measureMetrics();
console.log(`Baseline: ${JSON.stringify(baseline)}`);
console.log(`Without cache: ${JSON.stringify(withoutCache)}`);
if (withoutCache.response_time_ms < 1000) {
console.log('PASS: Acceptable performance without cache');
} else {
console.error('FAIL: System degrades too much without cache');
console.error('Recommendation: Implement circuit breaker');
}
// Restart cache
await startService('redis-01');
}Experiment 3: Network Latency
// Add latency to microservice calls
async function latencyExperiment() {
const baseline = await measureAPIResponseTime();
// Inject 500ms latency
await injectLatency({
target: 'api-service',
delay: 500,
jitter: 100,
duration: '2 minutes'
});
// Monitor for timeouts
const timeouts = await countTimeouts();
const errorRate = await calculateErrorRate();
if (errorRate > 0.05) {
console.error('FAIL: Error rate too high with latency');
console.error('Recommendation: Increase timeout thresholds');
}
if (timeouts > 0) {
console.error('FAIL: Requests timing out');
console.error('Recommendation: Implement retries with exponential backoff');
}
}Implementing Resilience Patterns
Circuit Breaker Pattern
class CircuitBreaker {
constructor(service, threshold = 5, timeout = 60000) {
this.service = service;
this.threshold = threshold;
this.timeout = timeout;
this.failures = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.lastFailureTime = null;
}
async call(method, ...args) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await this.service[method](...args);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
}
}
onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
this.state = 'OPEN';
console.error('Circuit breaker opened due to failures');
}
}
}
// Usage
const circuitBreaker = new CircuitBreaker(apiService);
try {
const result = await circuitBreaker.call('getData');
} catch (error) {
// Use fallback data
return fallbackData;
}Retry with Exponential Backoff
async function retryWithBackoff(fn, maxAttempts = 3, baseDelay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
await sleep(delay + Math.random() * 1000); // Add jitter
}
}
}
// Usage
const result = await retryWithBackoff(
() => fetch('https://api.example.com/data'),
5,
1000
);Bulkhead Pattern
class Bulkhead {
constructor(maxConcurrent = 10) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
async execute(fn) {
if (this.running >= this.maxConcurrent) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
return await fn();
} finally {
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
}
// Usage
const bulkhead = new Bulkhead(5); // Max 5 concurrent requests
for (const request of requests) {
bulkhead.execute(() => processRequest(request));
}Chaos Testing in CI/CD
GitHub Actions Workflow
name: Chaos Tests
on:
schedule:
- cron: '0 2 * * *' # Run daily at 2 AM
workflow_dispatch:
jobs:
chaos-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run chaos experiments
run: |
npm run chaos:test
- name: Upload chaos report
uses: actions/upload-artifact@v3
with:
name: chaos-report
path: reports/chaos/
- name: Create issue if experiment fails
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Chaos experiment failed',
body: 'Chaos experiment detected system weakness. Review logs.',
labels: ['chaos', 'bug']
});Best Practices
- 1Start Small: Begin with low-risk experiments in non-production
- 2Gradual Rollout: Increase blast radius as confidence grows
- 3Automated Rollback: Always have quick rollback mechanism
- 4Monitor Continuously: Real-time observability is essential
- 5Document Everything: Learnings should be shared widely
- 6Run During Off-Peak: Initially run during low-traffic periods
- 7Game Days: Schedule chaos experiments with the whole team
Conclusion
Chaos Engineering transforms failure from an unexpected disaster into a planned exercise. By proactively testing failure scenarios, you build systems that are resilient, reliable, and capable of withstanding production turbulence. Start small, measure everything, and continuously improve based on findings.