MySQL Veritabanı Yedekleme: Bacula Entegrasyonu
Veritabanı yedeklemesi, sistem yöneticilerinin en çok baş ağrısı yaşadığı konulardan biridir. MySQL verilerini kaybetmek, özellikle üretim ortamında, gerçek bir felakete dönüşebilir. Bacula ile MySQL yedeklemesini entegre etmek ise bu süreci hem otomatize hem de merkezi hale getirmenin en etkili yollarından biri. Bu yazıda, Bacula’nın MySQL ile nasıl entegre edileceğini, dikkat edilmesi gereken noktaları ve gerçek dünya senaryolarını ele alacağım.
Neden Bacula ile MySQL Yedeklemesi?
Bacula, kurumsal ortamlarda yaygın kullanılan, açık kaynaklı bir yedekleme çerçevesidir. Dosya sistemi yedeklemesinde gayet başarılıdır, ancak MySQL gibi aktif veritabanlarını yedeklerken özel bir yaklaşım gerektirir. Veritabanı dosyalarını doğrudan kopyalamak, tutarsız bir yedek almanıza yol açabilir çünkü yedekleme sırasında MySQL hala yazma işlemleri yapıyor olabilir.
Bacula’nın ClientRunBeforeJob ve ClientRunAfterJob direktifleri, bu sorunu çözmek için biçilmiş kaftan. Bu direktifler sayesinde yedekleme başlamadan önce mysqldump veya Percona XtraBackup ile tutarlı bir yedek alabilir, ardından Bacula bu yedeği kendi medya havuzuna aktarabilirsiniz.
Ön Gereksinimler
Başlamadan önce ortamınızın hazır olduğundan emin olun:
- Bacula Director, Storage Daemon ve File Daemon kurulu ve çalışır durumda
- MySQL 5.7+ veya MariaDB 10.3+
- Yedekleme scriptlerini çalıştıracak bir servis kullanıcısı
- Yeterli disk alanı (geçici dump dosyaları için)
MySQL’de Bacula’nın kullanacağı ayrı bir kullanıcı oluşturmanızı şiddetle tavsiye ederim. Root ile çalışmak hem güvenlik açısından hem de denetleme açısından kötü bir pratik.
mysql -u root -p
CREATE USER 'bacula_backup'@'localhost' IDENTIFIED BY 'GuvenliSifre123!';
GRANT SELECT, SHOW DATABASES, LOCK TABLES, RELOAD, REPLICATION CLIENT ON *.* TO 'bacula_backup'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Bu kullanıcıya verilen yetkiler bilinçli olarak seçilmiştir. SELECT ve LOCK TABLES mysqldump için zorunlu, RELOAD ise flush işlemleri için gerekli. REPLICATION CLIENT ise binary log pozisyonunu almak için kullanılır ki bu, point-in-time recovery açısından çok değerlidir.
MySQL Dump Script’i Hazırlama
Bacula yedeklemesi öncesinde çalışacak temel script şu şekilde olabilir:
#!/bin/bash
# /usr/local/bin/mysql_bacula_backup.sh
BACKUP_DIR="/var/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_USER="bacula_backup"
MYSQL_PASS="GuvenliSifre123!"
LOG_FILE="/var/log/mysql_bacula_backup.log"
RETENTION_HOURS=48
# Log fonksiyonu
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Backup dizinini oluştur
mkdir -p "$BACKUP_DIR"
log "MySQL yedekleme başladı"
# Tüm veritabanlarını listele ve tek tek yedekle
DATABASES=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "SHOW DATABASES;" | grep -Ev "(Database|information_schema|performance_schema|sys)")
for DB in $DATABASES; do
log "Yedekleniyor: $DB"
mysqldump
-u"$MYSQL_USER"
-p"$MYSQL_PASS"
--single-transaction
--routines
--triggers
--events
--master-data=2
--compress
"$DB" | gzip > "$BACKUP_DIR/${DB}_${DATE}.sql.gz"
if [ $? -eq 0 ]; then
log "Başarılı: $DB -> ${DB}_${DATE}.sql.gz"
else
log "HATA: $DB yedeklenemedi!"
exit 1
fi
done
# Eski yedekleri temizle
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +2 -delete
log "Eski yedekler temizlendi (48 saatten eski)"
log "MySQL yedekleme tamamlandı"
exit 0
Script’e çalıştırma yetkisi vermek için:
chmod 750 /usr/local/bin/mysql_bacula_backup.sh
chown root:bacula /usr/local/bin/mysql_bacula_backup.sh
Burada dikkat etmeniz gereken önemli bir nokta var: –single-transaction parametresi InnoDB tablolar için transaction tutarlılığı sağlar ve tabloları kilitlemez. MyISAM tablolarınız varsa bu parametre yeterli olmayabilir, o durumda –lock-tables eklemeniz gerekebilir. Ancak bu, yoğun yazma ortamlarında performans sorununa yol açabilir.
.my.cnf ile Şifre Güvenliği
Script içinde şifre yazmak iyi bir pratik değildir. Özellikle kurumsal ortamlarda, şifreyi bir konfigürasyon dosyasında saklamak ve script’te referans vermek çok daha güvenlidir.
# /etc/mysql/bacula_backup.cnf dosyasını oluştur
cat > /etc/mysql/bacula_backup.cnf << 'EOF'
[client]
user=bacula_backup
password=GuvenliSifre123!
host=localhost
EOF
chmod 600 /etc/mysql/bacula_backup.cnf
chown root:root /etc/mysql/bacula_backup.cnf
Script’i bu dosyayı kullanacak şekilde güncelleyin:
mysqldump
--defaults-extra-file=/etc/mysql/bacula_backup.cnf
--single-transaction
--routines
--triggers
--events
--master-data=2
"$DB" | gzip > "$BACKUP_DIR/${DB}_${DATE}.sql.gz"
Bu yöntemle hem process listesinde şifre görünmez hem de script’i versiyonlama sistemine eklerken yanlışlıkla şifre sızdırma riskini ortadan kaldırırsınız.
Bacula Job Konfigürasyonu
Şimdi asıl entegrasyon kısmına geliyoruz. Bacula Director konfigürasyon dosyasına aşağıdaki Job tanımını eklemeniz gerekiyor:
# /etc/bacula/bacula-dir.conf içine eklenecek kısım
Job {
Name = "MySQL-Yedekleme"
Type = Backup
Level = Full
Client = uretim-sunucu-fd
FileSet = "MySQL-Dosyalari"
Schedule = "MySQL-Gunluk-Schedule"
Storage = File-Depolama
Messages = Standard
Pool = MySQL-Pool
Priority = 10
ClientRunBeforeJob = "/usr/local/bin/mysql_bacula_backup.sh"
ClientRunAfterJob = "/usr/local/bin/mysql_bacula_cleanup.sh"
Write Bootstrap = "/var/lib/bacula/mysql-yedekleme.bsr"
}
FileSet {
Name = "MySQL-Dosyalari"
Include {
Options {
signature = MD5
compression = GZIP
}
File = /var/backup/mysql
}
Exclude {
File = /var/backup/mysql/*.tmp
}
}
Schedule {
Name = "MySQL-Gunluk-Schedule"
Run = Full daily at 02:00
}
Pool {
Name = MySQL-Pool
Pool Type = Backup
Recycle = yes
AutoPrune = yes
Volume Retention = 30 days
Maximum Volume Bytes = 50G
Maximum Volumes = 20
Label Format = "MySQL-"
}
ClientRunBeforeJob ve ClientRunAfterJob direktiflerinin önemi şurada: Bu komutlar Bacula File Daemon üzerinde, yani istemci sunucuda çalışır. Dolayısıyla script’in File Daemon’ın kurulu olduğu sunucuda mevcut olması gerekir.
Cleanup Script’i
Yedekleme tamamlandıktan sonra geçici dosyaları temizlemek için bir script daha hazırlayın:
#!/bin/bash
# /usr/local/bin/mysql_bacula_cleanup.sh
BACKUP_DIR="/var/backup/mysql"
LOG_FILE="/var/log/mysql_bacula_backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Cleanup başladı - Geçici dosyalar temizleniyor"
# Başarıyla Bacula'ya aktarılan dosyaları sil
# Sadece 24 saatten eski dosyaları sil (güvenlik payı)
find "$BACKUP_DIR" -name "*.sql.gz" -mmin +1440 -delete
if [ $? -eq 0 ]; then
log "Cleanup tamamlandı"
exit 0
else
log "HATA: Cleanup sırasında sorun oluştu"
exit 1
fi
chmod 750 /usr/local/bin/mysql_bacula_cleanup.sh
chown root:bacula /usr/local/bin/mysql_bacula_cleanup.sh
Gerçek Dünya Senaryosu: Büyük Veritabanı Sorunları
Bir e-ticaret şirketinde çalışırken 200GB’ı aşan bir MySQL veritabanıyla karşılaştım. Standart mysqldump yaklaşımı bu ölçekte çok yavaş kalıyordu ve yedekleme penceresi iş saatlerine uzuyordu. Bu tür durumlarda Percona XtraBackup devreye girer.
XtraBackup’ın avantajı, InnoDB tablolarını kilitlemeden hot backup alabilmesidir. Bacula entegrasyonunda bunu şöyle kullanabilirsiniz:
#!/bin/bash
# /usr/local/bin/xtrabackup_bacula.sh
BACKUP_DIR="/var/backup/mysql_xtra"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/xtrabackup_bacula.log"
XTRABACKUP_USER="bacula_backup"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "XtraBackup başladı"
# Backup dizini oluştur
mkdir -p "$BACKUP_DIR/$DATE"
# Full backup al
xtrabackup
--defaults-extra-file=/etc/mysql/bacula_backup.cnf
--backup
--target-dir="$BACKUP_DIR/$DATE"
--parallel=4
--compress
--compress-threads=4
2>> "$LOG_FILE"
if [ $? -ne 0 ]; then
log "HATA: XtraBackup başarısız oldu!"
exit 1
fi
# Prepare aşaması - restore için hazırla
xtrabackup
--prepare
--target-dir="$BACKUP_DIR/$DATE"
2>> "$LOG_FILE"
if [ $? -eq 0 ]; then
log "XtraBackup başarıyla tamamlandı: $BACKUP_DIR/$DATE"
exit 0
else
log "HATA: Prepare aşamasında sorun oluştu!"
exit 1
fi
XtraBackup kullandığınızda Bacula FileSet’inde /var/backup/mysql_xtra dizinini işaret etmeniz yeterli.
Yedek Doğrulama: Test Etmeden Yedek Sayılmaz
Bu konuda çok net bir görüşüm var: Test edilmemiş bir yedek, yedek değildir. Bacula’nın restore işlemini düzenli olarak test etmeniz şarttır. Aşağıdaki script, alınan MySQL yedeklerini otomatik olarak doğrular:
#!/bin/bash
# /usr/local/bin/mysql_backup_verify.sh
BACKUP_DIR="/var/backup/mysql"
TEST_DB="backup_test_$$"
LOG_FILE="/var/log/mysql_backup_verify.log"
ALERT_EMAIL="[email protected]"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/*.sql.gz 2>/dev/null | head -1)
if [ -z "$LATEST_BACKUP" ]; then
log "HATA: Hiç yedek dosyası bulunamadı!"
echo "MySQL yedek doğrulama başarısız: Dosya yok" | mail -s "ALARM: Yedek Doğrulama Hatası" "$ALERT_EMAIL"
exit 1
fi
log "Doğrulanacak yedek: $LATEST_BACKUP"
# Test veritabanı oluştur
mysql --defaults-extra-file=/etc/mysql/bacula_backup.cnf
-e "CREATE DATABASE $TEST_DB;" 2>> "$LOG_FILE"
# Yedeği test veritabanına restore et
zcat "$LATEST_BACKUP" | mysql
--defaults-extra-file=/etc/mysql/bacula_backup.cnf
"$TEST_DB" 2>> "$LOG_FILE"
RESTORE_STATUS=$?
# Test veritabanını sil
mysql --defaults-extra-file=/etc/mysql/bacula_backup.cnf
-e "DROP DATABASE IF EXISTS $TEST_DB;" 2>> "$LOG_FILE"
if [ $RESTORE_STATUS -eq 0 ]; then
log "Doğrulama başarılı: $LATEST_BACKUP"
exit 0
else
log "HATA: Doğrulama başarısız: $LATEST_BACKUP"
echo "MySQL yedek doğrulama başarısız: $LATEST_BACKUP" | mail -s "ALARM: Yedek Bozuk!" "$ALERT_EMAIL"
exit 1
fi
Bu script’i cron’a ekleyerek haftalık otomatik doğrulama yapabilirsiniz:
# crontab -e
0 4 * * 1 /usr/local/bin/mysql_backup_verify.sh
Sık Karşılaşılan Sorunlar ve Çözümleri
Bacula ile MySQL entegrasyonunda en sık şu problemlerle karşılaşıyorum:
ClientRunBeforeJob başarısız oluyor, Job devam ediyor: Bacula varsayılan olarak pre-job script hata verse bile devam edebilir. Bunu engellemek için script’inizin hata durumunda sıfırdan farklı bir çıkış kodu döndürdüğünden emin olun. Bacula, sıfırdan farklı çıkış kodunu hata olarak yorumlar ve Job’ı durdurur.
Disk dolması sorunu: Büyük veritabanlarında dump dosyaları çok yer kaplayabilir. FileSet’e bir boyut sınırı koymanızı öneririm. Ayrıca /var/backup/mysql için ayrı bir partition veya LVM volume kullanmak, ana sistemi korumanın akıllıca bir yolu.
Yedekleme sırasında performans düşüşü: --single-transaction kullanıldığında bile çok büyük tablolarda sorgu süreleri uzayabilir. Bunu minimize etmek için yedekleme saatini en düşük trafik dönemine (genellikle gece 02:00-04:00 arası) almak mantıklıdır. Ayrıca mysqldump’a --max-allowed-packet=512M parametresi eklemek büyük BLOB içeren tablolarda sorunları önler.
Binary log tutarsızlığı: Master-slave replikasyon ortamında yedeklemeyi slave üzerinden almak hem master’ı yüklememek hem de tutarlı bir binary log pozisyonu almak açısından idealdir.
Bacula Director’da Bildirim Ayarları
Yedekleme başarısız olduğunda haberdar olmak için Bacula’nın Messages konfigürasyonunu düzgün ayarlamak önemli:
# bacula-dir.conf Messages bölümü
Messages {
Name = Standard
mailcommand = "/usr/sbin/bsmtp -h localhost -f "(Bacula) <%r>" -s "Bacula: %t %e of %c %l" %r"
mail = [email protected] = all, !skipped
console = all, !skipped, !saved
append = "/var/log/bacula/bacula.log" = all, !skipped
catalog = all
}
mail = [email protected] = all, !skipped satırı, atlanan işlemler hariç tüm durumlar için e-posta gönderir. Üretim ortamında bu bildirimler hayat kurtarır.
Restore Prosedürü
Felaket anında paniğe kapılmamak için restore prosedürünü önceden bilmek ve tatbikat yapmak şart. Bacula üzerinden MySQL yedeğini restore etmek şu adımlarla yapılır:
Önce Bacula konsolundan dosyaları geri yükleyin:
bconsole
*restore
# İlgili Job'ı seçin, restore dizinini belirleyin
# Varsayılan olarak /tmp/bacula-restores altına gelir
Dosyalar geri yüklendikten sonra MySQL’e import edin:
# Restore edilen dizindeki dump dosyasını bul
ls /tmp/bacula-restores/var/backup/mysql/
# İlgili veritabanını restore et
zcat /tmp/bacula-restores/var/backup/mysql/production_db_20240115_020001.sql.gz |
mysql --defaults-extra-file=/etc/mysql/bacula_backup.cnf production_db
# Restore başarılı mı kontrol et
mysql --defaults-extra-file=/etc/mysql/bacula_backup.cnf
-e "SELECT COUNT(*) FROM production_db.orders;"
XtraBackup ile alınan yedekleri restore ederken süreç farklıdır:
# MySQL servisini durdur
systemctl stop mysql
# Mevcut data dizinini yedekle
mv /var/lib/mysql /var/lib/mysql_eski
# Restore et
xtrabackup --copy-back
--target-dir=/tmp/bacula-restores/var/backup/mysql_xtra/20240115_020001
# Yetkileri düzelt
chown -R mysql:mysql /var/lib/mysql
# MySQL'i başlat
systemctl start mysql
Monitoring ve Raporlama
Günlük yedekleme durumunu takip etmek için basit bir kontrol scripti oldukça işe yarar:
#!/bin/bash
# /usr/local/bin/check_mysql_backup_status.sh
BACKUP_DIR="/var/backup/mysql"
MAX_AGE_HOURS=25
ALERT_EMAIL="[email protected]"
# En son yedek dosyasının yaşını kontrol et
LATEST=$(find "$BACKUP_DIR" -name "*.sql.gz" -mmin -$((MAX_AGE_HOURS * 60)) | wc -l)
if [ "$LATEST" -eq 0 ]; then
echo "KRITIK: Son $MAX_AGE_HOURS saat içinde MySQL yedeği alınmamış!" |
mail -s "ALARM: MySQL Yedek Eksik" "$ALERT_EMAIL"
exit 2
fi
# Toplam yedek boyutunu raporla
TOTAL_SIZE=$(du -sh "$BACKUP_DIR" | cut -f1)
echo "MySQL yedekleri normal. Toplam boyut: $TOTAL_SIZE"
exit 0
Bu script’i Nagios, Zabbix veya benzeri bir monitoring sistemiyle entegre edebilirsiniz. Cron’a 5’er dakikalık aralıklarla eklemek yerine sabah kontrol saatinde çalıştırmak daha mantıklı:
0 8 * * * /usr/local/bin/check_mysql_backup_status.sh
Sonuç
Bacula ile MySQL entegrasyonu, başta karmaşık görünse de adım adım ele alındığında son derece yönetilebilir bir yapıya kavuşuyor. Bu yazıda anlattıklarımı özetleyecek olursam:
- Ayrı bir MySQL kullanıcısıyla en az yetki prensibini uygulayın
- Şifreleri script içine yazmak yerine
.cnfdosyasında saklayın - InnoDB için
--single-transactionkullanarak kilitlemesiz yedek alın - Büyük veritabanlarında Percona XtraBackup’ı değerlendirin
ClientRunBeforeJobveClientRunAfterJobdirektiflerini etkili kullanın- Yedekleri düzenli olarak test edin, doğrulanmamış yedek yok hükmündedir
- Monitoring ve bildirim mekanizmalarını kurmayı atlamamayın
- Restore prosedürünü yazılı hale getirin ve tatbikat yapın
Yedekleme sistemi kurmak bir kez yapılan iş değil, sürekli bakım gerektiren bir süreçtir. Bacula loglarını düzenli gözden geçirin, zaman zaman restore testi yapın ve ortamınız değiştikçe konfigürasyonunuzu güncelleyin. Bir gün bu sistem sizi gerçekten kurtardığında, harcanan her dakikanın değerini anlayacaksınız.
