Lab 22 — Chaos And Failure Recovery Drill
Learner Orientation
Lab Metadata
| Item | Value |
|---|---|
| Difficulty | Intermediate |
| Estimated time | 75-120 minutes |
| VMs | 2 |
| Minimum VM RAM | 2048 MB |
| SSH ports | 2236, 2237 |
| Internet required | Yes |
Before You Start, You Should Be Able To
- Yeast installed on a Linux/KVM host
- Comfort opening a terminal and changing directories
- Ability to run
yeast up,yeast ssh <instance>, andyeast destroy - Basic comfort with
curl,systemctl, and reading command output - Basic shell scripting comfort
Where Commands Run
- Run
yeastcommands from this lab folder on your laptop. - Run Linux service commands only after you SSH into the target VM.
- When a command says "from your laptop", leave the VM shell first with
exit. - When a browser URL uses
localhost, check whether the lab asked you to open an SSH tunnel first.
Expected Checkpoints
- After
yeast up,yeast statusshould show the expected VM or VMs as running. - After the main setup steps, the service, tool, or workflow introduced by the lab should respond to the verification commands.
- After
bash assets/validate.sh, the script should report all checks passed. - After
yeast destroy, the lab should be cleaned up before you start the next one.
Common Mistakes To Avoid
- Running a VM command on your laptop, or a laptop command inside the VM.
- Closing an SSH tunnel and then wondering why
localhost:<port>stopped working. - Skipping validation because the final page or command "looked fine".
- Forgetting to run
yeast destroybefore moving to the next lab.
The Story
An engineer on your team pushes a config change at 4 PM on a Friday. The database goes down. The app starts returning 500 errors. The proxy serves a blank page. Three things are broken, and nobody has a runbook.
Chaos engineering is the practice of deliberately breaking your system in controlled conditions so you understand how it fails and know exactly how to recover. The goal is not to destroy things — it is to practice recovery so that when real failures happen, you execute from muscle memory, not from panic.
This lab gives you a running three-tier platform and then breaks it in five ways. Before each break, you write the recovery procedure. During the break, you follow the procedure. After recovery, you verify the system is healthy.
Before You Start — Understanding The Concepts
What Is Chaos Engineering?
Chaos engineering is a discipline that involves experimenting on a system to build confidence in its ability to withstand turbulent conditions in production. It was pioneered at Netflix with Chaos Monkey, which randomly terminated production instances to prove their system could survive it.
You do not need to run chaos in production to benefit. Running it in a controlled lab environment — as you will today — builds the skills and procedures you need when uncontrolled failures happen in production.
What Is A Runbook?
A runbook is a documented procedure for responding to a specific operational scenario. It answers: "what do I do when X happens?"
A good runbook: - States the symptoms that trigger it - Lists the diagnosis steps (what to check first) - Lists the recovery steps in order - Includes verification steps (how do you know it worked?) - Includes escalation criteria (when to call for help)
A bad runbook is one that does not exist, or one that has never been tested.
Why Practice Under Pressure?
When a real incident happens, you are under pressure. Your hands shake. You mistype commands. You forget steps. You waste time because you have never done this before on this system.
Practicing in a lab, where nothing is on fire, builds procedural memory. The first time you restore a database under pressure should not be during a production incident.
What Is MTTR?
MTTR (Mean Time To Recovery) is the average time it takes to restore a service after a failure. It is the primary SLO for reliability. Chaos drills directly reduce MTTR by ensuring the recovery procedures are practiced and refined.
What You Are Building
A three-tier platform on two VMs:
- chaos-proxy — Nginx reverse proxy
- chaos-app — Python app + PostgreSQL
You will break it five ways and recover each one.
Your Laptop
│ HTTP 8880 → chaos-proxy port 80
│ SSH 2236 → chaos-proxy port 22
│ SSH 2237 → chaos-app port 22
▼
┌────────────────────────────────────────────────────┐
│ Private Network: 192.168.91.0/24 │
│ │
│ ┌────────────────────┐ ┌─────────────────────┐ │
│ │ chaos-proxy (.10) │──▶│ chaos-app (.20) │ │
│ │ Nginx :80 │ │ Python app :8000 │ │
│ │ │ │ PostgreSQL :5432 │ │
│ └────────────────────┘ └─────────────────────┘ │
└────────────────────────────────────────────────────┘
Starting The Lab
Step 1 — Build The Platform
Set Up The Database
yeast ssh chaos-app
sudo -u postgres psql << 'SQL'
CREATE USER appuser WITH PASSWORD 'chaoslab22';
CREATE DATABASE appdb OWNER appuser;
GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;
SQL
sudo -u postgres psql -d appdb << 'SQL'
CREATE TABLE items (id SERIAL PRIMARY KEY, name TEXT);
INSERT INTO items (name) VALUES ('alpha'),('beta'),('gamma');
SQL
PG_HBA=$(sudo -u postgres psql -t -c "SHOW hba_file;" | tr -d ' ')
echo "host appdb appuser 127.0.0.1/32 md5" | sudo tee -a "$PG_HBA"
sudo systemctl reload postgresql
Set Up The App
sudo pip3 install -q psycopg2-binary
cat > /home/ubuntu/app.py << 'PYEOF'
import json, os, psycopg2
from http.server import HTTPServer, BaseHTTPRequestHandler
DB = {"host":"localhost","port":5432,"dbname":"appdb","user":"appuser","password":"chaoslab22"}
class H(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/healthz":
b = json.dumps({"status":"ok"}).encode(); code = 200
else:
try:
with psycopg2.connect(**DB) as c:
with c.cursor() as cur:
cur.execute("SELECT id,name FROM items ORDER BY id")
rows = [{"id":r[0],"name":r[1]} for r in cur.fetchall()]
b = json.dumps({"items":rows}).encode(); code = 200
except Exception as e:
b = json.dumps({"error":str(e)}).encode(); code = 500
self.send_response(code)
self.send_header("Content-Type","application/json")
self.send_header("Content-Length",str(len(b)))
self.end_headers()
self.wfile.write(b)
def log_message(self,*a): pass
HTTPServer(("0.0.0.0",8000),H).serve_forever()
PYEOF
sudo tee /etc/systemd/system/app.service << 'EOF'
[Unit]
Description=Chaos App
After=postgresql.service
[Service]
User=ubuntu
ExecStart=/usr/bin/python3 /home/ubuntu/app.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now app
curl http://localhost:8000/healthz
exit
Set Up The Proxy
yeast ssh chaos-proxy
sudo tee /etc/nginx/sites-available/app << 'EOF'
server {
listen 80;
server_name _;
location / {
proxy_pass http://192.168.91.20:8000;
proxy_set_header Host $host;
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
curl http://localhost
exit
Verify end-to-end from your laptop:
Expected: {"items":[{"id":1,"name":"alpha"},...]}
Step 2 — Write Your Runbook First
Before breaking anything, write the recovery procedures. This is critical — doing it under pressure produces garbage.
yeast ssh chaos-app
cat > /home/ubuntu/runbook.md << 'EOF'
# Chaos Lab 22 — Recovery Runbook
## Break 1: App service stopped
Symptoms: 502 Bad Gateway from proxy, curl to app:8000 fails
Diagnosis: sudo systemctl status app
Recovery: sudo systemctl start app
Verify: curl http://localhost:8000/healthz
## Break 2: Database stopped
Symptoms: app returns 500 errors, {"error":"...connection refused..."}
Diagnosis: sudo systemctl status postgresql
Recovery: sudo systemctl start postgresql
Verify: sudo systemctl is-active postgresql && curl http://localhost:8000/items
## Break 3: Disk full
Symptoms: app writes fail, logs stop, database crashes
Diagnosis: df -h /
Recovery: find and remove large files, then restart affected services
Verify: df -h / && sudo systemctl restart postgresql app
## Break 4: Config file corrupted
Symptoms: service fails to start after restart
Diagnosis: sudo systemctl status app / nginx -t
Recovery: restore config from backup / fix syntax error
Verify: service starts, endpoint responds
## Break 5: Firewall blocks app port
Symptoms: proxy 502, but app locally is fine
Diagnosis: sudo ufw status — look for missing rule
Recovery: sudo ufw allow 8000
Verify: curl from proxy to app:8000
EOF
exit
Break 1 — App Service Stopped
yeast ssh chaos-app
sudo systemctl stop app
exit
curl http://localhost:8880
# Expected: 502 Bad Gateway
Diagnose and recover using your runbook. Do not read the answer below until you have tried.
yeast ssh chaos-app
sudo systemctl status app # shows it is stopped
sudo systemctl start app
curl http://localhost:8000/healthz # verify local
exit
curl http://localhost:8880 # verify through proxy
Time how long this took. Write it down. This is your MTTR for this failure.
Break 2 — Database Stopped
yeast ssh chaos-app
sudo systemctl stop postgresql
exit
curl http://localhost:8880
# Expected: 500 error — app is up but cannot reach DB
Follow your runbook:
yeast ssh chaos-app
# Symptom check:
curl http://localhost:8000/items # 500 — connection refused
sudo systemctl status postgresql # stopped
# Recovery:
sudo systemctl start postgresql
sleep 2
curl http://localhost:8000/items # 200 — items are back
exit
Break 3 — Fill The Disk
yeast ssh chaos-app
# Fill disk to ~90%
sudo dd if=/dev/zero of=/var/tmp/fill bs=1M count=8000 2>/dev/null || true
df -h / # should be ~90%+
exit
Now generate some requests and watch what happens when the disk is too full for log files:
The app may start returning errors. New data cannot be written to the database. Log files stop growing.
Diagnose and recover:
yeast ssh chaos-app
df -h / # disk near full
du -sh /var/tmp/* # find the culprit
sudo rm /var/tmp/fill
df -h / # back to normal
sudo systemctl restart postgresql app
exit
curl http://localhost:8880
Break 4 — Corrupt A Config File
yeast ssh chaos-app
sudo systemctl stop app
# Introduce a syntax error into the service file
sudo sed -i 's/ExecStart=/ExecStart=BROKEN /' /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl start app # will fail
Diagnose:
sudo systemctl status app # shows failed
sudo journalctl -u app --no-pager -n 10 # shows "BROKEN" in ExecStart
Recover:
sudo sed -i 's/ExecStart=BROKEN /ExecStart=/' /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl start app
sudo systemctl is-active app
curl http://localhost:8000/healthz
exit
Break 5 — Firewall Blocks The App Port
yeast ssh chaos-app
sudo ufw deny 8000
sudo ufw reload
exit
curl http://localhost:8880
# Expected: 502 — proxy cannot reach app even though app is running
This is subtle: the app is running fine, but the firewall is blocking it.
Diagnose:
yeast ssh chaos-proxy
curl http://192.168.91.20:8000 # hangs — network blocked, not refused
exit
yeast ssh chaos-app
sudo systemctl is-active app # active — app is fine
sudo ufw status # shows port 8000 DENY
Recover:
sudo ufw delete deny 8000
sudo ufw allow 8000
sudo ufw reload
exit
curl http://localhost:8880 # works again
Post-Drill Review
After all five breaks, review your runbook. Update it with: - The actual time each recovery took - Any step that was harder than expected - Commands you had to look up - The order that would have been fastest
This review is as important as the drill itself. The runbook gets better every time you practice.
Validate Your Work
Clean Up
Quick Recap
In Lab 22 — Chaos And Failure Recovery Drill, you moved from explanation to a working lab environment, verified the result, and practiced the operational habit that matters most: do the work, prove it works, then clean it up.
Keep this pattern for every lab:
- Build the thing.
- Verify it from the right place.
- Read the logs or status when it fails.
- Run the validation script.
- Destroy the lab before moving on.
What You Learned
- Why you write the runbook before the failure, not during it
- Five concrete failure modes and how to diagnose and recover each
- MTTR: how to measure it by timing your own recovery
- The diagnosis pattern for each failure type: stopped service → systemctl; database down → logs; disk full → df -h; config error → journalctl; firewall → ufw status
- The difference between "service is running" (systemctl) and "service is reachable" (network/firewall)
- Why chaos drills in the lab make production incidents faster and calmer to handle
What Is Next
Lab 23 — Terraform Fundamentals
You have been configuring servers manually and with Ansible. Terraform takes infrastructure-as-code further: it describes not just server configuration, but the infrastructure itself — VMs, networks, DNS records, cloud resources — as declarative code. Lab 23 teaches the fundamentals: plan, apply, state, variables, and outputs.