Rsync ile Docker Volume Yedekleme: Adım Adım Rehber

Docker ile production ortamı yönetiyorsanız, en büyük korkulardan biri gece yarısı telefon almak ve “veritabanı gitti” haberini duymaktır. Volume yedeklemesi olmayan bir Docker ortamı, üzerinde dengeleme yaptığınız bir ip gibidir. Rsync ise bu ipin altına serdiğiniz güvenlik ağıdır. Bu yazıda Docker volume’larını rsync ile nasıl yedekleyeceğinizi, bunu nasıl otomatize edeceğinizi ve gerçek bir felaket senaryosunda nasıl geri döneceğinizi ele alacağız.

Docker Volume Yapısını Anlamak

Rsync ile yedeklemeye başlamadan önce Docker volume’larının nerede durduğunu bilmek gerekiyor. Varsayılan olarak Docker, named volume’ları şu dizinde saklar:

/var/lib/docker/volumes/

Her volume, kendi adıyla bir klasör içinde _data dizininde tutulur. Örneğin postgres_data adında bir volume’unuz varsa, gerçek veriler şurada bulunur:

/var/lib/docker/volumes/postgres_data/_data/

Bunu doğrulamak için aşağıdaki komutu kullanabilirsiniz:

# Tüm volume'ları listele
docker volume ls

# Belirli bir volume'un detaylarını gör
docker volume inspect postgres_data

# Çıktıda Mountpoint alanını kontrol et
# "Mountpoint": "/var/lib/docker/volumes/postgres_data/_data"

Bind mount kullanıyorsanız durum biraz farklıdır. Bind mount’larda veri doğrudan host üzerindeki bir dizine bağlıdır ve siz bu dizini zaten biliyorsunuzdur. Named volume’larda ise bu soyutlama katmanı söz konusudur.

Önemli Not: /var/lib/docker/volumes/ dizinine erişmek için root yetkisi gerekir. Yedekleme scriptlerinizi buna göre yapılandırmanız gerekecek.

Yedekleme Öncesi Hazırlık

Dizin Yapısını Oluşturmak

Düzenli bir yedekleme ortamı için önce dizin yapısını oluşturalım:

# Yedekleme dizin yapısını oluştur
mkdir -p /backup/docker-volumes/{daily,weekly,monthly}
mkdir -p /backup/logs
mkdir -p /etc/backup-scripts

# İzinleri ayarla
chmod 700 /backup/docker-volumes
chmod 700 /etc/backup-scripts

Rsync’in Kurulu Olduğunu Doğrulamak

# Rsync sürümünü kontrol et
rsync --version

# Kurulu değilse Debian/Ubuntu için
apt-get install rsync -y

# RHEL/CentOS için
yum install rsync -y

Rsync’in 3.1.0 ve üzeri bir sürümde olması önerilir. Eski sürümlerde --info=progress2 gibi parametreler çalışmayabilir.

Temel Rsync Yedekleme Komutu

En basit haliyle bir Docker volume’unu rsync ile yedeklemek şöyle görünür:

# Tek bir volume'u yedekle
rsync -avz --progress 
  /var/lib/docker/volumes/postgres_data/_data/ 
  /backup/docker-volumes/daily/postgres_data/

# Tüm volume'ları tek seferde yedekle
rsync -avz --progress 
  /var/lib/docker/volumes/ 
  /backup/docker-volumes/daily/volumes/

Burada kullandığımız parametrelerin ne işe yaradığını açıklayalım:

  • -a: Archive modu. İzinleri, zaman damgalarını, sembolik linkleri ve sahiplik bilgilerini korur
  • -v: Verbose mod. Ne yapıldığını terminale yazar
  • -z: Transfer sırasında sıkıştırma uygular. Ağ üzerinden yedekleme yapıyorsanız bant genişliğini önemli ölçüde azaltır
  • –progress: Her dosya için ilerleme durumunu gösterir
  • –delete: Kaynak dizinden silinmiş dosyaları hedeften de siler. Dikkatli kullanın!
  • –exclude: Belirli dosya veya dizinleri yedekleme dışı bırakır
  • –backup: Üzerine yazılacak dosyaları silmek yerine yedek olarak saklar
  • –link-dest: Hard link kullanarak incremental yedekleme yapar, disk alanından tasarruf sağlar

Çalışan Container’ları Yedeklemek: Tutarlılık Sorunu

İşte burada gerçek sorun başlıyor. Çalışan bir PostgreSQL veya MySQL container’ını doğrudan rsync ile yedeklemek veri tutarsızlığına yol açabilir. Yazma işlemi tam ortadayken kopyalanan bir veritabanı dosyası bozuk olabilir.

Bu sorunu çözmenin birkaç yolu var:

Yöntem 1: Container’ı Durdurup Yedekle

Production ortamda pek önerilen bir yöntem değil ama küçük sistemlerde işe yarar:

#!/bin/bash
# Basit container durdur-yedekle-başlat scripti

CONTAINER_NAME="postgres_container"
VOLUME_NAME="postgres_data"
BACKUP_DIR="/backup/docker-volumes/daily"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "[$TIMESTAMP] Container durduruluyor: $CONTAINER_NAME"
docker stop $CONTAINER_NAME

echo "[$TIMESTAMP] Yedekleme başlıyor..."
rsync -avz --delete 
  /var/lib/docker/volumes/${VOLUME_NAME}/_data/ 
  ${BACKUP_DIR}/${VOLUME_NAME}/

echo "[$TIMESTAMP] Container yeniden başlatılıyor: $CONTAINER_NAME"
docker start $CONTAINER_NAME

echo "[$TIMESTAMP] Yedekleme tamamlandı."

Yöntem 2: Geçici Container ile Yedekleme (Önerilen)

Bu yöntem daha temiz. Çalışan container’a dokunmadan, volume’u başka bir geçici container’a bağlayıp oradan yedekliyoruz:

#!/bin/bash
# Geçici container ile volume yedekleme

VOLUME_NAME="postgres_data"
BACKUP_DIR="/backup/docker-volumes/daily"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/backup/logs/backup_${TIMESTAMP}.log"

echo "[$TIMESTAMP] Geçici container ile yedekleme başlıyor..." | tee -a $LOG_FILE

# Önce PostgreSQL'e checkpoint attır (veri tutarlılığı için)
docker exec postgres_container psql -U postgres -c "CHECKPOINT;" 2>/dev/null

# Geçici container oluştur ve rsync ile yedekle
docker run --rm 
  -v ${VOLUME_NAME}:/source:ro 
  -v ${BACKUP_DIR}:/backup 
  alpine sh -c "
    apk add --no-cache rsync > /dev/null 2>&1
    rsync -avz /source/ /backup/${VOLUME_NAME}/
    echo 'Yedekleme başarılı!'
  " 2>&1 | tee -a $LOG_FILE

echo "[$TIMESTAMP] İşlem tamamlandı." | tee -a $LOG_FILE

Yöntem 3: Database Dump + Rsync Kombinasyonu

Veritabanları için en güvenli yöntem dump almak ve sonra rsync ile taşımaktır:

#!/bin/bash
# PostgreSQL dump + rsync kombinasyonu

DB_CONTAINER="postgres_container"
DB_USER="postgres"
DB_NAME="myapp_db"
DUMP_DIR="/backup/docker-volumes/daily/dumps"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p $DUMP_DIR

# PostgreSQL dump al
echo "[$TIMESTAMP] PostgreSQL dump alınıyor..."
docker exec $DB_CONTAINER pg_dump -U $DB_USER $DB_NAME | 
  gzip > ${DUMP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz

# Dump'ı uzak sunucuya rsync ile gönder
rsync -avz --progress 
  ${DUMP_DIR}/ 
  [email protected]:/remote-backup/postgres-dumps/

echo "[$TIMESTAMP] Dump ve transfer tamamlandı."

Kapsamlı Otomatik Yedekleme Scripti

Şimdi işi ciddi tutalım. Aşağıdaki script production ortamında kullanabileceğiniz, hata kontrolü olan, loglama yapan ve birden fazla volume’u destekleyen kapsamlı bir çözüm sunar:

#!/bin/bash
# /etc/backup-scripts/docker-volume-backup.sh
# Docker Volume Otomatik Yedekleme Scripti
# Versiyon: 1.0

set -euo pipefail

# ==================== YAPILANDIRMA ====================
BACKUP_BASE="/backup/docker-volumes"
LOG_DIR="/backup/logs"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DATE_TODAY=$(date +%Y%m%d)
LOG_FILE="${LOG_DIR}/backup_${TIMESTAMP}.log"
RETENTION_DAILY=7      # Günlük yedek saklama süresi
RETENTION_WEEKLY=4     # Haftalık yedek saklama süresi
REMOTE_HOST="backup-server.internal"
REMOTE_USER="rsync-backup"
REMOTE_PATH="/data/backups/docker"
SSH_KEY="/root/.ssh/backup_rsa"

# Yedeklenecek volume'lar (isim:öncelik formatında)
VOLUMES=(
    "postgres_data:critical"
    "redis_data:normal"
    "app_uploads:normal"
    "nginx_conf:low"
)

# ==================== FONKSİYONLAR ====================

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

check_dependencies() {
    local deps=("rsync" "docker")
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" &>/dev/null; then
            log "ERROR" "$dep bulunamadı! Lütfen yükleyin."
            exit 1
        fi
    done
    log "INFO" "Bağımlılık kontrolü tamam."
}

send_notification() {
    local status=$1
    local message=$2
    # Slack webhook veya email göndermek için buraya ekleyebilirsiniz
    # curl -s -X POST -H 'Content-type: application/json' 
    #   --data "{"text":"Backup $status: $message"}" 
    #   "$SLACK_WEBHOOK_URL"
    log "INFO" "Bildirim: [$status] $message"
}

backup_volume() {
    local volume_name=$1
    local priority=$2
    local backup_dir="${BACKUP_BASE}/daily/${DATE_TODAY}/${volume_name}"
    local volume_path="/var/lib/docker/volumes/${volume_name}/_data"

    # Volume var mı kontrol et
    if ! docker volume inspect "$volume_name" &>/dev/null; then
        log "WARN" "$volume_name volume bulunamadı, atlanıyor."
        return 1
    fi

    mkdir -p "$backup_dir"

    log "INFO" "$volume_name yedekleniyor (öncelik: $priority)..."

    # Önceki günün yedeğini link-dest olarak kullan (incremental backup)
    local yesterday=$(date -d "yesterday" +%Y%m%d)
    local link_dest_opt=""
    if [ -d "${BACKUP_BASE}/daily/${yesterday}/${volume_name}" ]; then
        link_dest_opt="--link-dest=${BACKUP_BASE}/daily/${yesterday}/${volume_name}"
        log "INFO" "Incremental yedekleme: önceki yedek bulundu."
    fi

    # Rsync ile yedekle
    if rsync -avz 
        --delete 
        --delete-excluded 
        $link_dest_opt 
        --exclude="*.tmp" 
        --exclude="*.lock" 
        --exclude="*.pid" 
        --log-file="${LOG_DIR}/rsync_${volume_name}_${TIMESTAMP}.log" 
        "$volume_path/" 
        "$backup_dir/"; then
        log "INFO" "$volume_name yedekleme başarılı."
        return 0
    else
        log "ERROR" "$volume_name yedekleme BAŞARISIZ!"
        return 1
    fi
}

sync_to_remote() {
    log "INFO" "Uzak sunucuya senkronizasyon başlıyor: $REMOTE_HOST"

    if rsync -avz 
        --delete 
        -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" 
        "${BACKUP_BASE}/daily/${DATE_TODAY}/" 
        "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/daily/${DATE_TODAY}/"; then
        log "INFO" "Uzak senkronizasyon başarılı."
    else
        log "ERROR" "Uzak senkronizasyon BAŞARISIZ!"
        send_notification "HATA" "Uzak sunucu senkronizasyonu başarısız!"
    fi
}

cleanup_old_backups() {
    log "INFO" "Eski yedekler temizleniyor..."

    # Günlük yedekleri temizle
    find "${BACKUP_BASE}/daily" -maxdepth 1 -type d -mtime +${RETENTION_DAILY} 
        -exec rm -rf {} ; 2>/dev/null || true

    # Haftalık yedekleri temizle
    find "${BACKUP_BASE}/weekly" -maxdepth 1 -type d -mtime +$((RETENTION_WEEKLY * 7)) 
        -exec rm -rf {} ; 2>/dev/null || true

    log "INFO" "Temizlik tamamlandı."
}

# ==================== ANA AKIŞ ====================

mkdir -p "$LOG_DIR"
log "INFO" "========== Docker Volume Yedekleme Başlıyor =========="

check_dependencies

SUCCESS_COUNT=0
FAIL_COUNT=0

for volume_entry in "${VOLUMES[@]}"; do
    volume_name="${volume_entry%%:*}"
    priority="${volume_entry##*:}"

    if backup_volume "$volume_name" "$priority"; then
        ((SUCCESS_COUNT++))
    else
        ((FAIL_COUNT++))
    fi
done

# Uzak sunucuya gönder
sync_to_remote

# Eski yedekleri temizle
cleanup_old_backups

log "INFO" "========== Özet =========="
log "INFO" "Başarılı: $SUCCESS_COUNT | Başarısız: $FAIL_COUNT"

if [ $FAIL_COUNT -gt 0 ]; then
    send_notification "HATA" "$FAIL_COUNT volume yedeklenemedi!"
    exit 1
else
    send_notification "BAŞARILI" "Tüm volumeler yedeklendi."
fi

Cron ile Otomatize Etmek

Scripti hazırladıktan sonra crontab’a ekleyin:

# Scripti çalıştırılabilir yap
chmod +x /etc/backup-scripts/docker-volume-backup.sh

# Root crontab'ını düzenle
crontab -e

# Aşağıdaki satırları ekle:
# Her gece 02:00'de çalıştır
0 2 * * * /etc/backup-scripts/docker-volume-backup.sh >> /backup/logs/cron.log 2>&1

# Her Pazar 03:00'te haftalık yedek
0 3 * * 0 /etc/backup-scripts/docker-volume-weekly-backup.sh >> /backup/logs/cron-weekly.log 2>&1

Yedekten Geri Yükleme

Yedek almak işin yarısı. Asıl kritik olan geri yüklemenin de çalışıyor olması. Ayda en az bir kez geri yükleme testi yapın.

#!/bin/bash
# Docker volume'u yedekten geri yükle

VOLUME_NAME="postgres_data"
BACKUP_DATE="20240115"  # Hangi tarihten geri yüklenecek
BACKUP_SOURCE="/backup/docker-volumes/daily/${BACKUP_DATE}/${VOLUME_NAME}"

echo "UYARI: Bu işlem mevcut volume verilerinin üzerine yazacak!"
echo "Volume: $VOLUME_NAME"
echo "Kaynak: $BACKUP_SOURCE"
read -p "Devam etmek istiyor musunuz? (yes/no): " confirm

if [ "$confirm" != "yes" ]; then
    echo "İşlem iptal edildi."
    exit 0
fi

# İlgili container'ı durdur
echo "Container durduruluyor..."
docker stop postgres_container

# Mevcut volume'u yedekle (güvenlik için)
echo "Mevcut volume yedekleniyor..."
rsync -avz 
  /var/lib/docker/volumes/${VOLUME_NAME}/_data/ 
  /backup/docker-volumes/pre-restore/${VOLUME_NAME}_$(date +%Y%m%d_%H%M%S)/

# Yedekten geri yükle
echo "Geri yükleme başlıyor..."
rsync -avz --delete 
  ${BACKUP_SOURCE}/ 
  /var/lib/docker/volumes/${VOLUME_NAME}/_data/

# Container'ı yeniden başlat
echo "Container yeniden başlatılıyor..."
docker start postgres_container

# Birkaç saniye bekle ve sağlık kontrolü yap
sleep 5
if docker exec postgres_container pg_isready -U postgres; then
    echo "Geri yükleme başarılı! PostgreSQL hazır."
else
    echo "HATA: PostgreSQL hazır değil, logları kontrol edin!"
    docker logs postgres_container --tail 50
fi

SSH ile Uzak Sunucuya Güvenli Yedekleme

Yerel yedekleme tek başına yeterli değildir. 3-2-1 kuralı gereği en az bir kopyayı farklı bir fiziksel konumda tutmalısınız.

# Backup sunucusunda özel kullanıcı oluştur
useradd -m -s /bin/bash rsync-backup
mkdir -p /home/rsync-backup/.ssh
chmod 700 /home/rsync-backup/.ssh

# Ana sunucuda SSH key oluştur
ssh-keygen -t ed25519 -f /root/.ssh/backup_rsa -N "" -C "docker-backup@mainserver"

# Public key'i backup sunucusuna kopyala
ssh-copy-id -i /root/.ssh/backup_rsa.pub [email protected]

# Rsync daemon'ı yerine SSH üzerinden güvenli transfer
rsync -avz 
  -e "ssh -i /root/.ssh/backup_rsa -p 22" 
  /backup/docker-volumes/daily/ 
  [email protected]:/data/backups/docker/daily/

Backup sunucusunda ~/.ssh/authorized_keys dosyasına komutu kısıtlayarak güvenliği artırabilirsiniz:

# Sadece rsync komutuna izin ver
command="rsync --server -logDtprze.iLsfxC . /data/backups/docker/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-ed25519 AAAA... docker-backup@mainserver

Yedekleme Boyutunu İzlemek

Yedekleme dizinleri zamanla şişebilir. Düzenli kontrol için:

#!/bin/bash
# Yedekleme boyutu raporu

BACKUP_DIR="/backup/docker-volumes"

echo "===== Docker Volume Yedekleme Boyut Raporu ====="
echo "Tarih: $(date)"
echo ""
echo "Toplam yedekleme boyutu:"
du -sh $BACKUP_DIR

echo ""
echo "Günlük yedekler:"
du -sh ${BACKUP_DIR}/daily/* 2>/dev/null | sort -h

echo ""
echo "Son 5 rsync log dosyası:"
ls -lt /backup/logs/rsync_*.log 2>/dev/null | head -5

echo ""
echo "Disk durumu:"
df -h /backup

Gerçek Dünya Senaryosu: E-Ticaret Sitesi Kurtarma

Geçen yıl yaşadığımız bir olayı anlatayım. Bir e-ticaret müşterisinin sunucusunda disk arızası yaşandı. PostgreSQL volume’u, ürün görselleri için kullanılan uploads volume’u ve Redis session verisi gitmişti. Tam yedekleme sistemimiz sayesinde şu adımlarla 45 dakikada sistemi ayağa kaldırdık:

  • Yeni sunucu açıldı, Docker kuruldu
  • Named volume’lar oluşturuldu: docker volume create postgres_data
  • Rsync ile en son yedekten veriler geri yüklendi
  • docker-compose up -d ile tüm stack ayağa kalktı
  • Sadece son 3 saatlik sipariş verisi kayboldu (bu kısmı payment gateway loglarından manuel düzeltildi)

Eğer bu yedekleme sistemi olmasaydı, PostgreSQL dump’ı da yoksa muhtemelen 3-4 günlük veri kaybıyla karşılaşacaktık. Rsync’in incremental yedekleme özelliği sayesinde 50GB’lık volume her gece sadece 200-500MB ek alan kullanıyordu.

Sık Yapılan Hatalar

  • Sadece local yedek almak: Disk arızasında hem veriler hem yedekler gider. Mutlaka uzak lokasyon kullanın
  • Yedekleri test etmemek: “Yedek var” demek yeterli değil. Geri yükleme testleri yapılmayan yedekler güvensizdir
  • Çalışan veritabanını doğrudan kopyalamak: Tutarsız veri alırsınız. Dump veya checkpoint kullanın
  • Log dosyalarını yedeklemeye dahil etmek: Gereksiz alan tüketir, --exclude ile dışarıda bırakın
  • Saklama politikası tanımlamamak: Disk dolar, eski yedekler birikerek sistemleri felç eder

Sonuç

Rsync ile Docker volume yedeklemesi, özellikle kurumsal backup çözümleri için bütçe ayıramayan ekipler için son derece güçlü ve güvenilir bir yaklaşım. --link-dest ile incremental yedekleme yaparak disk alanından tasarruf edebilir, SSH üzerinden şifrelenmiş aktarım sağlayabilir ve cron ile tamamen otomatize edebilirsiniz.

Kritik olan nokta şu: Yedekleme sistemi kurmak, sadece başlangıç. Asıl iş geri yükleme prosedürlerini belgelemek, testleri düzenli yapmak ve log’ları izlemektir. Bu yazıdaki scriptleri kendi ortamınıza uyarlayın, volume isimlerini ve retention sürelerini ihtiyaçlarınıza göre düzenleyin ve ilk geri yükleme testinizi bu hafta yapın. Felaket anında soğukkanlı kalabilmek için o testleri önceden yapmış olmak şarttır.

Bir yanıt yazın

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