25 — The Production Gauntlet · Part II: Chaos Engineering¶
"Would you rather find the failure at 2 pm on a game-day, or 2 am in a real outage?"
⏱️ Time: 5–7 hours · 🎚️ Level: Advanced · 📋 Pehle chahiye: Ch24 – Build ShopFast · M8 Observability · Ch23 Incident Playbook
Three things you will be able to do when this chapter ends:
- Design and safely run a structured chaos experiment on a live Kubernetes workload.
- Diagnose the blast radius of infrastructure failures using real commands, metrics, and logs.
- Harden ShopFast — and any future system — against the twelve failure modes that most often cause real production outages.
What chaos engineering actually is (and isn't)¶
Origin¶
In 2010, Netflix moved to AWS and immediately discovered that distributed systems fail in ways that nobody predicted from reading the architecture diagram. Their answer was Chaos Monkey: a bot that randomly terminated EC2 instances in production during business hours. The logic was ruthless and correct — if you can't tolerate one random instance dying, you don't deserve to call yourself highly available.
Over the next decade, Netflix evolved this into a full discipline: the Chaos Engineering practice, formalized in the Principles of Chaos Engineering document (principlesofchaos.org).
The canonical definition:
Chaos Engineering is the discipline of experimenting on a system in order to build confidence in the system's capability to withstand turbulent conditions in production.
Note the word confidence. Not excitement. Not destruction. Confidence — the kind that lets an on-call engineer sleep, because they have verified the system's behavior under failure, not just assumed it.
What it is NOT¶
Common misunderstanding
Chaos engineering is not "randomly breaking production to see what happens." It is a scientific method applied to distributed systems: you form a hypothesis, you design a controlled experiment with a defined blast radius and abort condition, you measure, and you learn.
Random destruction is vandalism. Chaos engineering is engineering.
The five principles¶
| # | Principle | What it means for ShopFast |
|---|---|---|
| 1 | Build a hypothesis around steady state | Define what "healthy" looks like in numbers (p99 < 200ms, error rate < 0.1%). The experiment tests whether that state holds. |
| 2 | Vary real-world events | Inject failures that actually happen: pod crash, AZ loss, DB failover, DNS hiccup — not theoretical ones. |
| 3 | Run experiments in production | Staging lies. Production is where the real traffic patterns, data volumes, and resource contention live. Start in staging; graduate to prod game-days. |
| 4 | Automate experiments continuously | A one-off game-day decays. Chaos in CI/staging runs forever. |
| 5 | Minimize blast radius | Start with one pod. Graduate to one AZ. Never blow up the whole system for a first experiment. |
The maturity ladder¶
flowchart LR
L1["Level 1<br/>Manual<br/>ad-hoc kubectl"]:::run
L2["Level 2<br/>Staged<br/>Chaos Mesh CRDs<br/>on staging"]:::warn
L3["Level 3<br/>Scheduled<br/>game-days in prod<br/>with abort criteria"]:::store
L4["Level 4<br/>Continuous<br/>chaos in CI<br/>and production"]:::ok
L1 --> L2 --> L3 --> L4
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
🇮🇳 Hinglish intuition: School mein fire drill hoti hai — building nahi jalate, bas practice karte hain. Chaos engineering wahi hai. Aag lagana nahi seekhte; bujhana seekhte hain.
The tools¶
| Tool | Type | Best for | Blast-radius control |
|---|---|---|---|
kubectl delete pod / drain / cordon |
Manual | Quick pod/node experiments | Manual (you control scope) |
tc netem inside a pod |
Manual | Network latency, packet loss, corruption | Pod-scoped |
stress-ng |
Manual | CPU, memory, disk I/O pressure | Container-scoped |
iptables / nsenter |
Manual | Network drop rules, port blocks | Node or pod-scoped |
| Chaos Mesh | CRD-driven OSS | Full suite: PodChaos, NetworkChaos, StressChaos, IOChaos, TimeChaos | Namespace / label selector |
| LitmusChaos | CRD-driven OSS | Litmus experiments library, Argo Workflow integration | Namespace-scoped |
| AWS FIS | Managed AWS | AZ outage simulation, EC2/EKS node termination, RDS failover, API throttling | IAM-policy scoped |
| Gremlin | SaaS | Enterprise, multi-cloud, rich UI | Team/cluster scoped |
When to use each:
- Learning / one-off diagnosis →
kubectl+tc netem+stress-ng. No installation required. - Repeatable, reviewable experiments in staging → Chaos Mesh (CRDs live in Git, reviewed in PRs).
- AWS infrastructure failures (AZ, RDS, ElastiCache) → AWS FIS. It is the only tool that can actually tell AWS to promote a Multi-AZ RDS standby.
- Enterprise multi-team programs → Gremlin.
The experiment template¶
Every experiment in this chapter follows the same repeatable shape. Internalize this loop — it is how senior SREs think.
flowchart TD
SS["Steady State<br/>Define the healthy baseline metric"]:::ok
HYP["Hypothesis<br/>System will hold steady state<br/>despite injected failure"]:::run
BR["Blast Radius and Abort<br/>Scope failure + stop condition"]:::warn
INJ["Inject Failure<br/>kubectl or Chaos Mesh or AWS FIS"]:::store
OBS["Observe<br/>Grafana · Loki · kubectl · curl"]:::obs
RES{"Steady state<br/>held?"}
PASS["PASS<br/>Confidence gained<br/>Document result"]:::ok
FAIL["FAIL<br/>System broke<br/>Diagnose"]:::warn
FIX["Fix<br/>Config change · code change · manifest"]:::run
PREV["Prevent<br/>Alert · runbook · CI check"]:::obs
CG["Confidence gained<br/>Graduate to next level"]:::ok
SS --> HYP --> BR --> INJ --> OBS --> RES
RES -- "Yes" --> PASS --> CG
RES -- "No" --> FAIL --> FIX --> PREV --> CG
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
The experiment suite¶
Pre-flight before every experiment
E1 · Kill one pod — does the ReplicaSet self-heal?¶
Cross-link: M4 Kubernetes Core — ReplicaSet controller, readiness probes.
Steady state: orders-api serving requests; p99 latency < 200 ms; error rate < 0.1%.
Hypothesis: Killing one pod triggers an immediate ReplicaSet replacement. The readiness probe gates traffic so no request is routed to the dying pod. The SLO holds.
Blast radius: Single pod. Abort: if error rate > 1% for > 60 s, investigate before proceeding.
Inject — manual¶
# Pick one pod
POD=$(kubectl get pod -n shopfast -l app=orders-api \
-o jsonpath='{.items[0].metadata.name}')
# Kill it
kubectl delete pod "$POD" -n shopfast
# Watch the replacement
kubectl get pods -n shopfast -l app=orders-api -w
Inject — Chaos Mesh¶
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: kill-one-orders-pod
namespace: shopfast
spec:
action: pod-kill
mode: one # only one pod at a time
selector:
namespaces: [shopfast]
labelSelectors:
app: orders-api
duration: "30s"
Observe¶
# Replacement timing
kubectl get events -n shopfast --sort-by='.lastTimestamp' | tail -20
# Traffic — did we drop requests?
kubectl logs -n shopfast -l app=ingress-nginx --since=2m | grep "POST /orders" | awk '{print $NF}' | sort | uniq -c
# Grafana: shopfast_http_requests_total{status="5xx"} — should stay near zero
What you will see¶
flowchart LR
REQ["Incoming request"]:::net
ING["ingress-nginx<br/>health-checks replicas"]:::run
DEAD["Pod killed<br/>Endpoint removed<br/>from Service"]:::warn
NEW["New pod<br/>ContainerCreating<br/>→ Running"]:::ok
READY["readinessProbe passes<br/>Pod added back<br/>to Endpoints"]:::ok
REQ --> ING
ING -- "routes to healthy pods only" --> READY
ING -. "dead pod removed" .-> DEAD
DEAD -- "ReplicaSet creates" --> NEW --> READY
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Common fail: No readiness probe → traffic routes to the new pod before it is ready → burst of 502s. Fix: add a meaningful /healthz readiness probe with initialDelaySeconds: 5 and periodSeconds: 5.
Prevention: Lint your Deployments — a missing readiness probe should be a CI failure (Conftest/Kyverno policy).
E2 · Delete the Deployment — does Argo CD self-heal?¶
Cross-link: M7 GitOps — Argo CD selfHeal, PDB.
Steady state: Argo CD app shopfast-orders is Synced / Healthy. All pods running.
Hypothesis: Deleting the Deployment triggers Argo CD's selfHeal to restore it within the syncInterval (default 3 min). The PDB prevents all pods from being evicted simultaneously if a rolling replacement is attempted.
Blast radius: Entire orders-api Deployment. Abort: if restoration takes > 5 min, check Argo CD logs.
Inject — manual¶
# Delete the whole Deployment
kubectl delete deployment orders-api -n shopfast
# Watch Argo CD react
argocd app get shopfast-orders --refresh
watch -n5 "kubectl get pods -n shopfast"
Inject — verify selfHeal is on¶
argocd app get shopfast-orders -o json | jq '.spec.syncPolicy'
# Should show: "automated": {"selfHeal": true, "prune": true}
Observe¶
# Time to recovery
date; kubectl get deploy orders-api -n shopfast -w
# Argo CD sync log
argocd app history shopfast-orders
# PDB status during the gap
kubectl get pdb -n shopfast
What you will see — PDB in action:
flowchart TD
DEL["kubectl delete deployment<br/>orders-api"]:::warn
ARGO["Argo CD detects drift<br/>OutOfSync"]:::run
PDB["PodDisruptionBudget<br/>minAvailable: 2<br/>blocks simultaneous eviction"]:::store
SYNC["Argo CD applies<br/>manifest from Git"]:::ok
RS["ReplicaSet creates<br/>new pods"]:::ok
HEAL["App: Synced and Healthy"]:::ok
DEL --> ARGO --> SYNC --> RS --> HEAL
SYNC -. "during rolling replace<br/>PDB enforced" .-> PDB
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Common fail: selfHeal: false → Argo CD shows OutOfSync but does nothing. The system is dark until someone manually syncs. Fix: ensure automated.selfHeal: true in the Application CRD. PDB minAvailable < replicas → prevents self-healing if all pods are on one node. Fix: set minAvailable: 1 and topologySpreadConstraints.
E3 · Drain a node — graceful shutdown under real traffic¶
Cross-link: M9 Advanced K8s Internals — preStop, SIGTERM, graceful termination.
Steady state: 3-replica orders-api, pods spread across 2+ nodes.
Hypothesis: Draining a node triggers orderly pod eviction. The preStop sleep + graceful SIGTERM handling means zero dropped requests.
Blast radius: One node. Abort: if > 3 dropped connections, uncordon immediately.
Inject — manual¶
# Pick a node that has orders-api pods
NODE=$(kubectl get pod -n shopfast -l app=orders-api \
-o jsonpath='{.items[0].spec.nodeName}')
# Drain it (respects PDB)
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data \
--grace-period=60 --timeout=120s
# In a second terminal, watch error rate
while true; do
curl -s -o /dev/null -w "%{http_code}\n" https://shopfast.internal/orders
sleep 0.5
done
The graceful-shutdown config that saves you¶
# Deployment spec — both of these are required
spec:
template:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: orders-api
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # let LB deregister first
Why preStop sleep 5? Kubernetes removes the pod from Endpoints asynchronously. Without the sleep, kube-proxy may still route traffic to the pod during the 1–2s race between endpoint removal and the process receiving SIGTERM. Five seconds is cheap insurance.
Observe¶
# Are requests dropping?
kubectl logs -n shopfast -l app=ingress-nginx --since=1m | grep " 50[0-9] "
# Where did pods land after eviction?
kubectl get pods -n shopfast -o wide
# Node cordoned?
kubectl get node "$NODE"
Restore:
Common fail: terminationGracePeriodSeconds: 30 but the app takes 45 s to drain connections → SIGKILL arrives mid-request. Fix: set grace period ≥ your p99 request duration + 15 s buffer.
E4 · Lose an Availability Zone¶
Steady state: orders-api pods spread across 3 AZs. RDS Multi-AZ with standby in us-east-1b.
Hypothesis: Cordoning all nodes in one AZ causes pods to reschedule on surviving AZs (topologySpread). RDS Multi-AZ promotes standby in < 60 s. SLO recovers within 2 min.
Blast radius: All nodes in one AZ. Abort: if pods cannot schedule on remaining AZs (check PodsPending > 5 min).
Inject — AWS FIS¶
{
"description": "Terminate all EC2 instances in us-east-1a for EKS node group",
"targets": {
"eks-nodes-1a": {
"resourceType": "aws:ec2:instance",
"resourceTags": { "kubernetes.io/cluster/shopfast": "owned", "topology.kubernetes.io/zone": "us-east-1a" },
"selectionMode": "ALL"
}
},
"actions": {
"terminate-az-nodes": {
"actionId": "aws:ec2:terminate-instances",
"targets": { "Instances": "eks-nodes-1a" }
}
},
"stopConditions": [{ "source": "aws:cloudwatch:alarm", "value": "arn:aws:cloudwatch:...:alarm:shopfast-p99-breach" }]
}
Inject — manual (safe simulation)¶
# Cordon all nodes in us-east-1a without deleting them
for node in $(kubectl get nodes -l topology.kubernetes.io/zone=us-east-1a -o name); do
kubectl cordon $node
done
# Force eviction
for node in $(kubectl get nodes -l topology.kubernetes.io/zone=us-east-1a -o name); do
kubectl drain $node --ignore-daemonsets --delete-emptydir-data --force
done
Diagnose¶
# Did pods reschedule?
kubectl get pods -n shopfast -o wide | grep -v "us-east-1a"
# topologySpread satisfied?
kubectl describe pod -n shopfast -l app=orders-api | grep -A5 "Topology Spread"
# RDS failover event
aws rds describe-events --source-identifier shopfast-db \
--event-categories failover --duration 60
Common fail: topologySpreadConstraints set to DoNotSchedule but only 2 AZs remain and maxSkew: 1 cannot be satisfied → pods stuck in Pending. Fix: use whenUnsatisfiable: ScheduleAnyway for non-critical workloads, or ensure the node group has capacity in surviving AZs before experiments.
Restore:
for node in $(kubectl get nodes -l topology.kubernetes.io/zone=us-east-1a -o name); do
kubectl uncordon $node
done
E5 · Redis (cache) down — graceful degradation or hard crash?¶
Steady state: orders-api uses Redis as a read-through cache. Cache hit rate > 70% (visible in Grafana). Latency < 80 ms on cache hits.
Hypothesis: If Redis is unavailable, the application falls back to the database and continues serving requests (slower, but alive). No 5xx errors.
Blast radius: All cache reads/writes fail. DB load increases. Abort: if DB connections > 80% of pool.
Inject — manual¶
# Scale ElastiCache to zero replicas is not possible in AWS — simulate with NetworkPolicy block
kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-redis
namespace: shopfast
spec:
podSelector:
matchLabels:
app: orders-api
policyTypes: [Egress]
egress:
- ports:
- port: 443
- port: 80
- port: 5432 # allow DB — we are testing cache fallback
# Redis port 6379 intentionally omitted
EOF
Inject — Chaos Mesh¶
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: block-redis
namespace: shopfast
spec:
action: partition
mode: all
selector:
namespaces: [shopfast]
labelSelectors:
app: orders-api
direction: to
target:
mode: all
selector:
namespaces: [shopfast]
labelSelectors:
app: redis
duration: "5m"
Observe¶
# Are requests still succeeding?
while true; do
curl -s -o /dev/null -w "%{http_code}" https://shopfast.internal/orders/42
echo
sleep 1
done
# DB connection spike?
kubectl exec -n shopfast deploy/orders-api -- \
wget -qO- localhost:9090/metrics | grep db_pool_active
# Application logs — are cache errors surfacing as user errors?
kubectl logs -n shopfast -l app=orders-api --since=2m | grep -i "redis\|cache"
flowchart LR
REQ["GET /orders/42"]:::net
CACHE["Redis cache<br/>UNREACHABLE"]:::warn
DB["RDS Postgres<br/>read fallback"]:::store
RESP["200 OK<br/>slower but alive"]:::ok
ERR["500 Error<br/>if no fallback"]:::warn
REQ --> CACHE
CACHE -- "fallback path" --> DB --> RESP
CACHE -- "no fallback" --> ERR
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Fix — treat cache as optional:
# Before (brittle — no fallback)
def get_order(order_id):
cached = redis.get(f"order:{order_id}") # throws on connection error
return cached or db.query(order_id)
# After (resilient)
def get_order(order_id):
try:
cached = redis.get(f"order:{order_id}", socket_timeout=0.5)
if cached:
return cached
except (RedisError, ConnectionError):
metrics.increment("cache.miss.fallback")
return db.query(order_id)
Prevention: Set socket_connect_timeout and socket_timeout on the Redis client. Alert on cache.miss.fallback > 10% — that means something is wrong with Redis.
🇮🇳 Hinglish intuition: Cache ka role waiter ka hai, chef (DB) ka nahi. Agar waiter absent hai, directly kitchen se khana aata hai — restaurant band nahi hota.
Cleanup:
E6 · RDS failover — connection pool drain and retry¶
Steady state: orders-api connected to RDS Multi-AZ primary. DB query p99 < 20 ms.
Hypothesis: RDS Multi-AZ failover typically takes 60–120 s (AWS documented range; matches 23 — J3). During this window, existing connections break. The app's connection pool + retry logic absorbs the blip. No user-visible errors beyond a brief latency spike.
Blast radius: All DB writes fail for 60–120 s. Abort: if error rate stays > 1% for > 3 min post-failover, rollback app config.
Inject — AWS CLI¶
Observe¶
# Timeline of the failover
aws rds describe-events \
--source-identifier shopfast-db \
--duration 30 \
--query 'Events[*].[Message,Date]' \
--output table
# App side — are retries happening?
kubectl logs -n shopfast -l app=orders-api --since=3m | grep -E "retry|connection|timeout"
# Grafana: db_query_duration_p99 spike + recovery shape
What you will see:
flowchart TD
PRI["RDS Primary<br/>us-east-1a"]:::store
FAIL["Failover triggered<br/>DNS CNAME flips"]:::warn
POOL["Connection pool<br/>existing connections broken<br/>new connections refused"]:::warn
RETRY["App: retry with<br/>exponential backoff<br/>max 3 attempts"]:::run
STAND["RDS Standby<br/>us-east-1b<br/>promoted to primary"]:::ok
CONN["New connections<br/>established to new primary"]:::ok
USERS["User requests<br/>retried or queued<br/>transparent to user"]:::ok
PRI --> FAIL --> POOL
FAIL --> STAND --> CONN
POOL -- "retry loop" --> RETRY --> CONN --> USERS
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Fix — the three-part defense:
# 1. PgBouncer sidecar or RDS Proxy absorbs connection churn
# orders-api Deployment: add RDS Proxy endpoint to DATABASE_URL
# 2. Connection pool config (SQLAlchemy example)
pool_pre_ping=True, # test connection before using
pool_recycle=300, # recycle connections every 5 min
pool_timeout=10, # give up after 10 s if no free conn
max_overflow=5 # allow burst above pool_size
# 3. Retry decorator
@retry(stop=stop_after_attempt(3),
wait=wait_exponential(min=0.1, max=2),
retry=retry_if_exception_type(OperationalError))
def create_order(data):
...
Prevention: Add a readiness probe that checks the DB connection. If the DB is unreachable, the pod fails readiness → load balancer stops routing → no 500s reach users while the pool reconnects.
E7 · Network latency api→DB — timeouts and circuit breakers¶
Steady state: DB queries complete in < 20 ms. API p99 < 200 ms.
Hypothesis: Adding 500 ms of artificial latency between orders-api and RDS exposes missing timeout configuration and reveals cascading failure risk. The circuit breaker trips and returns a fast error instead of slow queue buildup.
Blast radius: All DB queries slow. Thread pool exhaustion risk. Abort: if pod CPU > 80% (sign of thread starvation).
Inject — tc netem inside the pod¶
# Get a shell on one orders-api pod
kubectl exec -it -n shopfast deploy/orders-api -- sh
# Inside the pod: add 500ms delay to egress traffic on port 5432
tc qdisc add dev eth0 root handle 1: prio
tc qdisc add dev eth0 parent 1:3 handle 30: netem delay 500ms
tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 \
match ip dport 5432 0xffff flowid 1:3
Inject — Chaos Mesh NetworkChaos¶
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: db-latency
namespace: shopfast
spec:
action: delay
mode: one
selector:
namespaces: [shopfast]
labelSelectors:
app: orders-api
delay:
latency: "500ms"
correlation: "25"
jitter: "50ms"
direction: to
externalTargets:
- shopfast-db.cluster.us-east-1.rds.amazonaws.com
duration: "10m"
Observe¶
# API latency spike visible?
curl -w "\nTotal: %{time_total}s\n" https://shopfast.internal/orders/1
# Thread pool exhaustion?
kubectl exec -n shopfast deploy/orders-api -- \
wget -qO- localhost:9090/metrics | grep -E "thread_pool|active_requests"
# Circuit breaker state (if implemented)
kubectl logs -n shopfast -l app=orders-api | grep "circuit"
The cascading failure path (before fix):
flowchart TD
REQ["High RPS<br/>order requests"]:::net
SLOW["DB query<br/>500ms instead of 20ms"]:::warn
THREADS["Worker threads blocked<br/>waiting for DB"]:::warn
QUEUE["Request queue<br/>fills up"]:::warn
OOM["Thread pool exhausted<br/>new requests rejected<br/>503 or timeout"]:::warn
POOL["Heap fills<br/>OOMKilled or<br/>unresponsive pod"]:::warn
REQ --> SLOW --> THREADS --> QUEUE --> OOM --> POOL
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Fix — three layers:
# Layer 1: Statement timeout — never wait more than 2 s for a query
cursor.execute("SET statement_timeout = '2000ms'")
# Layer 2: Circuit breaker (pybreaker / resilience4j)
@circuit_breaker(fail_max=5, reset_timeout=30)
def db_query(sql, params):
...
# Layer 3: Bulkhead — separate thread pool for DB queries
# so slow DB cannot block HTTP handler threads
db_executor = ThreadPoolExecutor(max_workers=10, thread_name_prefix="db")
Cleanup:
E8 · CPU and memory pressure — HPA, throttling, OOMKilled¶
Cross-link: M5 Sizing and Cost — requests/limits, VPA.
Steady state: orders-api CPU at 30% of limit. Memory stable. HPA at minReplicas: 2.
Hypothesis: A CPU spike triggers HPA scale-out within 90 s. Pods with CPU limit set too low are throttled (not killed) — latency rises but no OOM. A pod with memory overuse is OOMKilled and restarted.
Blast radius: Single pod stress. Abort: if HPA cannot scale (check node capacity).
Inject — CPU stress¶
# Run stress-ng in one pod
kubectl exec -n shopfast \
$(kubectl get pod -n shopfast -l app=orders-api -o name | head -1) \
-- stress-ng --cpu 2 --timeout 120s
Inject — memory pressure (trigger OOMKill)¶
kubectl exec -n shopfast \
$(kubectl get pod -n shopfast -l app=orders-api -o name | head -1) \
-- stress-ng --vm 1 --vm-bytes 600M --timeout 30s
# 600M > memory limit → OOMKilled
Observe¶
# HPA reaction
kubectl get hpa -n shopfast -w
# OOMKill detection
kubectl get events -n shopfast | grep OOMKill
kubectl describe pod -n shopfast <pod-name> | grep -A3 "Last State"
# CPU throttling (cgroup stats)
kubectl exec -n shopfast deploy/orders-api -- \
cat /sys/fs/cgroup/cpu/cpu.stat | grep throttled
The right-sizing fix:
resources:
requests:
cpu: "250m" # what the scheduler reserves
memory: "256Mi" # what the scheduler reserves
limits:
cpu: "1000m" # 4x request — room for burst, avoid over-throttle
memory: "512Mi" # 2x request — OOM at 512 not 256
# HPA — trigger scale-out at 70% of request (not limit)
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
🇮🇳 Hinglish intuition:
requests= aapka reserved seat on a train.limits= aap maximum kitni jagah le sakte ho. Agarlimitsbahut tight hai, train ruk jaati hai mid-journey (throttle). Agarlimitsnahi hai, aap poore compartment mein fail jaate ho (noisy neighbour).
Prevention: Set up a Grafana alert on container_oom_events_total > 0 and container_cpu_cfs_throttled_periods_total / container_cpu_cfs_periods_total > 0.25.
E9 · Disk fills up — ephemeral storage and log rotation¶
Cross-link: Ch23 Incident Playbook — Issue 14 (Disk full).
Steady state: Node disk usage < 70%. No eviction events.
Hypothesis: Unrotated logs fill the node's ephemeral storage, triggering pod eviction. The fix requires ephemeral-storage limits + log rotation.
Blast radius: Single node ephemeral disk. Abort: if more than 2 pods evicted simultaneously.
Inject — fill ephemeral storage¶
# Inside a pod — write a large file to emptyDir or /tmp
kubectl exec -n shopfast deploy/orders-api -- \
dd if=/dev/zero of=/tmp/bigfile bs=1M count=2000
# 2 GB file → should exceed ephemeral-storage limit
Observe¶
# Eviction events
kubectl get events -n shopfast | grep -i evict
# Node disk usage
kubectl describe node <node-name> | grep -A10 "Allocated resources"
# Kubelet eviction log
kubectl logs -n kube-system -l component=kubelet --since=5m | grep evict
Fix — three layers:
# 1. Set ephemeral-storage limit
resources:
limits:
ephemeral-storage: "1Gi"
# 2. Log rotation via Fluentd / Fluent Bit (already in kube-prometheus-stack)
# In ConfigMap for Fluent Bit:
[OUTPUT]
Name loki
Match *
Labels job=fluentbit
auto_kubernetes_labels on
# 3. Alert before eviction happens
# Prometheus rule:
- alert: NodeDiskPressure
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) < 0.15
for: 5m
labels:
severity: warning
E10 · DNS failure — CoreDNS down, service discovery breaks¶
Steady state: orders-api resolves postgres-svc.shopfast.svc.cluster.local in < 5 ms. CoreDNS healthy.
Hypothesis: Deleting CoreDNS pods breaks all in-cluster DNS, causing cascading service discovery failure. CoreDNS's own PDB + HPA should prevent this. The experiment validates that the PDB is correctly set.
Blast radius: All in-cluster DNS resolution. Abort condition: Restore immediately — this is a wide blast.
Inject — delete CoreDNS pods¶
# Check PDB first
kubectl get pdb -n kube-system
# Kill one CoreDNS pod
kubectl delete pod -n kube-system \
$(kubectl get pod -n kube-system -l k8s-app=kube-dns -o name | head -1)
# Verify DNS still works from another pod
kubectl exec -n shopfast deploy/orders-api -- \
nslookup postgres-svc.shopfast.svc.cluster.local
Diagnose a real DNS failure¶
# DNS resolution timing
kubectl exec -n shopfast deploy/orders-api -- \
time nslookup orders-api.shopfast.svc.cluster.local
# ndots setting — this determines search path overhead
kubectl exec -n shopfast deploy/orders-api -- cat /etc/resolv.conf
# CoreDNS logs
kubectl logs -n kube-system -l k8s-app=kube-dns --since=5m
The ndots trap:
# Default ndots:5 causes 5 DNS lookups per FQDN attempt
# For every call to "postgres-svc" the pod tries:
# postgres-svc.shopfast.svc.cluster.local → HIT (5th attempt)
# This adds ~5ms per query at scale
# Fix: set ndots:2 for pods that only talk to in-cluster services
spec:
dnsConfig:
options:
- name: ndots
value: "2"
- name: timeout
value: "2"
- name: attempts
value: "3"
Prevention:
# CoreDNS should have a PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: coredns-pdb
namespace: kube-system
spec:
minAvailable: 1
selector:
matchLabels:
k8s-app: kube-dns
Alert: coredns_dns_requests_total drops to zero → page immediately.
E11 · Payments dependency times out — the classic cascading failure¶
Steady state: External payments API responding in < 300 ms. Order success rate > 99%.
Hypothesis: If the payments API hangs (no response, not a fast error), our order-worker goroutines/threads block waiting. Without a circuit breaker, this exhausts the worker pool and order processing stops. With the circuit breaker, we get a fast degraded response and can queue the payment retry.
Blast radius: All payment processing. Orders placed but not charged until circuit opens. Abort: if the retry queue depth > 10,000 (data backlog risk).
Inject — simulate a hanging upstream¶
# Use a NetworkPolicy to drop outbound traffic to the payments endpoint
kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-payments
namespace: shopfast
spec:
podSelector:
matchLabels:
app: order-worker
policyTypes: [Egress]
egress:
- ports:
- port: 443
to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # block everything except internal
EOF
# Note: this drops packets (timeout) not refuses (fast error) — that is the worst case
Inject — Chaos Mesh HTTPChaos (if payments is HTTP-accessible in staging)¶
apiVersion: chaos-mesh.org/v1alpha1
kind: HTTPChaos
metadata:
name: payments-timeout
namespace: shopfast
spec:
mode: all
selector:
namespaces: [shopfast]
labelSelectors:
app: order-worker
target: Request
port: 443
path: "/v1/charge"
delay: "30s" # simulate a 30s hang
duration: "5m"
Observe¶
# Worker thread saturation
kubectl exec -n shopfast deploy/order-worker -- \
wget -qO- localhost:9090/metrics | grep -E "worker_active|queue_depth"
# Are new orders stuck?
kubectl logs -n shopfast -l app=order-worker --since=2m | grep -E "payment|timeout|circuit"
# Grafana: order_processing_duration_seconds — should spike then flatten at circuit-open latency
flowchart TD
ORD["Order placed<br/>order-worker picks up job"]:::run
PAY["Call payments API<br/>POST /v1/charge"]:::net
HANG["Payments API hangs<br/>no response"]:::warn
TIMEOUT["HTTP client timeout<br/>30s — too long"]:::warn
CIRCUIT["Circuit breaker<br/>OPEN after 5 failures"]:::store
QUEUE["Retry queue<br/>SQS or Redis list"]:::ok
FAST["Fast failure response<br/>Order queued for retry"]:::ok
USER["User: Order placed<br/>payment pending"]:::ok
ORD --> PAY --> HANG --> TIMEOUT
TIMEOUT -- "circuit closes after 5 failures" --> CIRCUIT
CIRCUIT --> QUEUE --> FAST --> USER
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Fix — the four-part defense:
# 1. Short timeout — never wait more than 5 s for a payment API call
response = requests.post(
PAYMENTS_URL,
json=payload,
timeout=(2.0, 5.0) # (connect_timeout, read_timeout)
)
# 2. Retry with exponential backoff + jitter
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10) + wait_random(0, 1),
retry=retry_if_exception_type(Timeout)
)
def charge_payment(payload): ...
# 3. Circuit breaker (open after 5 failures in 60 s, half-open after 30 s)
@circuit_breaker(fail_max=5, reset_timeout=30)
def charge_payment_with_breaker(payload):
return charge_payment(payload)
# 4. Async fallback — if circuit is open, enqueue for retry
def process_payment(order_id, payload):
try:
return charge_payment_with_breaker(payload)
except CircuitBreakerError:
enqueue_payment_retry(order_id, payload)
return {"status": "queued", "order_id": order_id}
Cleanup:
🇮🇳 Hinglish intuition: Circuit breaker bilkul ghar ka main switch hai. Wiring mein fault aane par pehle MCB trip karta hai — poora ghar nahi jalta, sirf ek circuit. Baaki ghar chalte rehta hai.
E12 · TLS certificate expires — the silent killer¶
Steady state: orders.shopfast.io TLS certificate valid for 85+ days. cert-manager auto-renewing. Ingress returning HTTPS 200.
Hypothesis: If cert-manager renewal fails silently (wrong email, DNS misconfiguration, rate limit), the certificate expires and all HTTPS traffic fails with a browser security error. The system needs proactive alerting at 20 days remaining.
Blast radius: 100% of HTTPS traffic blocked. Abort: This experiment is observational — we test the alert, not the actual expiry.
Inject — simulate near-expiry (non-destructive)¶
# Check current certificate expiry
kubectl get certificate -n shopfast
kubectl describe certificate orders-tls -n shopfast | grep -E "Not After|Renewal"
# Check the actual secret
kubectl get secret orders-tls -n shopfast -o jsonpath='{.data.tls\.crt}' \
| base64 -d | openssl x509 -noout -dates
# Force a cert-manager renewal to test the pipeline
kubectl annotate certificate orders-tls -n shopfast \
cert-manager.io/issuer-kind=ClusterIssuer --overwrite
kubectl patch certificate orders-tls -n shopfast \
--type merge -p '{"spec":{"renewBefore":"2159h"}}' # 90 days — triggers immediate renewal
The real diagnosis for a certificate outage¶
# 1. What is the browser seeing?
curl -vI https://orders.shopfast.io 2>&1 | grep -E "expire|SSL|certificate"
# 2. cert-manager controller logs
kubectl logs -n cert-manager deploy/cert-manager | tail -100 | grep -E "error|failed|orders-tls"
# 3. Certificate status
kubectl describe certificaterequest -n shopfast
# 4. ACME challenge status (if Let's Encrypt)
kubectl get challenge -n shopfast
kubectl describe challenge -n shopfast
Common failure modes:
| Root cause | Symptom | Fix |
|---|---|---|
| cert-manager not installed / crashed | certificate CRD not found |
Reinstall cert-manager, restore from GitOps |
| DNS-01 challenge failed | Challenge in pending state |
Fix IAM role for Route53 / DNS API credentials |
| Rate limited by Let's Encrypt | too many certificates already issued |
Switch to staging ACME, wait 1 week, or use a different domain |
ClusterIssuer misconfigured |
failed to determine issuer |
Check email field and server URL in ClusterIssuer spec |
| Manual certificate, never renewed | Certificate just expired | Replace immediately, then automate with cert-manager |
Fix — the full cert-manager setup:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: orders-tls
namespace: shopfast
spec:
secretName: orders-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- orders.shopfast.io
renewBefore: 720h # renew 30 days before expiry (default is 1/3 of lifetime)
Prevention — the alert that saves you:
# Prometheus rule — alert at 20 days remaining
- alert: TLSCertificateExpiringSoon
expr: |
(x509_cert_expiry - time()) / 86400 < 20
for: 1h
labels:
severity: warning
annotations:
summary: "TLS cert for {{ $labels.dnsnames }} expires in {{ $value | humanizeDuration }}"
runbook: "https://runbooks.shopfast.io/tls-renewal"
Install x509-certificate-exporter (Helm chart) to export cert expiry metrics for ALL certs in your cluster, including third-party ones cert-manager doesn't manage.
Running a Game Day¶
A game day is a scheduled, collaborative chaos experiment in a production-like environment. It is the highest-value activity in chaos engineering — everyone learns together, under realistic conditions, with a safety net.
The game-day template¶
flowchart LR
PLAN["Plan<br/>Select hypothesis<br/>Define abort criteria<br/>Schedule window"]:::run
ROLES["Assign Roles<br/>Commander<br/>Scribe<br/>Observer"]:::obs
BRIEF["Pre-game Brief<br/>Review steady state<br/>Confirm abort criteria<br/>Open dashboards"]:::ok
INJECT["Inject Failure<br/>Commander gives go-ahead<br/>Scribe timestamps"]:::warn
WATCH["Observe<br/>Observer calls out anomalies<br/>Scribe documents timeline"]:::obs
ABORT{"Abort condition<br/>triggered?"}
STOP["STOP immediately<br/>Restore system<br/>No blame"]:::warn
RCA["Blameless Review<br/>What happened<br/>What we learned<br/>Action items"]:::ok
PLAN --> ROLES --> BRIEF --> INJECT --> WATCH --> ABORT
ABORT -- "Yes" --> STOP --> RCA
ABORT -- "No" --> RCA
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e
Roles¶
| Role | Responsibility |
|---|---|
| Commander | Makes go/no-go decisions. Has abort authority. Not the one typing. |
| Scribe | Documents the timeline in real time: timestamps, observations, who said what. |
| Observer(s) | Watches dashboards, calls out anomalies, never injects anything. |
| Engineers | Execute the injection steps per the runbook. |
The game-day checklist¶
Full Game Day Checklist
7 days before: - [ ] Select experiment and write hypothesis - [ ] Define steady-state metrics and thresholds - [ ] Define abort conditions (be specific: "error rate > 1% for 60 s") - [ ] Get approval from product / on-call lead - [ ] Prepare the inject runbook and the restore runbook - [ ] Schedule the maintenance window (even if no maintenance is expected)
Day of, 30 min before:
- [ ] Confirm system is at steady state (check dashboards)
- [ ] Confirm all participants in a video call
- [ ] Commander reads the hypothesis and abort criteria aloud
- [ ] Scribe starts the log document with current timestamp
- [ ] All participants confirm they can see Grafana, Loki, and kubectl
During: - [ ] Commander gives "inject" go-ahead - [ ] Engineer injects failure; scribe timestamps - [ ] Observer watches for abort conditions - [ ] No multitasking — every observation goes through the scribe - [ ] Abort immediately if abort condition is triggered, no questions asked
After: - [ ] Restore system; verify steady state has returned - [ ] Blameless review — focus on system not people - [ ] Write action items with owners and due dates - [ ] Publish the game-day report to the team
Blameless post-mortem format¶
## Game Day: [Experiment name] — [Date]
**Hypothesis:** [What we expected]
**Result:** [What actually happened — pass/fail]
### Timeline
| Time | Event | Who observed |
|------|-------|--------------|
### What worked
- ...
### What we learned
- ...
### Action items
| Item | Owner | Due date |
|------|-------|----------|
The senior SRE chaos mindset¶
The twelve experiments above teach you how to run chaos experiments. This section teaches you how to think.
Assume failure, always¶
Every time you add a dependency, ask: "What happens to my service when this dependency is unavailable?" If the answer is "I don't know," that is your next chaos experiment. Senior SREs design for failure first and performance second.
Test the alert, not just the system¶
Half the value of a chaos experiment is verifying that your observability catches it. If you inject a Redis failure and your on-call dashboard doesn't light up, you have found a monitoring gap more dangerous than the failure itself. Cross-link: Ch23 Incident Playbook — always verify the runbook is triggered by the alert.
Blast-radius discipline¶
Start with the smallest possible scope. One pod. One request. One region of one service. Graduate outward only after the smaller experiment passes. This is how you do chaos engineering in production without getting fired.
"You don't know your system until you've broken it"¶
Every assumption about how your system behaves under failure is a hypothesis until you test it. The engineers who have run game-days are genuinely more confident during incidents — not because they enjoy pain, but because they have seen the failure modes before, at 2 pm, with the whole team watching. They know what the dashboards look like. They know the fix.
Automate chaos in CI and staging¶
The highest-leverage move is automating the non-destructive experiments (E1, E5 cache fallback, E11 circuit breaker) in your CI pipeline and running them on every merge to main. If E1 (kill one pod) fails in CI, you merged a regression in your readiness probe or replica count. Catch it there, not in production.
# .github/workflows/chaos.yml — run after deploy to staging
- name: Chaos smoke test
run: |
# E1: Kill one pod, verify recovery
kubectl delete pod -n shopfast-staging \
$(kubectl get pod -n shopfast-staging -l app=orders-api -o name | head -1)
sleep 30
kubectl wait --for=condition=Ready pod -l app=orders-api \
-n shopfast-staging --timeout=90s
# E5: Block Redis, verify non-5xx response
kubectl apply -f chaos/block-redis-staging.yaml
sleep 10
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://shopfast-staging.internal/orders/1)
[[ "$STATUS" == "200" ]] || exit 1
kubectl delete -f chaos/block-redis-staging.yaml
Interview scenarios¶
1. "What is chaos engineering, and how would you introduce it to a team that has never done it?"¶
Senior answer: Chaos engineering is a discipline for building confidence in a distributed system by running controlled experiments that inject real-world failure conditions. I would introduce it in three phases. First, I would audit the existing alerts and runbooks and identify the top three "we assume this works but have never tested it" claims — usually: failover works, the circuit breaker trips, and PDB prevents downtime during node drain. Then I would run one low-blast experiment on staging — kill one pod — and document the result. Finally, I would use that success story to get buy-in for a quarterly game day. The goal is not to break things; the goal is to build a team that is bored during incidents because they have seen everything before.
2. "Design a chaos experiment for a payment service."¶
Senior answer: First, define steady state: payment API p99 < 500 ms, success rate > 99.5%. Hypothesis: the service degrades gracefully when the upstream payment provider is slow. Blast radius: inject 3-second artificial latency on outbound calls to the payment provider using tc netem on the payments-service pod, with an abort condition of > 1% error rate for > 60 seconds. Observe: thread pool saturation metrics, circuit breaker state, and the user-visible error rate. Expected result: after 5 slow calls, the circuit breaker opens, subsequent calls fail fast (< 50 ms) with a queued retry response, and the user sees "payment is processing" rather than a 30-second hang. If the hypothesis fails, the team fixes the circuit breaker configuration before the experiment is closed.
3. "A game day took down staging. What went wrong?"¶
Senior answer: Several things could cause this, and I would investigate in order. First: was the blast radius larger than scoped? If we ran a node-drain experiment and the PDB was misconfigured or minAvailable was set to 0, all pods could have been evicted simultaneously. Second: was there a missing abort condition? If the abort criteria were not checked continuously, a cascading failure could develop unnoticed. Third: was the steady state actually steady? If staging was already degraded before the experiment, the baseline assumption was wrong. The fix for all three: tighten the blast radius, automate abort-condition monitoring during every experiment, and verify steady state explicitly before injecting. The staging outage is a learning event, not a failure — document it and use it to improve the game-day process.
4. "How do you chaos-test safely in production?"¶
Senior answer: Five principles. Start small: one pod, one region, not the whole cluster. Define explicit abort conditions and automate them — if a CloudWatch alarm fires, the FIS experiment stops automatically. Time-box experiments: 10 minutes maximum for a first production experiment. Run during business hours with the whole team watching, not at 3 am. And instrument everything before the experiment — you must be able to observe the blast radius in real time. AWS FIS has built-in stop conditions tied to CloudWatch alarms, which is exactly what you want for production experiments.
5. "What is the difference between chaos engineering and load testing?"¶
Senior answer: Load testing answers "how much traffic can the system handle?" — it is about capacity and performance at scale. Chaos engineering answers "what happens when components fail?" — it is about resilience and correctness under adverse conditions. They are complementary, not competing. A load test might reveal that the system fails at 10,000 RPS. A chaos experiment might reveal that the system fails when the cache goes down even at 100 RPS. You need both. The senior SRE runs load tests to find capacity limits and chaos experiments to find failure-mode gaps, and ideally combines them — run a chaos experiment while the system is under load to find the failures that only appear at scale.
Summary · The confidence you have earned¶
You walked into Chapter 24 with a blank AWS account and a set of requirements. You walked out with a production-grade system: orders-api behind TLS, RDS Multi-AZ, ElastiCache Redis, Argo CD GitOps, kube-prometheus-stack observability, HPA, PDB, NetworkPolicy, topologySpreadConstraints.
Now you have done something more important: you broke it, watched it fail, diagnosed it like a senior, fixed it, and prevented the recurrence. Twelve failure modes. Twelve lessons. Twelve reasons your future production system is harder to kill than it was when you started.
The difference between a junior engineer and a senior SRE is not IQ or years of experience. It is verified knowledge vs assumed knowledge. The junior assumes the circuit breaker works. The senior has run the game day and knows.
| Chapter | What you built | Confidence gained |
|---|---|---|
| Ch24 | ShopFast on AWS+K8s, production-grade | "I can build it" |
| Ch25 | Survived 12 chaos experiments | "I can operate it" |
The questions you can now answer with evidence, not guesses:
- Does our Argo CD self-heal work? (E2 — yes, we tested it.)
- Does Redis going down take down the service? (E5 — no, we built the fallback.)
- What happens during an RDS failover? (E6 — 45-second blip, absorbed by the pool.)
- Does the circuit breaker protect us from a flaky payments API? (E11 — yes, we verified it.)
- Will we know before a TLS cert expires? (E12 — yes, the alert fires at 20 days.)
That is what chaos engineering gives you: not a system that never fails, but a team that is never surprised.
🇮🇳 Final Hinglish thought: Ghabrana nahi hai. System todna seekh liya, to banana aur banana toh pehle se aata tha. Yahi senior SRE banta hai — jo 2 baje bhi shant rahta hai kyunki usne yeh sab pehle dekha hua hai.
Next: The roadmap continues in Module 11–18 Roadmap. The full incident reference lives in Ch23 — Production Incident Playbook.