Rsync ile İnkremental Yedek Arşivi: Hard Link Yöntemi
Yedekleme stratejileri söz konusu olduğunda, çoğu sistem yöneticisi ya “her gece tam yedek al” tuzağına düşer ya da karmaşık yedekleme yazılımlarının labirentinde kaybolur. Oysa rsync’in hard link yöntemi, bu iki uç arasında mükemmel bir denge sunar: tam yedek görünümünde, incremental yedek boyutunda arşivler. Bu yazıda bu yöntemi her açısıyla ele alacağız.
Hard Link Yöntemi Neden Bu Kadar Güçlü?
Önce temelden başlayalım. Geleneksel incremental yedeklemede “son tam yedekten bu yana değişen dosyaları” alırsın. Geri yükleme yaparken önce tam yedeği, sonra sırayla incremental parçaları uygulamak zorunda kalırsın. Bu hem zaman alır hem de hata payı yaratır.
Hard link yöntemi farklı çalışır. Her yedekleme klasörü, bağımsız bir tam yedek gibi görünür. /backup/2024-01-15/ dizinine girdiğinde tüm dosyaları eksiksiz görürsün. Ama aslında değişmeyen dosyalar önceki yedekle aynı inode‘u paylaşır, yani disk üzerinde tek bir kopya halinde durur. Sadece değişen dosyalar gerçekten kopyalanır.
Bunu somutlaştıralım: 100 GB’lık bir sunucunuzda her gün 500 MB veri değişiyor diyelim. Geleneksel yöntemle bir haftada 100 + (7 x 500 MB) = 103.5 GB yer kullanırsınız. Hard link yöntemiyle 7 günün her biri tam yedek gibi görünmesine rağmen toplam disk kullanımı 100 + (6 x 500 MB) = 103 GB civarında kalır. İki ay yedek tuttuğunuzda fark dramatik hale gelir.
Temel Kavramlar: Hard Link ve İnode
Linux dosya sistemini anlamadan bu yöntemi kullanmak, körü körüne bir şey yapmak gibidir. Hızlıca netleştirelim.
Her dosya disk üzerinde bir inode numarasıyla temsil edilir. İnode, dosyanın meta verilerini (boyut, izinler, zaman damgası, veri bloklarının adresleri) tutar. Dizin girdisi ise sadece “bu isim, şu inode’a işaret ediyor” der. Hard link oluşturduğunda, aynı inode’a işaret eden ikinci (veya üçüncü, dördüncü…) bir dizin girdisi yaratırsın.
# Hard link örneği
echo "test verisi" > /tmp/orijinal.txt
ln /tmp/orijinal.txt /tmp/hardlink.txt
# İnode numaralarının aynı olduğunu görelim
ls -li /tmp/orijinal.txt /tmp/hardlink.txt
# 2097153 -rw-r--r-- 2 root root 12 Jan 15 10:00 /tmp/hardlink.txt
# 2097153 -rw-r--r-- 2 root root 12 Jan 15 10:00 /tmp/orijinal.txt
# Biri silinse bile diğeri çalışmaya devam eder
rm /tmp/orijinal.txt
cat /tmp/hardlink.txt # Hala "test verisi" yazar
Rsync’in --link-dest parametresi tam da bunu kullanır. Değişmeyen dosyaları kopyalamak yerine, referans alınan yedekle hard link kurar. Böylece hem disk alanı hem de ağ trafiği açısından muazzam tasarruf sağlanır.
Temel Rsync Komutu ve Parametreler
Önce sade ama işlevsel bir rsync komutunu inceleyelim:
rsync -avz --delete
--link-dest=/backup/son_yedek
/kaynak/dizin/
/backup/$(date +%Y-%m-%d)/
Bu komuttaki parametreleri açıklayalım:
- -a (archive): Recursive kopyalama, sembolik linkleri koru, izinleri aktar, zaman damgalarını koru, sahipliği (ownership) koru
- -v (verbose): Ne yapıldığını göster
- -z (compress): Ağ üzerinden aktarımda sıkıştırma kullan (yerel yedeklemede gereksiz)
- –delete: Kaynaktan silinmiş dosyaları yedekten de sil
- –link-dest: Değişmeyen dosyalar için hard link referansı al
- $(date +%Y-%m-%d): Bugünün tarihiyle klasör oluştur
Dikkat edilmesi gereken önemli bir nokta: --link-dest parametresine verilen yol mutlak yol olmalıdır, yoksa rsync beklenmedik davranışlar sergileyebilir.
Gerçek Dünya Senaryosu: Web Sunucusu Yedeği
Bir prodüksiyon web sunucusunu düşünelim. /var/www, /etc/nginx, /etc/ssl ve /home dizinlerini uzak bir yedek sunucusuna her gece yedeklemek istiyoruz. Aynı zamanda son 30 günün yedeğini tutmak, 30 günden eskilerini otomatik silmek istiyoruz.
Önce yedek sunucusunda dizin yapısını oluşturalım:
# Yedek sunucusunda
mkdir -p /backup/webserver
mkdir -p /backup/webserver/logs
# SSH key-based authentication için (yedekleme sunucusunda)
useradd -m -s /bin/bash backup_user
mkdir -p /home/backup_user/.ssh
chmod 700 /home/backup_user/.ssh
Web sunucusunda SSH anahtarı oluşturup yedek sunucusuna kopyalayalım:
# Web sunucusunda
ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
ssh-copy-id -i /root/.ssh/backup_key.pub [email protected]
Şimdi asıl yedekleme scriptini yazalım:
#!/bin/bash
# /usr/local/bin/incremental-backup.sh
# Değişkenler
KAYNAK_DIZINLER="/var/www /etc/nginx /etc/ssl /home"
YEDEK_SUNUCU="[email protected]"
YEDEK_KOKU="/backup/webserver"
TARIH=$(date +%Y-%m-%d_%H-%M-%S)
BUGUNUN_YEDEGI="${YEDEK_KOKU}/${TARIH}"
SON_YEDEK="${YEDEK_KOKU}/son_yedek"
LOG_DOSYASI="${YEDEK_KOKU}/logs/backup_${TARIH}.log"
SAKLANACAK_GUN=30
# Log fonksiyonu
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_DOSYASI}"
}
log "Yedekleme başlatıldı: ${TARIH}"
# Son yedeğin var olup olmadığını kontrol et
if ssh -i /root/.ssh/backup_key "${YEDEK_SUNUCU}" "[ -d ${SON_YEDEK} ]"; then
LINK_DEST="--link-dest=${SON_YEDEK}"
log "Referans yedek bulundu: ${SON_YEDEK}"
else
LINK_DEST=""
log "Referans yedek yok, tam yedek alınıyor"
fi
# Rsync ile yedekleme
rsync -avz
--delete
--delete-excluded
--exclude="*.log"
--exclude="*.tmp"
--exclude="/var/www/*/cache/"
--exclude="node_modules/"
-e "ssh -i /root/.ssh/backup_key -o StrictHostKeyChecking=no"
${LINK_DEST}
${KAYNAK_DIZINLER}
"${YEDEK_SUNUCU}:${BUGUNUN_YEDEGI}/" 2>&1 | tee -a "${LOG_DOSYASI}"
RSYNC_CIKIS=$?
if [ ${RSYNC_CIKIS} -eq 0 ]; then
log "Rsync başarılı"
# son_yedek sembolik linkini güncelle
ssh -i /root/.ssh/backup_key "${YEDEK_SUNUCU}"
"rm -f ${SON_YEDEK} && ln -s ${BUGUNUN_YEDEGI} ${SON_YEDEK}"
log "son_yedek linki güncellendi -> ${BUGUNUN_YEDEGI}"
# Eski yedekleri temizle
log "Eski yedekler temizleniyor (${SAKLANACAK_GUN} günden eski)..."
ssh -i /root/.ssh/backup_key "${YEDEK_SUNUCU}"
"find ${YEDEK_KOKU} -maxdepth 1 -type d -name '20*'
| sort | head -n -${SAKLANACAK_GUN} | xargs -r rm -rf"
log "Temizlik tamamlandı"
else
log "HATA: Rsync başarısız oldu! Çıkış kodu: ${RSYNC_CIKIS}"
# Mail gönder
echo "Yedekleme hatası! Sunucu: $(hostname), Tarih: ${TARIH}" |
mail -s "HATA: Yedekleme Başarısız" [email protected]
exit 1
fi
log "Yedekleme tamamlandı"
exit 0
Son Yedeği Sembolik Link ile Takip Etmek
Scriptimizde son_yedek adında bir sembolik link kullandık. Bu yaklaşım çok önemli. --link-dest her zaman en son başarılı yedeğe işaret etmeli, bu yüzden sabit bir isim kullanmak mantıklı. Ama alternatif bir yaklaşım da var: en son tarihe göre dinamik bulmak.
#!/bin/bash
# En son yedek klasörünü bul (sembolik link olmadan)
YEDEK_KOKU="/backup/webserver"
# Tarih formatında klasörleri bul, en sonuncuyu al
SON_YEDEK=$(find "${YEDEK_KOKU}" -maxdepth 1 -type d -name "20*" | sort | tail -n 1)
if [ -n "${SON_YEDEK}" ]; then
echo "Son yedek bulundu: ${SON_YEDEK}"
LINK_DEST="--link-dest=${SON_YEDEK}"
else
echo "Hiç yedek yok, tam yedek alınacak"
LINK_DEST=""
fi
Bu yaklaşımın dezavantajı, başarısız bir yedeklemenin de “son yedek” olarak algılanabilmesidir. Bu yüzden sembolik link yöntemi daha güvenli.
Disk Kullanımını Doğru Ölçmek
Hard link yöntemiyle oluşturulmuş yedeklerde du komutu sizi yanıltabilir. Her klasör tam boyutta görünür ama bu aslında paylaşılan inode’ları çift sayıyor.
# Yanıltıcı sonuç - her klasörü ayrı ayrı ölçer
du -sh /backup/webserver/2024-01-*
# 95G /backup/webserver/2024-01-14
# 96G /backup/webserver/2024-01-15
# 95G /backup/webserver/2024-01-16
# Gerçek disk kullanımı - hard link'leri hesaba katar
du -sh --count-links /backup/webserver/
# 102G /backup/webserver/
# Daha doğru ölçüm için
du -sh /backup/webserver/
# 103G /backup/webserver/
# İnode bazlı paylaşımı görmek için
df -h /backup/
Hard link’lerin gerçekten çalışıp çalışmadığını doğrulamak için:
# İki farklı yedek günündeki aynı dosyanın inode numarasını karşılaştır
ls -i /backup/webserver/2024-01-14/etc/nginx/nginx.conf
ls -i /backup/webserver/2024-01-15/etc/nginx/nginx.conf
# Eğer aynıysa -> dosya değişmemiş, hard link kullanılmış
# Eğer farklıysa -> dosya değişmiş, yeni kopya oluşturulmuş
Cron ile Otomatikleştirme
Script hazır, şimdi otomatikleştirelim. Birkaç farklı senaryo için cron konfigürasyonları:
# crontab -e ile açın
# Her gece 02:30'da çalış
30 2 * * * /usr/local/bin/incremental-backup.sh
# Haftanın belirli günleri çalış (Pazartesi-Cuma)
30 2 * * 1-5 /usr/local/bin/incremental-backup.sh
# Her 6 saatte bir çalış
0 */6 * * * /usr/local/bin/incremental-backup.sh
# Birden fazla script ve log yönetimi
30 2 * * * /usr/local/bin/incremental-backup.sh >> /var/log/backup.log 2>&1
Cron çalışırken PATH ve ortam değişkenleri farklı olabilir. Script başına şunu ekleyin:
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
source /etc/environment 2>/dev/null || true
Yedekten Geri Yükleme
En sık unutulan konu: geri yükleme. Hard link yöntemiyle geri yükleme son derece basittir, çünkü her klasör zaten tam yedek gibi görünür.
# Belirli bir tarihin yedeğinden tek dosya geri yükleme
rsync -avz /backup/webserver/2024-01-15/var/www/html/index.php
/var/www/html/index.php
# Tüm bir dizini geri yükleme
rsync -avz --delete
/backup/webserver/2024-01-15/var/www/
/var/www/
# Uzak yedek sunucusundan geri yükleme
rsync -avz
-e "ssh -i /root/.ssh/backup_key"
[email protected]:/backup/webserver/2024-01-15/var/www/
/var/www/
# Sadece belirli dosya tiplerini geri yükle
rsync -avz
--include="*.php"
--include="*/"
--exclude="*"
/backup/webserver/2024-01-15/var/www/
/var/www/
Yaygın Hatalar ve Çözümleri
Soru: --link-dest çalışmıyor, her seferinde tam kopya alıyor.
Genellikle üç nedeni vardır. Birincisi, kaynak ve hedef farklı dosya sistemlerinde. Hard link’ler dosya sistemi sınırlarını aşamaz. Çözüm: yedek hedefinin tek bir dosya sisteminde olduğundan emin ol.
İkincisi, zaman damgaları tutarsız. Rsync dosyaları karşılaştırırken boyut ve mtime kullanır. Kaynak sunucunun saati yedek sunucusuyla farklıysa tüm dosyalar “değişmiş” görünür. NTP ayarlarını kontrol et.
# Zaman damgası sorununu debug etmek için
rsync -avzn --link-dest=/backup/son_yedek
/kaynak/ /backup/test/ 2>&1 | grep -v "^$" | head -50
# -n (dry-run) ile neyin kopyalanacağını görebilirsin
Soru: “Too many levels of symbolic links” hatası alıyorum.
--link-dest için sembolik link yerine gerçek yolu kullan:
# Sembolik linkin gerçek yolunu bul
readlink -f /backup/webserver/son_yedek
# Bu yolu --link-dest'e ver
GERCEK_YOL=$(readlink -f /backup/webserver/son_yedek)
rsync --link-dest="${GERCEK_YOL}" ...
Soru: Yedek klasörleri silinince hard link’ler ne olur?
Hard link mantığı sayesinde hiçbir şey kaybolmaz. Bir klasörü rm -rf ile sildiğinde, o klasördeki hard link’ler silinir ama inode’daki link sayısı azalır. Başka bir klasörde aynı inode’a işaret eden link varsa, veri diskten silinmez. Veri ancak son hard link silindiğinde gerçekten temizlenir.
Performans İyileştirmeleri
Büyük sunucularda rsync’i daha hızlı çalıştırmak için:
# Checksum yerine hızlı karşılaştırma (varsayılan zaten bu, ama açıkça belirtmek iyi)
rsync -avz --size-only --link-dest=...
# Çoklu bağlantı için xargs ile paralel rsync (farklı dizinler için)
echo "/var/wwwn/homen/etc" | xargs -P 3 -I{} rsync -az
--link-dest=/backup/son_yedek{}
{} backup-server:/backup/$(date +%Y-%m-%d){}
# Bant genişliği sınırlama (gece değil, gündüz çalıştırıyorsanız)
rsync -avz --bwlimit=10000 --link-dest=... # 10 MB/s limit
# Sıkıştırma seviyesini optimize et
rsync -avz --compress-level=3 --link-dest=...
Yedekleme Doğrulama Scripti
Yedek alındı ama gerçekten çalışıyor mu? Düzenli doğrulama yapılmalı:
#!/bin/bash
# /usr/local/bin/verify-backup.sh
YEDEK_KOKU="/backup/webserver"
MIN_YEDEK_SAYISI=7
SON_YEDEK_MAX_YAS=25 # saat cinsinden
# Son yedeğin yaşını kontrol et
SON_KLASOR=$(find "${YEDEK_KOKU}" -maxdepth 1 -type d -name "20*" | sort | tail -n 1)
if [ -z "${SON_KLASOR}" ]; then
echo "KRITIK: Hiç yedek klasörü bulunamadı!"
exit 2
fi
# Son yedeğin kaç saat önce oluşturulduğunu hesapla
SON_YEDEK_ZAMANI=$(stat -c %Y "${SON_KLASOR}")
SIMDI=$(date +%s)
GECEN_SAAT=$(( (SIMDI - SON_YEDEK_ZAMANI) / 3600 ))
if [ ${GECEN_SAAT} -gt ${SON_YEDEK_MAX_YAS} ]; then
echo "UYARI: Son yedek ${GECEN_SAAT} saat önce alındı! Beklenen maksimum: ${SON_YEDEK_MAX_YAS} saat"
exit 1
fi
# Yedek sayısını kontrol et
YEDEK_SAYISI=$(find "${YEDEK_KOKU}" -maxdepth 1 -type d -name "20*" | wc -l)
if [ ${YEDEK_SAYISI} -lt ${MIN_YEDEK_SAYISI} ]; then
echo "UYARI: Yedek sayısı ${YEDEK_SAYISI}, minimum ${MIN_YEDEK_SAYISI} olmalı!"
exit 1
fi
# Kritik dosyaların varlığını kontrol et
KONTROL_DOSYALARI=(
"${SON_KLASOR}/etc/nginx/nginx.conf"
"${SON_KLASOR}/var/www/html/index.php"
)
for DOSYA in "${KONTROL_DOSYALARI[@]}"; do
if [ ! -f "${DOSYA}" ]; then
echo "UYARI: Kritik dosya bulunamadı: ${DOSYA}"
exit 1
fi
done
echo "OK: ${YEDEK_SAYISI} yedek mevcut, son yedek ${GECEN_SAAT} saat önce alındı"
exit 0
Bu scripti Nagios, Zabbix veya basit bir cron ile düzenli çalıştırarak yedekleme sağlığını izleyebilirsiniz.
Sonuç
Rsync’in hard link yöntemi, karmaşık yedekleme yazılımlarına ihtiyaç duymadan kurumsal kalitede yedekleme arşivi oluşturmanızı sağlar. Her gün tam yedek görünümünde, minimum disk alanıyla, kolayca geri yüklenebilir bir arşiv yapısı elde edersiniz.
Özetlemek gerekirse bu yöntemde dikkat edilmesi gereken kritik noktalar:
--link-destiçin her zaman mutlak yol kullanınson_yedeksembolik linkini sadece başarılı yedeklemelerden sonra güncelleyin- Hard link’ler aynı dosya sisteminde çalışır, farklı partition veya disk için çalışmaz
dukomutu sizi yanıltabilir, gerçek disk kullanımı için dikkatli ölçüm yapın- NTP senkronizasyonunu ihmal etmeyin, zaman tutarsızlığı tüm yöntemi bozar
- Yedeklemenin yanı sıra düzenli geri yükleme testi yapın
Bu altyapıyı bir kez kurduğunuzda, hem disk alanından hem de restore zamanından ciddi tasarruf sağlayacaksınız. Yedekleme senaryonuza göre scripti kişiselleştirin, doğrulama adımlarını ihmal etmeyin ve en önemlisi: zaman zaman gerçekten geri yükleme yaparak sistemin çalıştığını kanıtlayın. Hiç test edilmemiş yedek, yedek değildir.
