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 -dile 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,
--excludeile 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.
