Arşivleme İşlemlerinde Doğrulama Otomasyonu: Yedek Sonrası Bütünlük Testini Betikle Çalıştırma
Geceleri uyuyabilmek istiyorsanız, yedek aldıktan sonra o yedeğin gerçekten işe yarayıp yaramayacağını bilmeniz gerekiyor. Yıllarca sistem yöneticiliği yaptım ve şunu öğrendim: bozuk bir yedekle kendinizi güvende hissetmek, hiç yedek almamaktan daha tehlikeli. En azından yedeği olmayan adam panikler ve bir şeyler yapmaya çalışır; bozuk yedeğe güvenen adam ise disaster recovery anında “ama yedeğimiz vardı” diye şoka girer.
Bu yazıda tar, gzip, bzip2 ve benzeri araçlarla oluşturduğunuz arşivlerin bütünlüğünü otomatik olarak doğrulayan betikler yazacağız. Sadece “dosya açılıyor mu” seviyesinde değil, checksum doğrulaması, dosya sayısı kontrolü, kritik dosyaların varlığı gibi katmanlı doğrulama yapacağız.
Neden Arşiv Doğrulaması Kritik?
Birkaç senaryo düşünelim. Disk sektör hatası yüzünden arşiv dosyası oluşturuluyor ama içeriği eksik. Ağ üzerinden kopyalama sırasında paket kaybı oluyor ve dosya boyutu tutarsız kalıyor. Disk doldu, arşiv yarıda kesildi ama tar exit code’u her zaman bunu yansıtmıyor. Ya da en basiti: cron job çalıştı, log’a “başarılı” yazdı, ama aslında arşivlenecek dizin mount edilmemişti ve boş bir tar oluşturdunuz.
Bunların hepsini yaşadım. Birini bizzat, diğerlerini meslektaşlarımın anlattıklarından.
# Basit bir test: tar'ın exit code'una güvenmek yeterli mi?
tar -czf /backup/test.tar.gz /var/www/html
echo "Exit code: $?"
# Çıktı: Exit code: 0
# Ama /var/www/html boşsa? Yine 0 döner.
ls -lh /backup/test.tar.gz
# Çıktı: -rw-r--r-- 1 root root 32 Mar 15 02:00 test.tar.gz
# 32 byte'lık "başarılı" yedek. Harika.
İşte bu yüzden exit code kontrolü yeterli değil. Katmanlı doğrulama şart.
Temel Doğrulama Yöntemleri
Arşiv İçeriğini Test Etmek
Tar’ın kendi test mekanizması vardır. -t flag’i arşivi okur ve içeriği listeler, -W ise doğrulama yapar.
# Arşiv içeriğini listele ve hata var mı bak
tar -tzf /backup/mybackup.tar.gz > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "Arşiv yapısal olarak sağlam"
else
echo "HATA: Arşiv bozuk veya eksik"
fi
# Daha detaylı: kaç dosya var?
FILE_COUNT=$(tar -tzf /backup/mybackup.tar.gz 2>/dev/null | wc -l)
echo "Arşivdeki dosya sayısı: $FILE_COUNT"
# gzip'in kendi bütünlük testi
gzip -t /backup/mybackup.tar.gz
echo "gzip test sonucu: $?"
# bzip2 için
bzip2 -t /backup/mybackup.tar.bz2
echo "bzip2 test sonucu: $?"
gzip -t: Dosyanın gzip formatını ve CRC değerini doğrular, çıkarma yapmaz. bzip2 -t: Aynı mantıkla bzip2 arşivini test eder. tar -tzf: Arşiv içeriğini listeler, bozuk blok varsa hata döner.
Checksum Doğrulaması
Arşiv oluştururken SHA256 veya MD5 hash’i kaydedin, sonra karşılaştırın. Bu özellikle arşivi uzak sunucuya kopyaladıktan sonra çok işe yarıyor.
# Arşiv oluştururken hash'i kaydet
BACKUP_FILE="/backup/db_backup_$(date +%Y%m%d).tar.gz"
tar -czf "$BACKUP_FILE" /var/lib/mysql/
# SHA256 hash'i hesapla ve kaydet
sha256sum "$BACKUP_FILE" > "${BACKUP_FILE}.sha256"
cat "${BACKUP_FILE}.sha256"
# Çıktı: a3f2c1d4e5... /backup/db_backup_20240315.tar.gz
# Doğrulama (aynı makinede veya kopyaladıktan sonra)
sha256sum -c "${BACKUP_FILE}.sha256"
# Başarılıysa: /backup/db_backup_20240315.tar.gz: OK
# Başarısızsa: /backup/db_backup_20240315.tar.gz: FAILED
sha256sum -c: Hash dosyasını okur ve belirtilen dosyaları doğrular. Değişiklik varsa “FAILED” yazar ve exit code 1 döner.
Kapsamlı Doğrulama Betiği
Şimdi gerçek işe gelelim. Aşağıdaki betik production ortamımda kullandığım ve zamanla olgunlaştırdığım bir template. Adım adım inşa edeceğiz.
#!/bin/bash
# backup_verify.sh - Arşiv bütünlük doğrulama betiği
# Versiyon: 2.1
# Kullanım: ./backup_verify.sh <arşiv_dosyası> [beklenen_min_dosya_sayısı]
set -euo pipefail
# Renk kodları
RED='33[0;31m'
GREEN='33[0;32m'
YELLOW='33[1;33m'
NC='33[0m' # No Color
# Değişkenler
ARCHIVE_FILE="${1:-}"
MIN_FILE_COUNT="${2:-1}"
LOG_FILE="/var/log/backup_verify.log"
VERIFY_ERRORS=0
VERIFY_WARNINGS=0
# Log fonksiyonu
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE"
}
# Hata fonksiyonu
error() {
log "ERROR" "${RED}$1${NC}"
((VERIFY_ERRORS++))
}
# Uyarı fonksiyonu
warn() {
log "WARN" "${YELLOW}$1${NC}"
((VERIFY_WARNINGS++))
}
# Başarı fonksiyonu
ok() {
log "OK" "${GREEN}$1${NC}"
}
# Giriş doğrulaması
if [ -z "$ARCHIVE_FILE" ]; then
echo "Kullanım: $0 <arşiv_dosyası> [min_dosya_sayısı]"
exit 1
fi
if [ ! -f "$ARCHIVE_FILE" ]; then
error "Dosya bulunamadı: $ARCHIVE_FILE"
exit 1
fi
log "INFO" "Doğrulama başlıyor: $ARCHIVE_FILE"
# 1. Dosya boyutu kontrolü
FILE_SIZE=$(stat -c%s "$ARCHIVE_FILE")
if [ "$FILE_SIZE" -lt 1024 ]; then
warn "Arşiv boyutu şüpheli küçük: ${FILE_SIZE} byte"
else
ok "Dosya boyutu: $(du -sh "$ARCHIVE_FILE" | cut -f1)"
fi
# 2. Arşiv tipi tespiti ve format doğrulaması
MIME_TYPE=$(file --mime-type -b "$ARCHIVE_FILE")
case "$MIME_TYPE" in
"application/gzip"|"application/x-gzip")
gzip -t "$ARCHIVE_FILE" 2>/dev/null && ok "gzip format doğrulaması geçti" || error "gzip format doğrulaması BAŞARISIZ"
TAR_FLAG="-tzf"
;;
"application/x-bzip2")
bzip2 -t "$ARCHIVE_FILE" 2>/dev/null && ok "bzip2 format doğrulaması geçti" || error "bzip2 format doğrulaması BAŞARISIZ"
TAR_FLAG="-tjf"
;;
"application/x-xz")
xz -t "$ARCHIVE_FILE" 2>/dev/null && ok "xz format doğrulaması geçti" || error "xz format doğrulaması BAŞARISIZ"
TAR_FLAG="-tJf"
;;
"application/x-tar")
TAR_FLAG="-tf"
ok "Ham tar arşivi tespit edildi"
;;
*)
warn "Bilinmeyen MIME tipi: $MIME_TYPE, tar ile deneniyor..."
TAR_FLAG="-tf"
;;
esac
# 3. Tar yapısal bütünlük testi
FILE_LIST=$(tar $TAR_FLAG "$ARCHIVE_FILE" 2>/dev/null)
TAR_EXIT=$?
if [ $TAR_EXIT -ne 0 ]; then
error "Tar içerik listesi alınamadı - arşiv bozuk olabilir"
else
ok "Tar yapısal doğrulama geçti"
fi
# 4. Dosya sayısı kontrolü
ACTUAL_COUNT=$(echo "$FILE_LIST" | grep -c . || true)
log "INFO" "Arşivdeki toplam kayıt: $ACTUAL_COUNT"
if [ "$ACTUAL_COUNT" -lt "$MIN_FILE_COUNT" ]; then
error "Yetersiz dosya sayısı: $ACTUAL_COUNT < $MIN_FILE_COUNT (beklenen minimum)"
else
ok "Dosya sayısı kontrolü geçti: $ACTUAL_COUNT dosya/dizin"
fi
# 5. Sonuç raporu
echo ""
log "INFO" "=== DOĞRULAMA SONUCU ==="
log "INFO" "Hata sayısı: $VERIFY_ERRORS"
log "INFO" "Uyarı sayısı: $VERIFY_WARNINGS"
if [ $VERIFY_ERRORS -gt 0 ]; then
error "DOĞRULAMA BAŞARISIZ - $VERIFY_ERRORS hata tespit edildi"
exit 1
elif [ $VERIFY_WARNINGS -gt 0 ]; then
warn "DOĞRULAMA UYARILARLA TAMAMLANDI"
exit 2
else
ok "DOĞRULAMA BAŞARIYLA TAMAMLANDI"
exit 0
fi
Cron ile Tam Otomasyon
Betiği yazdık ama otomasyonun güzelliği cron entegrasyonunda. Şöyle bir senaryo kuralım: Her gece yedek al, sabah 06:00’da doğrula, sorun varsa mail at.
#!/bin/bash
# full_backup_workflow.sh - Yedekleme ve otomatik doğrulama
BACKUP_DIR="/backup/nightly"
SOURCE_DIR="/var/www"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/www_backup_${DATE}.tar.gz"
HASH_FILE="${BACKUP_FILE}.sha256"
REPORT_FILE="${BACKUP_DIR}/verify_report_${DATE}.txt"
ADMIN_EMAIL="[email protected]"
MIN_EXPECTED_FILES=100
# Dizin oluştur
mkdir -p "$BACKUP_DIR"
echo "=== Yedekleme Başlıyor: $(date) ===" | tee "$REPORT_FILE"
# Kaynak dizin kontrolü
if [ ! -d "$SOURCE_DIR" ] || [ -z "$(ls -A $SOURCE_DIR)" ]; then
echo "KRITIK: Kaynak dizin boş veya yok: $SOURCE_DIR" | tee -a "$REPORT_FILE"
mail -s "[ALARM] Yedekleme Başarısız - Kaynak Boş" "$ADMIN_EMAIL" < "$REPORT_FILE"
exit 1
fi
# Kaynak dosya sayısını kaydet (karşılaştırma için)
SOURCE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
echo "Kaynak dosya sayısı: $SOURCE_COUNT" | tee -a "$REPORT_FILE"
# Yedek al
tar -czf "$BACKUP_FILE" "$SOURCE_DIR" 2>>"$REPORT_FILE"
BACKUP_EXIT=$?
if [ $BACKUP_EXIT -ne 0 ]; then
echo "HATA: tar komutu başarısız oldu (exit: $BACKUP_EXIT)" | tee -a "$REPORT_FILE"
mail -s "[ALARM] Yedekleme Başarısız" "$ADMIN_EMAIL" < "$REPORT_FILE"
exit 1
fi
# Hash oluştur
sha256sum "$BACKUP_FILE" > "$HASH_FILE"
echo "SHA256: $(cat $HASH_FILE)" | tee -a "$REPORT_FILE"
# Doğrulama yap
/usr/local/bin/backup_verify.sh "$BACKUP_FILE" "$MIN_EXPECTED_FILES" >> "$REPORT_FILE" 2>&1
VERIFY_EXIT=$?
case $VERIFY_EXIT in
0)
echo "SONUC: Doğrulama BAŞARILI" | tee -a "$REPORT_FILE"
# Başarı mailini sadece haftanın ilk günü gönder (log kirliliği önlemek için)
[ "$(date +%u)" -eq 1 ] && mail -s "[OK] Haftalık Yedek Raporu" "$ADMIN_EMAIL" < "$REPORT_FILE"
;;
1)
echo "SONUC: Doğrulama BAŞARISIZ - Acil müdahale gerekiyor!" | tee -a "$REPORT_FILE"
mail -s "[ALARM] Yedek Bütünlük Doğrulaması Başarısız" "$ADMIN_EMAIL" < "$REPORT_FILE"
;;
2)
echo "SONUC: Doğrulama uyarılarla tamamlandı" | tee -a "$REPORT_FILE"
mail -s "[UYARI] Yedek Doğrulama Uyarıları" "$ADMIN_EMAIL" < "$REPORT_FILE"
;;
esac
# Eski yedekleri temizle (30 günden eski)
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.sha256" -mtime +30 -delete
find "$BACKUP_DIR" -name "verify_report_*" -mtime +30 -delete
echo "=== İşlem Tamamlandı: $(date) ===" | tee -a "$REPORT_FILE"
Crontab’a eklemek için:
# crontab -e
# Her gece 02:00'de yedek al ve doğrula
0 2 * * * /usr/local/bin/full_backup_workflow.sh >> /var/log/backup_workflow.log 2>&1
Uzak Sunucuya Kopyalama Sonrası Doğrulama
Çok sık atlanan bir nokta: yedeği uzak sunucuya rsync veya scp ile kopyaladıktan sonra da doğrulama yapmak gerekiyor. Aktarım sırasında bozulma olabilir.
#!/bin/bash
# remote_verify.sh - Uzak sunucudaki arşivi doğrula
LOCAL_BACKUP="/backup/nightly/db_backup_latest.tar.gz"
REMOTE_HOST="backup-server.sirket.com.tr"
REMOTE_PATH="/remote-backup/db/"
REMOTE_USER="backupuser"
# Yerel hash'i hesapla
LOCAL_HASH=$(sha256sum "$LOCAL_BACKUP" | awk '{print $1}')
echo "Yerel SHA256: $LOCAL_HASH"
# Uzak sunucuya kopyala
rsync -avz --checksum "$LOCAL_BACKUP" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}"
# Uzak sunucuda hash doğrula
REMOTE_FILENAME=$(basename "$LOCAL_BACKUP")
REMOTE_HASH=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" "sha256sum ${REMOTE_PATH}${REMOTE_FILENAME}" | awk '{print $1}')
echo "Uzak SHA256: $REMOTE_HASH"
# Karşılaştır
if [ "$LOCAL_HASH" = "$REMOTE_HASH" ]; then
echo "OK: Dosya bütünlüğü doğrulandı - transfer başarılı"
# Uzak sunucuda tar bütünlük testi de yap
ssh "${REMOTE_USER}@${REMOTE_HOST}" "gzip -t ${REMOTE_PATH}${REMOTE_FILENAME} && echo 'Uzak gzip testi geçti'"
else
echo "HATA: Hash uyuşmazlığı - transfer sırasında bozulma tespit edildi!"
echo "Yerel: $LOCAL_HASH"
echo "Uzak: $REMOTE_HASH"
exit 1
fi
Kritik Dosya Varlığı Kontrolü
Bazı durumlarda sadece arşivin bozulmamış olması yetmez, içinde kesinlikle bulunması gereken dosyaların var olup olmadığını da kontrol etmek istersiniz. Özellikle konfigürasyon yedeklerinde bunu çok kullanıyorum.
#!/bin/bash
# content_verify.sh - Kritik dosya varlığı doğrulaması
ARCHIVE="/backup/config_backup.tar.gz"
ERRORS=0
# Arşivde bulunması gereken kritik dosyalar
CRITICAL_FILES=(
"etc/nginx/nginx.conf"
"etc/nginx/sites-enabled/default"
"etc/mysql/mysql.conf.d/mysqld.cnf"
"etc/ssh/sshd_config"
"etc/crontab"
)
echo "Kritik dosya varlığı kontrolü başlıyor..."
# Arşiv içeriğini bir kez al (performans için)
ARCHIVE_CONTENTS=$(tar -tzf "$ARCHIVE" 2>/dev/null)
for critical_file in "${CRITICAL_FILES[@]}"; do
if echo "$ARCHIVE_CONTENTS" | grep -q "$critical_file"; then
echo "OK: $critical_file bulundu"
else
echo "EKSIK: $critical_file arşivde YOK!"
((ERRORS++))
fi
done
# Boş dosya kontrolü (0 byte'lık dosyalar sorunlu olabilir)
EMPTY_FILES=$(tar -tvf "$ARCHIVE" 2>/dev/null | awk '$3 == 0 && !//$/ {print $NF}')
if [ -n "$EMPTY_FILES" ]; then
echo ""
echo "UYARI: Arşivde boş dosyalar tespit edildi:"
echo "$EMPTY_FILES"
fi
if [ $ERRORS -gt 0 ]; then
echo ""
echo "SONUC: $ERRORS kritik dosya eksik! Yedek güvenilir değil."
exit 1
else
echo ""
echo "SONUC: Tüm kritik dosyalar doğrulandı."
exit 0
fi
Doğrulama Sonuçlarını İzleme
Uzun vadede doğrulama geçmişini takip etmek isteyebilirsiniz. Basit bir JSON log formatı işleri kolaylaştırır:
#!/bin/bash
# verify_logger.sh - Doğrulama sonuçlarını yapısal logla kaydet
ARCHIVE_FILE="$1"
VERIFY_STATUS="$2" # SUCCESS, FAILED, WARNING
JSON_LOG="/var/log/backup_verify_history.json"
# Dosya yoksa boş array ile başlat
[ ! -f "$JSON_LOG" ] && echo "[]" > "$JSON_LOG"
# Yeni kayıt
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
FILE_SIZE=$(stat -c%s "$ARCHIVE_FILE" 2>/dev/null || echo 0)
FILE_COUNT=$(tar -tzf "$ARCHIVE_FILE" 2>/dev/null | wc -l || echo 0)
HOSTNAME=$(hostname -f)
NEW_ENTRY="{
"timestamp": "$TIMESTAMP",
"hostname": "$HOSTNAME",
"archive": "$ARCHIVE_FILE",
"size_bytes": $FILE_SIZE,
"file_count": $FILE_COUNT,
"status": "$VERIFY_STATUS"
}"
# JSON array'e ekle (jq varsa)
if command -v jq &>/dev/null; then
TEMP_FILE=$(mktemp)
jq ". += [$NEW_ENTRY]" "$JSON_LOG" > "$TEMP_FILE" && mv "$TEMP_FILE" "$JSON_LOG"
else
# jq yoksa basit append
echo "$NEW_ENTRY" >> /var/log/backup_verify_simple.log
fi
echo "Log kaydedildi: $VERIFY_STATUS - $ARCHIVE_FILE"
# Son 7 günün başarı oranını hesapla
if command -v jq &>/dev/null; then
TOTAL=$(jq 'length' "$JSON_LOG")
SUCCESS=$(jq '[.[] | select(.status == "SUCCESS")] | length' "$JSON_LOG")
echo "Genel başarı oranı: $SUCCESS/$TOTAL"
fi
Gerçek Dünya Notları
Birkaç pratik ipucu bunları defalarca zor yoldan öğrendim:
- Set -euo pipefail kullanın. Betiklerinizin sessizce başarısız olmasını önler. Pipe içindeki herhangi bir komut başarısız olursa tüm betik durur.
- Doğrulama zamanlaması: Yedek aldıktan hemen sonra aynı süreçte doğrulama yapın, ayrı bir cron job’a bırakmayın. Ayrı job bazen “yedeği henüz almadım ama doğrulamaya girdim” durumuna düşebilir.
- Disk alanı tükenirse: Tar yarım bir arşiv oluşturup exit code 0 dönebilir. Yedek başlamadan önce hedef disk alanını kontrol edin.
- Test restorasyon: Haftada bir rastgele bir yedeği geçici bir dizine açıp birkaç dosyanın içeriğini kontrol eden bir betik yazın. Bütünlük testi arşivin açılabilir olduğunu gösterir ama içeriğin doğru olduğunu kanıtlamaz.
- Hash dosyalarını ayrı yerde saklayın: Hash dosyası arşivle aynı disk’te duruyorsa disk bozulduğunda ikisi de gider. Hash’leri farklı bir lokasyona veya veritabanına yazın.
Sonuç
Yedekleme sistemleri “kur ve unut” değil, “kur, doğrula ve izle” anlayışıyla yönetilmeli. Burada anlattığım betikler başlangıç noktası; kendi ortamınızın ihtiyaçlarına göre uyarlayın. Hangi verilerin kritik olduğunu, kaç dosyanın normalde yedeklenmesi gerektiğini, boyut aralığının ne olması gerektiğini siz bilirsiniz.
En önemli adım, bu betikleri yazdıktan sonra test etmek. Kasıtlı olarak bozuk bir tar.gz oluşturun, boyutu küçültün, kritik bir dosyayı arşivden çıkarın. Betiğiniz bunları yakalamalı. Yakalamazsa production’da da yakalamayacak.
Uyku rahat gelsin.
