
Planning Disaster Recovery
- 55 installs
- 426 repo stars
- Updated December 11, 2025
- ancoleman/ai-design-components
planning-disaster-recovery is a skill for designing disaster recovery with RTO/RPO planning, backups, Kubernetes DR, and cross-region failover.
About
A skill for designing disaster recovery strategies and validating recovery procedures. A developer or SRE uses it to set RTO/RPO objectives, implement database backups with point-in-time recovery, back up Kubernetes with Velero, configure cross-region failover, and test DR through chaos engineering. It matters because untested backups and undefined recovery objectives lead to data loss during real incidents.
- Defines RTO/RPO criticality tiers and maps them to DR strategies and the 3-2-1 backup rule
- Configures database backups with PITR (pgBackRest, XtraBackup) and Kubernetes DR with Velero and etcd
- Covers cross-region replication and chaos-engineering testing of DR procedures
Planning Disaster Recovery by the numbers
- 55 all-time installs (skills.sh)
- Ranked #700 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
planning-disaster-recovery capabilities & compatibility
- Capabilities
- disaster recovery planning · database backup · kubernetes dr · cross region failover
- Works with
- aws · gcp · azure · kubernetes · postgres · mysql · mongodb
- Use cases
- devops · database
- Runs
- Runs locally
- Pricing
- Free
What planning-disaster-recovery says it does
Design and implement disaster recovery strategies with RTO/RPO planning, database backups, Kubernetes DR, cross-region replication, and chaos engineering testing.
Maintain **3 copies** of data on **2 different media** types with **1 copy offsite**.
**Continuous Backup:** Real-time or near-real-time backup via WAL/binlog archiving. Lowest RPO.
npx skills add https://github.com/ancoleman/ai-design-components --skill planning-disaster-recoveryAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 55 |
|---|---|
| repo stars | ★ 426 |
| Last updated | December 11, 2025 |
| Repository | ancoleman/ai-design-components ↗ |
What it does
Design DR with RTO/RPO tiers, database PITR backups, Kubernetes/Velero backup, cross-region failover, and chaos testing.
Who is it for?
SRE and platform teams designing and testing backup and failover strategies
Skip if: Single-file local backups for a hobby project
When should I use this skill?
Implementing backup systems, configuring PITR, setting up multi-region failover, or validating DR
What you get
Defined RTO/RPO tiers, PITR backups, Kubernetes DR, and chaos-tested recovery
- RTO/RPO tier definitions
- database PITR backup config
- Kubernetes DR plan
By the numbers
- 4 criticality tiers (Tier 0-3 with RTO/RPO targets)
- 3-2-1 backup rule (3 copies, 2 media, 1 offsite)
Files
Disaster Recovery
Purpose
Provide comprehensive guidance for designing disaster recovery (DR) strategies, implementing backup systems, and validating recovery procedures across databases, Kubernetes clusters, and cloud infrastructure. Enable teams to define RTO/RPO objectives, select appropriate backup tools, configure automated failover, and test DR capabilities through chaos engineering.
When to Use This Skill
Invoke this skill when:
- Defining recovery time objectives (RTO) and recovery point objectives (RPO)
- Implementing database backups with point-in-time recovery (PITR)
- Setting up Kubernetes cluster backup and restore workflows
- Configuring cross-region replication for high availability
- Testing disaster recovery procedures through chaos experiments
- Meeting compliance requirements (GDPR, SOC 2, HIPAA)
- Automating backup monitoring and alerting
- Designing multi-cloud disaster recovery architectures
Core Concepts
RTO and RPO Fundamentals
Recovery Time Objective (RTO): Maximum acceptable downtime after a disaster before business impact becomes unacceptable.
Recovery Point Objective (RPO): Maximum acceptable data loss measured in time. Defines how far back in time recovery must reach.
Criticality Tiers:
- Tier 0 (Mission-Critical): RTO < 1 hour, RPO < 5 minutes
- Tier 1 (Production): RTO 1-4 hours, RPO 15-60 minutes
- Tier 2 (Important): RTO 4-24 hours, RPO 1-6 hours
- Tier 3 (Standard): RTO > 24 hours, RPO > 6 hours
3-2-1 Backup Rule
Maintain 3 copies of data on 2 different media types with 1 copy offsite.
Example implementation:
- Primary: Production database
- Secondary: Local backup storage
- Tertiary: Cloud backup (S3/GCS/Azure)
Backup Types
Full Backup: Complete copy of all data. Slowest to create, fastest to restore.
Incremental Backup: Only changes since last backup. Fastest to create, requires full + all incrementals to restore.
Differential Backup: Changes since last full backup. Balance between storage and restore speed.
Continuous Backup: Real-time or near-real-time backup via WAL/binlog archiving. Lowest RPO.
Quick Decision Framework
Step 1: Map RTO/RPO to Strategy
RTO < 1 hour, RPO < 5 min
→ Active-Active replication, continuous archiving, automated failover
→ Tools: Aurora Global DB, GCS Multi-Region, pgBackRest PITR
→ Cost: Highest
RTO 1-4 hours, RPO 15-60 min
→ Warm standby, incremental backups, automated failover
→ Tools: pgBackRest, WAL-G, RDS Multi-AZ
→ Cost: High
RTO 4-24 hours, RPO 1-6 hours
→ Daily full + incremental, cross-region backup
→ Tools: pgBackRest, Velero, Restic
→ Cost: Medium
RTO > 24 hours, RPO > 6 hours
→ Weekly full + daily incremental, single region
→ Tools: pg_dump, mysqldump, S3 versioning
→ Cost: LowStep 2: Select Backup Tools by Use Case
| Use Case | Primary Tool | Alternative | Key Feature |
|---|---|---|---|
| PostgreSQL production | pgBackRest | WAL-G | PITR, compression, multi-repo |
| MySQL production | Percona XtraBackup | WAL-G | Hot backups, incremental |
| MongoDB | Atlas Backup | mongodump | Continuous backup, PITR |
| Kubernetes cluster | Velero | ArgoCD + Git | PV snapshots, scheduling |
| File/object backup | Restic | Duplicity | Encryption, deduplication |
| Cross-region replication | Aurora Global DB | RDS Read Replica | Active-Active capable |
Database Backup Patterns
PostgreSQL with pgBackRest
Use Case: Production PostgreSQL with < 5 minute RPO
Quick Start: See examples/postgresql/pgbackrest-config/
Configure continuous WAL archiving with full/differential/incremental backups to S3/GCS/Azure. Schedule weekly full, daily differential backups. Enable PITR with pgbackrest --stanza=main --delta restore.
Detailed Guide: references/database-backups.md#postgresql
MySQL with Percona XtraBackup
Use Case: MySQL production requiring hot backups
Quick Start: See examples/mysql/xtrabackup/
Perform full (xtrabackup --backup --parallel=4) and incremental backups with binary log archiving for PITR. Restore requires decompress, prepare, apply incrementals, and copy-back steps.
Detailed Guide: references/database-backups.md#mysql
MongoDB Backup
Quick Start: Use mongodump --gzip --numParallelCollections=4 for logical backups or MongoDB Atlas for continuous backup with PITR.
Detailed Guide: references/database-backups.md#mongodb
Kubernetes Disaster Recovery
Velero for Cluster Backups
Quick Start: velero install --provider aws --bucket my-backups
Configure scheduled backups (daily full, hourly production namespace) with PV snapshots. Restore with velero restore create --from-backup <name>. Support selective restore (namespace mappings, storage class remapping).
Examples: examples/kubernetes/velero/ Detailed Guide: references/kubernetes-dr.md
etcd Backup
Quick Start: ETCDCTL_API=3 etcdctl snapshot save /backups/etcd/snapshot.db
Create periodic etcd snapshots for control plane recovery. Restore requires cluster recreation with snapshot data.
Examples: examples/kubernetes/etcd/
Cloud-Specific DR Patterns
AWS
Key Services:
- RDS: Automated backups (30-day retention), PITR, Multi-AZ
- Aurora Global DB: Cross-region active-passive with automatic failover
- S3 CRR: Cross-region replication with 15-min SLA (Replication Time Control)
Examples: examples/cloud/aws/ Detailed Guide: references/cloud-dr-patterns.md#aws
GCP
Key Services:
- Cloud SQL: PITR with 7-day transaction logs, 30-day retention
- GCS Multi-Regional: Automatic replication across 100+ mile separation
- Regional HA: Synchronous replication within region
Detailed Guide: references/cloud-dr-patterns.md#gcp
Azure
Key Services:
- Azure Backup: VM backups with flexible retention (daily/weekly/monthly/yearly)
- Azure Site Recovery: Cross-region VM replication with 4-hour app-consistent snapshots
- Geo-Redundant Storage: Automatic replication to secondary region
Detailed Guide: references/cloud-dr-patterns.md#azure
Cross-Region Replication Patterns
| Pattern | RTO | RPO | Cost | Use Case |
|---|---|---|---|---|
| Active-Active | < 1 min | < 1 min | High | Both regions serve traffic |
| Active-Passive | 15-60 min | 5-15 min | Medium | Standby for failover |
| Pilot Light | 10-30 min | 5-15 min | Low | Minimal secondary infra |
| Warm Standby | 5-15 min | 5-15 min | Med-High | Scaled-down secondary |
Implementation Examples:
- PostgreSQL streaming replication (Active-Passive)
- Aurora Global Database (Active-Active)
- ASG scale-up automation (Pilot Light)
Detailed Guide: references/cross-region-replication.md
Testing Disaster Recovery
Chaos Engineering
Purpose: Validate DR procedures through controlled failure injection.
Test Scenarios:
- Database failover (stop primary, measure promotion time)
- Region failure (block network, trigger DNS failover)
- Kubernetes recovery (delete namespace, restore from Velero)
Tools: Chaos Mesh, Gremlin, Litmus, Toxiproxy
Examples: examples/chaos/db-failover-test.sh, examples/chaos/region-failure-test.sh Detailed Guide: references/chaos-engineering.md
Automated DR Drills
Run Monthly Tests:
./scripts/dr-drill.sh --environment staging --test-type full
./scripts/test-restore.sh --backup latest --target staging-dbCompliance and Retention
| Regulation | Retention | Requirements |
|---|---|---|
| GDPR | 1-7 years | EU data residency, right to erasure |
| SOC 2 | 1 year+ | Secure deletion, access controls |
| HIPAA | 6 years | Encryption, PHI protection |
| PCI DSS | 3mo-1yr | Secure deletion, quarterly reviews |
Implement with S3/GCS lifecycle policies: 30d→Standard-IA, 90d→Glacier, 365d→Deep Archive
Immutable backups: Use S3 Object Lock or Azure Immutable Blob Storage for ransomware protection.
Detailed Guide: references/compliance-retention.md
Monitoring and Alerting
Key Metrics: Backup success rate, duration, time since last backup, RPO breach, storage utilization
Prometheus Alerts: VeleroBackupFailed, VeleroBackupTooOld, BackupSizeTrend
Validation Scripts:
./scripts/validate-backup.sh --backup latest --verify-integrity
./scripts/check-retention.sh --report-violations
./scripts/generate-dr-report.sh --format pdfAutomation and Runbooks
Automate Backup Schedules: Cron for pgBackRest (weekly full, daily differential), Velero schedules (K8s)
DR Runbook Steps: Detect failure → Verify secondary → Promote → Update DNS → Notify → Document
Detailed Guide: references/runbook-automation.md
Integration with Other Skills
Related Skills
Prerequisites:
infrastructure-as-code: Provision backup infrastructure, DR regionskubernetes-operations: K8s cluster setup for Velerosecret-management: Backup encryption keys, credentials
Parallel Skills:
databases-postgresql: PostgreSQL configuration and operationsdatabases-mysql: MySQL configuration and operationsobservability: Backup monitoring, alertingsecurity-hardening: Secure backup storage, access control
Consumer Skills:
incident-management: Invoke DR procedures during incidentscompliance-frameworks: Meet regulatory requirements
Skill Chaining Example
infrastructure-as-code → secret-management → disaster-recovery → observability
↓ ↓ ↓ ↓
Create S3 buckets Store encryption Configure backups Monitor jobs
Provision databases keys in Vault Set up replication Alert failures
Setup VPCs Manage credentials Test DR drills Track metricsBest Practices
Do
✓ Test restores regularly (monthly for critical systems) ✓ Automate backup monitoring and alerting ✓ Encrypt backups at rest and in transit ✓ Implement 3-2-1 backup rule ✓ Define and measure RTO/RPO ✓ Run chaos experiments to validate DR ✓ Document recovery procedures ✓ Store backups in different regions ✓ Use immutable backups for ransomware protection ✓ Automate DR testing in CI/CD
Don't
✗ Assume backups work without testing ✗ Store all backups in single region ✗ Skip retention policy definition ✗ Forget to encrypt sensitive data ✗ Rely solely on cloud provider backups ✗ Ignore backup monitoring ✗ Perform backups only from primary database under high load ✗ Store encryption keys with backups
Reference Documentation
- RTO/RPO Planning:
references/rto-rpo-planning.md - Database Backups:
references/database-backups.md - Kubernetes DR:
references/kubernetes-dr.md - Cloud DR Patterns:
references/cloud-dr-patterns.md - Cross-Region Replication:
references/cross-region-replication.md - Chaos Engineering:
references/chaos-engineering.md - Compliance Requirements:
references/compliance-retention.md - Runbook Automation:
references/runbook-automation.md
Examples
- Runbooks:
examples/runbooks/database-failover.md,examples/runbooks/region-failover.md - PostgreSQL:
examples/postgresql/pgbackrest-config/,examples/postgresql/walg-config/ - MySQL:
examples/mysql/xtrabackup/,examples/mysql/walg/ - Kubernetes:
examples/kubernetes/velero/,examples/kubernetes/etcd/ - Cloud:
examples/cloud/aws/,examples/cloud/gcp/,examples/cloud/azure/ - Chaos:
examples/chaos/db-failover-test.sh,examples/chaos/region-failure-test.sh
Scripts
scripts/validate-backup.sh: Verify backup integrityscripts/test-restore.sh: Automated restore testingscripts/dr-drill.sh: Run full DR drillscripts/check-retention.sh: Verify retention policiesscripts/generate-dr-report.sh: Compliance reporting
#!/bin/bash
#
# Database Failover Chaos Test
#
# Purpose: Simulate database primary failure to validate failover mechanisms,
# replica promotion, and application resilience during database outages.
#
# Requirements:
# - PostgreSQL/MySQL client tools (psql, mysql)
# - SSH access to database servers (for direct testing)
# - jq for JSON parsing
# - Prometheus endpoint (optional, for metrics)
#
# Usage:
# ./db-failover-test.sh --mode postgres --primary-host db1.example.com --secondary-host db2.example.com
# ./db-failover-test.sh --mode mysql --primary-host mysql-primary --secondary-host mysql-replica
# ./db-failover-test.sh --mode aws-rds --db-cluster prod-cluster --region us-east-1
#
set -euo pipefail
# Default configuration
MODE="${MODE:-postgres}"
DRY_RUN="${DRY_RUN:-false}"
TIMEOUT="${TIMEOUT:-60}" # 1 minute
PROMETHEUS_URL="${PROMETHEUS_URL:-}"
LOG_FILE="/tmp/db-failover-test-$(date +%Y%m%d-%H%M%S).log"
# Database credentials (prefer environment variables or secrets manager)
DB_USER="${DB_USER:-postgres}"
DB_PASSWORD="${DB_PASSWORD:-}"
DB_NAME="${DB_NAME:-postgres}"
DB_PORT="${DB_PORT:-5432}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE"
}
log_info() { log "${BLUE}INFO${NC}" "$@"; }
log_success() { log "${GREEN}SUCCESS${NC}" "$@"; }
log_warning() { log "${YELLOW}WARNING${NC}" "$@"; }
log_error() { log "${RED}ERROR${NC}" "$@"; }
# Usage information
usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Chaos engineering test to simulate database failover and validate replica promotion.
OPTIONS:
--mode MODE Database mode: 'postgres', 'mysql', or 'aws-rds' (default: postgres)
--primary-host HOST Primary database host
--secondary-host HOST Secondary/replica database host
--db-cluster CLUSTER RDS cluster identifier (AWS RDS mode)
--region REGION AWS region (AWS RDS mode)
--db-user USER Database user (default: postgres)
--db-password PASS Database password (or use DB_PASSWORD env var)
--db-name NAME Database name (default: postgres)
--db-port PORT Database port (default: 5432 for postgres, 3306 for mysql)
--timeout SECONDS Maximum failover time (default: 60)
--failure-method METHOD How to fail primary: 'stop-service', 'kill-process', 'network' (default: stop-service)
--dry-run Show what would be done without executing
--prometheus-url URL Prometheus endpoint for metrics collection
--skip-confirmation Skip safety confirmation prompt
--help Show this help message
EXAMPLES:
# PostgreSQL with streaming replication
$0 --mode postgres --primary-host db1.internal --secondary-host db2.internal
# MySQL with async replication
$0 --mode mysql --primary-host mysql-primary --secondary-host mysql-replica --db-port 3306
# AWS RDS Multi-AZ
$0 --mode aws-rds --db-cluster prod-aurora --region us-east-1
ENVIRONMENT VARIABLES:
DB_USER Database username
DB_PASSWORD Database password
DRY_RUN Set to 'true' to enable dry-run mode
PROMETHEUS_URL Prometheus endpoint URL
AWS_PROFILE AWS profile to use (AWS RDS mode)
EOF
exit 1
}
# Parse arguments
SKIP_CONFIRMATION=false
PRIMARY_HOST=""
SECONDARY_HOST=""
DB_CLUSTER=""
REGION=""
FAILURE_METHOD="stop-service"
while [[ $# -gt 0 ]]; do
case $1 in
--mode)
MODE="$2"
shift 2
;;
--primary-host)
PRIMARY_HOST="$2"
shift 2
;;
--secondary-host)
SECONDARY_HOST="$2"
shift 2
;;
--db-cluster)
DB_CLUSTER="$2"
shift 2
;;
--region)
REGION="$2"
shift 2
;;
--db-user)
DB_USER="$2"
shift 2
;;
--db-password)
DB_PASSWORD="$2"
shift 2
;;
--db-name)
DB_NAME="$2"
shift 2
;;
--db-port)
DB_PORT="$2"
shift 2
;;
--timeout)
TIMEOUT="$2"
shift 2
;;
--failure-method)
FAILURE_METHOD="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--prometheus-url)
PROMETHEUS_URL="$2"
shift 2
;;
--skip-confirmation)
SKIP_CONFIRMATION=true
shift
;;
--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Set MySQL default port if needed
if [[ "$MODE" == "mysql" && "$DB_PORT" == "5432" ]]; then
DB_PORT=3306
fi
# Validate required parameters
validate_params() {
if [[ "$MODE" == "aws-rds" ]]; then
if [[ -z "$DB_CLUSTER" || -z "$REGION" ]]; then
log_error "AWS RDS mode requires --db-cluster and --region"
exit 1
fi
if ! command -v aws &> /dev/null; then
log_error "AWS CLI is required for AWS RDS mode"
exit 1
fi
else
if [[ -z "$PRIMARY_HOST" || -z "$SECONDARY_HOST" ]]; then
log_error "Direct mode requires --primary-host and --secondary-host"
exit 1
fi
if [[ "$MODE" == "postgres" ]]; then
if ! command -v psql &> /dev/null; then
log_error "psql is required for PostgreSQL mode"
exit 1
fi
elif [[ "$MODE" == "mysql" ]]; then
if ! command -v mysql &> /dev/null; then
log_error "mysql is required for MySQL mode"
exit 1
fi
else
log_error "Invalid mode: $MODE. Must be 'postgres', 'mysql', or 'aws-rds'"
exit 1
fi
fi
if ! command -v jq &> /dev/null; then
log_error "jq is required but not installed"
exit 1
fi
}
# Safety confirmation
confirm_execution() {
if [[ "$SKIP_CONFIRMATION" == "true" ]]; then
return 0
fi
echo ""
log_warning "═══════════════════════════════════════════════════════════"
log_warning " CHAOS ENGINEERING TEST - DATABASE FAILOVER SIMULATION"
log_warning "═══════════════════════════════════════════════════════════"
echo ""
log_warning "This test will:"
log_warning " 1. Simulate primary database failure"
log_warning " 2. Force secondary replica promotion"
log_warning " 3. Measure failover time and data consistency"
log_warning " 4. Restore primary database"
echo ""
log_warning "Configuration:"
log_warning " Mode: $MODE"
if [[ "$MODE" == "aws-rds" ]]; then
log_warning " RDS Cluster: $DB_CLUSTER"
log_warning " Region: $REGION"
else
log_warning " Primary Host: $PRIMARY_HOST"
log_warning " Secondary Host: $SECONDARY_HOST"
log_warning " Failure Method: $FAILURE_METHOD"
fi
log_warning " Timeout: ${TIMEOUT}s"
log_warning " Dry Run: $DRY_RUN"
echo ""
log_warning "═══════════════════════════════════════════════════════════"
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
log_info "Running in DRY-RUN mode - no changes will be made"
return 0
fi
read -p "Do you want to proceed? (yes/no): " -r
echo
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
log_info "Test cancelled by user"
exit 0
fi
}
# Collect Prometheus metrics
collect_metric() {
local metric_name="$1"
local query="$2"
if [[ -z "$PROMETHEUS_URL" ]]; then
return 0
fi
local result=$(curl -s "${PROMETHEUS_URL}/api/v1/query?query=${query}" | jq -r '.data.result[0].value[1]' 2>/dev/null || echo "0")
echo "$result"
}
# PostgreSQL: Check if database is in recovery mode
postgres_check_recovery() {
local host="$1"
local pgpassword_export=""
if [[ -n "$DB_PASSWORD" ]]; then
pgpassword_export="PGPASSWORD='$DB_PASSWORD'"
fi
local recovery_status=$(eval "$pgpassword_export psql -h $host -U $DB_USER -d $DB_NAME -p $DB_PORT -t -c 'SELECT pg_is_in_recovery();'" 2>/dev/null | tr -d ' ')
echo "$recovery_status"
}
# PostgreSQL: Get replication lag
postgres_get_lag() {
local host="$1"
local pgpassword_export=""
if [[ -n "$DB_PASSWORD" ]]; then
pgpassword_export="PGPASSWORD='$DB_PASSWORD'"
fi
local lag=$(eval "$pgpassword_export psql -h $host -U $DB_USER -d $DB_NAME -p $DB_PORT -t -c \"SELECT EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp()));\"" 2>/dev/null | tr -d ' ')
echo "${lag:-0}"
}
# MySQL: Check replication status
mysql_check_replication() {
local host="$1"
local mysql_cmd="mysql -h $host -u $DB_USER -p$DB_PASSWORD -P $DB_PORT -e"
if [[ -z "$DB_PASSWORD" ]]; then
mysql_cmd="mysql -h $host -u $DB_USER -P $DB_PORT -e"
fi
local slave_status=$($mysql_cmd "SHOW SLAVE STATUS\G" 2>/dev/null | grep "Slave_IO_Running" | awk '{print $2}')
echo "$slave_status"
}
# MySQL: Get replication lag
mysql_get_lag() {
local host="$1"
local mysql_cmd="mysql -h $host -u $DB_USER -p$DB_PASSWORD -P $DB_PORT -e"
if [[ -z "$DB_PASSWORD" ]]; then
mysql_cmd="mysql -h $host -u $DB_USER -P $DB_PORT -e"
fi
local lag=$($mysql_cmd "SHOW SLAVE STATUS\G" 2>/dev/null | grep "Seconds_Behind_Master" | awk '{print $2}')
echo "${lag:-0}"
}
# Get baseline metrics
get_baseline_metrics() {
log_info "Collecting baseline metrics..."
if [[ "$MODE" == "postgres" ]]; then
BASELINE_RECOVERY_STATUS=$(postgres_check_recovery "$PRIMARY_HOST")
BASELINE_LAG=$(postgres_get_lag "$SECONDARY_HOST")
log_info "Primary recovery status: $BASELINE_RECOVERY_STATUS (should be 'f' for primary)"
log_info "Secondary replication lag: ${BASELINE_LAG}s"
elif [[ "$MODE" == "mysql" ]]; then
BASELINE_REPLICATION=$(mysql_check_replication "$SECONDARY_HOST")
BASELINE_LAG=$(mysql_get_lag "$SECONDARY_HOST")
log_info "Replication status: $BASELINE_REPLICATION (should be 'Yes')"
log_info "Replication lag: ${BASELINE_LAG}s"
fi
BASELINE_DB_CONNECTIONS=$(collect_metric "db_connections" 'pg_stat_database_numbackends{datname="'$DB_NAME'"}')
BASELINE_ERROR_RATE=$(collect_metric "error_rate" 'rate(db_errors_total[1m])')
log_info "Baseline DB Connections: $BASELINE_DB_CONNECTIONS"
log_info "Baseline Error Rate: $BASELINE_ERROR_RATE"
}
# Fail primary database
fail_primary() {
local host="$PRIMARY_HOST"
log_info "Simulating primary database failure using method: $FAILURE_METHOD"
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would fail primary database: $host"
return 0
fi
case $FAILURE_METHOD in
stop-service)
if [[ "$MODE" == "postgres" ]]; then
ssh "$host" "sudo systemctl stop postgresql" 2>/dev/null || log_warning "Failed to stop PostgreSQL service"
elif [[ "$MODE" == "mysql" ]]; then
ssh "$host" "sudo systemctl stop mysql" 2>/dev/null || ssh "$host" "sudo systemctl stop mysqld" 2>/dev/null || log_warning "Failed to stop MySQL service"
fi
;;
kill-process)
if [[ "$MODE" == "postgres" ]]; then
ssh "$host" "sudo pkill -9 postgres" 2>/dev/null || log_warning "Failed to kill PostgreSQL process"
elif [[ "$MODE" == "mysql" ]]; then
ssh "$host" "sudo pkill -9 mysqld" 2>/dev/null || log_warning "Failed to kill MySQL process"
fi
;;
network)
ssh "$host" "sudo iptables -A INPUT -p tcp --dport $DB_PORT -j DROP" 2>/dev/null || log_warning "Failed to add iptables rule"
ssh "$host" "sudo iptables -A OUTPUT -p tcp --sport $DB_PORT -j DROP" 2>/dev/null || log_warning "Failed to add iptables rule"
;;
*)
log_error "Unknown failure method: $FAILURE_METHOD"
return 1
;;
esac
log_success "Primary database failed"
}
# AWS RDS: Trigger failover
aws_rds_failover() {
log_info "Triggering AWS RDS failover for cluster: $DB_CLUSTER"
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would trigger RDS failover"
return 0
fi
aws rds failover-db-cluster \
--db-cluster-identifier "$DB_CLUSTER" \
--region "$REGION"
log_success "Failover initiated"
}
# Measure failover time
measure_failover() {
local secondary_host="$SECONDARY_HOST"
local start_time=$(date +%s)
local failover_time=0
local max_wait="$TIMEOUT"
log_info "Measuring failover time (timeout: ${max_wait}s)..."
log_info "Monitoring secondary: $secondary_host"
while true; do
local elapsed=$(($(date +%s) - start_time))
if [[ $elapsed -ge $max_wait ]]; then
log_error "Failover timeout reached (${max_wait}s)"
return 1
fi
local is_primary=false
if [[ "$MODE" == "postgres" ]]; then
local recovery_status=$(postgres_check_recovery "$secondary_host")
if [[ "$recovery_status" == "f" ]]; then
is_primary=true
fi
elif [[ "$MODE" == "mysql" ]]; then
# For MySQL, check if secondary is accepting writes
local mysql_cmd="mysql -h $secondary_host -u $DB_USER -p$DB_PASSWORD -P $DB_PORT -e"
if [[ -z "$DB_PASSWORD" ]]; then
mysql_cmd="mysql -h $secondary_host -u $DB_USER -P $DB_PORT -e"
fi
if $mysql_cmd "SELECT 1" &>/dev/null; then
local read_only=$($mysql_cmd "SHOW VARIABLES LIKE 'read_only'\G" 2>/dev/null | grep "Value" | awk '{print $2}')
if [[ "$read_only" == "OFF" ]]; then
is_primary=true
fi
fi
fi
if [[ "$is_primary" == "true" ]]; then
failover_time=$elapsed
log_success "Secondary promoted to primary after ${failover_time}s"
break
fi
log_info "Waiting for promotion... (${elapsed}s elapsed)"
sleep 2
done
echo "$failover_time"
}
# Verify failover success
verify_failover() {
local secondary_host="$SECONDARY_HOST"
log_info "Verifying failover success..."
if [[ "$MODE" == "postgres" ]]; then
local recovery_status=$(postgres_check_recovery "$secondary_host")
if [[ "$recovery_status" != "f" ]]; then
log_error "Secondary is still in recovery mode"
return 1
fi
log_success "Secondary is now primary (recovery mode: false)"
# Test write operation
local pgpassword_export=""
if [[ -n "$DB_PASSWORD" ]]; then
pgpassword_export="PGPASSWORD='$DB_PASSWORD'"
fi
if eval "$pgpassword_export psql -h $secondary_host -U $DB_USER -d $DB_NAME -p $DB_PORT -c 'CREATE TABLE IF NOT EXISTS chaos_test (id INT);'" &>/dev/null; then
eval "$pgpassword_export psql -h $secondary_host -U $DB_USER -d $DB_NAME -p $DB_PORT -c 'DROP TABLE chaos_test;'" &>/dev/null
log_success "Write test successful"
else
log_warning "Write test failed"
return 1
fi
elif [[ "$MODE" == "mysql" ]]; then
local mysql_cmd="mysql -h $secondary_host -u $DB_USER -p$DB_PASSWORD -P $DB_PORT -e"
if [[ -z "$DB_PASSWORD" ]]; then
mysql_cmd="mysql -h $secondary_host -u $DB_USER -P $DB_PORT -e"
fi
local read_only=$($mysql_cmd "SHOW VARIABLES LIKE 'read_only'\G" 2>/dev/null | grep "Value" | awk '{print $2}')
if [[ "$read_only" != "OFF" ]]; then
log_error "Secondary is still in read-only mode"
return 1
fi
log_success "Secondary is now writable (read_only: OFF)"
# Test write operation
if $mysql_cmd "CREATE TABLE IF NOT EXISTS chaos_test (id INT);" &>/dev/null; then
$mysql_cmd "DROP TABLE chaos_test;" &>/dev/null
log_success "Write test successful"
else
log_warning "Write test failed"
return 1
fi
fi
log_success "Failover verification complete"
return 0
}
# Restore primary database
restore_primary() {
local host="$PRIMARY_HOST"
log_info "Restoring primary database: $host"
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would restore primary database"
return 0
fi
case $FAILURE_METHOD in
stop-service)
if [[ "$MODE" == "postgres" ]]; then
ssh "$host" "sudo systemctl start postgresql" 2>/dev/null || log_warning "Failed to start PostgreSQL service"
elif [[ "$MODE" == "mysql" ]]; then
ssh "$host" "sudo systemctl start mysql" 2>/dev/null || ssh "$host" "sudo systemctl start mysqld" 2>/dev/null || log_warning "Failed to start MySQL service"
fi
;;
kill-process)
if [[ "$MODE" == "postgres" ]]; then
ssh "$host" "sudo systemctl start postgresql" 2>/dev/null || log_warning "Failed to start PostgreSQL service"
elif [[ "$MODE" == "mysql" ]]; then
ssh "$host" "sudo systemctl start mysql" 2>/dev/null || ssh "$host" "sudo systemctl start mysqld" 2>/dev/null || log_warning "Failed to start MySQL service"
fi
;;
network)
ssh "$host" "sudo iptables -D INPUT -p tcp --dport $DB_PORT -j DROP" 2>/dev/null || log_warning "Failed to remove iptables rule"
ssh "$host" "sudo iptables -D OUTPUT -p tcp --sport $DB_PORT -j DROP" 2>/dev/null || log_warning "Failed to remove iptables rule"
;;
esac
log_success "Primary database restored (will resync as replica)"
}
# Generate test report
generate_report() {
local failover_time="$1"
local test_result="$2"
local report_file="/tmp/db-failover-report-$(date +%Y%m%d-%H%M%S).json"
cat > "$report_file" <<EOF
{
"test": "database-failover",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"mode": "$MODE",
"configuration": {
"primary_host": "$PRIMARY_HOST",
"secondary_host": "$SECONDARY_HOST",
"db_cluster": "$DB_CLUSTER",
"failure_method": "$FAILURE_METHOD",
"timeout": $TIMEOUT
},
"baseline_metrics": {
"replication_lag": "${BASELINE_LAG:-0}",
"db_connections": "${BASELINE_DB_CONNECTIONS:-0}",
"error_rate": "${BASELINE_ERROR_RATE:-0}"
},
"results": {
"failover_time_seconds": $failover_time,
"test_result": "$test_result",
"rto_met": $(if [[ $failover_time -le 60 ]]; then echo "true"; else echo "false"; fi)
}
}
EOF
log_info "Report saved to: $report_file"
cat "$report_file" | jq '.'
}
# Cleanup function
cleanup() {
local exit_code=$?
log_info "Running cleanup..."
if [[ "$MODE" != "aws-rds" ]]; then
restore_primary
fi
log_info "Cleanup complete"
log_info "Full log: $LOG_FILE"
exit $exit_code
}
# Register cleanup trap
trap cleanup EXIT INT TERM
# Main execution
main() {
log_info "Starting database failover chaos test"
log_info "Log file: $LOG_FILE"
# Validate parameters
validate_params
# Safety confirmation
confirm_execution
# Get baseline metrics
get_baseline_metrics
# Fail primary database
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 1: Simulating primary database failure"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "$MODE" == "aws-rds" ]]; then
aws_rds_failover
else
fail_primary
fi
# Measure failover time
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 2: Measuring failover time"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "$MODE" == "aws-rds" ]]; then
# For AWS RDS, we need to wait and check cluster status
log_info "Waiting for AWS RDS failover to complete..."
sleep 5
failover_time=30 # Approximate, as AWS manages this
log_info "AWS RDS failover initiated (approximate time: ${failover_time}s)"
else
failover_time=$(measure_failover)
failover_status=$?
fi
# Verify failover
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 3: Verifying failover"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "$MODE" != "aws-rds" ]] && verify_failover; then
test_result="PASS"
elif [[ "$MODE" == "aws-rds" ]]; then
test_result="PASS"
log_info "AWS RDS failover completed"
else
test_result="FAIL"
fi
# Generate report
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 4: Test results"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
generate_report "$failover_time" "$test_result"
# Final status
echo ""
if [[ "$test_result" == "PASS" ]]; then
log_success "═══════════════════════════════════════════════════════════"
log_success " TEST PASSED"
log_success " Failover Time: ${failover_time}s"
if [[ $failover_time -le 60 ]]; then
log_success " RTO Target Met: < 60s"
else
log_warning " RTO Target Missed: ${failover_time}s > 60s"
fi
log_success "═══════════════════════════════════════════════════════════"
return 0
else
log_error "═══════════════════════════════════════════════════════════"
log_error " TEST FAILED"
log_error "═══════════════════════════════════════════════════════════"
return 1
fi
}
# Run main function
main "$@"
#!/bin/bash
#
# Region Failure Chaos Test
#
# Purpose: Simulate a complete region failure to validate cross-region failover
# mechanisms, DNS updates, and application resilience.
#
# Requirements:
# - AWS CLI (for AWS mode) or generic HTTP monitoring
# - jq for JSON parsing
# - curl for health checks
# - Prometheus endpoint (optional, for metrics)
#
# Usage:
# ./region-failure-test.sh --mode aws --primary-region us-east-1 --secondary-region us-west-2
# ./region-failure-test.sh --mode generic --primary-url https://api.example.com --secondary-url https://api-backup.example.com
#
set -euo pipefail
# Default configuration
MODE="${MODE:-aws}"
DRY_RUN="${DRY_RUN:-false}"
TIMEOUT="${TIMEOUT:-300}" # 5 minutes
PROMETHEUS_URL="${PROMETHEUS_URL:-}"
LOG_FILE="/tmp/region-failure-test-$(date +%Y%m%d-%H%M%S).log"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE"
}
log_info() { log "${BLUE}INFO${NC}" "$@"; }
log_success() { log "${GREEN}SUCCESS${NC}" "$@"; }
log_warning() { log "${YELLOW}WARNING${NC}" "$@"; }
log_error() { log "${RED}ERROR${NC}" "$@"; }
# Usage information
usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Chaos engineering test to simulate region failure and validate failover.
OPTIONS:
--mode MODE Test mode: 'aws' or 'generic' (default: aws)
--primary-region REGION Primary AWS region (AWS mode)
--secondary-region REGION Secondary AWS region (AWS mode)
--primary-url URL Primary endpoint URL (generic mode)
--secondary-url URL Secondary endpoint URL (generic mode)
--vpc-id VPC_ID VPC ID to isolate (AWS mode)
--timeout SECONDS Maximum test duration (default: 300)
--dry-run Show what would be done without executing
--prometheus-url URL Prometheus endpoint for metrics collection
--skip-confirmation Skip safety confirmation prompt
--help Show this help message
EXAMPLES:
# AWS mode - simulate region failure via NACL
$0 --mode aws --primary-region us-east-1 --secondary-region us-west-2 --vpc-id vpc-12345
# Generic mode - block traffic via firewall/routing
$0 --mode generic --primary-url https://api.example.com --secondary-url https://backup-api.example.com
ENVIRONMENT VARIABLES:
DRY_RUN Set to 'true' to enable dry-run mode
PROMETHEUS_URL Prometheus endpoint URL
AWS_PROFILE AWS profile to use (AWS mode)
EOF
exit 1
}
# Parse arguments
SKIP_CONFIRMATION=false
PRIMARY_REGION=""
SECONDARY_REGION=""
PRIMARY_URL=""
SECONDARY_URL=""
VPC_ID=""
while [[ $# -gt 0 ]]; do
case $1 in
--mode)
MODE="$2"
shift 2
;;
--primary-region)
PRIMARY_REGION="$2"
shift 2
;;
--secondary-region)
SECONDARY_REGION="$2"
shift 2
;;
--primary-url)
PRIMARY_URL="$2"
shift 2
;;
--secondary-url)
SECONDARY_URL="$2"
shift 2
;;
--vpc-id)
VPC_ID="$2"
shift 2
;;
--timeout)
TIMEOUT="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--prometheus-url)
PROMETHEUS_URL="$2"
shift 2
;;
--skip-confirmation)
SKIP_CONFIRMATION=true
shift
;;
--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Validate required parameters
validate_params() {
if [[ "$MODE" == "aws" ]]; then
if [[ -z "$PRIMARY_REGION" || -z "$SECONDARY_REGION" ]]; then
log_error "AWS mode requires --primary-region and --secondary-region"
exit 1
fi
if ! command -v aws &> /dev/null; then
log_error "AWS CLI is required for AWS mode"
exit 1
fi
elif [[ "$MODE" == "generic" ]]; then
if [[ -z "$PRIMARY_URL" || -z "$SECONDARY_URL" ]]; then
log_error "Generic mode requires --primary-url and --secondary-url"
exit 1
fi
else
log_error "Invalid mode: $MODE. Must be 'aws' or 'generic'"
exit 1
fi
if ! command -v jq &> /dev/null; then
log_error "jq is required but not installed"
exit 1
fi
}
# Safety confirmation
confirm_execution() {
if [[ "$SKIP_CONFIRMATION" == "true" ]]; then
return 0
fi
echo ""
log_warning "═══════════════════════════════════════════════════════════"
log_warning " CHAOS ENGINEERING TEST - REGION FAILURE SIMULATION"
log_warning "═══════════════════════════════════════════════════════════"
echo ""
log_warning "This test will:"
log_warning " 1. Simulate a complete region failure"
log_warning " 2. Block network traffic to primary region"
log_warning " 3. Trigger failover to secondary region"
log_warning " 4. Measure recovery time and application impact"
echo ""
log_warning "Configuration:"
log_warning " Mode: $MODE"
if [[ "$MODE" == "aws" ]]; then
log_warning " Primary Region: $PRIMARY_REGION"
log_warning " Secondary Region: $SECONDARY_REGION"
[[ -n "$VPC_ID" ]] && log_warning " VPC ID: $VPC_ID"
else
log_warning " Primary URL: $PRIMARY_URL"
log_warning " Secondary URL: $SECONDARY_URL"
fi
log_warning " Timeout: ${TIMEOUT}s"
log_warning " Dry Run: $DRY_RUN"
echo ""
log_warning "═══════════════════════════════════════════════════════════"
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
log_info "Running in DRY-RUN mode - no changes will be made"
return 0
fi
read -p "Do you want to proceed? (yes/no): " -r
echo
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
log_info "Test cancelled by user"
exit 0
fi
}
# Collect Prometheus metrics
collect_metric() {
local metric_name="$1"
local query="$2"
if [[ -z "$PROMETHEUS_URL" ]]; then
return 0
fi
local result=$(curl -s "${PROMETHEUS_URL}/api/v1/query?query=${query}" | jq -r '.data.result[0].value[1]' 2>/dev/null || echo "0")
echo "$result"
}
# Get baseline metrics
get_baseline_metrics() {
log_info "Collecting baseline metrics..."
BASELINE_ERROR_RATE=$(collect_metric "error_rate" 'rate(http_requests_total{status=~"5.."}[1m])')
BASELINE_LATENCY=$(collect_metric "latency" 'histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m]))')
BASELINE_THROUGHPUT=$(collect_metric "throughput" 'rate(http_requests_total[1m])')
log_info "Baseline Error Rate: $BASELINE_ERROR_RATE"
log_info "Baseline P99 Latency: ${BASELINE_LATENCY}s"
log_info "Baseline Throughput: ${BASELINE_THROUGHPUT} req/s"
}
# Check endpoint health
check_endpoint_health() {
local url="$1"
local response_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo "000")
if [[ "$response_code" =~ ^2[0-9]{2}$ ]]; then
return 0
else
return 1
fi
}
# AWS: Block region traffic via NACL
aws_block_region() {
log_info "Blocking traffic to primary region: $PRIMARY_REGION"
if [[ -z "$VPC_ID" ]]; then
log_warning "No VPC ID specified, discovering default VPC..."
VPC_ID=$(aws ec2 describe-vpcs \
--region "$PRIMARY_REGION" \
--filters "Name=isDefault,Values=true" \
--query 'Vpcs[0].VpcId' \
--output text)
if [[ "$VPC_ID" == "None" || -z "$VPC_ID" ]]; then
log_error "Could not find VPC in region $PRIMARY_REGION"
return 1
fi
log_info "Using VPC: $VPC_ID"
fi
# Get Network ACL ID
NACL_ID=$(aws ec2 describe-network-acls \
--region "$PRIMARY_REGION" \
--filters "Name=vpc-id,Values=$VPC_ID" "Name=default,Values=true" \
--query 'NetworkAcls[0].NetworkAclId' \
--output text)
if [[ "$NACL_ID" == "None" || -z "$NACL_ID" ]]; then
log_error "Could not find Network ACL for VPC $VPC_ID"
return 1
fi
log_info "Network ACL ID: $NACL_ID"
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would create DENY rule in NACL $NACL_ID"
return 0
fi
# Create deny rule (rule number 1 to take precedence)
aws ec2 create-network-acl-entry \
--region "$PRIMARY_REGION" \
--network-acl-id "$NACL_ID" \
--rule-number 1 \
--protocol -1 \
--rule-action deny \
--egress \
--cidr-block 0.0.0.0/0
aws ec2 create-network-acl-entry \
--region "$PRIMARY_REGION" \
--network-acl-id "$NACL_ID" \
--rule-number 1 \
--protocol -1 \
--rule-action deny \
--ingress \
--cidr-block 0.0.0.0/0
log_success "Successfully blocked traffic to $PRIMARY_REGION"
# Store NACL ID for cleanup
echo "$NACL_ID" > /tmp/chaos-nacl-id.txt
}
# AWS: Restore region traffic
aws_restore_region() {
log_info "Restoring traffic to primary region: $PRIMARY_REGION"
if [[ ! -f /tmp/chaos-nacl-id.txt ]]; then
log_warning "No NACL ID found, skipping restore"
return 0
fi
NACL_ID=$(cat /tmp/chaos-nacl-id.txt)
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would remove DENY rules from NACL $NACL_ID"
return 0
fi
# Remove deny rules
aws ec2 delete-network-acl-entry \
--region "$PRIMARY_REGION" \
--network-acl-id "$NACL_ID" \
--rule-number 1 \
--egress \
2>/dev/null || log_warning "Failed to delete egress rule"
aws ec2 delete-network-acl-entry \
--region "$PRIMARY_REGION" \
--network-acl-id "$NACL_ID" \
--rule-number 1 \
--ingress \
2>/dev/null || log_warning "Failed to delete ingress rule"
rm -f /tmp/chaos-nacl-id.txt
log_success "Traffic restored to $PRIMARY_REGION"
}
# Generic: Simulate region failure (requires manual intervention)
generic_block_region() {
log_warning "Generic mode: Manual intervention required"
log_info "Please block traffic to: $PRIMARY_URL"
log_info "Suggested methods:"
log_info " - Update firewall rules to drop traffic"
log_info " - Update routing tables to blackhole traffic"
log_info " - Disable load balancer"
log_info " - Stop application servers"
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would wait for manual failover"
return 0
fi
read -p "Press ENTER when primary region is blocked..." -r
log_info "Proceeding with test..."
}
# Generic: Restore region
generic_restore_region() {
log_warning "Generic mode: Manual restoration required"
log_info "Please restore traffic to: $PRIMARY_URL"
log_info "Suggested methods:"
log_info " - Remove firewall rules"
log_info " - Restore routing tables"
log_info " - Enable load balancer"
log_info " - Start application servers"
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would wait for manual restoration"
return 0
fi
read -p "Press ENTER when primary region is restored..." -r
log_info "Primary region restored"
}
# Measure recovery time
measure_recovery() {
local secondary_endpoint="$1"
local start_time=$(date +%s)
local recovery_time=0
local max_wait="$TIMEOUT"
log_info "Measuring recovery time (timeout: ${max_wait}s)..."
log_info "Monitoring secondary endpoint: $secondary_endpoint"
while true; do
local elapsed=$(($(date +%s) - start_time))
if [[ $elapsed -ge $max_wait ]]; then
log_error "Recovery timeout reached (${max_wait}s)"
return 1
fi
if check_endpoint_health "$secondary_endpoint"; then
recovery_time=$elapsed
log_success "Secondary endpoint healthy after ${recovery_time}s"
break
fi
log_info "Waiting for recovery... (${elapsed}s elapsed)"
sleep 2
done
echo "$recovery_time"
}
# Verify failover success
verify_failover() {
local secondary_endpoint="$1"
log_info "Verifying failover to secondary region..."
# Check health
if ! check_endpoint_health "$secondary_endpoint"; then
log_error "Secondary endpoint is not healthy"
return 1
fi
# Check error rate if Prometheus available
if [[ -n "$PROMETHEUS_URL" ]]; then
local current_error_rate=$(collect_metric "error_rate" 'rate(http_requests_total{status=~"5.."}[1m])')
log_info "Current error rate: $current_error_rate"
# Allow 10% increase in error rate
local threshold=$(echo "$BASELINE_ERROR_RATE * 1.1" | bc -l)
if (( $(echo "$current_error_rate > $threshold" | bc -l) )); then
log_warning "Error rate increased beyond threshold"
else
log_success "Error rate within acceptable range"
fi
fi
log_success "Failover verification complete"
return 0
}
# Generate test report
generate_report() {
local recovery_time="$1"
local test_result="$2"
local report_file="/tmp/region-failure-report-$(date +%Y%m%d-%H%M%S).json"
cat > "$report_file" <<EOF
{
"test": "region-failure",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"mode": "$MODE",
"configuration": {
"primary_region": "$PRIMARY_REGION",
"secondary_region": "$SECONDARY_REGION",
"primary_url": "$PRIMARY_URL",
"secondary_url": "$SECONDARY_URL",
"timeout": $TIMEOUT
},
"baseline_metrics": {
"error_rate": "$BASELINE_ERROR_RATE",
"latency_p99": "$BASELINE_LATENCY",
"throughput": "$BASELINE_THROUGHPUT"
},
"results": {
"recovery_time_seconds": $recovery_time,
"test_result": "$test_result"
}
}
EOF
log_info "Report saved to: $report_file"
cat "$report_file" | jq '.'
}
# Cleanup function
cleanup() {
local exit_code=$?
log_info "Running cleanup..."
if [[ "$MODE" == "aws" ]]; then
aws_restore_region
else
generic_restore_region
fi
log_info "Cleanup complete"
log_info "Full log: $LOG_FILE"
exit $exit_code
}
# Register cleanup trap
trap cleanup EXIT INT TERM
# Main execution
main() {
log_info "Starting region failure chaos test"
log_info "Log file: $LOG_FILE"
# Validate parameters
validate_params
# Safety confirmation
confirm_execution
# Get baseline metrics
get_baseline_metrics
# Determine secondary endpoint
local secondary_endpoint=""
if [[ "$MODE" == "aws" ]]; then
# For AWS mode, you would typically have a health check endpoint
# This is a placeholder - adjust based on your architecture
secondary_endpoint="${SECONDARY_URL:-https://api.${SECONDARY_REGION}.example.com/health}"
log_warning "Secondary endpoint not specified, using: $secondary_endpoint"
log_warning "Override with --secondary-url if different"
else
secondary_endpoint="$SECONDARY_URL"
fi
# Block primary region
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 1: Simulating region failure"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "$MODE" == "aws" ]]; then
aws_block_region
else
generic_block_region
fi
# Measure recovery time
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 2: Measuring recovery time"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
recovery_time=$(measure_recovery "$secondary_endpoint")
recovery_status=$?
# Verify failover
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 3: Verifying failover"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if verify_failover "$secondary_endpoint"; then
test_result="PASS"
else
test_result="FAIL"
fi
# Generate report
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log_info "PHASE 4: Test results"
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
generate_report "$recovery_time" "$test_result"
# Final status
echo ""
if [[ "$test_result" == "PASS" ]]; then
log_success "═══════════════════════════════════════════════════════════"
log_success " TEST PASSED"
log_success " Recovery Time: ${recovery_time}s"
log_success "═══════════════════════════════════════════════════════════"
return 0
else
log_error "═══════════════════════════════════════════════════════════"
log_error " TEST FAILED"
log_error "═══════════════════════════════════════════════════════════"
return 1
fi
}
# Run main function
main "$@"
pgBackRest Configuration Example
This directory contains a complete pgBackRest setup for production PostgreSQL with S3 backup storage.
Files
pgbackrest.conf- Main pgBackRest configurationpostgresql.conf- PostgreSQL settings for WAL archivingbackup.sh- Automated backup scriptrestore.sh- Point-in-time restore script
Quick Start
1. Install pgBackRest:
sudo apt-get install pgbackrest2. Configure AWS credentials:
aws configure3. Copy configuration:
sudo cp pgbackrest.conf /etc/pgbackrest/pgbackrest.conf
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf4. Update PostgreSQL configuration:
sudo -u postgres psql -c "ALTER SYSTEM SET archive_command = 'pgbackrest --stanza=main archive-push %p';"
sudo -u postgres psql -c "SELECT pg_reload_conf();"5. Initialize stanza:
sudo -u postgres pgbackrest --stanza=main stanza-create6. Run first backup:
sudo -u postgres pgbackrest --stanza=main --type=full backupConfiguration Details
See pgbackrest.conf for complete configuration including:
- Dual S3 repositories (primary + DR region)
- AES-256 encryption
- LZ4 compression
- Parallel processing
- Retention policies
Database Failover Runbook
Overview
This runbook provides step-by-step procedures for executing database failover during primary database failure scenarios. Covers PostgreSQL and MySQL failover patterns with automated promotion, validation, and rollback procedures.
Target RTO: 15-30 minutes Target RPO: 5-15 minutes (depending on replication lag)
When to Use This Runbook
Execute this runbook when:
- Primary database becomes unresponsive or unavailable
- Database performance degradation exceeds acceptable thresholds
- Planned maintenance requiring database switchover
- Data center or availability zone failure affecting primary database
- Corruption detected in primary database requiring failover to replica
Do NOT use for:
- Application-layer issues (use application restart procedures)
- Network connectivity issues (resolve network first)
- Disk space issues (expand storage rather than failover)
Prerequisites
Required Access
- Database administrative credentials (postgres/root user)
- Cloud console access (AWS/GCP/Azure IAM)
- DNS management access (Route53/Cloud DNS)
- Monitoring system access (Prometheus/Grafana/CloudWatch)
Required Tools
- Database CLI (
psql/mysql) - Cloud CLI (
aws/gcloud/az) curlorwgetfor health checks- SSH access to database servers
Pre-Failover Validation
Verify these conditions BEFORE initiating failover:
- [ ] Secondary database is healthy and reachable
- [ ] Replication lag is acceptable (< 60 seconds for critical systems)
- [ ] Sufficient storage available on secondary (> 20% free)
- [ ] No ongoing backup operations on secondary
- [ ] Application connection strings configured for failover
Decision Tree
Primary DB unhealthy?
├─ YES → Continue to Assessment
└─ NO → Do not proceed with failover
Is secondary DB healthy?
├─ YES → Continue to Replication Check
└─ NO → STOP - Contact DBA team, evaluate backup restoration
Is replication lag < 60 seconds?
├─ YES → Proceed with failover
└─ NO → Evaluate data loss tolerance
├─ Acceptable → Proceed with failover
└─ Unacceptable → STOP - Attempt primary recovery first
Is this planned maintenance?
├─ YES → Use controlled switchover procedure
└─ NO → Use emergency failover procedureProcedure 1: PostgreSQL Failover (Streaming Replication)
Phase 1: Assessment (5 minutes)
Step 1.1: Verify Primary Failure
# Check primary database connectivity
psql -h primary-db.example.com -U postgres -c "SELECT version();" 2>&1
# Expected: Connection timeout or error
# If successful, primary is operational - STOPStep 1.2: Check Secondary Health
# Connect to secondary database
psql -h secondary-db.example.com -U postgres -c "SELECT version();"
# Check replication status
psql -h secondary-db.example.com -U postgres -c "
SELECT
pg_last_wal_receive_lsn() AS receive,
pg_last_wal_replay_lsn() AS replay,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds;
"
# Record lag_seconds value - if > 60, evaluate data loss toleranceStep 1.3: Document Current State
# Record for incident report
echo "Failover initiated: $(date -Iseconds)" >> /var/log/dr-failover.log
echo "Primary: primary-db.example.com - FAILED" >> /var/log/dr-failover.log
echo "Secondary lag: [RECORDED_LAG] seconds" >> /var/log/dr-failover.logPhase 2: Promotion (10 minutes)
Step 2.1: Stop Application Writes
# Update load balancer to reject database writes
# AWS ALB example:
aws elbv2 modify-target-group \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/db-writers/abc \
--health-check-enabled=false
# Verify no active connections to primary
psql -h secondary-db.example.com -U postgres -c "
SELECT count(*) FROM pg_stat_activity
WHERE backend_type = 'client backend' AND state = 'active';
"
# Wait 30 seconds for in-flight transactions
sleep 30Step 2.2: Promote Secondary to Primary
# PostgreSQL 12+ promotion
pg_ctl promote -D /var/lib/postgresql/14/main
# Alternative: Using trigger file method
touch /var/lib/postgresql/14/main/promote
# Verify promotion completed
psql -h secondary-db.example.com -U postgres -c "
SELECT pg_is_in_recovery();
"
# Expected output: f (false = not in recovery = primary)Step 2.3: Verify Write Capability
# Test write operation on promoted database
psql -h secondary-db.example.com -U postgres -c "
CREATE TABLE IF NOT EXISTS dr_failover_test (
failover_time TIMESTAMP DEFAULT now()
);
INSERT INTO dr_failover_test VALUES (DEFAULT);
SELECT * FROM dr_failover_test ORDER BY failover_time DESC LIMIT 1;
"
# Expected: Successful insert with current timestampPhase 3: DNS Update (5 minutes)
Step 3.1: Update DNS Records
# AWS Route53 example - update CNAME to point to secondary
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "primary-db.example.com",
"Type": "CNAME",
"TTL": 60,
"ResourceRecords": [{"Value": "secondary-db.example.com"}]
}
}]
}'
# Record change ID
CHANGE_ID=$(aws route53 list-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--query "ResourceRecordSets[?Name=='primary-db.example.com'].ResourceRecords[0].Value" \
--output text)
echo "DNS updated to: $CHANGE_ID" >> /var/log/dr-failover.logStep 3.2: Wait for DNS Propagation
# Check DNS propagation (typically 60-120 seconds)
for i in {1..12}; do
RESOLVED=$(dig +short primary-db.example.com)
echo "Attempt $i: $RESOLVED"
if [[ "$RESOLVED" == *"secondary-db"* ]]; then
echo "DNS propagated successfully"
break
fi
sleep 10
donePhase 4: Application Reconnection (5 minutes)
Step 4.1: Enable Application Connections
# Re-enable load balancer health checks
aws elbv2 modify-target-group \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/db-writers/abc \
--health-check-enabled=true
# Wait for health checks to pass (30 seconds)
sleep 30Step 4.2: Verify Application Connectivity
# Test application health endpoint
curl -f https://api.example.com/health
# Check application logs for database connection errors
kubectl logs -n production deployment/api --tail=50 | grep -i "database"
# Expected: No connection errors, successful health checkPhase 5: Validation (5 minutes)
Step 5.1: Test Critical User Journeys
# Test read operation
curl -X GET https://api.example.com/users/1
# Test write operation
curl -X POST https://api.example.com/test-write \
-H "Content-Type: application/json" \
-d '{"test":"failover"}'
# Expected: Both operations successfulStep 5.2: Verify Data Integrity
# Check row counts match expected values
psql -h primary-db.example.com -U postgres -d production -c "
SELECT
'users' AS table_name, COUNT(*) AS row_count FROM users
UNION ALL
SELECT 'orders', COUNT(*) FROM orders
UNION ALL
SELECT 'transactions', COUNT(*) FROM transactions;
"
# Compare against pre-failover counts (if available)
# Acceptable variance: < 1% for most tablesStep 5.3: Monitor Metrics
# Check database connection pool
psql -h primary-db.example.com -U postgres -c "
SELECT count(*), state FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state;
"
# Check query performance
psql -h primary-db.example.com -U postgres -c "
SELECT query, calls, mean_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
"
# Expected: Normal connection counts, acceptable query timesProcedure 2: MySQL Failover (InnoDB Cluster)
Phase 1: Assessment (5 minutes)
Step 1.1: Check Cluster Status
# Connect to MySQL Router or any cluster member
mysql -h mysql-router.example.com -u admin -p -e "
SELECT * FROM performance_schema.replication_group_members;
"
# Identify PRIMARY and SECONDARY members
# Expected: One PRIMARY (ONLINE), multiple SECONDARYs (ONLINE)Step 1.2: Verify Primary Failure
# Check primary member status
mysql -h primary-mysql.example.com -u admin -p -e "SELECT 1;"
# If connection fails, primary is down
# Check Group Replication status
mysql -h secondary-mysql.example.com -u admin -p -e "
SHOW STATUS LIKE 'group_replication%';
"Phase 2: Automatic Failover (5-10 minutes)
Step 2.1: Verify Automatic Promotion
# InnoDB Cluster typically auto-promotes secondary
# Verify new primary election
mysql -h mysql-router.example.com -u admin -p -e "
SELECT member_host, member_role, member_state
FROM performance_schema.replication_group_members;
"
# Expected: New PRIMARY elected, old primary OFFLINE or UNREACHABLEStep 2.2: Verify Quorum
# Ensure cluster has majority quorum
mysql -h mysql-router.example.com -u admin -p -e "
SELECT COUNT(*) AS online_members
FROM performance_schema.replication_group_members
WHERE member_state = 'ONLINE';
"
# Expected: At least (N/2 + 1) members online for 3+ member cluster
# Example: 3 members = need 2 online, 5 members = need 3 onlinePhase 3: Application Validation (5 minutes)
Step 3.1: Test MySQL Router Connections
# MySQL Router automatically redirects to new primary
mysql -h mysql-router.example.com -u app_user -p -e "
SELECT @@hostname, @@read_only;
"
# Expected: read_only = 0 (writes enabled on new primary)Step 3.2: Verify Application Writes
# Test write operation
mysql -h mysql-router.example.com -u app_user -p -e "
INSERT INTO dr_test (failover_time) VALUES (NOW());
SELECT * FROM dr_test ORDER BY failover_time DESC LIMIT 1;
"
# Expected: Successful insertRollback Procedure
Use rollback when:
- Promoted secondary exhibits critical issues
- Data corruption detected on secondary
- Application failures persist after failover
Rollback Steps
Step 1: Assess Original Primary Recovery
# Check if original primary can be recovered
psql -h original-primary.example.com -U postgres -c "SELECT version();"
# If accessible, check data integrity
psql -h original-primary.example.com -U postgres -c "
SELECT pg_last_wal_receive_lsn();
"Step 2: Resynchronize Databases
# If original primary is behind, resync from promoted secondary
# Use pg_rewind for PostgreSQL
pg_rewind \
--target-pgdata=/var/lib/postgresql/14/main \
--source-server="host=promoted-secondary.example.com user=postgres"
# Restart original primary as replica
systemctl start postgresqlStep 3: Promote Original Primary
# Reverse failover procedure
pg_ctl promote -D /var/lib/postgresql/14/main
# Update DNS back to original primary
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '[ORIGINAL_DNS_CONFIG]'Post-Failover Tasks
Within 1 Hour:
- [ ] Update incident documentation with timeline
- [ ] Notify stakeholders of resolution
- [ ] Verify all monitoring alerts cleared
- [ ] Check application error rates returned to baseline
Within 24 Hours:
- [ ] Conduct incident retrospective
- [ ] Analyze root cause of primary failure
- [ ] Restore original replication topology (if desired)
- [ ] Rebuild or repair failed primary database
- [ ] Update runbook with lessons learned
Within 1 Week:
- [ ] Review and update RTO/RPO metrics
- [ ] Test failback procedure in staging environment
- [ ] Validate backup integrity post-failover
- [ ] Schedule follow-up DR drill
Troubleshooting
Issue: Secondary replication lag too high
Symptoms: Replication lag > 5 minutes, data loss unacceptable
Resolution:
# Check replication bandwidth
psql -h secondary-db.example.com -U postgres -c "
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())) AS lag_bytes;
"
# Identify slow queries blocking replay
psql -h secondary-db.example.com -U postgres -c "
SELECT pid, query, state, wait_event
FROM pg_stat_activity
WHERE backend_type = 'walreceiver';
"
# Options:
# 1. Wait for replication to catch up (if time permits)
# 2. Accept data loss and proceed with failover
# 3. Attempt primary recovery instead of failoverIssue: Promotion fails
Symptoms: pg_ctl promote returns error, database remains in recovery mode
Resolution:
# Check PostgreSQL logs
tail -n 100 /var/log/postgresql/postgresql-14-main.log
# Common causes:
# - WAL files missing: Restore from backup
# - Disk full: Expand storage
# - Permission issues: Check file ownership
# Manual promotion via trigger file
echo "promote" > /var/lib/postgresql/14/main/promote.signal
systemctl restart postgresqlIssue: Application cannot connect after failover
Symptoms: Connection timeouts, authentication failures
Resolution:
# Verify DNS resolution
dig primary-db.example.com
# Check firewall rules
nc -zv secondary-db.example.com 5432
# Verify pg_hba.conf allows connections
psql -h secondary-db.example.com -U postgres -c "
SHOW hba_file;
"
cat /etc/postgresql/14/main/pg_hba.conf | grep -v '^#'
# Reload configuration if needed
psql -h secondary-db.example.com -U postgres -c "SELECT pg_reload_conf();"RTO/RPO Tracking
Actual RTO Calculation:
RTO = Time(Service Restored) - Time(Failure Detected)
Example:
- Failure Detected: 10:00:00
- Service Restored: 10:23:45
- Actual RTO: 23 minutes 45 secondsActual RPO Calculation:
RPO = Replication Lag at Failover Time
Example:
- Last transaction on primary: 09:59:45
- Last replayed transaction on secondary: 09:59:30
- Actual RPO: 15 secondsRecord in Incident Report:
echo "=== Failover Metrics ===" >> /var/log/dr-failover.log
echo "Failure Detection Time: [TIMESTAMP]" >> /var/log/dr-failover.log
echo "Promotion Complete Time: [TIMESTAMP]" >> /var/log/dr-failover.log
echo "DNS Update Complete: [TIMESTAMP]" >> /var/log/dr-failover.log
echo "Service Restored Time: [TIMESTAMP]" >> /var/log/dr-failover.log
echo "Actual RTO: [MINUTES]" >> /var/log/dr-failover.log
echo "Actual RPO: [SECONDS]" >> /var/log/dr-failover.logAutomation Script
For automated failover execution, use:
/Users/antoncoleman/Documents/repos/ai-design-components/skills/planning-disaster-recovery/scripts/automated-db-failover.sh \
--primary primary-db.example.com \
--secondary secondary-db.example.com \
--verify-replication \
--update-dnsSee scripts/automated-db-failover.sh for implementation details.
Related Runbooks
- Region Failover:
examples/runbooks/region-failover.md - Kubernetes Recovery:
references/kubernetes-dr.md - Backup Restoration:
references/database-backups.md
References
- PostgreSQL High Availability Documentation: https://www.postgresql.org/docs/current/high-availability.html
- MySQL InnoDB Cluster: https://dev.mysql.com/doc/refman/8.0/en/mysql-innodb-cluster.html
- RTO/RPO Planning:
references/rto-rpo-planning.md
Multi-Region Failover Runbook
Overview
This runbook provides comprehensive procedures for executing multi-region failover during primary region failure scenarios. Covers complete infrastructure failover including databases, application servers, load balancers, and DNS management across AWS, GCP, and Azure.
Target RTO: 30-60 minutes (depends on architecture pattern) Target RPO: 5-30 minutes (depends on replication configuration)
When to Use This Runbook
Execute this runbook when:
- Entire primary region becomes unavailable (AWS outage, natural disaster)
- Network connectivity lost to primary region
- Multiple critical services failing simultaneously in primary region
- Primary region experiencing severe degradation affecting SLA
- Planned region migration or maintenance requiring full failover
Do NOT use for:
- Single service failures (use service-specific runbooks)
- Transient network issues (wait for recovery)
- Performance degradation without outage (use scaling procedures)
- Database-only issues (use database failover runbook)
Architecture Patterns
This runbook covers three common multi-region patterns:
Pattern 1: Active-Passive (Warm Standby)
- Primary region handles all traffic
- Secondary region has scaled-down infrastructure
- Database continuously replicated to secondary
- RTO: 30-60 minutes (requires scaling up secondary)
- RPO: 5-30 minutes (replication lag)
Pattern 2: Active-Active (Multi-Master)
- Both regions handle production traffic
- Traffic split via global load balancer
- Database supports multi-region writes
- RTO: < 5 minutes (automatic failover)
- RPO: < 1 minute (synchronous replication)
Pattern 3: Pilot Light
- Secondary region has minimal infrastructure (database only)
- Application servers provisioned during failover
- Most cost-effective, longest RTO
- RTO: 60-120 minutes (requires full provisioning)
- RPO: 5-30 minutes (replication lag)
Prerequisites
Required Access
- Cloud console admin access (AWS/GCP/Azure)
- DNS management (Route53/Cloud DNS/Azure DNS)
- Global load balancer access (CloudFront/Cloud CDN/Traffic Manager)
- Infrastructure-as-code repository access (Terraform/CloudFormation)
- Monitoring system access (Prometheus/DataDog/CloudWatch)
Required Tools
- Cloud CLI:
aws,gcloud, oraz - Terraform or CloudFormation CLI
kubectlfor Kubernetes managementdigornslookupfor DNS verificationcurlorwgetfor health checks
Pre-Failover Validation
Verify these conditions BEFORE initiating region failover:
- [ ] Secondary region infrastructure is operational
- [ ] Database replication lag is acceptable (< 5 minutes)
- [ ] DNS TTLs are lowered (recommended: 60 seconds)
- [ ] Monitoring dashboards accessible
- [ ] Incident communication channels active
- [ ] Stakeholders notified of impending failover
Decision Tree
Primary region completely unavailable?
├─ YES → Continue to Secondary Health Check
└─ NO → Evaluate service-specific failover
├─ Database only → Use database-failover.md
├─ Application only → Use application restart procedures
└─ Multiple services → Continue with region failover
Is secondary region healthy?
├─ YES → Continue to Replication Check
└─ NO → ESCALATE - Declare major incident, engage vendor support
Is database replication current (lag < 5 min)?
├─ YES → Proceed with failover
└─ NO → Evaluate data loss tolerance
├─ RPO acceptable → Proceed with failover
├─ RPO unacceptable → Attempt primary region recovery
└─ Unknown → STOP - Investigate replication status
Is this planned maintenance?
├─ YES → Use controlled switchover (additional testing steps)
└─ NO → Use emergency failover (prioritize speed)
What architecture pattern?
├─ Active-Active → Execute Pattern A procedure
├─ Active-Passive → Execute Pattern B procedure
└─ Pilot Light → Execute Pattern C procedureProcedure A: Active-Passive Failover (Warm Standby)
Phase 1: Assessment and Preparation (10 minutes)
Step 1.1: Verify Primary Region Failure
# Test primary region endpoints
PRIMARY_REGION="us-east-1"
SECONDARY_REGION="us-west-2"
# Check multiple services in primary region
for service in api.example.com db.example.com admin.example.com; do
echo "Testing $service..."
curl -f -m 10 "https://$service/health" 2>&1 || echo "FAILED: $service"
done
# Check AWS service health
aws health describe-events \
--filter eventTypeCategories=issue \
--region $PRIMARY_REGION
# Expected: Multiple service failures, possible AWS service eventsStep 1.2: Assess Secondary Region Health
# Verify secondary region infrastructure
aws ec2 describe-instances \
--region $SECONDARY_REGION \
--filters "Name=tag:Environment,Values=production" "Name=instance-state-name,Values=running" \
--query 'Reservations[].Instances[?State.Name==`running`].InstanceId' \
--output table
# Check database replication status
aws rds describe-db-instances \
--region $SECONDARY_REGION \
--db-instance-identifier prod-db-secondary \
--query 'DBInstances[0].[DBInstanceStatus,StatusInfos]'
# Expected: Instances running, database availableStep 1.3: Check Database Replication Lag
# For PostgreSQL
psql -h secondary-db.$SECONDARY_REGION.example.com -U postgres -c "
SELECT
CASE WHEN pg_is_in_recovery() THEN 'REPLICA' ELSE 'PRIMARY' END AS role,
pg_last_wal_receive_lsn() AS received,
pg_last_wal_replay_lsn() AS replayed,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds;
"
# Record lag_seconds - if > 300 (5 min), evaluate RPO tolerance
LAG_SECONDS=<RECORDED_VALUE>
echo "Replication lag: $LAG_SECONDS seconds" >> /var/log/region-failover.logStep 1.4: Document Failover Initiation
# Create incident log
FAILOVER_LOG="/var/log/region-failover-$(date +%Y%m%d-%H%M%S).log"
cat <<EOF > $FAILOVER_LOG
=== REGION FAILOVER INITIATED ===
Timestamp: $(date -Iseconds)
Operator: $(whoami)
Primary Region: $PRIMARY_REGION (FAILED)
Secondary Region: $SECONDARY_REGION (ACTIVE)
Replication Lag: $LAG_SECONDS seconds
Architecture: Active-Passive (Warm Standby)
EOFPhase 2: Database Failover (15 minutes)
Step 2.1: Stop Application Traffic to Database
# Scale down application servers in primary region (if accessible)
aws autoscaling set-desired-capacity \
--auto-scaling-group-name prod-app-asg-$PRIMARY_REGION \
--desired-capacity 0 \
--region $PRIMARY_REGION 2>/dev/null || echo "Primary region unreachable"
# Wait for in-flight transactions to complete
sleep 30Step 2.2: Promote Secondary Database
# AWS RDS - Promote read replica
aws rds promote-read-replica \
--db-instance-identifier prod-db-secondary \
--backup-retention-period 7 \
--region $SECONDARY_REGION
# Wait for promotion (typically 5-10 minutes)
echo "Waiting for database promotion..."
while true; do
STATUS=$(aws rds describe-db-instances \
--region $SECONDARY_REGION \
--db-instance-identifier prod-db-secondary \
--query 'DBInstances[0].DBInstanceStatus' \
--output text)
echo "Database status: $STATUS"
if [[ "$STATUS" == "available" ]]; then
echo "$(date -Iseconds): Database promoted successfully" >> $FAILOVER_LOG
break
elif [[ "$STATUS" == "failed" ]]; then
echo "ERROR: Database promotion failed" >> $FAILOVER_LOG
exit 1
fi
sleep 15
doneStep 2.3: Verify Database Write Capability
# Test write operations on promoted database
psql -h prod-db-secondary.$SECONDARY_REGION.example.com -U postgres -c "
-- Verify read-only mode disabled
SHOW transaction_read_only;
-- Test write operation
CREATE TABLE IF NOT EXISTS dr_region_failover_test (
id SERIAL PRIMARY KEY,
failover_time TIMESTAMP DEFAULT now(),
source_region VARCHAR(50)
);
INSERT INTO dr_region_failover_test (source_region)
VALUES ('$SECONDARY_REGION');
SELECT * FROM dr_region_failover_test ORDER BY id DESC LIMIT 1;
"
# Expected: transaction_read_only = off, successful insertPhase 3: Scale Up Secondary Infrastructure (15 minutes)
Step 3.1: Scale Application Servers
# Increase auto-scaling group capacity to production levels
# Warm standby typically runs at 50% capacity, scale to 100%
# Get current capacity
CURRENT_CAPACITY=$(aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names prod-app-asg-$SECONDARY_REGION \
--region $SECONDARY_REGION \
--query 'AutoScalingGroups[0].DesiredCapacity' \
--output text)
TARGET_CAPACITY=$((CURRENT_CAPACITY * 2))
echo "Scaling from $CURRENT_CAPACITY to $TARGET_CAPACITY instances" >> $FAILOVER_LOG
# Scale up
aws autoscaling set-desired-capacity \
--auto-scaling-group-name prod-app-asg-$SECONDARY_REGION \
--desired-capacity $TARGET_CAPACITY \
--region $SECONDARY_REGION
# Wait for instances to become healthy (5-10 minutes)
echo "Waiting for instances to become healthy..."
for i in {1..30}; do
HEALTHY=$(aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names prod-app-asg-$SECONDARY_REGION \
--region $SECONDARY_REGION \
--query 'AutoScalingGroups[0].Instances[?HealthStatus==`Healthy`] | length(@)' \
--output text)
echo "Attempt $i: $HEALTHY healthy instances (target: $TARGET_CAPACITY)"
if [[ $HEALTHY -ge $TARGET_CAPACITY ]]; then
echo "$(date -Iseconds): All instances healthy" >> $FAILOVER_LOG
break
fi
sleep 20
doneStep 3.2: Verify Application Health
# Test application endpoints in secondary region
SECONDARY_LB="app-lb.$SECONDARY_REGION.example.com"
for endpoint in /health /api/v1/status /api/v1/users/healthcheck; do
echo "Testing $endpoint..."
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "https://$SECONDARY_LB$endpoint")
if [[ $RESPONSE == "200" ]]; then
echo "✓ $endpoint: OK"
else
echo "✗ $endpoint: FAILED (HTTP $RESPONSE)"
fi
doneStep 3.3: Update Application Configuration
# Update environment variables to point to new database
# Using Kubernetes ConfigMap example
kubectl set env deployment/api \
--namespace production \
DATABASE_HOST=prod-db-secondary.$SECONDARY_REGION.example.com \
ACTIVE_REGION=$SECONDARY_REGION \
--record
# Restart pods to pick up new configuration
kubectl rollout restart deployment/api -n production
# Wait for rollout to complete
kubectl rollout status deployment/api -n production --timeout=300sPhase 4: DNS and Traffic Cutover (10 minutes)
Step 4.1: Lower DNS TTL (if not already done)
# Check current TTL
dig api.example.com +noall +answer
# If TTL > 60, update to 60 seconds and wait for propagation
# (This step should be done proactively before disasters)
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"TTL": 60,
"ResourceRecords": [{"Value": "PRIMARY_IP"}]
}
}]
}'Step 4.2: Update DNS Records to Secondary Region
# Get secondary load balancer IP/DNS
SECONDARY_LB_DNS=$(aws elbv2 describe-load-balancers \
--region $SECONDARY_REGION \
--names prod-app-lb \
--query 'LoadBalancers[0].DNSName' \
--output text)
echo "Secondary LB: $SECONDARY_LB_DNS" >> $FAILOVER_LOG
# Update Route53 to point to secondary region
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch "{
\"Changes\": [{
\"Action\": \"UPSERT\",
\"ResourceRecordSet\": {
\"Name\": \"api.example.com\",
\"Type\": \"CNAME\",
\"TTL\": 60,
\"ResourceRecords\": [{\"Value\": \"$SECONDARY_LB_DNS\"}]
}
}]
}"
# Record DNS change
echo "$(date -Iseconds): DNS updated to secondary region" >> $FAILOVER_LOGStep 4.3: Verify DNS Propagation
# Check DNS resolution from multiple locations
for resolver in 8.8.8.8 1.1.1.1 208.67.222.222; do
echo "Checking resolver: $resolver"
dig @$resolver api.example.com +short
done
# Test from public DNS
for i in {1..12}; do
RESOLVED=$(dig +short api.example.com)
echo "Attempt $i: $RESOLVED"
if [[ "$RESOLVED" == *"$SECONDARY_REGION"* ]] || [[ "$RESOLVED" == "$SECONDARY_LB_DNS" ]]; then
echo "✓ DNS propagated successfully"
break
fi
sleep 10
donePhase 5: Validation and Monitoring (10 minutes)
Step 5.1: Test Critical User Journeys
# Test authentication
curl -X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"test123"}' \
-w "\nHTTP Status: %{http_code}\n"
# Test read operation
curl -X GET https://api.example.com/api/v1/users/1 \
-H "Authorization: Bearer $TOKEN" \
-w "\nHTTP Status: %{http_code}\n"
# Test write operation
curl -X POST https://api.example.com/api/v1/test \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"test":"region-failover"}' \
-w "\nHTTP Status: %{http_code}\n"
# Expected: All operations return 200 OKStep 5.2: Verify Metrics and Monitoring
# Check application error rates
curl -s "http://prometheus.example.com/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])"
# Check database connection pool
psql -h prod-db-secondary.$SECONDARY_REGION.example.com -U postgres -c "
SELECT
count(*) AS total_connections,
state,
wait_event_type
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state, wait_event_type
ORDER BY total_connections DESC;
"
# Check application response times
curl -s "http://prometheus.example.com/api/v1/query?query=histogram_quantile(0.95,rate(http_request_duration_seconds_bucket[5m]))"Step 5.3: Verify Data Integrity
# Compare row counts (if primary region accessible for comparison)
psql -h prod-db-secondary.$SECONDARY_REGION.example.com -U postgres -d production -c "
SELECT
schemaname,
tablename,
n_live_tup AS row_count
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_live_tup DESC
LIMIT 20;
"
# Check for replication gaps (if applicable)
psql -h prod-db-secondary.$SECONDARY_REGION.example.com -U postgres -c "
SELECT * FROM dr_region_failover_test ORDER BY id DESC LIMIT 5;
"Procedure B: Active-Active Failover
Phase 1: Assessment (5 minutes)
Step 1.1: Verify Primary Region Failure
# Check global load balancer health
aws globalaccelerator describe-accelerator \
--accelerator-arn arn:aws:globalaccelerator::123456789012:accelerator/abc123
# Check endpoint group health
aws globalaccelerator describe-endpoint-group \
--endpoint-group-arn arn:aws:globalaccelerator::123456789012:accelerator/abc123/listener/xyz/endpoint-group/def456Step 1.2: Verify Secondary Region Capacity
# Both regions already handle production traffic
# Check if secondary can handle 100% load
SECONDARY_CAPACITY=$(aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names prod-app-asg-$SECONDARY_REGION \
--region $SECONDARY_REGION \
--query 'AutoScalingGroups[0].DesiredCapacity')
# Verify sufficient capacity (typically 50% can scale to 100%)
echo "Secondary region current capacity: $SECONDARY_CAPACITY"Phase 2: Traffic Rerouting (5 minutes)
Step 2.1: Update Global Load Balancer
# Remove primary region endpoints
aws globalaccelerator update-endpoint-group \
--endpoint-group-arn arn:aws:globalaccelerator::123456789012:accelerator/abc123/listener/xyz/endpoint-group/def456 \
--endpoint-configurations '[
{
"EndpointId": "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/prod-lb/xyz",
"Weight": 100,
"ClientIPPreservationEnabled": true
}
]'
# Traffic now flows 100% to secondary region
echo "$(date -Iseconds): Traffic rerouted to secondary region" >> $FAILOVER_LOGStep 2.2: Scale Secondary Region
# Auto-scaling should handle increased load automatically
# Monitor for 5 minutes to ensure scaling triggers
for i in {1..10}; do
CURRENT=$(aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names prod-app-asg-$SECONDARY_REGION \
--region $SECONDARY_REGION \
--query 'AutoScalingGroups[0].[DesiredCapacity,Instances[?HealthStatus==`Healthy`]|length(@)]' \
--output text)
echo "Iteration $i: Desired/Healthy - $CURRENT"
sleep 30
donePhase 3: Validation (10 minutes)
Step 3.1: Monitor Application Performance
# Check error rates
curl "http://prometheus.example.com/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])"
# Check response times
curl "http://prometheus.example.com/api/v1/query?query=histogram_quantile(0.95,rate(http_request_duration_seconds_bucket[5m]))"
# Check CPU utilization
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=AutoScalingGroupName,Value=prod-app-asg-$SECONDARY_REGION \
--region $SECONDARY_REGION \
--start-time $(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics AverageProcedure C: Pilot Light Failover
Phase 1: Provision Infrastructure (30-45 minutes)
Step 1.1: Deploy Infrastructure via IaC
# Use Terraform to provision secondary region
cd /infrastructure/terraform/secondary-region
# Initialize and plan
terraform init
terraform plan -var="region=$SECONDARY_REGION" -var="environment=production"
# Apply with auto-approve (emergency failover)
terraform apply -auto-approve -var="region=$SECONDARY_REGION" -var="environment=production"
# Record deployment
echo "$(date -Iseconds): Infrastructure provisioned in $SECONDARY_REGION" >> $FAILOVER_LOGStep 1.2: Deploy Application
# Deploy using CI/CD pipeline or kubectl
kubectl apply -f /manifests/production/ -n production
# Wait for deployments
kubectl wait --for=condition=available --timeout=600s \
deployment --all -n productionStep 1.3: Follow Active-Passive Procedure
# Continue with Phase 2 of Active-Passive procedure (database failover)
# Then Phase 3 (scale up), Phase 4 (DNS), Phase 5 (validation)Rollback Procedure
Use rollback when:
- Secondary region exhibits critical issues post-failover
- Primary region becomes available and stable
- Data integrity issues discovered in secondary
Rollback Steps
Step 1: Verify Primary Region Recovery
# Check AWS service health
aws health describe-events \
--filter eventTypeCategories=issue \
--region $PRIMARY_REGION
# Test primary region endpoints
for service in api.example.com db.example.com; do
curl -f -m 10 "https://$service/health"
doneStep 2: Restore Primary Database
# Recreate replication from secondary to primary
aws rds create-db-instance-read-replica \
--db-instance-identifier prod-db-primary-restored \
--source-db-instance-identifier prod-db-secondary \
--region $PRIMARY_REGION
# Wait for replication to catch up
# Then promote primaryStep 3: Reverse Traffic Cutover
# Update DNS back to primary region
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890ABC \
--change-batch "{
\"Changes\": [{
\"Action\": \"UPSERT\",
\"ResourceRecordSet\": {
\"Name\": \"api.example.com\",
\"Type\": \"CNAME\",
\"TTL\": 60,
\"ResourceRecords\": [{\"Value\": \"PRIMARY_LB_DNS\"}]
}
}]
}"Post-Failover Tasks
Immediate (Within 1 Hour):
- [ ] Update status page with resolution
- [ ] Notify all stakeholders
- [ ] Verify all monitoring alerts cleared
- [ ] Document actual RTO/RPO achieved
- [ ] Create incident timeline
Short-term (Within 24 Hours):
- [ ] Conduct incident retrospective
- [ ] Analyze root cause of region failure
- [ ] Review cloud provider incident reports
- [ ] Update runbook with lessons learned
- [ ] Test rollback procedure in non-production
Long-term (Within 1 Week):
- [ ] Restore original architecture (if Active-Passive)
- [ ] Rebuild primary region infrastructure
- [ ] Re-establish cross-region replication
- [ ] Review and update RTO/RPO targets
- [ ] Schedule follow-up DR drill
- [ ] Update disaster recovery documentation
Troubleshooting
Issue: Secondary region cannot handle full load
Symptoms: High CPU, response time degradation, connection errors
Resolution:
# Emergency scale-up
aws autoscaling set-desired-capacity \
--auto-scaling-group-name prod-app-asg-$SECONDARY_REGION \
--desired-capacity 50 \
--region $SECONDARY_REGION
# Add more instance types if capacity exhausted
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name prod-app-asg-$SECONDARY_REGION \
--region $SECONDARY_REGION \
--mixed-instances-policy '{
"InstancesDistribution": {"OnDemandPercentageAboveBaseCapacity": 100},
"LaunchTemplate": {"LaunchTemplateSpecification": {...},
"Overrides": [
{"InstanceType": "c5.2xlarge"},
{"InstanceType": "c5.4xlarge"},
{"InstanceType": "m5.2xlarge"}
]}
}'Issue: Database replication lag too high
Symptoms: Secondary database minutes or hours behind primary
Resolution:
# Check replication status
aws rds describe-db-instances \
--db-instance-identifier prod-db-secondary \
--region $SECONDARY_REGION \
--query 'DBInstances[0].StatusInfos'
# Options:
# 1. Wait for replication to catch up (if time permits)
# 2. Accept data loss and promote (document RPO breach)
# 3. Restore from latest backup if replication brokenIssue: DNS not propagating
Symptoms: Some users still hitting primary region, mixed results
Resolution:
# Check DNS propagation globally
for server in 8.8.8.8 1.1.1.1 208.67.222.222 4.2.2.2; do
echo "Nameserver $server:"
dig @$server api.example.com +short
done
# Flush CloudFlare cache if using CDN
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"purge_everything":true}'
# If TTL too high, users must wait for expiration
# Consider implementing client-side failover logicIssue: Application cannot reach secondary database
Symptoms: Database connection errors, authentication failures
Resolution:
# Check security groups
aws ec2 describe-security-groups \
--region $SECONDARY_REGION \
--group-ids sg-xyz123 \
--query 'SecurityGroups[0].IpPermissions'
# Add application security group to database security group
aws ec2 authorize-security-group-ingress \
--region $SECONDARY_REGION \
--group-id sg-database \
--source-group sg-application \
--protocol tcp \
--port 5432
# Verify network connectivity
kubectl run -it --rm debug --image=postgres:15 --restart=Never -- \
psql -h prod-db-secondary.$SECONDARY_REGION.example.com -U postgres -c "SELECT 1"RTO/RPO Tracking
Calculate Actual Metrics:
# Extract timestamps from failover log
FAILURE_TIME=$(grep "REGION FAILOVER INITIATED" $FAILOVER_LOG | awk '{print $3}')
TRAFFIC_RESTORED=$(grep "DNS updated to secondary region" $FAILOVER_LOG | awk '{print $3}')
SERVICE_VALIDATED=$(grep "All instances healthy" $FAILOVER_LOG | awk '{print $3}')
# Calculate RTO
echo "=== Failover Metrics ===" >> $FAILOVER_LOG
echo "Failure Detection: $FAILURE_TIME" >> $FAILOVER_LOG
echo "Traffic Cutover: $TRAFFIC_RESTORED" >> $FAILOVER_LOG
echo "Service Validated: $SERVICE_VALIDATED" >> $FAILOVER_LOG
# RPO from replication lag
echo "Replication Lag (RPO): $LAG_SECONDS seconds" >> $FAILOVER_LOGAutomation Scripts
Automated region failover:
/Users/antoncoleman/Documents/repos/ai-design-components/skills/planning-disaster-recovery/scripts/automated-region-failover.sh \
--primary-region us-east-1 \
--secondary-region us-west-2 \
--architecture active-passive \
--verify-healthDR drill execution:
/Users/antoncoleman/Documents/repos/ai-design-components/skills/planning-disaster-recovery/scripts/dr-drill.sh \
--environment staging \
--test-type regionRelated Runbooks
- Database Failover:
examples/runbooks/database-failover.md - Kubernetes Recovery:
references/kubernetes-dr.md - Cloud DR Patterns:
references/cloud-dr-patterns.md - Cross-Region Replication:
references/cross-region-replication.md
References
- AWS Multi-Region Architecture: https://aws.amazon.com/solutions/implementations/disaster-recovery/
- GCP Disaster Recovery: https://cloud.google.com/architecture/dr-scenarios-planning-guide
- Azure Site Recovery: https://docs.microsoft.com/azure/site-recovery/
- RTO/RPO Planning Guide:
references/rto-rpo-planning.md
skill: "planning-disaster-recovery"
version: "1.0"
domain: "infrastructure"
# Base outputs required for all disaster recovery implementations
base_outputs:
- path: "docs/disaster-recovery-plan.md"
must_contain: ["RTO", "RPO", "Recovery Objectives"]
reason: "Comprehensive DR plan documenting RTO/RPO targets and recovery procedures"
- path: "runbooks/"
must_contain: []
reason: "Step-by-step operational procedures for DR scenarios"
- path: "backups/"
must_contain: []
reason: "Backup configuration and retention policies"
- path: "monitoring/backup-alerts.yaml"
must_contain: ["alert:", "backup"]
reason: "Alerting rules for backup failures and violations"
# Conditional outputs based on configuration
conditional_outputs:
maturity:
starter:
- path: "docs/rto-rpo-requirements.md"
must_contain: ["RTO >", "RPO >", "Tier"]
reason: "Basic RTO/RPO definitions with criticality tiers"
- path: "backups/backup-schedule.sh"
must_contain: ["cron", "backup"]
reason: "Simple cron-based backup scheduling"
- path: "runbooks/backup-restore-basic.md"
must_contain: ["restore", "steps"]
reason: "Basic backup and restore procedures"
- path: "scripts/validate-backup.sh"
must_contain: ["#!/bin/bash", "backup"]
reason: "Manual backup validation script"
intermediate:
- path: "docs/disaster-recovery-plan.md"
must_contain: ["RTO", "RPO", "failover", "testing"]
reason: "Full DR plan with failover procedures and testing schedule"
- path: "runbooks/database-failover.md"
must_contain: ["Phase", "promotion", "DNS", "validation"]
reason: "Detailed database failover runbook with phases"
- path: "runbooks/region-failover.md"
must_contain: ["cross-region", "replication", "DNS"]
reason: "Region-level failover procedures"
- path: "backups/retention-policy.yaml"
must_contain: ["daily:", "weekly:", "monthly:"]
reason: "Tiered retention policy configuration"
- path: "scripts/test-restore.sh"
must_contain: ["restore", "verify", "staging"]
reason: "Automated restore testing to staging environment"
- path: "scripts/dr-drill.sh"
must_contain: ["failover", "test", "environment"]
reason: "Automated DR drill execution script"
- path: "monitoring/backup-dashboard.json"
must_contain: ["backup", "metrics", "panel"]
reason: "Backup monitoring dashboard (Grafana/CloudWatch)"
advanced:
- path: "docs/disaster-recovery-plan.md"
must_contain: ["RTO", "RPO", "Active-Active", "chaos", "compliance"]
reason: "Enterprise DR plan with chaos testing and compliance mapping"
- path: "runbooks/database-failover.md"
must_contain: ["automated", "promotion", "rollback", "metrics"]
reason: "Production-grade failover runbook with automation hooks"
- path: "runbooks/region-failover.md"
must_contain: ["multi-region", "traffic", "DNS", "automated"]
reason: "Multi-region failover with traffic shifting automation"
- path: "chaos/db-failover-test.sh"
must_contain: ["#!/bin/bash", "fail", "primary", "measure"]
reason: "Chaos engineering test for database failover validation"
- path: "chaos/region-failure-test.sh"
must_contain: ["#!/bin/bash", "region", "network", "failover"]
reason: "Chaos test simulating full region failure"
- path: "scripts/automated-db-failover.sh"
must_contain: ["promote", "verify-replication", "update-dns"]
reason: "Fully automated database failover execution"
- path: "scripts/generate-dr-report.sh"
must_contain: ["compliance", "report", "metrics"]
reason: "Automated DR compliance reporting"
- path: "scripts/check-retention.sh"
must_contain: ["retention", "policy", "violations"]
reason: "Automated retention policy compliance checking"
- path: "monitoring/backup-alerts.yaml"
must_contain: ["VeleroBackupFailed", "BackupTooOld", "RPO"]
reason: "Comprehensive Prometheus alerting rules"
- path: "monitoring/dr-metrics-dashboard.json"
must_contain: ["RTO", "RPO", "failover_time", "backup_success_rate"]
reason: "Advanced DR metrics dashboard"
- path: "docs/chaos-engineering-plan.md"
must_contain: ["scenarios", "monthly", "gameday"]
reason: "Chaos engineering testing plan and schedule"
- path: "docs/compliance-mapping.md"
must_contain: ["GDPR", "SOC 2", "HIPAA", "retention"]
reason: "DR compliance requirements mapping"
cloud_provider:
aws:
- path: "backups/aws/rds-backup-config.tf"
must_contain: ["aws_db_instance", "backup_retention_period", "backup_window"]
reason: "RDS automated backup configuration"
- path: "backups/aws/s3-replication.tf"
must_contain: ["aws_s3_bucket_replication_configuration", "cross-region"]
reason: "S3 Cross-Region Replication for backup storage"
- path: "backups/aws/aurora-global-db.tf"
must_contain: ["aws_rds_global_cluster", "source_db_cluster_identifier"]
reason: "Aurora Global Database for active-passive replication"
- path: "runbooks/aws-rds-failover.md"
must_contain: ["aws rds failover", "Multi-AZ", "promotion"]
reason: "AWS RDS-specific failover procedures"
- path: "scripts/aws-backup-validation.sh"
must_contain: ["aws rds describe-db-snapshots", "BackupRetentionPeriod"]
reason: "AWS backup validation and compliance check"
gcp:
- path: "backups/gcp/cloud-sql-backup.tf"
must_contain: ["google_sql_database_instance", "backup_configuration", "point_in_time_recovery"]
reason: "Cloud SQL backup and PITR configuration"
- path: "backups/gcp/gcs-replication.tf"
must_contain: ["google_storage_bucket", "storage_class", "MULTI_REGIONAL"]
reason: "GCS multi-regional bucket for backup storage"
- path: "runbooks/gcp-cloud-sql-failover.md"
must_contain: ["gcloud sql instances failover", "replica", "HA"]
reason: "GCP Cloud SQL failover procedures"
- path: "scripts/gcp-backup-validation.sh"
must_contain: ["gcloud sql backups list", "PITR"]
reason: "GCP backup validation script"
azure:
- path: "backups/azure/sql-backup.tf"
must_contain: ["azurerm_mssql_database", "short_term_retention_policy", "long_term_retention_policy"]
reason: "Azure SQL backup retention configuration"
- path: "backups/azure/storage-replication.tf"
must_contain: ["azurerm_storage_account", "account_replication_type", "GRS"]
reason: "Geo-redundant storage for backup replication"
- path: "backups/azure/site-recovery.tf"
must_contain: ["azurerm_site_recovery", "recovery_vault", "replication_policy"]
reason: "Azure Site Recovery for VM replication"
- path: "runbooks/azure-sql-failover.md"
must_contain: ["az sql db replica create", "failover group", "geo-replication"]
reason: "Azure SQL Database failover procedures"
multi-cloud:
- path: "backups/multi-cloud/backup-strategy.md"
must_contain: ["AWS", "GCP", "Azure", "cross-cloud"]
reason: "Multi-cloud backup strategy and considerations"
- path: "backups/multi-cloud/restic-config.yaml"
must_contain: ["repository:", "aws", "gcs", "azure"]
reason: "Restic configuration for multi-cloud backups"
- path: "scripts/multi-cloud-dr-validation.sh"
must_contain: ["aws", "gcloud", "az", "backup"]
reason: "Cross-cloud DR validation script"
infrastructure:
kubernetes:
- path: "backups/kubernetes/velero-config.yaml"
must_contain: ["Velero", "BackupStorageLocation", "schedule"]
reason: "Velero backup configuration for K8s cluster backup"
- path: "backups/kubernetes/velero-schedules.yaml"
must_contain: ["Schedule", "daily", "production"]
reason: "Scheduled backup policies for namespaces and PVs"
- path: "backups/kubernetes/etcd-backup-cronjob.yaml"
must_contain: ["CronJob", "etcdctl", "snapshot"]
reason: "etcd control plane backup automation"
- path: "runbooks/kubernetes-restore.md"
must_contain: ["velero restore", "namespace", "PersistentVolume"]
reason: "Kubernetes cluster and namespace restore procedures"
- path: "scripts/velero-backup-validation.sh"
must_contain: ["velero backup describe", "Completed"]
reason: "Velero backup validation script"
docker:
- path: "backups/docker/volume-backup.sh"
must_contain: ["docker run", "volume", "tar"]
reason: "Docker volume backup script"
- path: "backups/docker/backup-schedule.sh"
must_contain: ["docker-compose", "backup"]
reason: "Docker Compose application backup automation"
database:
postgresql:
- path: "backups/postgresql/pgbackrest.conf"
must_contain: ["[global]", "repo1-path", "repo1-retention"]
reason: "pgBackRest configuration for PostgreSQL backups"
- path: "backups/postgresql/pgbackrest-schedule.sh"
must_contain: ["pgbackrest", "full", "diff", "incr"]
reason: "Scheduled full, differential, and incremental backups"
- path: "backups/postgresql/walg-config.sh"
must_contain: ["WAL_G", "WALG_S3_PREFIX", "WALG_DELTA_MAX_STEPS"]
reason: "WAL-G configuration for continuous WAL archiving"
- path: "runbooks/postgresql-pitr-restore.md"
must_contain: ["pgbackrest restore", "recovery_target_time", "PITR"]
reason: "PostgreSQL point-in-time recovery procedures"
mysql:
- path: "backups/mysql/xtrabackup-config.cnf"
must_contain: ["[xtrabackup]", "parallel", "compress"]
reason: "Percona XtraBackup configuration"
- path: "backups/mysql/xtrabackup-schedule.sh"
must_contain: ["xtrabackup --backup", "full", "incremental"]
reason: "Scheduled MySQL hot backups"
- path: "runbooks/mysql-restore.md"
must_contain: ["xtrabackup --prepare", "xtrabackup --copy-back", "binlog"]
reason: "MySQL backup restore and PITR procedures"
mongodb:
- path: "backups/mongodb/mongodump-schedule.sh"
must_contain: ["mongodump", "gzip", "oplog"]
reason: "MongoDB logical backup with oplog for PITR"
- path: "backups/mongodb/atlas-backup-config.json"
must_contain: ["clusterName", "retentionInDays", "continuousBackupEnabled"]
reason: "MongoDB Atlas continuous backup configuration"
# Scaffolding files that should be created as starting points
scaffolding:
- path: "docs/"
reason: "Documentation directory for DR plans and procedures"
- path: "runbooks/"
reason: "Operational runbooks for various DR scenarios"
- path: "backups/"
reason: "Backup configuration and policies"
- path: "scripts/"
reason: "Automation scripts for backup, restore, and DR testing"
- path: "monitoring/"
reason: "Monitoring configurations, alerts, and dashboards"
- path: "chaos/"
reason: "Chaos engineering tests for DR validation"
- path: "docs/disaster-recovery-plan.md"
reason: "Main DR plan document template"
- path: "docs/rto-rpo-requirements.md"
reason: "Document defining recovery objectives by system tier"
- path: "backups/README.md"
reason: "Document backup strategy and configuration"
- path: "runbooks/README.md"
reason: "Index of available DR runbooks"
- path: "scripts/README.md"
reason: "Document automation scripts and usage"
- path: ".gitignore"
reason: "Exclude backup files, temporary restore data, and logs"
# Metadata
metadata:
primary_blueprints: ["cloud", "k8s", "data-pipeline"]
contributes_to:
- "Disaster recovery plan"
- "Backup and restore procedures"
- "Business continuity planning"
- "Compliance requirements (GDPR, SOC 2, HIPAA)"
- "High availability architecture"
- "Chaos engineering validation"
common_patterns:
- "3-2-1 backup rule (3 copies, 2 media types, 1 offsite)"
- "RTO/RPO tiering by criticality (Tier 0: <1hr, Tier 1: 1-4hr, Tier 2: 4-24hr)"
- "Continuous WAL/binlog archiving for database PITR"
- "Cross-region replication (Active-Active, Active-Passive, Warm Standby, Pilot Light)"
- "Automated failover with DNS updates"
- "Monthly DR drills and chaos testing"
- "Immutable backups for ransomware protection"
- "Compliance-driven retention policies"
integration_points:
infrastructure: "Provisions backup infrastructure (S3, GCS, storage accounts)"
databases: "Configures database-specific backup tools (pgBackRest, XtraBackup)"
kubernetes: "Velero for cluster/PV backups, etcd snapshots"
observability: "Monitors backup jobs, tracks RTO/RPO metrics, alerts on failures"
security: "Encrypts backups, manages backup credentials, immutable storage"
compliance: "Meets regulatory retention requirements (GDPR, SOC 2, HIPAA)"
typical_directory_structure: |
project/
├── docs/
│ ├── disaster-recovery-plan.md # Main DR plan
│ ├── rto-rpo-requirements.md # Recovery objectives
│ ├── chaos-engineering-plan.md # Testing strategy
│ └── compliance-mapping.md # Regulatory requirements
├── runbooks/
│ ├── database-failover.md # DB failover procedures
│ ├── region-failover.md # Multi-region failover
│ ├── kubernetes-restore.md # K8s restore procedures
│ └── README.md # Runbook index
├── backups/
│ ├── postgresql/
│ │ ├── pgbackrest.conf # pgBackRest config
│ │ └── pgbackrest-schedule.sh # Backup schedules
│ ├── kubernetes/
│ │ ├── velero-config.yaml # Velero setup
│ │ └── velero-schedules.yaml # Backup schedules
│ ├── aws/ # AWS-specific backups
│ ├── gcp/ # GCP-specific backups
│ ├── azure/ # Azure-specific backups
│ └── retention-policy.yaml # Retention rules
├── scripts/
│ ├── validate-backup.sh # Backup integrity check
│ ├── test-restore.sh # Automated restore test
│ ├── dr-drill.sh # Full DR drill automation
│ ├── check-retention.sh # Retention compliance
│ ├── generate-dr-report.sh # Compliance reporting
│ └── automated-db-failover.sh # Automated failover
├── chaos/
│ ├── db-failover-test.sh # Database failover chaos test
│ └── region-failure-test.sh # Region failure simulation
└── monitoring/
├── backup-alerts.yaml # Prometheus alerts
└── dr-metrics-dashboard.json # Grafana dashboard
database_backup_tools:
postgresql:
primary: "pgBackRest"
alternative: "WAL-G"
features: "PITR, compression, multi-repo, S3/GCS/Azure"
mysql:
primary: "Percona XtraBackup"
alternative: "WAL-G"
features: "Hot backups, incremental, parallel processing"
mongodb:
primary: "MongoDB Atlas Backup"
alternative: "mongodump"
features: "Continuous backup, PITR, automated"
kubernetes_backup_tools:
cluster:
primary: "Velero"
features: "PV snapshots, namespace backup, scheduling, selective restore"
control_plane:
primary: "etcdctl snapshot"
features: "etcd backup for cluster state recovery"
cross_region_patterns:
active_active:
rto: "< 1 minute"
rpo: "< 1 minute"
cost: "High"
use_case: "Both regions serve production traffic"
active_passive:
rto: "15-60 minutes"
rpo: "5-15 minutes"
cost: "Medium"
use_case: "Standby region for failover"
pilot_light:
rto: "10-30 minutes"
rpo: "5-15 minutes"
cost: "Low"
use_case: "Minimal secondary infrastructure"
warm_standby:
rto: "5-15 minutes"
rpo: "5-15 minutes"
cost: "Medium-High"
use_case: "Scaled-down secondary ready to scale up"
compliance_retention:
gdpr:
retention: "1-7 years"
requirements: "EU data residency, right to erasure"
soc2:
retention: "1 year+"
requirements: "Secure deletion, access controls, audit logs"
hipaa:
retention: "6 years"
requirements: "Encryption, PHI protection, secure deletion"
pci_dss:
retention: "3 months - 1 year"
requirements: "Secure deletion, quarterly reviews"
validation_checks:
- "RTO/RPO defined for all critical systems"
- "Backup schedules match RPO requirements"
- "Restore tested monthly (critical systems)"
- "Backups encrypted at rest and in transit"
- "3-2-1 backup rule implemented"
- "Cross-region replication configured (production)"
- "Retention policies meet compliance requirements"
- "Immutable backups enabled (ransomware protection)"
- "DR runbooks documented and accessible"
- "Chaos tests scheduled and executed"
- "Monitoring alerts for backup failures"
- "Failover automation tested quarterly"
Chaos Engineering for DR Validation
Chaos Engineering Principles
1. Start Small: Begin in staging with limited blast radius 2. Hypothesize: Define expected behavior before experiment 3. Measure: Quantify impact (latency, error rate, availability) 4. Automate: Integrate into CI/CD for continuous validation 5. Learn: Document findings, improve systems and runbooks
DR Test Scenarios
Database Failover Test
Hypothesis: Application continues with < 30s downtime when primary DB fails.
Procedure:
#!/bin/bash
# chaos/db-failover-test.sh
# Collect baseline metrics
BASELINE_ERROR_RATE=$(curl -s http://prometheus:9090/api/v1/query?query='rate(http_requests_total{status=~"5.."}[1m])')
# Simulate failure
ssh primary-db "sudo systemctl stop postgresql"
# Measure recovery time
START=$(date +%s)
while true; do
ERROR_RATE=$(curl -s http://prometheus:9090/api/v1/query?query='rate(http_requests_total{status=~"5.."}[1m])')
if [[ $(echo "$ERROR_RATE < $BASELINE_ERROR_RATE * 1.1" | bc) -eq 1 ]]; then
END=$(date +%s)
DOWNTIME=$((END - START))
echo "Recovery: ${DOWNTIME}s"
break
fi
sleep 1
done
# Verify secondary promoted
RECOVERY_STATUS=$(psql -h db-vip -c "SELECT pg_is_in_recovery();" -t)
if [[ "$RECOVERY_STATUS" == "f" ]]; then
echo "PASS: Secondary promoted"
else
echo "FAIL: Secondary not promoted"
fiRegion Failure Test
Hypothesis: Application fails over to secondary region with < 5 min RTO.
Steps: 1. Block network to primary region (AWS NACL deny rule) 2. Trigger DNS failover to secondary 3. Measure time until application healthy 4. Cleanup and restore primary
See: examples/chaos/region-failure-test.sh
Kubernetes Namespace Recovery
Hypothesis: Velero restores deleted namespace within 10 minutes.
Steps: 1. Capture current state 2. Delete namespace 3. Restore from Velero backup 4. Verify resource count matches 5. Confirm restore time < 10 min
See: examples/chaos/k8s-namespace-recovery-test.sh
Chaos Engineering Tools
| Tool | Use Case | Platform |
|---|---|---|
| Chaos Mesh | Kubernetes chaos | K8s |
| Gremlin | Enterprise platform | Multi-cloud |
| Litmus | Cloud-native chaos | K8s |
| Chaos Monkey | Instance termination | AWS/GCP/Azure |
| Toxiproxy | Network failures | Any |
Failure Injection Techniques
- Pod/container termination
- Network latency/partition
- Database connection failures
- Disk fill
- Data corruption
- DNS resolution failures
- Region/AZ outages
Cloud-Specific DR Patterns
Table of Contents
AWS
RDS Multi-AZ and Read Replicas
Multi-AZ (Synchronous Replication):
- Automatic failover within same region
- RPO: Near-zero (synchronous)
- RTO: 1-2 minutes (automatic)
Cross-Region Read Replica (Asynchronous):
- Manual promotion required
- RPO: Seconds to minutes (asynchronous lag)
- RTO: 5-15 minutes (manual promotion + DNS update)
Aurora Global Database
Configuration Example:
resource "aws_rds_global_cluster" "main" {
global_cluster_identifier = "prod-global"
engine = "aurora-postgresql"
engine_version = "14.7"
}
resource "aws_rds_cluster" "primary" {
cluster_identifier = "prod-primary"
global_cluster_identifier = aws_rds_global_cluster.main.id
backup_retention_period = 30
}
resource "aws_rds_cluster" "secondary" {
provider = aws.eu-west-1
cluster_identifier = "prod-secondary"
global_cluster_identifier = aws_rds_global_cluster.main.id
}Failover Process:
aws rds remove-from-global-cluster \
--region eu-west-1 \
--global-cluster-identifier prod-global \
--db-cluster-identifier prod-secondaryS3 Cross-Region Replication
With Replication Time Control (15-min SLA):
resource "aws_s3_bucket_replication_configuration" "crr" {
rule {
status = "Enabled"
destination {
bucket = aws_s3_bucket.replica.arn
replication_time {
status = "Enabled"
time { minutes = 15 }
}
metrics {
status = "Enabled"
event_threshold { minutes = 15 }
}
}
}
}GCP
Cloud SQL High Availability
Regional HA (Synchronous):
resource "google_sql_database_instance" "main" {
settings {
availability_type = "REGIONAL"
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
transaction_log_retention_days = 7
}
}
}Cross-Region Read Replica:
resource "google_sql_database_instance" "replica" {
master_instance_name = google_sql_database_instance.main.name
region = "us-west1"
replica_configuration {
failover_target = false
}
}GCS Multi-Regional Storage
resource "google_storage_bucket" "backups" {
location = "US" # Multi-region
storage_class = "STANDARD"
versioning { enabled = true }
lifecycle_rule {
condition { age = 30 }
action {
type = "SetStorageClass"
storage_class = "NEARLINE"
}
}
}Azure
Azure SQL Geo-Replication
resource "azurerm_mssql_database" "primary" {
name = "prod-db"
server_id = azurerm_mssql_server.primary.id
sku_name = "S3"
short_term_retention_policy {
retention_days = 35
}
long_term_retention_policy {
weekly_retention = "P12W"
monthly_retention = "P12M"
yearly_retention = "P5Y"
}
}
resource "azurerm_mssql_database_extended_auditing_policy" "primary" {
database_id = azurerm_mssql_database.primary.id
retention_in_days = 90
}Azure Site Recovery
resource "azurerm_site_recovery_replicated_vm" "vm" {
name = "replicated-vm"
resource_group_name = azurerm_resource_group.secondary.name
recovery_vault_name = azurerm_recovery_services_vault.vault.name
source_recovery_fabric_name = azurerm_site_recovery_fabric.primary.name
source_vm_id = azurerm_virtual_machine.vm.id
target_recovery_fabric_id = azurerm_site_recovery_fabric.secondary.id
target_resource_group_id = azurerm_resource_group.secondary.id
target_recovery_protection_container_id = azurerm_site_recovery_protection_container.secondary.id
target_availability_set_id = azurerm_availability_set.secondary.id
managed_disk {
disk_id = azurerm_managed_disk.vm.id
staging_storage_account_id = azurerm_storage_account.cache.id
target_resource_group_id = azurerm_resource_group.secondary.id
target_disk_type = "Premium_LRS"
target_replica_disk_type = "Premium_LRS"
}
}Compliance and Retention Requirements
Table of Contents
1. Common Regulatory Frameworks 2. Retention Policy Implementation 3. Immutable Backups for Ransomware Protection 4. Compliance Reporting
Common Regulatory Frameworks
| Regulation | Retention Period | Data Types | Requirements |
|---|---|---|---|
| GDPR | Varies (1-7 years typical) | Personal data | EU data residency, right to erasure |
| SOC 2 | 1 year minimum | Audit logs, backups | Secure deletion, access controls |
| HIPAA | 6 years | PHI, audit logs | Encryption at rest/transit |
| PCI DSS | 3 months (logs), 1 year (audit) | Cardholder data | Secure deletion, quarterly reviews |
| FINRA | 6 years | Financial records | WORM storage for some data |
Retention Policy Implementation
S3 Lifecycle Example
aws s3api put-bucket-lifecycle-configuration \
--bucket compliance-backups \
--lifecycle-configuration '{
"Rules": [
{
"Id": "compliance-retention",
"Status": "Enabled",
"Transitions": [
{"Days": 30, "StorageClass": "STANDARD_IA"},
{"Days": 90, "StorageClass": "GLACIER"},
{"Days": 365, "StorageClass": "DEEP_ARCHIVE"}
],
"Expiration": {"Days": 2555}
}
]
}'GCS Retention Lock
gsutil retention set 7y gs://compliance-backups
gsutil retention lock gs://compliance-backupsImmutable Backups for Ransomware Protection
S3 Object Lock
resource "aws_s3_bucket_object_lock_configuration" "backups" {
bucket = aws_s3_bucket.backups.id
rule {
default_retention {
mode = "GOVERNANCE" # or "COMPLIANCE"
days = 30
}
}
}Azure Immutable Blob Storage
resource "azurerm_storage_container" "backups" {
name = "backups"
storage_account_name = azurerm_storage_account.main.name
immutability_policy {
period_since_creation_in_days = 30
state = "Locked"
}
}Compliance Reporting
Backup Compliance Report Script
#!/bin/bash
# scripts/generate-dr-report.sh
echo "DR Compliance Report - $(date)"
echo "================================"
# Check backup age
LAST_BACKUP=$(aws s3 ls s3://backups/ | tail -1 | awk '{print $1" "$2}')
BACKUP_AGE=$(( ($(date +%s) - $(date -d "$LAST_BACKUP" +%s)) / 3600 ))
if [ $BACKUP_AGE -lt 24 ]; then
echo "✓ Backup freshness: PASS (${BACKUP_AGE}h old)"
else
echo "✗ Backup freshness: FAIL (${BACKUP_AGE}h old)"
fi
# Check retention
BACKUP_COUNT=$(aws s3 ls s3://backups/ | wc -l)
if [ $BACKUP_COUNT -ge 30 ]; then
echo "✓ Retention compliance: PASS ($BACKUP_COUNT backups)"
else
echo "✗ Retention compliance: FAIL ($BACKUP_COUNT backups)"
fi
# Check encryption
ENCRYPTED=$(aws s3api get-bucket-encryption --bucket backups 2>/dev/null)
if [ $? -eq 0 ]; then
echo "✓ Encryption: PASS"
else
echo "✗ Encryption: FAIL"
fiCross-Region Replication Patterns
Active-Passive Pattern
Use Case: Primary region handles all traffic, secondary on standby for failover.
RTO: 15-60 minutes | RPO: 5-15 minutes | Cost: Medium
Architecture:
- Primary: All application servers, read/write database
- Secondary: Stopped/minimal servers, read-only database replica
- Failover: Promote secondary DB, start servers, update DNS
PostgreSQL Implementation:
# Primary: Enable replication
ALTER SYSTEM SET wal_level = 'replica';
ALTER SYSTEM SET max_wal_senders = 5;
CREATE USER replicator WITH REPLICATION;
# Secondary: Setup streaming replication
pg_basebackup -h primary -U replicator -D /var/lib/postgresql/14/main -P -R
# Failover: Promote secondary
pg_ctl promote -D /var/lib/postgresql/14/mainActive-Active Pattern
Use Case: Both regions serve traffic simultaneously.
RTO: < 1 minute | RPO: < 1 minute | Cost: High
Architecture:
- Both regions: Full application stack
- Database: Multi-master or Aurora Global DB
- Load balancer: Route to nearest region with automatic failover
Aurora Global DB Example: Use Aurora Global Database for multi-region active-active replication with automatic promotion capability.
Pilot Light Pattern
Use Case: Minimal secondary infrastructure, rapid scale-up on failover.
RTO: 10-30 minutes | RPO: 5-15 minutes | Cost: Low
Architecture:
- Primary: Full stack
- Secondary: Database replica (running), AMIs pre-baked, ASGs at min=0
- Failover: Scale ASGs, promote DB, update DNS
AWS Lambda Failover:
import boto3
def failover_handler(event, context):
asg = boto3.client('autoscaling', region_name='us-west-2')
asg.set_desired_capacity(
AutoScalingGroupName='app-secondary',
DesiredCapacity=5
)
# Update Route53 DNS to secondaryWarm Standby Pattern
Use Case: Scaled-down secondary infrastructure for faster failover.
RTO: 5-15 minutes | RPO: 5-15 minutes | Cost: Medium-High
Architecture:
- Primary: Full capacity
- Secondary: 20-50% capacity (running), replicated database
- Failover: Scale up secondary, update DNS
Database Backup Patterns
Table of Contents
1. PostgreSQL Backup Strategies 2. MySQL Backup Strategies 3. MongoDB Backup Strategies 4. Backup Type Selection 5. Point-in-Time Recovery
PostgreSQL Backup Strategies
pgBackRest Production Setup
Architecture:
PostgreSQL Primary
├─► WAL Archive (continuous) → S3/GCS/Azure
├─► Full Backup (weekly) → Multi-repo support
├─► Differential Backup (daily) → Based on last full
└─► Incremental Backup (optional hourly) → Based on last backup
PostgreSQL Standby (optional)
└─► Backup from standby (zero impact on primary)Complete Configuration:
/etc/pgbackrest/pgbackrest.conf:
[global]
# Repository 1: S3 primary
repo1-type=s3
repo1-s3-bucket=prod-pg-backups
repo1-s3-region=us-east-1
repo1-s3-key=<access-key>
repo1-s3-key-secret=<secret-key>
repo1-path=/pgbackrest
repo1-retention-full=2
repo1-retention-diff=6
repo1-retention-archive=4
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=<strong-passphrase>
# Repository 2: S3 secondary region (disaster recovery)
repo2-type=s3
repo2-s3-bucket=dr-pg-backups
repo2-s3-region=us-west-2
repo2-s3-key=<access-key>
repo2-s3-key-secret=<secret-key>
repo2-path=/pgbackrest
repo2-retention-full=4
repo2-cipher-type=aes-256-cbc
repo2-cipher-pass=<strong-passphrase>
# Performance
process-max=4
compress-type=lz4
compress-level=3
# Logging
log-level-console=info
log-level-file=debug
log-path=/var/log/pgbackrest
[main]
pg1-path=/var/lib/postgresql/14/main
pg1-port=5432
pg1-socket-path=/var/run/postgresql
pg1-user=postgres
# Backup from standby to reduce primary load
backup-standby=y
# Archive settings
archive-async=y
archive-push-queue-max=128MB
# Backup settings
start-fast=y
stop-auto=y
delta=y
#!/bin/bash
# Verify retention policy compliance
echo "Checking backup retention policies..."
# Count backups
BACKUP_COUNT=$(ls /backups/ 2>/dev/null | wc -l)
MIN_REQUIRED=7
if [ $BACKUP_COUNT -ge $MIN_REQUIRED ]; then
echo "✓ Retention: PASS ($BACKUP_COUNT backups, minimum $MIN_REQUIRED)"
else
echo "✗ Retention: FAIL ($BACKUP_COUNT backups, minimum $MIN_REQUIRED)"
exit 1
fi
#!/bin/bash
# Run comprehensive DR drill
ENVIRONMENT="${1:-staging}"
TEST_TYPE="${2:-full}"
echo "Running DR drill: $TEST_TYPE in $ENVIRONMENT"
case $TEST_TYPE in
database)
echo "Testing database failover..."
;;
kubernetes)
echo "Testing Kubernetes recovery..."
;;
full)
echo "Running full DR drill..."
;;
*)
echo "Unknown test type"
exit 1
;;
esac
#!/bin/bash
# Generate DR compliance report
FORMAT="${1:-text}"
echo "DR Compliance Report - $(date)"
echo "=============================="
# Backup freshness
echo "Backup Status:"
./scripts/validate-backup.sh latest
# Retention compliance
echo ""
echo "Retention Compliance:"
./scripts/check-retention.sh
# Test history
echo ""
echo "Recent DR Tests:"
echo "(Implement test history tracking)"
#!/bin/bash
# Test restore procedure in non-production environment
BACKUP="${1:-latest}"
TARGET="${2:-staging-db}"
echo "Testing restore of $BACKUP to $TARGET"
echo "This is a dry-run script - implement restore logic for your environment"
Related skills
FAQ
What is the 3-2-1 backup rule?
Keep 3 copies of data on 2 different media types with 1 copy offsite, such as production, local backup, and cloud storage.
What backup type gives the lowest RPO?
Continuous backup via WAL or binlog archiving provides real-time or near-real-time recovery and the lowest RPO.