Chaos Testing
Validate resilience by injecting controlled failures to verify that fallbacks, retries, and circuit breakers work under real conditions
When to Use
- Validating that resilience patterns (circuit breakers, retries, fallbacks) actually work
- Preparing for production incidents by simulating them in controlled environments
- Building confidence that the system degrades gracefully under partial failure
- Discovering hidden dependencies and single points of failure
Instructions
- Start with a steady state hypothesis: "Users can still check out even when the recommendation service is down."
- Inject one failure at a time. Do not combine failures until individual effects are understood.
- Start in development/staging. Move to production only with tight blast radius controls.
- Types of failure injection: latency, errors, resource exhaustion, dependency unavailability, clock skew.
- Measure impact on user-facing metrics (error rate, latency p99, success rate), not just internal metrics.
- Build failure injection as middleware or wrappers that can be toggled on/off.
// chaos/fault-injector.ts
interface FaultConfig {
enabled: boolean;
latencyMs?: number; // Add artificial latency
errorRate?: number; // 0.0 to 1.0 probability of error
errorCode?: number; // HTTP status to return
timeoutRate?: number; // 0.0 to 1.0 probability of timeout
targetServices?: string[]; // Only affect specific services
}
export class FaultInjector {
private config: FaultConfig = { enabled: false };
configure(config: Partial<FaultConfig>) {
this.config = { ...this.config, ...config };
}
async maybeInjectFault(serviceName: string): Promise<void> {
if (!this.config.enabled) return;
if (this.config.targetServices && !this.config.targetServices.includes(serviceName)) return;
// Inject latency
if (this.config.latencyMs) {
await new Promise((r) => setTimeout(r, this.config.latencyMs));
}
// Inject timeout (never resolves until AbortController cancels)
if (this.config.timeoutRate && Math.random() < this.config.timeoutRate) {
await new Promise(() => {}); // Hang forever — caller's timeout should catch this
}
// Inject error
if (this.config.errorRate && Math.random() < this.config.errorRate) {
throw new ChaosError(`Injected fault for ${serviceName}`, this.config.errorCode ?? 500);
}
}
}
export class ChaosError extends Error {
constructor(
message: string,
public readonly statusCode: number
) {
super(message);
this.name = 'ChaosError';
}
}
// Integration with services
const faultInjector = new FaultInjector();
// Enable in test/staging via environment variable
if (process.env.CHAOS_ENABLED === 'true') {
faultInjector.configure({
enabled: true,
targetServices: ['payment-api'],
errorRate: 0.3, // 30% of payment API calls fail
latencyMs: 2000, // Add 2s latency to all calls
});
}
// Wrap service calls
export async function callPaymentAPI(orderId: string): Promise<PaymentResult> {
await faultInjector.maybeInjectFault('payment-api');
return fetch(`https://payment.example.com/charge/${orderId}`).then((r) => r.json());
}
// Chaos test scenario
describe('checkout resilience', () => {
it('completes checkout when payment service has 50% error rate', async () => {
faultInjector.configure({
enabled: true,
targetServices: ['payment-api'],
errorRate: 0.5,
});
// Circuit breaker + retry should handle transient failures
const result = await checkout(testOrder);
expect(result.status).toBe('completed');
faultInjector.configure({ enabled: false });
});
it('uses cached prices when pricing service is down', async () => {
faultInjector.configure({
enabled: true,
targetServices: ['pricing-api'],
errorRate: 1.0, // 100% failure
});
const result = await getProductPrice('sku-123');
expect(result.source).toBe('cache');
expect(result.price).toBeGreaterThan(0);
faultInjector.configure({ enabled: false });
});
});
Details
Chaos engineering principles (Netflix):
- Define steady state (what "normal" looks like in metrics)
- Hypothesize that steady state continues during failure
- Introduce real-world failures (network, disk, process)
- Try to disprove the hypothesis
- Fix weaknesses found
Failure types to test:
- Latency injection: Simulate slow responses (100ms, 1s, 5s, 30s)
- Error injection: Return 500, 503, connection refused
- Resource exhaustion: Fill disk, exhaust memory, saturate CPU
- Dependency death: Kill a database, cache, or downstream service entirely
- Clock skew: Jump time forward/backward (affects TTLs, JWT expiry)
- Network partition: Split services so they cannot communicate
Tools: toxiproxy (TCP proxy with configurable toxics), chaos-mesh (Kubernetes-native), litmus (Kubernetes chaos), gremlin (SaaS platform), pumba (Docker container chaos).
Production chaos safety:
- Always have a kill switch to stop the experiment immediately
- Limit blast radius (specific percentage of traffic, specific instances)
- Run during business hours when the team is available
- Start with the smallest possible impact and scale up
- Monitor user-facing metrics, not just infrastructure metrics
Source
https://principlesofchaos.org/
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.
1---2name: resilience-chaos-testing3description: Chaos Testing4---5# Chaos Testing67> Validate resilience by injecting controlled failures to verify that fallbacks, retries, and circuit breakers work under real conditions89## When to Use1011- Validating that resilience patterns (circuit breakers, retries, fallbacks) actually work12- Preparing for production incidents by simulating them in controlled environments13- Building confidence that the system degrades gracefully under partial failure14- Discovering hidden dependencies and single points of failure1516## Instructions17181. Start with a steady state hypothesis: "Users can still check out even when the recommendation service is down."192. Inject one failure at a time. Do not combine failures until individual effects are understood.203. Start in development/staging. Move to production only with tight blast radius controls.214. Types of failure injection: latency, errors, resource exhaustion, dependency unavailability, clock skew.225. Measure impact on user-facing metrics (error rate, latency p99, success rate), not just internal metrics.236. Build failure injection as middleware or wrappers that can be toggled on/off.2425```typescript26// chaos/fault-injector.ts27interface FaultConfig {28 enabled: boolean;29 latencyMs?: number; // Add artificial latency30 errorRate?: number; // 0.0 to 1.0 probability of error31 errorCode?: number; // HTTP status to return32 timeoutRate?: number; // 0.0 to 1.0 probability of timeout33 targetServices?: string[]; // Only affect specific services34}3536export class FaultInjector {37 private config: FaultConfig = { enabled: false };3839 configure(config: Partial<FaultConfig>) {40 this.config = { ...this.config, ...config };41 }4243 async maybeInjectFault(serviceName: string): Promise<void> {44 if (!this.config.enabled) return;45 if (this.config.targetServices && !this.config.targetServices.includes(serviceName)) return;4647 // Inject latency48 if (this.config.latencyMs) {49 await new Promise((r) => setTimeout(r, this.config.latencyMs));50 }5152 // Inject timeout (never resolves until AbortController cancels)53 if (this.config.timeoutRate && Math.random() < this.config.timeoutRate) {54 await new Promise(() => {}); // Hang forever — caller's timeout should catch this55 }5657 // Inject error58 if (this.config.errorRate && Math.random() < this.config.errorRate) {59 throw new ChaosError(`Injected fault for ${serviceName}`, this.config.errorCode ?? 500);60 }61 }62}6364export class ChaosError extends Error {65 constructor(66 message: string,67 public readonly statusCode: number68 ) {69 super(message);70 this.name = 'ChaosError';71 }72}73```7475```typescript76// Integration with services77const faultInjector = new FaultInjector();7879// Enable in test/staging via environment variable80if (process.env.CHAOS_ENABLED === 'true') {81 faultInjector.configure({82 enabled: true,83 targetServices: ['payment-api'],84 errorRate: 0.3, // 30% of payment API calls fail85 latencyMs: 2000, // Add 2s latency to all calls86 });87}8889// Wrap service calls90export async function callPaymentAPI(orderId: string): Promise<PaymentResult> {91 await faultInjector.maybeInjectFault('payment-api');92 return fetch(`https://payment.example.com/charge/${orderId}`).then((r) => r.json());93}94```9596```typescript97// Chaos test scenario98describe('checkout resilience', () => {99 it('completes checkout when payment service has 50% error rate', async () => {100 faultInjector.configure({101 enabled: true,102 targetServices: ['payment-api'],103 errorRate: 0.5,104 });105106 // Circuit breaker + retry should handle transient failures107 const result = await checkout(testOrder);108 expect(result.status).toBe('completed');109110 faultInjector.configure({ enabled: false });111 });112113 it('uses cached prices when pricing service is down', async () => {114 faultInjector.configure({115 enabled: true,116 targetServices: ['pricing-api'],117 errorRate: 1.0, // 100% failure118 });119120 const result = await getProductPrice('sku-123');121 expect(result.source).toBe('cache');122 expect(result.price).toBeGreaterThan(0);123124 faultInjector.configure({ enabled: false });125 });126});127```128129## Details130131**Chaos engineering principles (Netflix):**1321331. Define steady state (what "normal" looks like in metrics)1342. Hypothesize that steady state continues during failure1353. Introduce real-world failures (network, disk, process)1364. Try to disprove the hypothesis1375. Fix weaknesses found138139**Failure types to test:**140141- **Latency injection:** Simulate slow responses (100ms, 1s, 5s, 30s)142- **Error injection:** Return 500, 503, connection refused143- **Resource exhaustion:** Fill disk, exhaust memory, saturate CPU144- **Dependency death:** Kill a database, cache, or downstream service entirely145- **Clock skew:** Jump time forward/backward (affects TTLs, JWT expiry)146- **Network partition:** Split services so they cannot communicate147148**Tools:** `toxiproxy` (TCP proxy with configurable toxics), `chaos-mesh` (Kubernetes-native), `litmus` (Kubernetes chaos), `gremlin` (SaaS platform), `pumba` (Docker container chaos).149150**Production chaos safety:**151152- Always have a kill switch to stop the experiment immediately153- Limit blast radius (specific percentage of traffic, specific instances)154- Run during business hours when the team is available155- Start with the smallest possible impact and scale up156- Monitor user-facing metrics, not just infrastructure metrics157158## Source159160https://principlesofchaos.org/161162## Process1631641. Read the instructions and examples in this document.1652. Apply the patterns to your implementation, adapting to your specific context.1663. Verify your implementation against the details and edge cases listed above.167168## Harness Integration169170- **Type:** knowledge — this skill is a reference document, not a procedural workflow.171- **No tools or state** — consumed as context by other skills and agents.172173## Success Criteria174175- The patterns described in this document are applied correctly in the implementation.176- Edge cases and anti-patterns listed in this document are avoided.