Yedekleme Doğrulama ve Test: Bacula Rehberi

Yıllarca sistem yöneticiliği yapan herkes şu acı gerçeği en az bir kez yaşamıştır: Felaket anında yedeğin bozuk çıkması. Bacula kurulumu güzel, zamanlanmış görevler çalışıyor, log’lar yeşil görünüyor ama restore etmeye çalıştığında ya dosyalar eksik, ya catalog tutarsız, ya da tape sürücüsü okuyamıyor. Bu yazıda Bacula’da yedekleme doğrulama ve test süreçlerini, gerçek dünya senaryolarıyla birlikte ele alacağız. Çünkü yedek almak yetmez, o yedeğin çalışıp çalışmadığını bilmek zorundasın.

Neden Yedekleme Doğrulama Bu Kadar Kritik

Bacula güçlü bir yedekleme sistemi ama “set and forget” mantığıyla bırakılırsa sürprizler kaçınılmaz olur. Şirketlerde en sık karşılaştığım senaryo şu: DBA bir gün “veritabanını geri yükle” dediğinde son 3 haftanın yedeğinin boş job tamamladığı ortaya çıkıyor. Job başarılı görünüyor, hata yok, ama FileSet yanlış tanımlanmış ve hiçbir dosya dahil edilmemiş.

Doğrulama süreci üç katmana ayrılır:

  • Catalog doğrulama: Bacula’nın kendi veritabanındaki metadata’nın tutarlı olup olmadığını kontrol etmek
  • Veri bütünlüğü doğrulama: Yedeklenen dosyaların checksum’larının orijinallerle eşleşip eşleşmediğini kontrol etmek
  • Restore testi: Gerçek bir restore işlemi yaparak verinin kullanılabilir olduğunu kanıtlamak

Bu üçünü düzenli yapmayan bir yedekleme stratejisi, sadece kağıt üzerinde var olan bir stratejidir.

Bacula Verify Job ile Temel Doğrulama

Bacula’nın Verify job tipi, en çok göz ardı edilen özelliklerden biri. Backup job’ının yanına muhakkak bir Verify job eklenmeli.

# /etc/bacula/bacula-dir.conf içine eklenecek Verify Job örneği

Job {
  Name = "VerifyJob-WebServer"
  Type = Verify
  Level = VolumeToCatalog
  Client = webserver-fd
  FileSet = "WebServer-FileSet"
  Schedule = "WeeklyCycle"
  Storage = File1
  Messages = Standard
  Pool = Default
  Priority = 10
}

Buradaki Level parametresinin seçimi çok önemli:

  • InitCatalog: İlk kez çalıştırıldığında dosya imzalarını catalog’a kaydeder
  • Catalog: Dosya sistemini catalog ile karşılaştırır, değişiklik varsa raporlar
  • VolumeToCatalog: Volume üzerindeki dosyaları catalog’daki metadata ile karşılaştırır
  • DiskToCatalog: Disk üzerindeki mevcut dosyaları son backup’taki catalog kaydıyla karşılaştırır
  • Data: Tüm volume verilerini baştan sona okur, tam veri bütünlüğü kontrolü yapar

Üretim ortamında en çok kullandığım kombinasyon VolumeToCatalog seviyesidir. Volume okunabilir mi, dosya sayıları tutuyor mu, checksum’lar eşleşiyor mu diye bakar. Data seviyesi daha kapsamlı ama büyük volume’larda ciddi zaman alır.

# bconsole üzerinden manuel verify job başlatma
bconsole << EOF
run job=VerifyJob-WebServer level=VolumeToCatalog yes
wait
messages
quit
EOF

Job tamamlandıktan sonra çıktıyı kontrol et. Eğer “Verify OK” yerine “Verify: File not found” veya “MD5 signature error” görüyorsan sorun var demektir.

Catalog Tutarlılık Kontrolü

Catalog Bacula’nın beyni. MySQL veya PostgreSQL üzerinde çalışan bu veritabanı bozulursa restore işlemleri kabus haline gelir.

# Bacula catalog'unu kontrol etmek için
# MySQL kullanıyorsan:
mysqlcheck -u bacula -p bacula --check --auto-repair

# PostgreSQL kullanıyorsan:
sudo -u postgres psql bacula -c "
SELECT schemaname, tablename, 
       pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables 
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;"

Catalog’da en sık karşılaştığım sorun, Job tablosundaki tutarsız kayıtlar. Bir job yarım kalmış ama “T” (Terminated) yerine “R” (Running) statüsünde kalmış olabilir. Bunları temizlemek gerekir:

# bconsole ile catalog durumunu gözden geçir
bconsole << 'EOF'
list jobs
list pools
list volumes
status director
quit
EOF

Bu komutun çıktısında Purged veya Error statüsündeki volume’lara dikkat et. Error statüsündeki bir volume’dan restore denemek çoğunlukla başarısızlıkla sonuçlanır.

Otomatik Restore Testi Scripti

Gerçek anlamda doğrulama, gerçek bir restore testi yapmaktır. Aylık veya haftalık periyotlarda otomatik restore testi çalıştırıp sonuçları e-posta ile raporlayan bir script hazırladım, yıllarca kullandım:

#!/bin/bash
# /usr/local/sbin/bacula-restore-test.sh
# Otomatik Bacula restore test scripti

RESTORE_DIR="/tmp/bacula-restore-test"
LOG_FILE="/var/log/bacula/restore-test.log"
REPORT_EMAIL="[email protected]"
CLIENT="webserver-fd"
FILESET="WebServer-FileSet"
TEST_FILES=("/var/www/html/index.php" "/etc/nginx/nginx.conf")
DATE=$(date '+%Y-%m-%d %H:%M:%S')

# Log fonksiyonu
log() {
    echo "[$DATE] $1" | tee -a "$LOG_FILE"
}

# Restore dizinini hazırla
rm -rf "$RESTORE_DIR"
mkdir -p "$RESTORE_DIR"

log "Restore testi başlatılıyor..."

# En son başarılı backup job ID'sini bul
LAST_JOB_ID=$(bconsole << 'BCEOF' | grep -E "^[[:space:]]*[0-9]" | tail -1 | awk '{print $1}'
list jobs client=webserver-fd jobtype=B jobstatus=T
quit
BCEOF
)

if [ -z "$LAST_JOB_ID" ]; then
    log "HATA: Başarılı backup job bulunamadı!"
    echo "Restore testi BAŞARISIZ: Job bulunamadı" | mail -s "[KRITIK] Bacula Restore Testi Başarısız" "$REPORT_EMAIL"
    exit 1
fi

log "Test edilecek Job ID: $LAST_JOB_ID"

# Restore işlemini başlat
bconsole << EOF
restore jobid=$LAST_JOB_ID where=$RESTORE_DIR select all done yes
wait
messages
quit
EOF

# Restore edilen dosyaları kontrol et
FAIL=0
for TEST_FILE in "${TEST_FILES[@]}"; do
    RESTORED_FILE="${RESTORE_DIR}${TEST_FILE}"
    if [ -f "$RESTORED_FILE" ]; then
        ORIG_SIZE=$(stat -c%s "$TEST_FILE" 2>/dev/null || echo "0")
        REST_SIZE=$(stat -c%s "$RESTORED_FILE")
        if [ "$ORIG_SIZE" -eq "$REST_SIZE" ]; then
            log "OK: $TEST_FILE restore edildi ve boyut eşleşiyor ($REST_SIZE bytes)"
        else
            log "UYARI: $TEST_FILE boyut uyuşmazlığı! Orijinal: $ORIG_SIZE, Restore: $REST_SIZE"
            FAIL=1
        fi
    else
        log "HATA: $TEST_FILE restore edilemedi!"
        FAIL=1
    fi
done

# Sonuç raporu gönder
if [ "$FAIL" -eq 0 ]; then
    log "Restore testi BAŞARILI"
    echo "Restore testi başarıyla tamamlandı. Job ID: $LAST_JOB_ID" | 
        mail -s "[OK] Bacula Restore Testi Başarılı - $(date '+%Y-%m-%d')" "$REPORT_EMAIL"
else
    log "Restore testi BAŞARISIZ"
    cat "$LOG_FILE" | mail -s "[KRITIK] Bacula Restore Testi Başarısız - $(date '+%Y-%m-%d')" "$REPORT_EMAIL"
fi

# Geçici dosyaları temizle
rm -rf "$RESTORE_DIR"

exit $FAIL

Bu scripti cron’a ekle:

# Her Pazar sabahı 03:00'de çalıştır
0 3 * * 0 /usr/local/sbin/bacula-restore-test.sh >> /var/log/bacula/restore-test-cron.log 2>&1

Volume Bütünlüğü Kontrolü

Tape veya disk volume’larının fiziksel olarak okunabilir olduğunu test etmek ayrı bir konudur. Özellikle tape ortamında bu test hayati önem taşır.

#!/bin/bash
# Volume bütünlük testi
# /usr/local/sbin/check-bacula-volumes.sh

BCONSOLE=/usr/sbin/bconsole
STORAGE="File1"
LOG="/var/log/bacula/volume-check.log"

echo "=== Volume Kontrol Raporu $(date) ===" >> "$LOG"

# Full backup volume'larını listele ve kontrol et
bconsole << 'EOF' | tee -a "$LOG"
list volumes pool=Full
quit
EOF

# Her volume için içerik listesi al
# Bu komut volume'u fiziksel olarak okur
VOLUMES=$(bconsole << 'EOF' | grep -oP 'Vol-w+' | sort -u
list volumes
quit
EOF
)

for VOL in $VOLUMES; do
    echo "Volume kontrol ediliyor: $VOL" >> "$LOG"
    bconsole << EOF >> "$LOG" 2>&1
list volume=$VOL
quit
EOF
done

echo "Kontrol tamamlandı: $(date)" >> "$LOG"

Tape ortamı kullanıyorsan btape aracı çok değerlidir:

# btape ile tape testi (disk tabanlı değil, gerçek tape için)
# /dev/nst0 yerine kendi tape device adını kullan

btape /dev/nst0 << 'EOF'
test
quit
EOF

btape test komutu tape’in yazma/okuma döngülerini test eder, hata oranını raporlar. Tape’in ömrü dolmak üzereyse burada uyarı alırsın.

Gerçek Dünya Senaryosu: Database Server Restore Testi

Bir e-ticaret şirketinde çalışırken her ayın ilk Pazar günü veritabanı restore testi yapardık. Senaryo şuydu: Production MySQL sunucusunun Bacula yedeğini alıp ayrı bir test sunucusuna restore etmek ve veritabanının sağlıklı ayağa kalkıp kalkmadığını kontrol etmek.

#!/bin/bash
# MySQL + Bacula entegre restore test scripti
# /usr/local/sbin/mysql-restore-test.sh

TEST_SERVER="192.168.1.100"
TEST_DB_PORT="3307"
RESTORE_BASE="/tmp/mysql-restore-test"
MYSQL_DATADIR="${RESTORE_BASE}/mysql-data"
BACKUP_CLIENT="dbserver-fd"

# Test için geçici MySQL instance başlat
prepare_test_mysql() {
    mkdir -p "$MYSQL_DATADIR"
    
    # Restore işlemi
    log "MySQL datadir restore ediliyor..."
    bconsole << EOF
restore client=$BACKUP_CLIENT where=$MYSQL_DATADIR select all done yes
wait
quit
EOF
    
    # Geçici MySQL config
    cat > /tmp/mysql-test.cnf << MYCNF
[mysqld]
datadir=${MYSQL_DATADIR}/var/lib/mysql
socket=/tmp/mysql-test.sock
port=${TEST_DB_PORT}
pid-file=/tmp/mysql-test.pid
log-error=/tmp/mysql-test.err
innodb_recovery_mode=1
MYCNF
    
    # MySQL'i test modunda başlat
    mysqld_safe --defaults-file=/tmp/mysql-test.cnf --user=mysql &
    MYSQL_PID=$!
    sleep 10
    
    # Veritabanı kontrol et
    if mysql --socket=/tmp/mysql-test.sock -u root -e "SHOW DATABASES;" > /dev/null 2>&1; then
        log "MySQL başarıyla ayağa kalktı"
        
        # Kritik tabloları kontrol et
        TABLES=$(mysql --socket=/tmp/mysql-test.sock -u root 
            -e "SELECT COUNT(*) FROM eticaret.siparisler;" 2>/dev/null)
        
        if [ -n "$TABLES" ]; then
            log "BASARILI: eticaret.siparisler tablosu erişilebilir. Kayıt sayısı: $TABLES"
        else
            log "HATA: Kritik tablo erişilemiyor!"
        fi
    else
        log "HATA: MySQL test instance başlatılamadı!"
        cat /tmp/mysql-test.err >> "$LOG_FILE"
    fi
    
    # Temizlik
    kill $MYSQL_PID 2>/dev/null
    wait $MYSQL_PID 2>/dev/null
    rm -rf "$RESTORE_BASE"
    rm -f /tmp/mysql-test.cnf /tmp/mysql-test.sock /tmp/mysql-test.pid
}

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/bacula/db-restore-test.log; }

prepare_test_mysql

Bu tür bir senaryo bize üç farklı seferde sorun yakaladı: Bir seferinde MySQL innodb log dosyaları yedeğe dahil edilmemişti ve veritabanı kurtarılamaz durumdaydı. Başka bir seferde snapshot sırasında yazma devam ettiği için tutarsız bir yedek almıştık.

Bacula Director Log Analizi

Manuel kontrol dışında, logları otomatik parse ederek sorunları erken tespit etmek gerekir:

#!/bin/bash
# Bacula log analiz scripti
# Son 24 saatin backup sonuçlarını özetle

LOG_FILE="/var/log/bacula/bacula.log"
MAIL_TO="[email protected]"
HOSTNAME=$(hostname -f)

# Son 24 saatteki job'ları analiz et
YESTERDAY=$(date -d "24 hours ago" '+%Y-%m-%d %H:%M:%S')

echo "=== Bacula 24 Saatlik Rapor - $HOSTNAME ==="
echo "Rapor zamanı: $(date)"
echo ""

# Başarılı job'lar
SUCCESS=$(grep -c "Backup OK" "$LOG_FILE" 2>/dev/null || echo 0)
echo "Başarılı Backup: $SUCCESS"

# Uyarıyla tamamlanan job'lar
WARNINGS=$(grep -c "Backup OK -- with warnings" "$LOG_FILE" 2>/dev/null || echo 0)
echo "Uyarılı Backup: $WARNINGS"

# Hatalı job'lar
ERRORS=$(grep -c "Backup Error" "$LOG_FILE" 2>/dev/null || echo 0)
echo "Hatalı Backup: $ERRORS"

# Volume doluluğunu kontrol et
echo ""
echo "=== Volume Durumu ==="
bconsole << 'EOF' | grep -E "(Volume|Status|Bytes)"
list volumes
quit
EOF

# Kritik durum varsa alert gönder
if [ "$ERRORS" -gt 0 ]; then
    {
        echo "DİKKAT: Son 24 saatte $ERRORS hatalı backup tespit edildi!"
        echo ""
        grep -A 5 "Backup Error" "$LOG_FILE" | tail -50
    } | mail -s "[KRITIK] Bacula Backup Hatası - $HOSTNAME" "$MAIL_TO"
fi

if [ "$WARNINGS" -gt 0 ]; then
    echo ""
    echo "Uyarı detayları:"
    grep -A 3 "with warnings" "$LOG_FILE" | tail -30
fi

FileSet Doğrulama: Gerçekten Doğru Dosyalar Yedekleniyor mu

FileSet yapılandırmasının doğru çalıştığını periyodik olarak kontrol etmek gerekir. Özellikle uygulama güncellemeleri sonrası dizin yapısı değişebilir.

# bconsole ile FileSet içeriğini listele
bconsole << 'EOF'
list filesets
quit
EOF

Daha kapsamlı bir yaklaşım için son backup job’ındaki dosyaları listele ve beklenen kritik dosyaların dahil olup olmadığını kontrol et:

#!/bin/bash
# FileSet doğrulama - Kritik dosyaların yedeğe dahil olduğunu kontrol et

CRITICAL_PATHS=(
    "/etc/nginx"
    "/var/www/html"
    "/etc/ssl/certs"
    "/home/deploy/.ssh"
    "/opt/uygulama/config"
)

LAST_JOB=$(bconsole << 'EOF' | grep -E "^s+[0-9]" | awk '{print $1}' | tail -1
list jobs jobtype=B jobstatus=T
quit
EOF
)

echo "Son Job ID: $LAST_JOB"
echo "Kontrol ediliyor..."

for PATH_CHECK in "${CRITICAL_PATHS[@]}"; do
    COUNT=$(bconsole << EOF | grep -c "$PATH_CHECK"
list files jobid=$LAST_JOB
quit
EOF
    )
    
    if [ "$COUNT" -gt 0 ]; then
        echo "[OK] $PATH_CHECK - $COUNT dosya/dizin dahil"
    else
        echo "[HATA] $PATH_CHECK - YEDEKTE BULUNAMADI!"
    fi
done

Retention Policy Doğrulaması

Yedekleme politikası gereği belirli yedeklerin belirli süre tutulması gerekiyor. Bunun gerçekten uygulanıp uygulanmadığını kontrol etmek önemli:

# /etc/bacula/bacula-dir.conf içindeki Pool tanımı
Pool {
  Name = Full
  Pool Type = Backup
  Recycle = yes
  AutoPrune = yes
  Volume Retention = 90 days
  Maximum Volume Jobs = 1
  Maximum Volume Bytes = 50G
  Label Format = "Full-"
}

Bu ayarların gerçekten çalışıp çalışmadığını test etmek için:

# 90 günden eski volume'ların otomatik temizlendiğini doğrula
bconsole << 'EOF'
list volumes pool=Full
prune volumes pool=Full yes
list volumes pool=Full
quit
EOF

İki liste arasındaki fark sana otomatik temizleme politikasının çalışıp çalışmadığını gösterir.

Tatbikat: Tam Felaket Kurtarma Testi

Yılda en az bir kez tam bir DR tatbikatı yapılmalı. Bunu kurumsal ortamda uygularken şu adımları takip ediyorum:

  • Hazırlık aşaması: Test sunucusunu hazırla, mevcut sistemi snapshot al, ağ izolasyonu sağla
  • Bacula catalog restore: Catalog sunucusu çöktü varsayımıyla catalog’u sıfırdan import et
  • Volume mount ve import: bscan ile volume’ları yeniden tara
  • Tam sistem restore: OS dışı tüm kritik verileri restore et
  • Servis kontrol: Her servisin ayağa kalkıp kalkmadığını doğrula
  • Veri tutarlılık kontrolü: Checksum karşılaştırması ve uygulama seviyesinde veri doğrulama
  • Zaman ölçümü: RTO (Recovery Time Objective) hedefine ulaşılıp ulaşılmadığını kaydet
# bscan ile volume'ları yeniden catalog'a import etme
# Catalog tamamen kaybedildiği senaryosu için

bscan -b /etc/bacula/bacula-sd.conf 
      -d 50 
      -v 
      -s 
      -m 
      -V "Full-Vol-001" 
      FileStorage

# Parametreler:
# -b: Storage daemon config dosyası
# -d: Debug seviyesi
# -v: Verbose çıktı
# -s: Volume'daki tüm dosyaları catalog'a ekle
# -m: Mevcut catalog kayıtlarını güncelle
# -V: Volume adı

Tatbikat sonrası mutlaka yazılı bir rapor hazırla. Kaç dakikada hangi sistemi geri getirdin, hangi adımda takıldın, hangi eksiklikleri fark ettin. Bu rapor bir sonraki tatbikat ve Bacula konfigürasyon iyileştirmeleri için yol haritası olur.

Monitoring ile Entegrasyon

Zabbix veya Nagios kullanıyorsan Bacula job durumlarını monitoring sistemine entegre etmelisin:

#!/bin/bash
# Zabbix için Bacula job durumu kontrolü
# /etc/zabbix/scripts/check_bacula_job.sh

JOB_NAME="$1"
WARN_HOURS="${2:-25}"  # 25 saatten uzun süre backup yapılmadıysa uyar
CRIT_HOURS="${3:-49}"  # 49 saatten uzun süre backup yapılmadıysa kritik

# Son başarılı job zamanını al
LAST_SUCCESS=$(bconsole << EOF | grep -oP 'd{4}-d{2}-d{2} d{2}:d{2}:d{2}' | tail -1
list jobs job=$JOB_NAME jobstatus=T
quit
EOF
)

if [ -z "$LAST_SUCCESS" ]; then
    echo "CRITICAL: $JOB_NAME için başarılı backup kaydı bulunamadı"
    exit 2
fi

# Saat hesapla
LAST_TS=$(date -d "$LAST_SUCCESS" +%s)
NOW_TS=$(date +%s)
DIFF_HOURS=$(( (NOW_TS - LAST_TS) / 3600 ))

if [ "$DIFF_HOURS" -ge "$CRIT_HOURS" ]; then
    echo "CRITICAL: $JOB_NAME son başarılı backup ${DIFF_HOURS} saat önce"
    exit 2
elif [ "$DIFF_HOURS" -ge "$WARN_HOURS" ]; then
    echo "WARNING: $JOB_NAME son başarılı backup ${DIFF_HOURS} saat önce"
    exit 1
else
    echo "OK: $JOB_NAME son başarılı backup ${DIFF_HOURS} saat önce"
    exit 0
fi

Sonuç

Bacula’da yedekleme doğrulama bir seçenek değil, zorunluluktur. Pek çok ekip yedek alındığında işin bittiğini düşünür oysa asıl işin yarısı orada başlar. Özetlemek gerekirse:

  • Verify job’larını her backup job’ının yanına ekle, VolumeToCatalog seviyesinden başla
  • Aylık bazda gerçek restore testi yap ve sonuçları kayıt altına al
  • Kritik sistemler için database gibi uygulama seviyesinde restore testi yapılmalı, sadece dosya varlığını kontrol etmek yeterli değil
  • Catalog sağlığını haftalık kontrol et, innodb gibi veritabanı motorunun kendi bakım görevlerini de ihmal etme
  • Log analizini otomasyona bağla, her sabah mail ile günlük rapor al
  • Yılda en az bir kez tam DR tatbikatı yap ve RTO/RPO hedeflerini ölç
  • Monitoring sistemine Bacula metriklerini entegre et, gecenin körü sürprizle uyanmak yerine önceden haber al

Yedekleme sistemi ne kadar iyi kurulmuş olursa olsun, test edilmemiş bir yedek, yoktan farksızdır. Bu söz klişe görünebilir ama veri kaybı yaşayan her ekip bunu acı yoldan öğreniyor. Senin ekibin öğrenmek zorunda kalmasın.

Bir yanıt yazın

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