Felaket Kurtarma için Otomasyon: Script ve Runbook Hazırlama

Bir felaketin tam ortasında, saatler geçerken ve yöneticiler seni arıyorken “acaba bu adımı atladım mı?” sorusunu sormak istemezsin. İşte tam bu yüzden felaket kurtarma süreçlerini otomatize etmek ve runbook’larla belgelemek, bir sysadmin’in yapabileceği en değerli yatırımlardan biridir. Bu yazıda, gerçek dünya senaryoları üzerinden DR (Disaster Recovery) otomasyonunu nasıl kuracağını, script’leri nasıl yazacağını ve runbook’ları nasıl oluşturacağını adım adım ele alacağız.

Felaket Kurtarma Otomasyonuna Neden İhtiyaç Var?

Geleneksel DR yaklaşımında bir PDF belgesi, birkaç sayfadan oluşan elle yürütülen adımlar ve “umarım doğru yapıyorumdur” endişesi vardır. Bu yaklaşımın birkaç temel sorunu var:

  • İnsan hatası her zaman kapıda bekliyor, özellikle stres altında
  • Tutarsızlık ekipler arasında ciddi farklar yaratıyor
  • Test edilmemiş prosedürler gerçek bir felakette sürprizle sonuçlanıyor
  • Zaman kaybı her dakikanın para olduğu durumlarda kritik önem taşıyor

RTO (Recovery Time Objective) ve RPO (Recovery Point Objective) hedeflerinizi tutturmanın en güvenilir yolu, mümkün olan her şeyi otomatize etmektir.

Runbook Nedir ve Nasıl Yapılandırılır?

Runbook, bir sistem arızası veya felaket senaryosunda izlenecek adımları, sorumlulukları ve script referanslarını içeren operasyonel bir belgedir. İyi bir runbook şu bileşenleri içermeli:

  • Tetikleyici koşul: Hangi durum bu runbook’u devreye sokuyor?
  • Önkoşullar: Hangi erişimlere, araçlara ihtiyaç var?
  • Adım adım prosedürler: Her adım açık, net ve doğrulanabilir olmalı
  • Script referansları: Hangi otomasyon script’leri kullanılıyor?
  • Doğrulama adımları: Her aşama sonrasında ne kontrol edilmeli?
  • Rollback planı: İşler ters giderse ne yapılacak?
  • Escalation path: Kim aranacak, hangi sırayla?

Runbook Şablonu

Bir runbook’un temel yapısını YAML formatında tutmak, hem okunabilirliği artırır hem de otomasyon araçlarıyla entegrasyonu kolaylaştırır.

cat /opt/dr/runbooks/db-failover.yaml
#!/bin/bash
# runbook-executor.sh - Runbook adımlarını sırayla çalıştırır ve loglar

RUNBOOK_NAME="db-failover"
LOG_DIR="/var/log/dr"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="${LOG_DIR}/${RUNBOOK_NAME}_${TIMESTAMP}.log"

mkdir -p "$LOG_DIR"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

check_prerequisites() {
    log "Ön koşullar kontrol ediliyor..."
    
    # SSH bağlantısı kontrolü
    if ! ssh -o ConnectTimeout=5 -q dr-db-server exit; then
        log "HATA: DR sunucusuna SSH bağlantısı kurulamıyor"
        exit 1
    fi
    
    # Gerekli araçların varlığını kontrol et
    for tool in mysql mysqldump rsync; do
        if ! command -v "$tool" &>/dev/null; then
            log "HATA: $tool bulunamadı"
            exit 1
        fi
    done
    
    log "Ön koşullar başarıyla doğrulandı"
}

check_prerequisites

Ana DR Otomasyon Script’i

Gerçek dünyada bir felaket senaryosunu düşünelim: Birincil veritabanı sunucunuz çöktü, replikasyon durdu ve secondary sunucuyu production’a almanız gerekiyor. Bu senaryo için kapsamlı bir otomasyon script’i yazalım.

#!/bin/bash
# db-dr-failover.sh
# Kullanım: ./db-dr-failover.sh [--dry-run] [--force]

set -euo pipefail

# Konfigürasyon
PRIMARY_DB_HOST="db-primary.internal"
SECONDARY_DB_HOST="db-secondary.internal"
LOAD_BALANCER_VIP="10.0.1.100"
DB_PORT="3306"
NOTIFICATION_EMAIL="[email protected]"
SLACK_WEBHOOK="https://hooks.slack.com/services/XXXXX"
DRY_RUN=false
FORCE=false

# Argüman işleme
while [[ "$#" -gt 0 ]]; do
    case $1 in
        --dry-run) DRY_RUN=true ;;
        --force) FORCE=true ;;
        *) echo "Bilinmeyen parametre: $1"; exit 1 ;;
    esac
    shift
done

# Log fonksiyonu
log() {
    local level="$1"
    local message="$2"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" | 
        tee -a "/var/log/dr/failover_$(date +%Y%m%d).log"
}

# Bildirim fonksiyonu
notify() {
    local message="$1"
    # E-posta bildirimi
    echo "$message" | mail -s "DR Failover Bildirimi" "$NOTIFICATION_EMAIL"
    
    # Slack bildirimi
    curl -s -X POST "$SLACK_WEBHOOK" 
        -H 'Content-type: application/json' 
        --data "{"text": "DR ALERT: $message"}" > /dev/null
    
    log "INFO" "Bildirim gönderildi: $message"
}

# Primary sunucu sağlık kontrolü
check_primary_health() {
    log "INFO" "Primary sunucu kontrol ediliyor: $PRIMARY_DB_HOST"
    
    if mysqladmin -h "$PRIMARY_DB_HOST" -P "$DB_PORT" 
        -u monitor -pmonitorpass ping &>/dev/null; then
        log "INFO" "Primary sunucu sağlıklı görünüyor"
        return 0
    else
        log "WARN" "Primary sunucu yanıt vermiyor"
        return 1
    fi
}

# Replikasyon durumu kontrolü
check_replication_status() {
    local slave_status
    slave_status=$(mysql -h "$SECONDARY_DB_HOST" -u monitor 
        -pmonitorpass -e "SHOW SLAVE STATUSG" 2>/dev/null)
    
    local behind_master
    behind_master=$(echo "$slave_status" | 
        grep "Seconds_Behind_Master" | awk '{print $2}')
    
    log "INFO" "Replikasyon gecikmesi: ${behind_master} saniye"
    
    if [[ "$behind_master" -gt 60 ]] && [[ "$FORCE" == "false" ]]; then
        log "ERROR" "Replikasyon çok geride (${behind_master}s). --force ile zorla"
        exit 1
    fi
}

# Yük dengeleyici yapılandırması güncelleme
update_load_balancer() {
    log "INFO" "Yük dengeleyici güncelleniyor..."
    
    if [[ "$DRY_RUN" == "true" ]]; then
        log "DRY-RUN" "LB güncelleme simüle ediliyor"
        return 0
    fi
    
    # HAProxy backend'ini güncelle
    echo "disable server mysql_backend/primary" | 
        socat stdio /var/run/haproxy/admin.sock
    
    echo "enable server mysql_backend/secondary" | 
        socat stdio /var/run/haproxy/admin.sock
    
    log "INFO" "Yük dengeleyici güncellendi"
}

# Ana failover prosedürü
main() {
    log "INFO" "=== DB FAILOVER BAŞLADI ==="
    notify "Veritabanı failover prosedürü başlatıldı"
    
    # Adım 1: Primary kontrol
    if check_primary_health; then
        if [[ "$FORCE" == "false" ]]; then
            log "WARN" "Primary sağlıklı. --force olmadan devam edilmiyor"
            exit 0
        fi
    fi
    
    # Adım 2: Secondary replikasyon durumu
    check_replication_status
    
    # Adım 3: Secondary'yi read-only modundan çıkar
    log "INFO" "Secondary promote ediliyor..."
    if [[ "$DRY_RUN" == "false" ]]; then
        mysql -h "$SECONDARY_DB_HOST" -u admin -padminpass 
            -e "STOP SLAVE; RESET SLAVE ALL; SET GLOBAL read_only = OFF;"
    fi
    
    # Adım 4: Yük dengeleyiciyi güncelle
    update_load_balancer
    
    # Adım 5: DNS güncelle (opsiyonel)
    log "INFO" "DNS kaydı güncelleniyor..."
    if [[ "$DRY_RUN" == "false" ]]; then
        # nsupdate veya cloud DNS API çağrısı buraya gelir
        true
    fi
    
    log "INFO" "=== FAILOVER TAMAMLANDI ==="
    notify "Failover başarıyla tamamlandı. Yeni primary: $SECONDARY_DB_HOST"
}

main

Yedekleme Doğrulama Otomasyonu

Felaket kurtarmanın en kritik ama en çok ihmal edilen adımı, yedeklemelerin gerçekten çalışıp çalışmadığını doğrulamaktır. “Yedek aldım” demek ile “yedeği restore edebildim” demek çok farklı şeylerdir.

#!/bin/bash
# backup-verify.sh - Yedekleri otomatik olarak test ortamına restore eder ve doğrular

BACKUP_DIR="/backup/mysql"
TEST_DB_HOST="db-test.internal"
VERIFY_LOG="/var/log/dr/backup-verify.log"
ALERT_THRESHOLD=7  # 7 günden eski yedek varsa alert

verify_backup_integrity() {
    local backup_file="$1"
    local db_name="$2"
    
    echo "Yedek bütünlüğü kontrol ediliyor: $backup_file"
    
    # Sıkıştırılmış dosyayı kontrol et
    if ! gzip -t "$backup_file" 2>/dev/null; then
        echo "HATA: $backup_file bozuk veya geçersiz"
        return 1
    fi
    
    # Satır sayısını kontrol et (minimum beklenen)
    local line_count
    line_count=$(zcat "$backup_file" | wc -l)
    
    if [[ "$line_count" -lt 100 ]]; then
        echo "UYARI: Yedek dosyası çok küçük ($line_count satır)"
        return 1
    fi
    
    echo "Bütünlük kontrolü geçti: $line_count satır"
    return 0
}

test_restore() {
    local backup_file="$1"
    local test_db="verify_$(date +%s)"
    
    echo "Test restore başlatılıyor: $test_db"
    
    # Test veritabanı oluştur
    mysql -h "$TEST_DB_HOST" -u admin -padmin 
        -e "CREATE DATABASE $test_db;"
    
    # Restore et
    if zcat "$backup_file" | mysql -h "$TEST_DB_HOST" 
        -u admin -padmin "$test_db"; then
        
        # Tablo sayısını doğrula
        local table_count
        table_count=$(mysql -h "$TEST_DB_HOST" -u admin -padmin 
            -e "SELECT COUNT(*) FROM information_schema.tables 
                WHERE table_schema='$test_db';" -s 2>/dev/null)
        
        echo "Restore başarılı. Tablo sayısı: $table_count"
        
        # Temizle
        mysql -h "$TEST_DB_HOST" -u admin -padmin 
            -e "DROP DATABASE $test_db;"
        return 0
    else
        echo "HATA: Restore başarısız"
        mysql -h "$TEST_DB_HOST" -u admin -padmin 
            -e "DROP DATABASE IF EXISTS $test_db;"
        return 1
    fi
}

# Son yedeği bul ve doğrula
latest_backup=$(find "$BACKUP_DIR" -name "*.sql.gz" 
    -mtime -1 | sort -r | head -1)

if [[ -z "$latest_backup" ]]; then
    echo "KRITIK: Son 24 saatte yedek bulunamadı!" | 
        mail -s "Yedek Doğrulama BAŞARISIZ" [email protected]
    exit 1
fi

verify_backup_integrity "$latest_backup" && 
    test_restore "$latest_backup"

Uygulama Katmanı DR Script’leri

Veritabanı dışında, uygulama sunucularının failover’ı da otomatize edilmeli. Nginx/Apache yapılandırma yönetiminden uygulama sağlık kontrollerine kadar her şey script’lenmeli.

#!/bin/bash
# app-healthcheck-dr.sh
# Uygulama sağlığını izler, sorun tespit edince DR prosedürünü tetikler

APP_ENDPOINTS=(
    "https://app1.sirket.com/health"
    "https://app2.sirket.com/health"
    "https://api.sirket.com/status"
)

DR_TRIGGER_SCRIPT="/opt/dr/scripts/app-failover.sh"
FAILURE_COUNT_FILE="/tmp/dr_failure_count"
MAX_FAILURES=3
CHECK_INTERVAL=30

check_endpoint() {
    local url="$1"
    local http_code
    
    http_code=$(curl -s -o /dev/null -w "%{http_code}" 
        --connect-timeout 5 
        --max-time 10 
        "$url")
    
    if [[ "$http_code" == "200" ]]; then
        return 0
    else
        echo "Endpoint başarısız: $url (HTTP $http_code)"
        return 1
    fi
}

increment_failure_count() {
    local current=0
    [[ -f "$FAILURE_COUNT_FILE" ]] && current=$(cat "$FAILURE_COUNT_FILE")
    echo $((current + 1)) > "$FAILURE_COUNT_FILE"
    echo $((current + 1))
}

reset_failure_count() {
    echo 0 > "$FAILURE_COUNT_FILE"
}

# Ana kontrol döngüsü
failed=false
for endpoint in "${APP_ENDPOINTS[@]}"; do
    if ! check_endpoint "$endpoint"; then
        failed=true
    fi
done

if [[ "$failed" == "true" ]]; then
    count=$(increment_failure_count)
    echo "Başarısız kontrol sayısı: $count / $MAX_FAILURES"
    
    if [[ "$count" -ge "$MAX_FAILURES" ]]; then
        echo "Eşik aşıldı! DR tetikleniyor..."
        reset_failure_count
        bash "$DR_TRIGGER_SCRIPT"
    fi
else
    reset_failure_count
    echo "Tüm endpoint'ler sağlıklı"
fi

DR Test Otomasyonu

En iyi DR planı test edilmemiş bir plandır. Aylık veya çeyreklik DR tatbikatlarını otomatize etmek için bir framework oluşturmak gerekiyor.

#!/bin/bash
# dr-drill.sh - Otomatik DR tatbikatı çalıştırır ve rapor üretir

DRILL_DATE=$(date +%Y%m%d_%H%M%S)
REPORT_DIR="/opt/dr/reports"
REPORT_FILE="${REPORT_DIR}/drill_${DRILL_DATE}.txt"

mkdir -p "$REPORT_DIR"

report() {
    local status="$1"
    local test_name="$2"
    local duration="$3"
    local notes="$4"
    
    printf "%-10s | %-40s | %-10s | %sn" 
        "$status" "$test_name" "${duration}s" "$notes" 
        | tee -a "$REPORT_FILE"
}

run_test() {
    local test_name="$1"
    local test_command="$2"
    local start_time
    local end_time
    local duration
    
    start_time=$(date +%s)
    
    if eval "$test_command" &>/dev/null; then
        end_time=$(date +%s)
        duration=$((end_time - start_time))
        report "PASS" "$test_name" "$duration" "Başarılı"
    else
        end_time=$(date +%s)
        duration=$((end_time - start_time))
        report "FAIL" "$test_name" "$duration" "Başarısız - manuel inceleme gerekli"
    fi
}

echo "=== DR Tatbikatı: $DRILL_DATE ===" | tee "$REPORT_FILE"
echo "Ortam: Production | Yürüten: $(whoami)" | tee -a "$REPORT_FILE"
echo "----------------------------------------" | tee -a "$REPORT_FILE"

# Test 1: Yedek bütünlüğü
run_test "Yedek bütünlük kontrolü" 
    "bash /opt/dr/scripts/backup-verify.sh --quick"

# Test 2: DR sunucusuna bağlantı
run_test "DR site bağlantı testi" 
    "ping -c 3 dr-site.internal"

# Test 3: Failover tahmini süresi
run_test "Failover süresi ölçümü" 
    "bash /opt/dr/scripts/db-dr-failover.sh --dry-run"

# Test 4: Yedek restore süresi
run_test "Restore süresi testi" 
    "bash /opt/dr/scripts/backup-verify.sh --restore-test"

# Test 5: Monitoring bildirimleri
run_test "Alert sistemi testi" 
    "bash /opt/dr/scripts/test-notifications.sh"

echo "----------------------------------------" | tee -a "$REPORT_FILE"
echo "Tatbikat tamamlandı. Rapor: $REPORT_FILE" | tee -a "$REPORT_FILE"

# Raporu e-posta ile gönder
mail -s "DR Tatbikat Raporu - $DRILL_DATE" 
    -a "From: [email protected]" 
    [email protected] < "$REPORT_FILE"

Konfigürasyon Yönetimi ve Versiyon Kontrolü

Script’lerin kendisi de bir DR riskidir. Onları Git’te tutmak, versiyonlamak ve dağıtımını otomatize etmek kritik önem taşır.

#!/bin/bash
# dr-scripts-deploy.sh
# DR script'lerini tüm hedef sunuculara deploy eder

GIT_REPO="[email protected]:sirket/dr-scripts.git"
DEPLOY_DIR="/opt/dr"
TARGET_SERVERS=(
    "dr-site-1.internal"
    "dr-site-2.internal"
    "backup-server.internal"
)
DEPLOY_USER="dr-deploy"
BRANCH="main"

deploy_to_server() {
    local server="$1"
    
    echo "Deploy ediliyor: $server"
    
    ssh "${DEPLOY_USER}@${server}" bash << EOF
        set -e
        
        # Repo yoksa clone et, varsa pull yap
        if [[ ! -d "${DEPLOY_DIR}/.git" ]]; then
            git clone "$GIT_REPO" "$DEPLOY_DIR"
        else
            cd "$DEPLOY_DIR"
            git fetch origin
            git checkout "$BRANCH"
            git pull origin "$BRANCH"
        fi
        
        # İzinleri düzelt
        chmod 700 "${DEPLOY_DIR}/scripts"
        chmod 600 "${DEPLOY_DIR}/config"
        chmod +x "${DEPLOY_DIR}/scripts"/*.sh
        
        # Syntax kontrolü
        for script in ${DEPLOY_DIR}/scripts/*.sh; do
            bash -n "$script" || echo "SYNTAX HATASI: $script"
        done
        
        echo "Deploy tamamlandı: $(git log -1 --format='%h %s')"
EOF
    
    if [[ $? -eq 0 ]]; then
        echo "Başarılı: $server"
    else
        echo "HATA: $server deploy başarısız"
        return 1
    fi
}

for server in "${TARGET_SERVERS[@]}"; do
    deploy_to_server "$server"
done

Gerçek Dünya Senaryosu: E-ticaret Sitesi DR Planı

Bir e-ticaret şirketini düşün. Black Friday gecesi saat 02:00’de birincil veri merkezi güç kaybetti. Elimizde ne olmalı?

Tetikleyici: Monitoring sistemi hem birincil site’den hem de uygulamadan yanıt alamıyor, otomatik PagerDuty alarmı çalıyor.

Otomatik tepki zinciri:

  • Monitoring script 5 dakika boyunca 3 başarısız kontrol kaydetti
  • app-healthcheck-dr.sh eşiği aştı ve app-failover.sh‘ı tetikledi
  • DNS TTL 60 saniye olarak önceden ayarlanmıştı, hızlı geçiş mümkün
  • Load balancer DR site’e trafik yönlendirdi
  • Uygulama DR veritabanına bağlandı (replikasyon son 30 dakikadan biriydi)
  • Tüm süreç 8 dakika 23 saniyede tamamlandı

Önemli noktalar:

  • DNS TTL’nin önceden düşürülmüş olması kritikti
  • Replikasyon sağlığı sürekli izleniyordu
  • Runbook’taki her adım script ile destekleniyordu
  • On-call mühendis sadece onay vermek zorundaydı, aksi takdirde her şey otomatik çalışıyordu

Monitoring Entegrasyonu

DR otomasyonu Prometheus, Grafana veya Zabbix gibi monitoring sistemleriyle entegre edilmeli:

  • DR tatbikat metrikleri Grafana dashboard’unda görünmeli
  • RTO/RPO sapmaları otomatik alert üretmeli
  • Yedek başarı oranı günlük raporlarda yer almalı
  • Replikasyon gecikmesi gerçek zamanlı izlenmeli
  • Script çalışma süreleri log aggregation sistemine gönderilmeli

Runbook Güncelleme Süreci

Script ve runbook’lar statik belgeler değil, yaşayan dokümanlardır. Güncelleme sürecini de otomatize etmek gerekir:

  • Her production değişikliğinde ilgili runbook gözden geçirilmeli
  • Tatbikat sonrası bulgular mutlaka runbook’a yansıtılmalı
  • Runbook’ların Git commit history’si tutulmalı
  • Değişikliklerde ekip e-posta listesine otomatik bildirim gönderilmeli
  • Her çeyreğe bir runbook “masa başı egzersizi” yapılmalı

Sonuç

Felaket kurtarma otomasyonu bir lüks değil, üretim ortamı işleten her organizasyonun zorunluluğudur. Bugün ele aldığımız başlıkları kendi ortamınıza uyarlarken şu sıralamayı takip etmeni öneririm: önce mevcut prosedürleri dokümante et, sonra her adımı script’le destekle, ardından dry-run modunda test et ve son olarak tatbikatlarla doğrula.

Unutma, test etmediğin bir DR planın yoktur. En mükemmel script demetleri bile gerçek ortamda sürprizler üretebilir. Bu yüzden düzenli tatbikatlar, iyi yazılmış runbook’lardan bile daha değerlidir. Script’lerini yazarken daima --dry-run modunu ekle, logları düzgün tut ve bildirimleri test et. Gece 03:00’te alarm çaldığında elinde hem çalışan bir otomasyon hem de onu takip edebileceğin net bir runbook olsun. Bu ikisi birlikte olduğunda, felaket bir kriz olmaktan çıkıp yönetilen bir prosedüre dönüşür.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir