Restic ile MySQL Veritabanı Yedekleme
Veritabanı yedekleme meselesine gelince çoğu sysadmin’in aklına ilk olarak mysqldump + cron + bir yere kopyala kombinasyonu geliyor. Bu yaklaşım işe yarıyor, evet. Ama sürümleme yok, şifreleme yok, tekilleştirme (deduplication) yok. Bir noktada “ya önceki haftanın yedeğine dönmem gerekse?” diye sorduğunuzda cevap çoğunlukla “yok” oluyor. Restic tam burada devreye giriyor. Block-level deduplication, AES-256 şifreleme ve çoklu backend desteğiyle MySQL yedeklerini gerçek anlamda güvenilir bir seviyeye taşıyabiliriz.
Restic Nedir ve MySQL İçin Neden Tercih Edilir?
Restic, Go ile yazılmış modern bir yedekleme aracı. Her yedekleme işlemine “snapshot” diyor ve bu snapshot’lar birbirinden bağımsız görünse de arkaplanda değişen blokları deduplication ile paylaşıyor. Yani her gece tam yedek alsanız bile disk kullanımı lineer artmıyor.
MySQL için özellikle önemli olan birkaç özelliği var:
- Deduplication: MySQL dump dosyaları büyük kısmıyla aynı veriyi taşır. Restic bunu fark edip tekrar eden blokları saklamaz
- Şifreleme: Repository’e yazan her şey otomatik AES-256 ile şifrelenir
- Tutarlı snapshot geçmişi:
keep-daily 7 keep-weekly 4 keep-monthly 6gibi politikalar tek satırda tanımlanabilir - Çoklu backend: SFTP, S3, B2, Rclone üzerinden herhangi bir cloud storage
Kurulum ve İlk Hazırlık
Restic Kurulumu
# Ubuntu/Debian
apt update && apt install -y restic
# RHEL/CentOS/Rocky
dnf install -y restic
# Ya da direkt binary olarak (her zaman güncel versiyon için önerilir)
wget https://github.com/restic/restic/releases/download/v0.16.4/restic_0.16.4_linux_amd64.bz2
bunzip2 restic_0.16.4_linux_amd64.bz2
mv restic_0.16.4_linux_amd64 /usr/local/bin/restic
chmod +x /usr/local/bin/restic
Versiyon kontrolü yapalım:
restic version
# restic 0.16.4 compiled with go1.21.5 on linux/amd64
Ortam Değişkenleri ve Yapılandırma
Kimlik bilgilerini script içine gömmek yerine bir environment dosyası kullanmak çok daha güvenli bir yaklaşım. Bu dosyayı /etc/restic/env altına koyalım:
mkdir -p /etc/restic
chmod 700 /etc/restic
cat > /etc/restic/env << 'EOF'
# Restic repository parolası
RESTIC_PASSWORD=buraya_guclu_bir_parola_yazin
# SFTP backend örneği
RESTIC_REPOSITORY=sftp:[email protected]:/backup/mysql-repo
# S3 backend kullanıyorsanız
# RESTIC_REPOSITORY=s3:https://s3.eu-central-1.amazonaws.com/sirket-yedek-bucket/mysql
# AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# MySQL kimlik bilgileri
MYSQL_USER=backup_user
MYSQL_PASSWORD=mysql_backup_parolasi
MYSQL_HOST=localhost
EOF
chmod 600 /etc/restic/env
MySQL Backup Kullanıcısı Oluşturma
Root ile yedek almak kötü pratik. Sadece gerekli izinlere sahip bir kullanıcı oluşturalım:
-- MySQL'e root ile bağlan
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'mysql_backup_parolasi';
-- Yedekleme için gereken minimum izinler
GRANT SELECT, SHOW VIEW, RELOAD, REPLICATION CLIENT, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';
-- MySQL 8+ için ek olarak
GRANT PROCESS ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;
Bu kullanıcı veri okuyabilir, flush yapabilir ama yazamaz, silemez. Production ortamında bu ayrımı yapmak kritik.
SFTP Backend için Restic Repository Başlatma
Remote sunucuda önce hedef dizini hazırlayalım:
# Backup sunucusunda
useradd -m -s /bin/bash backup-user
mkdir -p /backup/mysql-repo
chown backup-user:backup-user /backup/mysql-repo
# Ana sunucudan SSH key-based auth için
ssh-keygen -t ed25519 -f /root/.ssh/id_restic -N ""
ssh-copy-id -i /root/.ssh/id_restic.pub [email protected]
SSH config’e ekleyelim ki restic her seferinde doğru key’i kullansın:
cat >> /root/.ssh/config << 'EOF'
Host backup-server
HostName 192.168.1.100
User backup-user
IdentityFile /root/.ssh/id_restic
StrictHostKeyChecking accept-new
EOF
Repository’i init edelim:
source /etc/restic/env
restic init
# Çıktı şuna benzer:
# created restic repository abc123def456 at sftp:[email protected]:/backup/mysql-repo
# Please note that knowledge of your password is required to access
# the repository. Losing your password means that your data is
# irrecoverably lost.
Repository ID’yi ve parolayı mutlaka güvenli bir yere not edin. Parola kaybolursa yedekler de gitti demektir.
Yedekleme Script’i
Şimdi işin özüne gelelim. Sade ama production’a hazır bir script yazalım:
cat > /usr/local/bin/mysql-restic-backup.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
# Environment değişkenlerini yükle
source /etc/restic/env
# Değişkenler
BACKUP_DIR="/tmp/mysql-backup-$$"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/restic-mysql.log"
RETENTION_DAILY=7
RETENTION_WEEKLY=4
RETENTION_MONTHLY=6
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
cleanup() {
log "Geçici dosyalar temizleniyor..."
rm -rf "$BACKUP_DIR"
}
trap cleanup EXIT
log "===== MySQL Yedekleme Başlıyor: $TIMESTAMP ====="
# Geçici dizin oluştur
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
# Tüm veritabanlarını listele (sistem veritabanlarını hariç tut)
DATABASES=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST"
--batch --skip-column-names
-e "SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('information_schema','performance_schema','sys','mysql');" 2>/dev/null)
if [ -z "$DATABASES" ]; then
log "HATA: Veritabanı listesi alınamadı!"
exit 1
fi
# Her veritabanını ayrı ayrı dump et
for DB in $DATABASES; do
log "Dump alınıyor: $DB"
DUMP_FILE="$BACKUP_DIR/${DB}_${TIMESTAMP}.sql.gz"
mysqldump
-u"$MYSQL_USER"
-p"$MYSQL_PASSWORD"
-h"$MYSQL_HOST"
--single-transaction
--routines
--triggers
--events
--hex-blob
--master-data=2
"$DB" 2>/dev/null | gzip -6 > "$DUMP_FILE"
if [ $? -eq 0 ]; then
SIZE=$(du -sh "$DUMP_FILE" | cut -f1)
log "$DB tamamlandı. Boyut: $SIZE"
else
log "HATA: $DB dump başarısız!"
exit 1
fi
done
# Metadata dosyası ekle
cat > "$BACKUP_DIR/backup_info.txt" << INFO
Timestamp: $TIMESTAMP
Host: $(hostname)
MySQL Version: $(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST" -e "SELECT VERSION();" -s -N 2>/dev/null)
Databases: $DATABASES
INFO
# Restic'e yedekle
log "Restic snapshot oluşturuluyor..."
restic backup "$BACKUP_DIR"
--tag "mysql"
--tag "$(hostname)"
--tag "$TIMESTAMP"
--compression max
--verbose 2>> "$LOG_FILE"
log "Retention politikası uygulanıyor..."
restic forget
--keep-daily $RETENTION_DAILY
--keep-weekly $RETENTION_WEEKLY
--keep-monthly $RETENTION_MONTHLY
--tag "mysql"
--prune
--verbose 2>> "$LOG_FILE"
log "Repository bütünlüğü kontrol ediliyor..."
restic check 2>> "$LOG_FILE"
log "===== Yedekleme Tamamlandı ====="
SCRIPT
chmod +x /usr/local/bin/mysql-restic-backup.sh
Script’teki --single-transaction parametresi InnoDB tablolar için kritik. Bu sayede dump alınırken tablo kilitlenmez ve production traffic etkilenmez. MyISAM tablolarınız varsa --lock-tables eklemek gerekebilir.
Cron Job ile Otomatikleştirme
# /etc/cron.d/restic-mysql-backup dosyası oluştur
cat > /etc/cron.d/restic-mysql-backup << 'EOF'
# MySQL Restic Yedekleme - Her gece 02:30'da çalışır
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
30 2 * * * root /usr/local/bin/mysql-restic-backup.sh >> /var/log/restic-mysql.log 2>&1
EOF
chmod 644 /etc/cron.d/restic-mysql-backup
Ya da systemd timer kullanmak isteyenler için daha kontrollü bir yapı:
# /etc/systemd/system/restic-mysql.service
cat > /etc/systemd/system/restic-mysql.service << 'EOF'
[Unit]
Description=Restic MySQL Backup
After=network-online.target mysql.service
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/local/bin/mysql-restic-backup.sh
User=root
StandardOutput=journal
StandardError=journal
EOF
# /etc/systemd/system/restic-mysql.timer
cat > /etc/systemd/system/restic-mysql.timer << 'EOF'
[Unit]
Description=Restic MySQL Backup Timer
Requires=restic-mysql.service
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now restic-mysql.timer
# Timer durumunu kontrol et
systemctl status restic-mysql.timer
RandomizedDelaySec=300 ile birden fazla sunucunuz varsa aynı anda backup sunucusunu boğmaları önlenmiş olur.
Snapshot Yönetimi ve Geri Yükleme
Mevcut Snapshot’ları Listeleme
source /etc/restic/env
# Tüm snapshot'lar
restic snapshots
# MySQL tag'li snapshot'lar
restic snapshots --tag mysql
# Belirli bir host'un snapshot'ları
restic snapshots --host uygulama-sunucu-01 --tag mysql
Belirli Bir Dosyayı Geri Yükleme
Diyelim ki dün gece siparisler veritabanını yanlışlıkla drop ettiniz. Panik yok:
source /etc/restic/env
# Önce snapshot ID'yi bul
restic snapshots --tag mysql --last
# Snapshot içindeki dosyaları listele
restic ls latest /tmp/mysql-backup-12345
# Sadece o veritabanının dump dosyasını çıkar
restic restore latest
--include "*siparisler*"
--target /tmp/restore-$(date +%Y%m%d)
# Geri yüklenen dosyayı kontrol et
ls -lh /tmp/restore-$(date +%Y%m%d)/tmp/mysql-backup-*/
# MySQL'e import et
gunzip -c /tmp/restore-*/tmp/mysql-backup-*/siparisler_*.sql.gz |
mysql -u root -p siparisler_restore
Belirli Bir Zamana Dönme
# 3 gün önceki snapshot'ı kullan
restic restore
--target /tmp/mysql-restore
$(restic snapshots --tag mysql --json |
python3 -c "
import json,sys
from datetime import datetime, timedelta
snaps = json.load(sys.stdin)
target = datetime.now() - timedelta(days=3)
closest = min(snaps, key=lambda x: abs(datetime.fromisoformat(x['time'][:19]) - target))
print(closest['id'])
")
Log Rotasyonu ve İzleme
Backup logları zamanla şişer. Logrotate ayarı ekleyelim:
cat > /etc/logrotate.d/restic-mysql << 'EOF'
/var/log/restic-mysql.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
create 640 root adm
}
EOF
Backup’ın gerçekten çalışıp çalışmadığını izlemek için basit bir kontrol scripti:
cat > /usr/local/bin/check-restic-backup.sh << 'SCRIPT'
#!/bin/bash
source /etc/restic/env
# Son snapshot'ın kaç saat önce alındığını kontrol et
LAST_SNAP_TIME=$(restic snapshots --tag mysql --json --last 2>/dev/null |
python3 -c "
import json,sys
from datetime import datetime, timezone
data = json.load(sys.stdin)
if data:
t = datetime.fromisoformat(data[-1]['time'][:19]).replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
hours = (now - t).total_seconds() / 3600
print(f'{hours:.1f}')
else:
print('999')
")
THRESHOLD=26 # 26 saatten eskiyse alarm
if (( $(echo "$LAST_SNAP_TIME > $THRESHOLD" | bc -l) )); then
echo "KRITIK: Son MySQL yedeği ${LAST_SNAP_TIME} saat önce alındı!"
# Buraya mail veya Slack notification ekleyebilirsiniz
exit 2
else
echo "OK: Son MySQL yedeği ${LAST_SNAP_TIME} saat önce alındı."
exit 0
fi
SCRIPT
chmod +x /usr/local/bin/check-restic-backup.sh
Bu scripti Nagios/Zabbix/Checkmk gibi bir monitoring sistemine ekleyebilirsiniz.
Büyük Veritabanları için Optimizasyon İpuçları
Birkaç yüz GB’lık veritabanlarında dump + restic akışını daha verimli hale getirmek mümkün. Dump’ı diske yazmak yerine pipe üzerinden restic’e vermek yerine (restic bu şekilde çalışmıyor native olarak), sıkıştırma seviyesini ve paralel iş sayısını ayarlamak önemli:
--compression max: CPU maliyeti artıyor ama network ve depolama kazancı yüksekgzip -1yerinepigz -p 4: Çok çekirdekli sıkıştırma, özellikle 10GB+ dump’larda ciddi fark yaratır--pack-size 128: Restic’in paket boyutunu artırarak büyük repository’lerde metadata overhead azaltılabilir
Ayrıca büyük sistemlerde her veritabanını ayrı thread’de dump almak toplam süreyi önemli ölçüde kısaltır:
# Paralel dump örneği (xargs ile)
echo "$DATABASES" | xargs -P 4 -I {} bash -c
'mysqldump --single-transaction -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "{}" 2>/dev/null |
gzip -6 > "'"$BACKUP_DIR"'/{}_dump.sql.gz"'
Repository Bakımı
Restic repository’leri zaman zaman bakıma ihtiyaç duyar. Haftalık check, aylık check --read-data çalıştırmak iyi pratik:
source /etc/restic/env
# Hızlı yapısal kontrol (metadata)
restic check
# Tam veri bütünlüğü kontrolü (aylık yapılması önerilir, yavaştır)
restic check --read-data
# Repository istatistikleri
restic stats --mode raw-data
prune operasyonu silinen snapshot’ların arka planda tuttuğu verileri temizler. Script’te zaten --prune ile birleştirdik ama bağımsız çalıştırmak da mümkün:
restic forget --keep-last 30 --prune --tag mysql
Sonuç
Restic + MySQL kombinasyonu başlangıçta birkaç saatlik kurulum gerektiriyor ama karşılığı fazlasıyla alınıyor. mysqldump + rsync yöntemiyle kıyaslandığında elde edilen avantajlar somut:
- Disk kullanımı deduplication sayesinde dramatik düşüyor. Benzer veri içeren günlük yedekler %80-90 daha az yer tutuyor
- Şifreleme backup sunucusunun ele geçirilmesi senaryosunda veriyi koruyor
- Granüler geri yükleme ile tek bir tabloyu içeren dump dosyasını dakikalar içinde bulup restore edebiliyorsunuz
- Retention politikaları tek komutla yönetiliyor, el işi yok
En önemli nokta: backup sisteminizi bir kez kurup unutmayın. Ayda en az bir kez restore testi yapın. Çalışmayan backup, backup sayılmaz. /tmp/restore-test gibi bir dizine gerçek bir veritabanını restore edip içeriğini kontrol etmek 15 dakikanızı alır ve sizi çok büyük bir krizden kurtarabilir.
