Chaos Engineering for SRE: GameDays and Failure Injection That Actually Work

Chaos Engineering for SRE: GameDays and Failure Injection That Actually Work

Chaos engineering is the practice of intentionally breaking production systems to find weaknesses before they find you. Done wrong, it's just causing outages on purpose. Done right, it's a systematic method for building confidence in your system's resilience.

The difference between chaos that helps and chaos that hurts is structure: hypothesis, blast radius control, measurement, and learning.

The Hypothesis-First Approach

Every chaos experiment starts with a hypothesis, not an action.

Bad: "Let's kill some pods and see what happens." Good: "We believe that when the database primary fails, our application will detect it within 30 seconds and promote a replica with less than 5 seconds of user-visible impact."

The hypothesis has four parts:

  1. Steady state: What does normal look like? (success rate 99.9%, p99 < 200ms)
  2. Hypothesis: What do we believe will happen during failure?
  3. Experiment: What failure will we inject?
  4. Result: Does the hypothesis hold? What did we learn?

Document every experiment this way. If you can't write the hypothesis, you're not ready to run the experiment.

GameDay Structure

A GameDay is a structured event where the team intentionally causes failures and validates their responses. It's not ad-hoc breaking things — it's a rehearsal with defined success criteria.

Pre-GameDay preparation (1 week before):

  • Define 3-5 failure scenarios with hypotheses
  • Set blast radius limits (max customers affected, rollback triggers)
  • Brief the on-call team on what's coming
  • Prepare rollback procedures for each scenario
  • Confirm monitoring and alerting is working

Day-of structure:

09:00 - Briefing: review scenarios, assign roles
09:30 - Establish baseline: verify steady state metrics
10:00 - Scenario 1: inject failure, observe, validate hypothesis
10:30 - Debrief Scenario 1: what happened vs. hypothesis?
11:00 - Scenario 2...
14:00 - Full debrief: learnings, action items, SLO impact assessment

Roles:

  • Chaos lead: Runs the experiments, controls blast radius
  • Observer: Watches metrics and records what actually happens
  • Responder: Responds as if this is a real incident (not "just a test")
  • Rollback owner: Authorized to stop the experiment immediately

Common Failure Injection Patterns

Pod failures (Kubernetes):

# Kill random pods in a deployment
kubectl delete pod -l app=api --force --grace-period=0

# Kill the leader of a stateful set
kubectl delete pod api-statefulset-0

# Drain a node (simulates node failure)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

Network failures:

# Add latency to network traffic
tc qdisc add dev eth0 root netem delay 200ms 50ms

# Drop 10% of packets
tc qdisc add dev eth0 root netem loss 10%

# Partition network (block traffic to/from specific IP)
iptables -A OUTPUT -d 10.0.0.5 -j DROP

Resource exhaustion:

# CPU saturation
stress-ng --cpu $(nproc) --timeout 60

# Memory pressure
stress-ng --vm 1 --vm-bytes 80% --timeout 60

# Disk I/O saturation
stress-ng --io 4 --timeout 60

Application-level failures (Chaos Monkey patterns):

class ChaosMiddleware:
    """Inject failures at the application layer."""
    
    def __init__(self, error_rate: float = 0.0, latency_ms: float = 0.0):
        self.error_rate = error_rate
        self.latency_ms = latency_ms
    
    def process_request(self, request):
        # Inject latency
        if self.latency_ms > 0:
            time.sleep(self.latency_ms / 1000)
        
        # Inject errors
        if random.random() < self.error_rate:
            raise InjectedException("Chaos: simulated failure")
        
        return self.next_handler(request)

Chaos Experiment Automation

Manual GameDays are valuable but rare. Automated chaos experiments run continuously.

# chaos_experiments.py
class ChaosExperiment:
    def __init__(self, name: str, hypothesis: str, blast_radius: BlastRadius):
        self.name = name
        self.hypothesis = hypothesis
        self.blast_radius = blast_radius
    
    def run(self) -> ExperimentResult:
        # Measure steady state
        steady_state = self.measure_steady_state()
        
        if not steady_state.is_healthy:
            return ExperimentResult.skipped(
                reason="System not in steady state, unsafe to inject failure"
            )
        
        # Inject failure within blast radius limits
        try:
            self.inject_failure()
            
            # Observe for experiment duration
            observations = self.observe(duration_seconds=60)
            
            # Verify hypothesis
            hypothesis_held = self.evaluate_hypothesis(observations)
            
            return ExperimentResult(
                hypothesis_held=hypothesis_held,
                steady_state_before=steady_state,
                observations=observations,
                hypothesis=self.hypothesis
            )
        finally:
            # Always clean up
            self.restore_steady_state()

class DatabaseFailoverExperiment(ChaosExperiment):
    def __init__(self):
        super().__init__(
            name="database_primary_failure",
            hypothesis="Application detects primary failure within 30s and promotes replica "
                       "with <5s user-visible impact (error rate spike < 10%)",
            blast_radius=BlastRadius(max_error_rate=0.10, max_duration_seconds=120)
        )
    
    def inject_failure(self):
        kill_database_primary()
    
    def evaluate_hypothesis(self, observations):
        # Time to detect failover
        failover_detected_at = observations.first_event("replica_promoted")
        detection_time = failover_detected_at - observations.start_time
        
        # User-visible impact window
        error_spike_duration = observations.duration_above_error_rate(threshold=0.01)
        max_error_rate = observations.max_error_rate()
        
        return (
            detection_time <= 30 and
            error_spike_duration <= 5 and
            max_error_rate <= 0.10
        )

Measuring Chaos ROI

Chaos engineering has real costs: time spent running experiments, risk of actual customer impact, and engineering effort to fix discovered weaknesses. Measure the return.

Track:

  • Hypotheses tested per quarter
  • % of hypotheses that failed (the interesting ones — these found real problems)
  • Mean time to detect injected failures (measures monitoring quality)
  • Fixes implemented after chaos discovery
  • SLO violations prevented (estimated based on what would have happened without the fix)
def generate_chaos_roi_report(quarter: str) -> ChaosROIReport:
    experiments = get_experiments(quarter=quarter)
    
    failed_hypotheses = [e for e in experiments if not e.hypothesis_held]
    fixes_implemented = get_fixes_from_chaos(quarter=quarter)
    
    return ChaosROIReport(
        experiments_run=len(experiments),
        hypotheses_failed=len(failed_hypotheses),
        failure_rate=len(failed_hypotheses) / len(experiments),
        weaknesses_discovered=len(failed_hypotheses),
        fixes_implemented=len(fixes_implemented),
        fix_implementation_rate=len(fixes_implemented) / max(len(failed_hypotheses), 1),
        estimated_incidents_prevented=estimate_prevented_incidents(fixes_implemented)
    )

A healthy chaos program finds real problems 20-30% of the time. If every experiment passes, you're not testing hard enough. If every experiment fails, your system is too fragile to chaos safely.

Chaos in CI/CD

Not all chaos needs humans present. Some experiments can run automatically in staging:

# .github/workflows/chaos-staging.yml
name: Chaos Engineering - Staging

on:
  schedule:
    - cron: '0 10 * * 2'  # Every Tuesday at 10am

jobs:
  chaos-experiments:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Verify staging health before chaos
        run: pytest tests/chaos/prereqs/ --timeout=60

      - name: Run pod failure experiment
        run: python chaos/experiments/pod_failure.py --environment staging

      - name: Run network partition experiment
        run: python chaos/experiments/network_partition.py --environment staging

      - name: Run dependency latency experiment
        run: python chaos/experiments/dependency_latency.py --environment staging

      - name: Generate chaos report
        if: always()
        run: python chaos/reports/generate.py --output chaos-report.json

      - name: Post results
        if: always()
        run: python chaos/reports/post-to-slack.py --file chaos-report.json

Stopping Chaos Safely

Define automatic abort conditions before every experiment. If these trigger, the experiment stops immediately — no human decision required.

class BlastRadiusGuard:
    def __init__(self, max_error_rate: float, max_duration_seconds: int):
        self.max_error_rate = max_error_rate
        self.max_duration_seconds = max_duration_seconds
        self.start_time = time.time()
    
    def check(self) -> bool:
        """Returns True if experiment should abort."""
        # Abort if error rate exceeds blast radius limit
        current_error_rate = get_current_error_rate()
        if current_error_rate > self.max_error_rate:
            logger.warning(
                f"Aborting: error rate {current_error_rate:.2%} > limit {self.max_error_rate:.2%}"
            )
            return True
        
        # Abort if experiment has been running too long
        elapsed = time.time() - self.start_time
        if elapsed > self.max_duration_seconds:
            logger.warning(f"Aborting: experiment exceeded {self.max_duration_seconds}s limit")
            return True
        
        return False

HelpMeTest can continuously validate your system during chaos experiments — running health checks against your endpoints and measuring response times throughout the experiment. This gives you independent verification of what users experience, separate from your internal metrics.

Summary

Effective chaos engineering requires:

  1. Hypothesis-first design — define expected behavior before injecting failures
  2. Structured GameDays with assigned roles, defined success criteria, and debrief
  3. Automated experiments for continuous staging validation
  4. Measured ROI to justify the investment and direct future experiments
  5. Automatic abort conditions that stop experiments before they cause real harm

Chaos engineering done right doesn't cause outages — it prevents them.

Read more

Start now free