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.sheşiği aştı veapp-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.
