dr-drill-runner
The full DR-drill discipline for one service: author the runbook (per-tier RTO + RPO), pre-drill checklist (data sync state, alert silencing, customer comms), drill workflow (announce, fail-over, verify, fail-back) with timestamps, the supervised run protocol (refuse without declared RTO/RPO or against production, RTO/RPO monitoring cadence, abort-on-breach), and an auditor-ready post-drill report. Backup-integrity verification (SHA-256 + signature, restore spot checks, cross-region replication, retention, key recovery) and restore-time / RTO measurement (TTF segments, PITR latency, parallel-restore tuning, trend tracking) are worked in references. Per Google Cloud DR planning guide; covers cold / warm / hot standby tier-specific patterns. Use when a scheduled or post-incident failover drill for one service is being planned, executed, or written up, or when a new tier-1 service ships without a drill defined.
Install with skills.sh (any agent)
npx skills add testland/qa --skill dr-drill-runnerdr-drill-runner
Per the Google Cloud DR planning guide (opens in new window), DR planning requires "end-to-end recovery design addressing backup, restoration, and cleanup procedures." Drills test that the procedure works AND that the team can run it. Both surface different failures.
When to use
Step 1 - Define RTO + RPO per service tier
Per the Google Cloud DR planning guide (opens in new window):
| Metric | Definition |
|---|---|
| RTO | Maximum acceptable length of time the application can be offline |
| RPO | Maximum acceptable data loss (time window) |
| Tier | Example RTO | Example RPO | Pattern |
|---|---|---|---|
| 1 (revenue-critical) | < 15 min | < 1 min | Hot standby (active-active) |
| 2 (customer-impacting) | < 4 hr | < 1 hr | Warm standby |
| 3 (internal) | < 24 hr | < 24 hr | Cold (rebuild from backup) |
Document per service in a service catalog; drills enforce the contract.
Step 2 - DR-pattern tier per service
Per the Google Cloud DR planning guide (opens in new window):
Drill expectations differ:
Step 3 - Pre-drill checklist
## Pre-Drill Checklist - `<service>` `<date>`
- [ ] Drill window scheduled (low-traffic; aligned with
customer-comm window)
- [ ] Drill scope decided (region, single service, full app)
- [ ] Replication lag confirmed within RPO at T-30 min
- [ ] Monitoring alerts SILENCED for expected failure indicators
(alert routing redirected to drill channel)
- [ ] On-call notified (avoid duplicate paging during drill)
- [ ] Customer comms sent if customer-impacting drill
- [ ] Rollback path documented (what triggers abort?)
- [ ] Drill commander assigned (owns go/no-go calls)
- [ ] Postmortem time scheduled (within 48hr of drill end)Skipping the pre-drill = drills become incidents.
Step 4 - Drill workflow
## Drill Workflow
### T-0: Announce
- Post in #drill-channel; confirm all participants ready.
- Drill commander gives "GO" - record T-0 timestamp.
### T+0..N: Fail-over
- Execute the runbook step-by-step (everyone follows the doc; no
improvisation).
- Capture timestamp of each step.
### Verify
- Run the verification suite (smoke + customer-impact + data integrity).
- Compare actual vs expected RTO; if RTO breached, decide:
abort + rollback, or continue + capture learning.
### Fail-back
- If hot/warm: redirect traffic back to primary.
- If cold: tear down DR environment + restore primary.
- Verify primary is healthy before claiming drill complete.
### Cleanup
- Re-enable alerts (Step 3).
- Send "all clear" customer comms.
- Reconcile any drill-introduced data divergence.Step 5 - Post-drill report
## Drill Report - `<service>` `<date>`
**Drill objective:** Verify warm standby fails over within RTO 4hr.
**Timeline:**
- T-30 min: Replication lag verified (52s - within RPO 1hr) ✓
- T-0: Announced, on-call silenced
- T+12m: Failover initiated
- T+47m: Standby took traffic
- T+1h22m: Verified service healthy on standby
- T+2h11m: Failback to primary
- T+3h05m: Drill complete
**RTO observed:** 1h22m (target: 4hr) ✓
**Issues found:**
1. CRITICAL: DNS TTL was 24hr in standby DNS records; users
couldn't reach service for 23min after failover. Fix: lower
TTL to 60s in standby zone before next drill.
2. MAJOR: Secret-manager copy step was undocumented; commander
improvised. Fix: add Step 3.4 to runbook.
3. MINOR: One alert wasn't silenced in advance; on-call was paged.
**Action items (with owners + dates):**
- DNS TTL fix → @platform-team - 2026-05-20
- Runbook Step 3.4 → @sre - 2026-05-13
- Alert routing audit → @sre - 2026-05-13
**Next drill:** 2026-08-06 (quarterly cadence).Step 6 - Cold-tier-specific drill pattern
Cold drills = bring up from backup. Verifies:
Step 7 - Hot-tier-specific drill pattern
Hot drills = redirect traffic between active replicas. Verifies:
Step 8 - Cadence
| Tier | Cadence |
|---|---|
| 1 | Monthly (game-day style) |
| 2 | Quarterly |
| 3 | Annually |
Per the Google Cloud DR planning guide (opens in new window): "test it regularly, noting any issues." Without cadence, runbooks rot.
Run protocol (executing the drill end to end)
Nobody delegates failover execution; a human drill commander runs the five stages below against this protocol. This is the rehearsed DR path - for injecting unrehearsed failures see the chaos-drill-protocol skill in this plugin.
Refuse to start when:
Stage 1 - pre-drill. Run the Step 3 checklist. Verify backup SHA-256 integrity, replication lag within RPO at T-30 min, and encryption-key recoverability in the DR region per references/backup-verification.md. Verify DR-environment configuration drift is within bounds - per the AWS DR testing whitepaper (opens in new window), "Manage configuration drift at the DR Region. Ensure that your infrastructure, data, and configuration are as needed at the DR Region."
Stage 2 - failover. Record T-0; execute the Step 4 runbook step by step, capturing a timestamp per step. Per the AWS DR testing whitepaper (opens in new window), "Our experience has shown that the only error recovery that works is the path you test frequently" - a runbook step that requires improvisation is logged as a MAJOR finding immediately, never silently adapted.
Stage 3 - RTO/RPO monitor. While failover is active, sample on a fixed interval (60 s is a workable default): replication lag at the DR side, smoke-suite pass rate, and data-row spot checks. Measure time-to-functional for the Restore + Verification segments per references/restore-time.md and compare against the per-segment RTO budget. If observed TTF exceeds the RTO - abort the failover, record the breach metrics, and skip directly to the report. Record the peak RPO gap (replication lag at fail-over time); flag if it exceeds the declared RPO.
Stage 4 - fail-back. Per Step 4: redirect traffic back (warm/hot) or tear down + restore primary (cold), re-enable silenced alerts, send the all clear, verify primary health before recording fail-back complete, reconcile drill-introduced data divergence.
Stage 5 - report. Emit the Step 5 post-drill report, including the observed TTF, total RTO, and peak RPO gap versus their declared targets. Schedule the postmortem within 48 hours; every finding gets an owner + due date before the drill closes.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Skip pre-drill checklist | Drill becomes incident | Step 3 mandatory |
| One person knows the runbook | Bus-factor 1; drill panics when they're out | Rotate drill commander |
| Skip post-drill report | Lessons lost; same issues recur | Step 5 mandatory + 48hr deadline |
| Test failover only; skip failback | Failback is the actual prod path; bugs hide | Step 4 covers both |
| Lower RTO target after a missed drill | Goalpost moving | Hold the line + invest in fixes |
Limitations
References
Backup verification harness
View source (opens in new window)Backup verification harness
Deep dive for dr-drill-runner - the backup-integrity half of DR readiness. "An untested backup is not a backup." Consult when a service's backups have never been restore-tested, when the backup tool is being swapped, or when an audit needs proof that backups are integrity-checked and restorable.
Backups silently fail in many ways - wrong encryption key, missing volume, corrupted file, expired credential. Per the Google Cloud DR planning guide (opens in new window), DR success requires "end-to-end recovery design addressing backup, restoration, and cleanup procedures." This skill authors the verification harness.
When to use
Step 1 - Catalog backup types
## Backup Catalog - `<service>`
| Type | Source | Frequency | Retention | Tool |
|---|---|---|---|---|
| Full DB dump | postgres prod | Daily 02:00 UTC | 30 days | pg_dump + S3 |
| Logical schema | postgres prod | Hourly | 24 hours | logical replication slot |
| File store | S3 prod bucket | Continuous | 90 days | S3 versioning + cross-region |
| Audit log | append-only S3 | Continuous | 7 years | S3 + Glacier |
| Secrets / KMS keys | Vault prod | Daily | 7 days | Vault snapshot + encrypted S3 |Each row needs its own verification step (Step 3).
Step 2 - Integrity checks at backup time
#!/usr/bin/env bash
set -e
BACKUP_FILE="postgres-prod-$(date +%Y%m%d).sql.gz"
BACKUP_PATH="/backups/$BACKUP_FILE"
# Take backup
pg_dump -h prod-db -U replica db_name | gzip > "$BACKUP_PATH"
# Generate SHA-256 + sign
sha256sum "$BACKUP_PATH" > "$BACKUP_PATH.sha256"
gpg --detach-sign --armor "$BACKUP_PATH"
# Upload to backup destination
aws s3 cp "$BACKUP_PATH" "s3://backup/postgres/$BACKUP_FILE"
aws s3 cp "$BACKUP_PATH.sha256" "s3://backup/postgres/$BACKUP_FILE.sha256"
aws s3 cp "$BACKUP_PATH.asc" "s3://backup/postgres/$BACKUP_FILE.asc"
# Tag with metadata
aws s3api put-object-tagging \
--bucket backup --key "postgres/$BACKUP_FILE" \
--tagging 'TagSet=[{Key=integrity_verified,Value=true},{Key=created,Value='$(date -Iseconds)'}]'Tests assert:
Step 3 - Restore-to-test-env spot check
A restore that has never been done is not a backup. Schedule:
# CI cron: weekly random sample
- cron: "0 4 * * 1" # Monday 04:00 UTC
job:
- name: Pick random backup
run: |
DAYS=(1 7 14 30)
DAYS_AGO=${DAYS[$RANDOM % ${#DAYS[@]}]}
BACKUP=$(date -d "$DAYS_AGO days ago" +%Y%m%d)
echo "BACKUP=postgres-prod-$BACKUP.sql.gz" >> $GITHUB_ENV
- name: Verify integrity
run: |
aws s3 cp s3://backup/postgres/$BACKUP.sha256 .
aws s3 cp s3://backup/postgres/$BACKUP .
sha256sum -c "$BACKUP.sha256"
- name: Restore to test DB
run: |
gunzip "$BACKUP"
psql -h test-db -U test -f "${BACKUP%.gz}" db_test
- name: Spot check
run: |
psql -h test-db -U test db_test -c "SELECT COUNT(*) FROM orders WHERE created_at > NOW() - INTERVAL '1 day'"
# Verify count > 0 (or whatever invariant fits)Step 4 - Partial-restore test
Real DR scenarios often need single-table or single-object restore (not full DB):
# Single-table extract + restore
pg_restore --table=orders --data-only \
-h test-db -U test -d db_test \
postgres-prod-backup.dumpTest: extract one table; assert rowcount + checksum match the production-time snapshot.
For S3 single-object:
aws s3 cp \
s3://backup-versioned/object-key \
--version-id "VERSION_ID_AT_DESIRED_TIME" \
./restored-objectStep 5 - Cross-region replication test
def test_backup_replicated_to_dr_region():
# Take a backup in primary region
backup_path_primary = take_backup_to(region="us-east-1")
# Wait for replication SLA
deadline = time.time() + 300 # 5 min SLA
while time.time() < deadline:
if exists_in(region="us-west-2", path=backup_path_primary):
return
time.sleep(10)
pytest.fail("Cross-region replication exceeded 5min SLA")Per the Google Cloud DR planning guide (opens in new window): "Security synchronization" also matters - DR region must have the same KMS keys / IAM / secrets, not just the data.
Step 6 - Retention-policy verification
def test_old_backups_purged_per_retention_policy():
# 30-day retention; 100-day-old backup should not exist
target = (datetime.utcnow() - timedelta(days=100)).strftime("%Y%m%d")
obj_key = f"postgres/postgres-prod-{target}.sql.gz"
response = s3.head_object(Bucket="backup", Key=obj_key)
# Should 404
pytest.fail(f"Backup {obj_key} still exists past 30-day retention")Wrap in a try/except - actual missing object = pass.
def test_recent_backups_present():
# Last 30 days should have at least one daily backup each
for d in range(30):
date = (datetime.utcnow() - timedelta(days=d)).strftime("%Y%m%d")
key = f"postgres/postgres-prod-{date}.sql.gz"
s3.head_object(Bucket="backup", Key=key) # raises if missingStep 7 - Encryption verification
For encrypted backups, verify both:
def test_backup_encrypted_with_correct_key():
obj = s3.head_object(Bucket="backup", Key=key)
assert obj["ServerSideEncryption"] == "aws:kms"
assert obj["SSEKMSKeyId"] == EXPECTED_KMS_KEY_ARNStep 8 - Customer-induced backup test (compliance)
Some regulations (HIPAA, SOC 2) require demonstrated ability to restore on demand. Author the workflow:
## Customer-Induced Backup Restore Test
1. Customer requests demo restore via support ticket.
2. SRE picks a random recent backup; restores to clean isolated env.
3. Customer verifies their data via read-only SQL or UI.
4. Cleanup: tear down env, sanitize logs.
5. Document: ticket + timestamps + verification artifacts → audit log.Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Verify backup file exists; not contents | Corrupt files pass | SHA + restore (Steps 2-3) |
| Test restore once, never again | Bit rot, key rotation, schema drift surface later | Weekly cadence (Step 3) |
| Skip partial-restore test | Real DR usually wants partial; full restore takes too long | Step 4 |
| Skip key recovery | Backup encrypted with key not in DR region; useless | Step 7 |
| Trust replication "succeeded" status | Async replication can claim success then fail | Step 5 explicit verification |
Limitations
References
Restore-time SLA tests
View source (opens in new window)Restore-time SLA tests
Deep dive for dr-drill-runner - the RTO-measurement half of DR readiness. Bound time-to-functional (TTF) at or under the documented RTO; flag silent regressions when restore time grows over months. Consult when a service documents an RTO nobody has actually timed, when the backup has grown by an order of magnitude, or right after a backup-tool change.
Per the Google Cloud DR planning guide (opens in new window), RTO is "the maximum acceptable length of time that your application can be offline." Restore-time tests measure the actual time-to-functional (TTF) for each backup type and gate it on the RTO budget.
When to use
Step 1 - Define TTF segments
Time-to-functional = sum of:
| Segment | Definition |
|---|---|
| Detection | Time from incident to "something's wrong" |
| Decision | Time from detection to "initiate DR" |
| Provisioning | Time to spin up DR environment (IaC apply) |
| Restore | Time to apply the latest backup |
| Verification | Time to run smoke tests + accept traffic |
| Cutover | DNS / load balancer switch + propagation |
Each segment has its own SLA. The aggregate is the RTO.
This skill focuses on Restore + Verification segments.
Step 2 - Baseline: timed restore
import subprocess, time
import pytest
@pytest.mark.benchmark
def test_postgres_restore_time_under_rto():
# Setup: clean target DB
subprocess.run(["psql", "-h", "test-db", "-c", "DROP DATABASE IF EXISTS db_test"])
subprocess.run(["psql", "-h", "test-db", "-c", "CREATE DATABASE db_test"])
backup = "postgres-prod-latest.sql.gz"
start = time.time()
subprocess.run(
["bash", "-c", f"gunzip -c {backup} | psql -h test-db -d db_test"],
check=True,
)
elapsed = time.time() - start
RTO_BUDGET_SECONDS = 4 * 3600 # 4 hours
# The 0.5 split (restore gets half the RTO, the rest goes to provision +
# verify + cutover) is a planning choice, NOT a standard. Set the fraction
# from your own per-segment RTO budget (Step 1).
RESTORE_SEGMENT_FRACTION = 0.5
budget = RTO_BUDGET_SECONDS * RESTORE_SEGMENT_FRACTION
assert elapsed < budget, f"Restore took {elapsed:.0f}s; budget {budget:.0f}s"Run weekly in CI; track trend.
Step 3 - Parallel-restore optimization
Many backup tools support parallelization. Test:
# pg_restore parallel
pg_restore -j 8 -d db_test backup.dump # 8 parallel workers
# WAL-E / pgbackrest parallel restore
pgbackrest --stanza=prod --process-max=8 restoredef test_parallel_restore_faster_than_serial():
serial = run_restore(parallel_jobs=1)
parallel = run_restore(parallel_jobs=8)
speedup = serial / parallel
# 3.0x is an illustrative target; real speedup depends on I/O saturation,
# CPU count, and backup format. Set the expected ratio from your own
# measured serial-vs-parallel baseline rather than this placeholder.
assert speedup > 3.0, f"Parallel restore only {speedup:.1f}x faster"Find the sweet spot (often 4-8 jobs); past that, contention diminishes returns.
Step 4 - Point-in-time-recovery (PITR) latency
PITR = restore the database to an arbitrary point in the past (within retention). Restore time + WAL replay time:
PITR recovers a pre-existing base backup forward to a target time by replaying archived WAL. It needs two things that must already exist before the restore: a base backup taken earlier and retained, and a continuous WAL archive covering the window up to the target. Do NOT call pg_basebackup at restore time: a backup taken "now" captures the present, leaving nothing earlier to recover to. Per the PostgreSQL PITR docs (opens in new window):
def test_pitr_to_5min_ago_under_30min():
target_time = datetime.utcnow() - timedelta(minutes=5)
# 1. Lay down the PRE-EXISTING base backup into a clean data dir
# (untar the retained base backup; do not take a fresh one here).
restore_retained_base_backup(dest="/restore")
# 2. PG12+ recovery config: restore_command pulls archived WAL,
# recovery_target_time is the stop point, and recovery.signal triggers
# targeted recovery (recovery.conf was removed in PG12).
write_conf("/restore/postgresql.auto.conf", {
"restore_command": "cp /wal_archive/%f %p",
"recovery_target_time": f"'{target_time.isoformat()}'",
"recovery_target_action": "promote",
})
Path("/restore/recovery.signal").touch()
# 3. Time the restore + WAL replay: this is the real PITR latency.
start = time.time()
subprocess.run(["pg_ctl", "start", "-D", "/restore", "-w"], check=True)
wait_for_recovery_complete(timeout=1800)
elapsed = time.time() - start
# 1800s is illustrative; set the budget from your service's RTO segment SLA.
assert elapsed < 1800, f"PITR took {elapsed:.0f}s; budget 30min"PITR latency = base restore + WAL replay. Tests both segments.
Step 5 - Object-store partial restore
For S3 / GCS / Azure Blob restores, time the partial restore (not whole-bucket):
def test_partial_object_restore_under_5_min():
keys_to_restore = sample_500_keys_from_inventory()
start = time.time()
for key in keys_to_restore:
s3.copy_object(
Bucket="restore-target",
Key=key,
CopySource={"Bucket": "backup-versioned", "Key": key, "VersionId": ...},
)
elapsed = time.time() - start
assert elapsed < 300, f"500-object restore took {elapsed:.0f}s"The 500-object count and the 300s budget are illustrative; size both from your own per-account object inventory and restore SLA.
Step 6 - Track restore-time trend
Backup grows over time → restore time grows. Track:
def emit_restore_time_metric(elapsed_seconds, backup_size_bytes):
metrics_client.gauge("dr.restore_time_seconds", elapsed_seconds)
metrics_client.gauge("dr.backup_size_bytes", backup_size_bytes)
metrics_client.gauge("dr.restore_throughput_bytes_per_sec",
backup_size_bytes / elapsed_seconds)Alert when restore time grows beyond a threshold you choose (e.g. 20% over 90 days; tune to your data-growth profile). Sustained growth indicates a need for backup compaction, more parallelism, or RTO renegotiation.
Step 7 - Verification time
Restore success ≠ functional. Verification adds time:
def test_post_restore_smoke_under_5_min():
do_restore()
start = time.time()
run_smoke_suite("dr-environment")
elapsed = time.time() - start
assert elapsed < 300, f"Smoke tests took {elapsed:.0f}s; budget 5min"Smoke suite scope: critical paths only. Full regression is too slow for the RTO window.
Step 8 - Cold-start vs warm-cache
After restore, applications hit cold caches → first requests slow. Test that the cold-start latency is within service SLA:
def test_cold_start_latency_within_sla():
# Restore complete; app started; first requests
latencies = []
for _ in range(100):
start = time.time()
requests.get("https://dr-env.svc/api/products")
latencies.append(time.time() - start)
p99_cold = sorted(latencies)[99]
# 2.0s is a placeholder; set the cold-start bound from your service's SLO.
assert p99_cold < 2.0, f"Cold-start p99 {p99_cold:.2f}s exceeds 2s SLA"Cache-warm step may be needed in DR runbook (loading common queries before declaring "functional").
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Test on yesterday's backup, claim "RTO met" | Real DR uses minutes-old backup | Weekly cadence with realistic data freshness |
| Skip parallel test; use single thread | Aggregate RTO + budget breached at scale | Step 3 sweet-spot tuning |
| Skip verification time | Restore "complete"; users still 5xx | Step 7 must be timed |
| No trend tracking | Silent regression months in | Step 6 metric + alert |
| RTO unit on DB only, ignore app | App may take longer than DB | Step 8 cold-start |
Limitations
References
Related skills
chaos-drill-protocol
Run protocol and run workflow for a chaos experiment that has already been designed: the four pre-flight gates (non-production target, measured healthy baseline, live observability, a rollback that has actually been exercised), how to pick a conservative blast-radius bound, the sampling cadence and abort criteria fixed in writing before injection, the per-runner inject and abort commands (Chaos Mesh / Litmus / Gremlin / Toxiproxy), the refuse-to-start rules (no blast-radius bound, production context, degraded baseline, offline observability, unexercised rollback), and the recovery-validation step with its tolerance and timeout. Owns execution safety only, not experiment design: the steady-state hypothesis, the fault to inject, and the experiment file come from chaos-experiment-author. Use when an experiment definition exists and a fault is about to be injected into a running system, and the go/no-go gates, abort thresholds, and recovery check still need to be agreed and written down before the fault starts.
chaos-experiment-author
Build-an-X workflow for a chaos experiment per the Principles of Chaos Engineering - defines steady-state hypothesis, picks the variables (real-world events: network latency, node failure, region outage), sets the blast radius (which percentage / namespace / user cohort), automates execution, and emits the verdict (steady-state held / didn't hold). Includes the five-check pre-flight validation of the steady-state hypothesis (measurable, baselined, SLI-backed tolerance, defined measurement window, metric moves under the fault) with hard-reject rules, and routes the tool choice: Chaos Mesh has its own standalone skill, while LitmusChaos and Gremlin setup live in this skill's references. Use to scope and pre-flight-validate a chaos experiment before running it via Chaos Mesh / Litmus / Gremlin / Toxiproxy.
chaos-mesh
Configures Chaos Mesh for Kubernetes-native chaos engineering - picks fault types (PodChaos, NetworkChaos, StressChaos, IOChaos, TimeChaos, DNSChaos, KernelChaos, HTTPChaos), targets via label selectors, controls blast radius via namespace whitelists + selector filters, schedules via CronJobs, observes via dashboard. Distinct from Litmus by architecture (Chaos Mesh has its own dashboard + workflow orchestration; Litmus uses ChaosCenter UI). Use when the target system runs on Kubernetes and fault experiments should be declared as CRDs in the cluster alongside the workloads they target.
error-budget-tests
Build error-budget gate tests - SLO + error-budget calculation per Google SRE workbook ("difference between target uptime and actual uptime"); burn-rate alerting; monthly-budget exhaustion test; freeze-trigger when budget consumed. Per sre.google embracing-risk reference. Includes the incident-metrics reference for MTTR / MTBF / MTTD / MTTA - per-incident record schema, calculation formulae, exclusion rules, dashboards-as-code, and target-vs-actual alerting. Use when an SLO and error budget are written down but nothing verifies that burn-rate alerts fire or that the release freeze engages when the budget runs out, or when MTTR / MTBF dashboards report numbers nobody can reproduce.
failure-injection-test-author
Orchestrates WireMock fault stubs (HTTP-level fault: 500s, malformed JSON, slow responses) with Toxiproxy (TCP-level: latency, packet loss, reset) into a single resilience test scenario - the test starts both, applies fault per scenario, runs the SUT against the impaired endpoints, verifies the SUT's resilience patterns. Use when one test must reproduce a combined network + HTTP failure - a cross-layer failure mode from an incident postmortem that neither pure HTTP fault stubs nor pure TCP chaos can cover alone, because most real failures span both layers.
toxiproxy-chaos
Configures Toxiproxy for TCP-level fault injection - runs as a sidecar / proxy between client and upstream, applies toxics (latency, bandwidth, slow_close, timeout, slicer, limit_data, reset_peer) via control API. Focused on the proxy itself rather than an API-level chaos runner, including non-test usage (chaos in dev environments, integration tests, pre-prod simulation). Use when the team needs TCP-precise fault injection in development / integration environments without K8s or commercial tooling.