Restic ile Yedekleme Sağlık Kontrolü ve Doğrulama

Yedekleme aldığını sanmak ile yedeklemenin gerçekten çalışır durumda olduğunu bilmek arasında dağlar kadar fark var. Onlarca sistem yöneticisiyle konuştuğumda gördüm ki çoğu kişi yedekleme scriptini çalıştırıp “tamam, yedek alınıyor” diye içi rahat ediyor. Ta ki felakete uğrayana kadar. Restic bu konuda oldukça güçlü araçlar sunuyor ve bugün bu araçları nasıl kullanacağımızı, otomatik sağlık kontrollerini nasıl kuracağımızı ve yedeklerimizin gerçekten restore edilebilir olduğunu nasıl doğrulayacağımızı derinlemesine inceleyeceğiz.

Restic Repository Sağlığı Neden Kritik?

Restic, yedeklerinizi bir repository içinde blok bazlı (content-addressable) olarak saklar. Bu mimari harika bir veri tekilleştirme sağlarken aynı zamanda repository bütünlüğünün korunması kritik hale gelir. Ağ kesintileri, donanım hataları, storage tarafındaki sorunlar veya yarım kalan işlemler repository’yi bozabilir.

Peki bu bozulma ne zaman fark edilir? Çoğu zaman tam da restore etmeye çalıştığınız o kritik anda. Bu yüzden proaktif doğrulama yapmak, sysadmin’in birinci önceliği olmalı.

Temel check Komutu

Restic’in check komutu repository’nin yapısal bütünlüğünü doğrular. En basit haliyle:

restic -r /mnt/backup/myrepo check

Bu komut ne yapar?

  • Repository’nin index yapısını kontrol eder
  • Pack dosyalarının varlığını doğrular
  • Snapshot referanslarının tutarlılığını inceler
  • Orphan (sahipsiz) veri bloklarını tespit eder

Çıktı şöyle görünür:

using temporary cache in /tmp/restic-check-cache-123456789
repository 1a2b3c4d opened successfully, password is correct
created new cache in /tmp/restic-check-cache-123456789
[0:00] 100.00%  1 / 1 index files loaded
no errors were found

Eğer bir sorun varsa şöyle bir çıktı alabilirsiniz:

pack 4a5b6c7d: does not exist
error: repository contains errors

Bu noktada panik yapmadan önce sorunun ne olduğunu anlamanız gerekiyor.

Veri Bloklarını da Doğrulayan Derinlemesine Kontrol

Varsayılan check komutu sadece metadata’yı doğrular, asıl veri bloklarını okumaz. Gerçek bir sağlık kontrolü için --read-data flag’ini kullanmanız gerekir:

restic -r /mnt/backup/myrepo check --read-data

Bu komut repository’deki her pack dosyasını okur ve checksum’ları doğrular. Küçük bir repository’de dakikalar içinde biter ama büyük repository’lerde saatler alabilir. Bu yüzden genellikle haftalık veya aylık olarak çalıştırılır.

Büyük repository’ler için daha akıllıca bir yaklaşım olan rastgele örnekleme özelliğini kullanabilirsiniz:

# Repository'nin %10'unu rastgele örnekleyerek kontrol et
restic -r /mnt/backup/myrepo check --read-data-subset=10%

Bu yaklaşım özellikle uzak depolama (S3, Backblaze B2, sftp) kullanırken hem bant genişliğini hem de maliyeti kontrol altında tutmanızı sağlar.

Belirli bir boyut veya pack sayısı da belirtebilirsiniz:

# İlk 5 pack dosyasını kontrol et
restic -r /mnt/backup/myrepo check --read-data-subset=5/50

Remote Repository ile Sağlık Kontrolü

Rclone üzerinden uzak bir depolamaya bağlıyken sağlık kontrolü yapmak biraz farklı bir konfigürasyon gerektiriyor. Önce bir örnek senaryo kuralım: S3-uyumlu bir depolamada (örneğin Wasabi veya Backblaze B2) restic repository’niz var.

# Environment variable'larla S3 bağlantısı
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export RESTIC_REPOSITORY="s3:s3.wasabisys.com/mybucket/backups"
export RESTIC_PASSWORD="your-repo-password"

# Metadata kontrolü
restic check

# Rastgele %5 veri okuma ile kontrol
restic check --read-data-subset=5%

Rclone backend kullanıyorsanız:

export RESTIC_REPOSITORY="rclone:myremote:backup-bucket/restic-repo"
export RESTIC_PASSWORD_FILE="/etc/restic/password"

restic check --read-data-subset=15%

Çıkış kodlarına dikkat edin. Restic başarılı kontrol için 0, hata durumunda 1 döndürür. Bu, scriptleme açısından son derece kullanışlıdır.

Snapshot Doğrulama ve Detaylı İnceleme

Sağlıklı bir repository var ama snapshot’larınız gerçekten restore edilebilir mi? Bunu anlamak için birkaç farklı yaklaşım var.

İlk olarak snapshot’larınızı listeleyin ve son snapshot’ın zamanını kontrol edin:

restic snapshots --json | python3 -c "
import json, sys
from datetime import datetime, timezone

data = json.load(sys.stdin)
if not data:
    print('CRITICAL: No snapshots found!')
    sys.exit(2)

latest = max(data, key=lambda x: x['time'])
snap_time = datetime.fromisoformat(latest['time'].replace('Z', '+00:00'))
now = datetime.now(timezone.utc)
hours_ago = (now - snap_time).total_seconds() / 3600

print(f'Latest snapshot: {latest["id"][:8]}')
print(f'Time: {snap_time}')
print(f'Hours ago: {hours_ago:.1f}')

if hours_ago > 25:
    print('WARNING: Last backup is older than 25 hours!')
    sys.exit(1)
else:
    print('OK: Recent backup exists')
"

Bu script aynı zamanda Nagios/Icinga tipi monitoring sistemleriyle entegre edilebilir çünkü uygun çıkış kodları kullanıyor.

Gerçek Dünya Restore Testi

Yedeklerinizi test etmeden “yedek alıyorum” demek, sigortasız araba sürmekle aynı şey. Ayda en az bir kere kısmi restore testi yapmanızı kesinlikle tavsiye ederim.

#!/bin/bash
# restore_test.sh - Kısmi restore testi

REPO="rclone:wasabi-backup:mycompany/restic"
PASSWORD_FILE="/etc/restic/password"
TEST_DIR="/tmp/restic-restore-test"
LOG_FILE="/var/log/restic-restore-test.log"

echo "=== Restore Test Başlıyor: $(date) ===" >> "$LOG_FILE"

# Geçici dizini temizle
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"

# Son snapshot'ı al
LATEST_SNAP=$(restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  snapshots --json --last | python3 -c 
  "import json,sys; d=json.load(sys.stdin); print(d[-1]['id'] if d else '')")

if [ -z "$LATEST_SNAP" ]; then
    echo "HATA: Snapshot bulunamadi" >> "$LOG_FILE"
    exit 1
fi

echo "Test edilecek snapshot: $LATEST_SNAP" >> "$LOG_FILE"

# Sadece kritik bir dizini restore et (örneğin /etc/nginx)
restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  restore "$LATEST_SNAP" 
  --include "/etc/nginx" 
  --target "$TEST_DIR" 2>> "$LOG_FILE"

RESTORE_EXIT=$?

if [ $RESTORE_EXIT -eq 0 ]; then
    # Restore edilen dosyaların varlığını kontrol et
    if [ -d "$TEST_DIR/etc/nginx" ]; then
        FILE_COUNT=$(find "$TEST_DIR/etc/nginx" -type f | wc -l)
        echo "BASARILI: $FILE_COUNT dosya restore edildi" >> "$LOG_FILE"
        # Test dizinini temizle
        rm -rf "$TEST_DIR"
        exit 0
    else
        echo "HATA: Restore klasoru beklenen yapıda degil" >> "$LOG_FILE"
        exit 1
    fi
else
    echo "HATA: Restore islemi basarisiz (exit: $RESTORE_EXIT)" >> "$LOG_FILE"
    exit 1
fi

Otomatik Sağlık Kontrol Scripti

Bütün kontrolleri bir araya getiren, e-posta ve Slack bildirimi gönderen kapsamlı bir script hazırlayalım:

#!/bin/bash
# restic_health_check.sh
# Cron: 0 6 * * * /usr/local/bin/restic_health_check.sh

set -euo pipefail

# Konfigürasyon
REPO="${RESTIC_REPOSITORY:-rclone:myremote:backups/restic}"
PASSWORD_FILE="/etc/restic/password"
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:-}"
ALERT_EMAIL="[email protected]"
MAX_BACKUP_AGE_HOURS=25
CHECK_SUBSET="10%"
LOG_FILE="/var/log/restic-health.log"
REPORT=""
STATUS="OK"

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

send_slack_alert() {
    local message="$1"
    if [ -n "$SLACK_WEBHOOK" ]; then
        curl -s -X POST "$SLACK_WEBHOOK" 
          -H 'Content-type: application/json' 
          --data "{"text": "*Restic Sağlık Kontrolü*n${message}"}" > /dev/null
    fi
}

send_email_alert() {
    local subject="$1"
    local body="$2"
    echo -e "$body" | mail -s "$subject" "$ALERT_EMAIL" 2>/dev/null || true
}

log "=== Restic Sağlık Kontrolü Başlıyor ==="

# 1. Temel Repository Kontrolü
log "1. Repository metadata kontrolü..."
if restic -r "$REPO" --password-file "$PASSWORD_FILE" check 2>&1 | tee -a "$LOG_FILE"; then
    log "Metadata kontrolü: BAŞARILI"
else
    STATUS="CRITICAL"
    log "Metadata kontrolü: BAŞARISIZ"
fi

# 2. Veri örnekleme kontrolü
log "2. Veri bütünlüğü kontrolü (%${CHECK_SUBSET})..."
if restic -r "$REPO" --password-file "$PASSWORD_FILE" 
   check --read-data-subset="$CHECK_SUBSET" 2>&1 | tee -a "$LOG_FILE"; then
    log "Veri kontrolü: BAŞARILI"
else
    STATUS="CRITICAL"
    log "Veri kontrolü: BAŞARISIZ"
fi

# 3. Son yedek yaşı kontrolü
log "3. Son yedek zamanı kontrol ediliyor..."
LATEST_TIME=$(restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  snapshots --json --last 2>/dev/null | 
  python3 -c "import json,sys; d=json.load(sys.stdin); print(d[-1]['time'] if d else 'NONE')")

if [ "$LATEST_TIME" = "NONE" ]; then
    STATUS="CRITICAL"
    log "Son snapshot zamanı: BULUNAMADI"
else
    HOURS_OLD=$(python3 -c "
from datetime import datetime, timezone
t = datetime.fromisoformat('${LATEST_TIME}'.replace('Z','+00:00'))
diff = (datetime.now(timezone.utc) - t).total_seconds() / 3600
print(f'{diff:.1f}')
")
    log "Son yedek $HOURS_OLD saat önce alındı"
    if (( $(echo "$HOURS_OLD > $MAX_BACKUP_AGE_HOURS" | bc -l) )); then
        STATUS="WARNING"
        log "UYARI: Yedek $MAX_BACKUP_AGE_HOURS saatten daha eski!"
    fi
fi

# 4. Snapshot sayısı kontrolü
SNAP_COUNT=$(restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  snapshots --json 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
log "Toplam snapshot sayısı: $SNAP_COUNT"

# Sonuç değerlendirme
log "=== Kontrol Tamamlandı - Durum: $STATUS ==="

if [ "$STATUS" != "OK" ]; then
    ALERT_MSG="Durum: $STATUSnRepository: $REPOnn$(echo -e "$REPORT")"
    send_slack_alert "$ALERT_MSG"
    send_email_alert "Restic Yedekleme Uyarisi: $STATUS" "$ALERT_MSG"
    exit 1
fi

exit 0

Prune Sonrası Bütünlük Kontrolü

Restic’in forget ve prune operasyonları sonrasında repository’nin tutarlı kalmasını doğrulamak çok önemli. Bu operasyonlar veri siler ve bazen beklenmedik durumlar yaşanabilir:

#!/bin/bash
# prune_and_verify.sh

REPO="rclone:b2:my-bucket/restic"
PASSWORD_FILE="/etc/restic/password"

echo "Eski snapshot'ları temizleniyor..."

# Retention policy uygula
restic -r "$REPO" --password-file "$PASSWORD_FILE" forget 
  --keep-daily 7 
  --keep-weekly 4 
  --keep-monthly 6 
  --keep-yearly 2 
  --prune

FORGET_STATUS=$?

if [ $FORGET_STATUS -ne 0 ]; then
    echo "HATA: forget/prune başarısız (exit: $FORGET_STATUS)"
    exit 1
fi

echo "Prune sonrası bütünlük kontrolü..."

# Prune sonrası mutlaka check çalıştır
restic -r "$REPO" --password-file "$PASSWORD_FILE" check

CHECK_STATUS=$?

if [ $CHECK_STATUS -ne 0 ]; then
    echo "KRİTİK: Prune sonrası repository bütünlük hatası!"
    echo "Acil müdahale gerekiyor. Repository'yi incelleyin."
    # Acil bildirim gönder
    echo "Restic repo bütünlük hatası: $REPO" | 
      mail -s "KRİTİK: Yedekleme Hatası" [email protected]
    exit 1
fi

echo "Tüm kontroller başarılı."

Repository Onarımı: repair Komutu

Restic 0.14.0 ile birlikte gelen repair komutu, bazı repository sorunlarını otomatik olarak çözebilir. Bu komutu dikkatlice kullanın, önce ne yapacağını anlayın:

# Önce sorunlu pack'leri listele
restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  check 2>&1 | grep "pack" | head -20

# Index'i yeniden oluştur (index bozulması durumunda)
restic -r "$REPO" --password-file "$PASSWORD_FILE" repair index

# Snapshot'ları eksik bloklara karşı onar
restic -r "$REPO" --password-file "$PASSWORD_FILE" repair snapshots

# Onarım sonrası tekrar kontrol et
restic -r "$REPO" --password-file "$PASSWORD_FILE" check --read-data-subset=20%

Önemli Not: repair komutu bazı durumlarda veri kaybına yol açabilir. Eksik veri bloklarına sahip snapshot’lar işaretlenerek erişilemez hale getirilebilir. Bu yüzden mümkünse önce repository’nin bir kopyasını alın.

Monitoring Sistemine Entegrasyon

Prometheus ve Grafana kullanan bir ortamda restic metriklerini nasıl toplayabileceğinizi göstereyim. Textfile collector yöntemi en pratik olanı:

#!/bin/bash
# restic_metrics.sh - Prometheus textfile collector için
# /etc/cron.d/restic-metrics: */30 * * * * root /usr/local/bin/restic_metrics.sh

REPO="rclone:wasabi:backups/prod-restic"
PASSWORD_FILE="/etc/restic/password"
METRICS_FILE="/var/lib/prometheus/node-exporter/restic.prom"
TEMP_FILE="${METRICS_FILE}.tmp"

# Snapshot bilgilerini al
SNAP_DATA=$(restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  snapshots --json --last 5 2>/dev/null || echo "[]")

SNAP_COUNT=$(echo "$SNAP_DATA" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")

LATEST_TIMESTAMP=$(echo "$SNAP_DATA" | python3 -c "
import json, sys
from datetime import datetime, timezone
d = json.load(sys.stdin)
if d:
    t = datetime.fromisoformat(d[-1]['time'].replace('Z','+00:00'))
    print(int(t.timestamp()))
else:
    print(0)
" 2>/dev/null || echo "0")

# Check sonucu
if restic -r "$REPO" --password-file "$PASSWORD_FILE" check 2>/dev/null; then
    CHECK_STATUS=1
else
    CHECK_STATUS=0
fi

# Metrikleri yaz
cat > "$TEMP_FILE" << EOF
# HELP restic_check_status Repository sağlık durumu (1=sağlıklı, 0=sorunlu)
# TYPE restic_check_status gauge
restic_check_status{repository="prod"} $CHECK_STATUS

# HELP restic_last_backup_timestamp Son yedekleme Unix timestamp
# TYPE restic_last_backup_timestamp gauge
restic_last_backup_timestamp{repository="prod"} $LATEST_TIMESTAMP

# HELP restic_snapshot_count Toplam snapshot sayısı
# TYPE restic_snapshot_count gauge
restic_snapshot_count{repository="prod"} $SNAP_COUNT
EOF

mv "$TEMP_FILE" "$METRICS_FILE"

Sık Karşılaşılan Sorunlar ve Çözümleri

Gerçek hayatta en çok şu durumlarla karşılaşılıyor:

Lock kalan repository sorunu: Bir yedekleme işlemi yarıda kesilirse repository üzerinde bir lock kalabilir. Bu durum sonraki check veya backup işlemlerini engeller.

# Mevcut lock'ları listele
restic -r "$REPO" --password-file "$PASSWORD_FILE" list locks

# Eski lock'ları temizle (dikkatli kullanın, başka bir işlem varsa bozabilir)
restic -r "$REPO" --password-file "$PASSWORD_FILE" unlock

Cache bozulması: Yerel cache bazen yanlış sonuçlara yol açabilir.

# Cache'i bypass ederek kontrol et
restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  check --no-cache

# Cache'i sıfırla
restic cache --cleanup

Timeout sorunları: Büyük remote repository’lerde check işlemi zaman aşımına uğrayabilir. Bu durumda daha küçük subset kullanın veya bağlantı timeout’unu artırın:

# Rclone backend için timeout ayarı
export RCLONE_TIMEOUT=600s

restic -r "$REPO" --password-file "$PASSWORD_FILE" 
  check --read-data-subset=5%

Check Sıklığı Önerileri

Ortam büyüklüğüne ve kritikliğine göre farklı stratejiler uygun olur:

  • Günlük: Metadata-only check (restic check) her gece çalışmalı
  • Haftalık: %15-20 veri örnekleme ile check, tercihen hafta sonu
  • Aylık: Tam veri doğrulama (--read-data) ve gerçek restore testi
  • Her prune sonrası: Mutlaka metadata check çalıştırılmalı

Bu kontrolleri cron veya systemd timer ile otomatize edin. Özellikle systemd timer kullanıyorsanız OnCalendar direktifini esnek biçimde kullanabilirsiniz:

# /etc/systemd/system/restic-check.timer
[Unit]
Description=Restic Repository Health Check

[Timer]
OnCalendar=*-*-* 04:30:00
RandomizedDelaySec=1800
Persistent=true

[Install]
WantedBy=timers.target

Sonuç

Restic güçlü ve güvenilir bir araç ama “set and forget” mantığıyla kullanılamaz. Bugün anlattığım adımları uygularsanız yedekleme sağlığınız konusunda çok daha güvende olursunuz:

  • Günlük metadata check ile repository yapısını izleyin
  • Haftalık veri örnekleme ile gerçek bütünlüğü doğrulayın
  • Aylık restore testi ile geri yüklemenin çalıştığını kanıtlayın
  • Prune sonrası otomatik check ile temizlik işlemlerini güvenceye alın
  • Monitoring entegrasyonu ile anında haberdar olun

En kötü senaryo şu: Felakete uğruyorsunuz, restore için komutları çalıştırıyorsunuz ve “repository contains errors” çıktısını görüyorsunuz. Bu anı yaşamamak için bugün bir sağlık kontrol rutini oluşturun. Gelecekteki kendiniz size teşekkür edecek.

Bir yanıt yazın

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