Chaos Engineering: Building Resilient Systems

DevOps
Chaos Engineering: Building Resilient Systems

Chaos Engineering: Building Resilient Systems Through Controlled Failure

The planning and execution of controlled failure experiments, known as Chaos Engineering, is based on a rigorous discipline to increase confidence in the ability of distributed systems to withstand turbulent conditions in production. Instead of just testing whether something works, this practice assumes that failures will occur and seeks to identify vulnerabilities before they cause real disruptions.

Think of Chaos Engineering as applying a vaccine to your system: intentionally injecting a small amount of a harmful agent into a healthy organism to train its defenses and ensure it can combat a real and much more dangerous threat in the future.

Fundamental Principles for Planning and Executing Experiments

1. Build a Hypothesis Around the "Steady State"

Before introducing any failure, it's necessary to define the normal and healthy behavior of the system, called the steady state.

  • Measurable Metrics: Focus should be on output metrics that reflect user experience, such as latency, error rates, or throughput, rather than internal attributes like CPU load.
  • Use of SLIs and SLOs: It's recommended to use Service Level Indicators (SLIs) and Service Level Objectives (SLOs) to establish the baseline and acceptable performance limits.
  • The Hypothesis: The experiment should predict that the steady state will be maintained even after fault injection.

2. Simulate Real-World Events

Experiments should reflect realistic failures that can occur in the production environment.

  • Types of Failures: Prioritize events based on their estimated frequency or potential impact, such as server crashes, disk failures, network latency, or sudden traffic spikes.
  • Variation: It's essential to vary variables to cover hardware, software, and third-party dependency failures.

3. Run Experiments in Production

While it's recommended to start in pre-production or staging environments to validate tools, the ultimate goal is to perform tests in production.

  • Fidelity: Only the production environment, with real traffic and live dependencies, provides an accurate picture of system resilience.
  • Relevance: Tests in isolated environments may fail to capture specific traffic patterns or security configurations of reality.

4. Minimize the Blast Radius

Safety is a critical principle to avoid unnecessary damage to users and business.

  • Start Small: The experiment should be designed to affect only a small portion of the system or a limited set of users.
  • Rollback Plan: There must be an immediate reversal mechanism (like a "panic button" or kill switch) that stops the experiment and restores the steady state if something goes wrong.

5. Automate and Execute Continuously

Since systems constantly change due to new deployments and configuration updates, manual testing is unsustainable.

  • CI/CD Pipeline: Automation allows experiments to be an integral part of the continuous delivery cycle, ensuring new vulnerabilities are detected quickly.

Practical Execution Process

To apply these principles, teams generally follow this workflow:

  1. Readiness Assessment: Review the architecture to identify failure points and critical dependencies.
  2. Experiment Definition: Choose the specific failure (e.g., inject 200ms latency) and success metric.
  3. Game Days: Dedicated days where development and operations teams gather to execute tests, observe real-time behavior, and learn from results.
  4. Analysis and Adjustment: If the hypothesis is refuted, the identified vulnerability must be fixed before repeating the test.

Example: Simple Chaos Experiment

Here's a basic example of how you might implement a simple chaos experiment using Python:


import time
import random
import requests
from datetime import datetime

class ChaosExperiment:
    def __init__(self, service_url, steady_state_threshold=200):
        self.service_url = service_url
        self.steady_state_threshold = steady_state_threshold  # ms
        self.baseline_metrics = []
        
    def measure_steady_state(self, duration=60):
        """Measure baseline performance for comparison"""
        print(f"Measuring steady state for {duration} seconds...")
        
        for _ in range(duration):
            start_time = time.time()
            try:
                response = requests.get(self.service_url, timeout=5)
                latency = (time.time() - start_time) * 1000
                self.baseline_metrics.append({
                    'timestamp': datetime.now(),
                    'latency': latency,
                    'status_code': response.status_code
                })
            except Exception as e:
                self.baseline_metrics.append({
                    'timestamp': datetime.now(),
                    'latency': float('inf'),
                    'error': str(e)
                })
            time.sleep(1)
            
        avg_latency = sum(m['latency'] for m in self.baseline_metrics 
                         if m['latency'] != float('inf')) / len(self.baseline_metrics)
        print(f"Baseline average latency: {avg_latency:.2f}ms")
        
    def inject_network_latency(self, delay_ms=200):
        """Simulate network latency injection"""
        print(f"Injecting {delay_ms}ms network latency...")
        # In a real scenario, this would use tools like tc, toxiproxy, or chaos mesh
        # For demonstration, we'll simulate with sleep
        time.sleep(delay_ms / 1000)
        
    def run_experiment(self):
        """Execute the chaos experiment"""
        print("Starting Chaos Engineering Experiment")
        print("Hypothesis: System will maintain < 200ms response time under network stress")
        
        # Step 1: Establish baseline
        self.measure_steady_state(30)
        
        # Step 2: Inject failure
        print("\nInjecting chaos...")
        experiment_metrics = []
        
        for i in range(30):
            start_time = time.time()
            
            # Randomly inject latency to simulate network issues
            if random.random() < 0.3:  # 30% chance of latency
                self.inject_network_latency(150)
                
            try:
                response = requests.get(self.service_url, timeout=5)
                latency = (time.time() - start_time) * 1000
                experiment_metrics.append({
                    'timestamp': datetime.now(),
                    'latency': latency,
                    'status_code': response.status_code
                })
            except Exception as e:
                experiment_metrics.append({
                    'timestamp': datetime.now(),
                    'latency': float('inf'),
                    'error': str(e)
                })
            time.sleep(1)
            
        # Step 3: Analyze results
        self.analyze_results(experiment_metrics)
        
    def analyze_results(self, experiment_metrics):
        """Analyze experiment results against hypothesis"""
        valid_metrics = [m for m in experiment_metrics if m['latency'] != float('inf')]
        
        if not valid_metrics:
            print("❌ HYPOTHESIS REJECTED: System completely failed")
            return
            
        avg_experiment_latency = sum(m['latency'] for m in valid_metrics) / len(valid_metrics)
        max_latency = max(m['latency'] for m in valid_metrics)
        
        print(f"\nExperiment Results:")
        print(f"Average latency during chaos: {avg_experiment_latency:.2f}ms")
        print(f"Maximum latency: {max_latency:.2f}ms")
        print(f"Success rate: {len(valid_metrics)/len(experiment_metrics)*100:.1f}%")
        
        if avg_experiment_latency < self.steady_state_threshold:
            print("✅ HYPOTHESIS CONFIRMED: System maintained performance")
        else:
            print("❌ HYPOTHESIS REJECTED: System degraded beyond acceptable limits")
            print("🔧 Action required: Investigate and improve system resilience")

# Usage example
if __name__ == "__main__":
    experiment = ChaosExperiment("https://httpbin.org/delay/0.1")
    experiment.run_experiment()
  

Benefits of Chaos Engineering

  • Proactive Problem Detection: Identify weaknesses before they impact users
  • Increased Confidence: Build trust in system resilience through empirical evidence
  • Improved Incident Response: Teams become better prepared for real failures
  • Documentation of System Behavior: Better understanding of how systems fail and recover
  • Cultural Change: Promotes a mindset of resilience and continuous improvement

Popular Tools

  • Chaos Monkey: Netflix's original tool for randomly terminating instances
  • Litmus: Cloud-native chaos engineering framework for Kubernetes
  • Chaos Mesh: Chaos engineering platform for Kubernetes environments
  • Gremlin: Commercial chaos engineering platform
  • Toxiproxy: Proxy for simulating network conditions

Getting Started

If you're new to Chaos Engineering, start with these steps:

  1. Ensure you have proper monitoring and observability in place
  2. Start with non-production environments
  3. Begin with simple experiments (e.g., restart a single service)
  4. Gradually increase complexity and scope
  5. Always have rollback mechanisms ready
  6. Document everything and share learnings with your team

Remember: Chaos Engineering is not about breaking things randomly. It's about learning how your system behaves under stress and building confidence in its resilience through scientific experimentation.

Further Reading

Related Articles

February 18, 2026

DORA Metrics: Measuring DevOps Performance and Team Excellence

Learn how to measure and improve your team's software delivery performance using the four key DORA metrics

Read More
February 02, 2026

Database Access via Docker CLI: Complete Guide

Master database management directly from the command line without heavy GUI tools

Read More