Veritabanı yedeklemesini manuel yapmak, bir gün mutlaka unutacağın bir şeydir. O unuttuğun gün de tam en kritik verinin kaybolduğu gün olur. Bunu hepimiz ya bizzat yaşadık, ya da bir meslektaşımızın yaşadığını gördük. Bu yüzden yedekleme işini otomasyona bırakmak, sysadmin hayatının olmazsa olmazlarından biri. Bu yazıda cron kullanarak MySQL, PostgreSQL ve MongoDB veritabanlarını nasıl otomatik yedekleyeceğini, bu yedekleri nasıl sıkıştırıp döndüreceğini ve tüm süreci nasıl izleyeceğini adım adım anlatacağım.
Temel Kavramlar ve Hazırlık
Başlamadan önce birkaç şeyi netleştirelim. Cron, Unix/Linux sistemlerde belirli zaman aralıklarında komut veya script çalıştırmaya yarayan bir zamanlayıcı servistir. crontab dosyası ise bu görevlerin tanımlandığı yerdir.
Cron ifadesi beş alandan oluşur:
* * * * * komut
│ │ │ │ │
│ │ │ │ └── Haftanın günü (0-7, 0 ve 7 Pazar)
│ │ │ └──── Ay (1-12)
│ │ └────── Ayın günü (1-31)
│ └──────── Saat (0-23)
└────────── Dakika (0-59)
Birkaç pratik örnek vermek gerekirse:
0 2 *: Her gün saat 02:00’de çalışır0 /6: Her 6 saatte bir çalışır30 1 0: Her Pazar saat 01:30’da çalışır0 3 1: Her ayın 1’inde saat 03:00’de çalışır
Yedekleme scriptlerimizi yazmadan önce gerekli dizin yapısını oluşturalım:
mkdir -p /opt/backup/{mysql,postgresql,mongodb}
mkdir -p /opt/backup/logs
mkdir -p /opt/scripts/backup
chmod 700 /opt/scripts/backup
chmod 750 /opt/backup
MySQL/MariaDB Yedekleme Scripti
En yaygın kullanılan senaryo MySQL yedeklemesidir. Basit bir dump script’i ile başlayalım, sonra üzerine inşa ederiz.
#!/bin/bash
# /opt/scripts/backup/mysql_backup.sh
set -euo pipefail
# Konfigürasyon
DB_HOST="localhost"
DB_USER="backup_user"
DB_PASS="guvenli_sifre_buraya"
BACKUP_DIR="/opt/backup/mysql"
LOG_FILE="/opt/backup/logs/mysql_backup.log"
RETENTION_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
# Log fonksiyonu
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "MySQL yedekleme basliyor..."
# Tüm veritabanlarını listele (sistem DB'leri hariç)
DATABASES=$(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS"
-e "SHOW DATABASES;" 2>/dev/null |
grep -Ev "^(Database|information_schema|performance_schema|mysql|sys)$")
# Her veritabanı için ayrı yedek al
for DB in $DATABASES; do
BACKUP_FILE="$BACKUP_DIR/${DB}_${DATE}.sql.gz"
log "Yedekleniyor: $DB"
mysqldump -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS"
--single-transaction
--routines
--triggers
--events
"$DB" | gzip -9 > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
log "Basarili: $BACKUP_FILE ($(du -sh "$BACKUP_FILE" | cut -f1))"
else
log "HATA: $DB yedeklenemedi!"
fi
done
# Eski yedekleri temizle
log "Eski yedekler temizleniyor ($RETENTION_DAYS gun oncesi)..."
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -exec log "Silindi: {}" ;
log "MySQL yedekleme tamamlandi."
Burada --single-transaction parametresi önemli; InnoDB tablolarında tutarlı bir snapshot alınmasını sağlar, tabloları kilitlemez. Production ortamında bu olmadan yedek almak veri tutarsızlığına yol açabilir.
Yedekleme kullanıcısını minimum yetkiyle oluşturmak iyi bir pratik:
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'guvenli_sifre_buraya';
GRANT SELECT, SHOW DATABASES, LOCK TABLES, RELOAD, SUPER, REPLICATION CLIENT ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;
PostgreSQL Yedekleme Scripti
PostgreSQL için pg_dump ve pg_dumpall araçlarını kullanıyoruz. MySQL’den farklı olarak şifre yönetimi için .pgpass dosyası veya environment variable kullanmak daha güvenli.
#!/bin/bash
# /opt/scripts/backup/postgresql_backup.sh
set -euo pipefail
BACKUP_DIR="/opt/backup/postgresql"
LOG_FILE="/opt/backup/logs/postgresql_backup.log"
RETENTION_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
PG_USER="postgres"
# pgpass dosyası: hostname:port:database:username:password
export PGPASSFILE="/root/.pgpass"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "PostgreSQL yedekleme basliyor..."
# Global nesneleri yedekle (roller, tablespace'ler)
log "Global nesneler yedekleniyor..."
pg_dumpall -U "$PG_USER" --globals-only |
gzip -9 > "$BACKUP_DIR/globals_${DATE}.sql.gz"
# Her veritabanını ayrı yedekle
DATABASES=$(psql -U "$PG_USER" -t -c
"SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres';" 2>/dev/null |
tr -d ' ' | grep -v '^$')
for DB in $DATABASES; do
BACKUP_FILE="$BACKUP_DIR/${DB}_${DATE}.dump"
log "Yedekleniyor: $DB"
# Custom format kullanıyoruz, paralel restore için daha uygun
pg_dump -U "$PG_USER"
--format=custom
--compress=9
--verbose
"$DB" > "$BACKUP_FILE" 2>>"$LOG_FILE"
log "Basarili: $BACKUP_FILE ($(du -sh "$BACKUP_FILE" | cut -f1))"
done
# Temizlik
find "$BACKUP_DIR" -name "*.dump" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
log "PostgreSQL yedekleme tamamlandi."
PostgreSQL için custom format (--format=custom) kullanmanın avantajı, daha sonra pg_restore ile sadece belirli tabloları veya şemaları geri yükleyebilmesidir. Bu büyük veritabanlarında hayat kurtarır.
MongoDB Yedekleme Scripti
MongoDB için mongodump aracını kullanıyoruz. Authentication varsa connection string formatı kullanmak en temiz yol:
#!/bin/bash
# /opt/scripts/backup/mongodb_backup.sh
set -euo pipefail
BACKUP_DIR="/opt/backup/mongodb"
LOG_FILE="/opt/backup/logs/mongodb_backup.log"
RETENTION_DAYS=5
DATE=$(date +%Y%m%d_%H%M%S)
MONGO_URI="mongodb://backup_user:sifre@localhost:27017"
TEMP_DIR="/tmp/mongodump_${DATE}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "MongoDB yedekleme basliyor..."
# Tüm veritabanlarını dump et
mongodump
--uri="$MONGO_URI"
--out="$TEMP_DIR"
--gzip
2>>"$LOG_FILE"
if [ $? -eq 0 ]; then
# Tek arşiv dosyası oluştur
ARCHIVE_FILE="$BACKUP_DIR/mongodb_full_${DATE}.tar.gz"
tar -czf "$ARCHIVE_FILE" -C "$(dirname $TEMP_DIR)" "$(basename $TEMP_DIR)"
log "Basarili: $ARCHIVE_FILE ($(du -sh "$ARCHIVE_FILE" | cut -f1))"
# Temp dizini temizle
rm -rf "$TEMP_DIR"
else
log "HATA: MongoDB dump basarisiz!"
rm -rf "$TEMP_DIR"
exit 1
fi
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "MongoDB yedekleme tamamlandi."
E-posta Bildirimleri ile Monitoring
Yedekleme çalıştı mı, hata var mı? Bunu takip etmek için script’e bildirim mekanizması ekleyelim. mailutils veya sendmail kurulu olduğunu varsayıyorum:
#!/bin/bash
# /opt/scripts/backup/backup_notify.sh
# Ana backup scriptlerinin üstünde çalışan wrapper
SCRIPT_PATH=$1
SCRIPT_NAME=$(basename "$SCRIPT_PATH")
LOG_FILE="/opt/backup/logs/notifications.log"
ADMIN_EMAIL="[email protected]"
HOSTNAME=$(hostname -f)
run_backup() {
OUTPUT=$(bash "$SCRIPT_PATH" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
# Hata durumunda mail at
echo "$OUTPUT" | mail
-s "[HATA] $HOSTNAME - $SCRIPT_NAME basarisiz!"
"$ADMIN_EMAIL"
echo "[$(date)] HATA: $SCRIPT_NAME (exit code: $EXIT_CODE)" >> "$LOG_FILE"
return 1
else
# Başarı durumunda da haftalık özet için log tut
echo "[$(date)] BASARILI: $SCRIPT_NAME" >> "$LOG_FILE"
# Sadece Pazartesi sabahı özet mail at
if [ $(date +%u) -eq 1 ]; then
echo "$OUTPUT" | mail
-s "[BILGI] $HOSTNAME - Haftalik yedekleme ozeti"
"$ADMIN_EMAIL"
fi
fi
}
run_backup
Crontab Yapılandırması
Scriptlerimiz hazır, şimdi crontab’a ekleyelim. Root olarak crontab -e ile açıyoruz:
# /etc/cron.d/database_backups
# Database Yedekleme Gorevleri
# Her gece 02:00'de MySQL yedekleme
0 2 * * * root /opt/scripts/backup/backup_notify.sh /opt/scripts/backup/mysql_backup.sh
# Her gece 02:30'da PostgreSQL yedekleme
30 2 * * * root /opt/scripts/backup/backup_notify.sh /opt/scripts/backup/postgresql_backup.sh
# Her gece 03:00'de MongoDB yedekleme
0 3 * * * root /opt/scripts/backup/backup_notify.sh /opt/scripts/backup/mongodb_backup.sh
# Her Pazar tam yedek (aylık arşiv için)
0 1 * * 0 root tar -czf /opt/backup/weekly_$(date +%Y%W).tar.gz /opt/backup/mysql /opt/backup/postgresql
/etc/cron.d/ altına koymak, sistem genelinde geçerli olmasını sağlar ve crontab format’ı biraz farklıdır; kullanıcı adını da belirtmek gerekir.
Script dosyalarının çalıştırılabilir olduğundan emin olalım:
chmod +x /opt/scripts/backup/*.sh
ls -la /opt/scripts/backup/
Uzak Sunucuya Yedek Kopyalama
Yerel yedekler tek başına yeterli değil. 3-2-1 kuralını hatırlayalım: 3 kopya, 2 farklı medya, 1 off-site. rsync ile uzak sunucuya kopyalama:
#!/bin/bash
# /opt/scripts/backup/sync_remote.sh
BACKUP_DIR="/opt/backup"
REMOTE_USER="backup"
REMOTE_HOST="backup-server.sirket.com"
REMOTE_PATH="/backups/$(hostname -s)"
LOG_FILE="/opt/backup/logs/sync_remote.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Uzak sunucuya senkronizasyon basliyor..."
rsync -avz
--delete
--delete-after
--exclude="logs/"
-e "ssh -i /root/.ssh/backup_rsa -o StrictHostKeyChecking=no"
"$BACKUP_DIR/"
"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/"
if [ $? -eq 0 ]; then
log "Senkronizasyon basarili."
else
log "HATA: Senkronizasyon basarisiz!"
exit 1
fi
SSH key oluşturup backup sunucusuna eklemek gerekiyor:
ssh-keygen -t ed25519 -f /root/.ssh/backup_rsa -N ""
ssh-copy-id -i /root/.ssh/backup_rsa.pub [email protected]
Crontab’a ekliyoruz, yedekleme bitince çalışsın diye 04:00’e ayarlıyoruz:
0 4 * * * root /opt/scripts/backup/backup_notify.sh /opt/scripts/backup/sync_remote.sh
Yedek Doğrulama
Yedek aldın ama çalışıyor mu? Test etmeden bilemezsin. Ayda bir otomatik restore testi yapalım:
#!/bin/bash
# /opt/scripts/backup/verify_backup.sh
BACKUP_DIR="/opt/backup/mysql"
LOG_FILE="/opt/backup/logs/verify.log"
TEST_DB="backup_verify_test"
LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/*.sql.gz 2>/dev/null | head -1)
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
if [ -z "$LATEST_BACKUP" ]; then
log "HATA: Dogrulanacak yedek dosyasi bulunamadi!"
exit 1
fi
log "Dogrulanacak yedek: $LATEST_BACKUP"
# Test veritabanı oluştur
mysql -u root -e "CREATE DATABASE IF NOT EXISTS $TEST_DB;" 2>>"$LOG_FILE"
# Yedeği restore et
gunzip -c "$LATEST_BACKUP" | mysql -u root "$TEST_DB" 2>>"$LOG_FILE"
if [ $? -eq 0 ]; then
TABLE_COUNT=$(mysql -u root "$TEST_DB" -e "SHOW TABLES;" 2>/dev/null | wc -l)
log "Dogrulama BASARILI: $TEST_DB veritabanina $TABLE_COUNT tablo restore edildi."
else
log "HATA: Restore islemi basarisiz!"
fi
# Test veritabanını temizle
mysql -u root -e "DROP DATABASE IF EXISTS $TEST_DB;" 2>>"$LOG_FILE"
log "Test veritabani temizlendi."
Bunu aylık çalıştırmak için:
0 5 1 * * root /opt/scripts/backup/backup_notify.sh /opt/scripts/backup/verify_backup.sh
Şifre Güvenliği
Script içinde düz metin şifre yazmak kötü bir pratik. Birkaç alternatif yaklaşım var.
MySQL için .my.cnf kullanımı:
cat > /root/.my.cnf << 'EOF'
[client]
user=backup_user
password=guvenli_sifre_buraya
host=localhost
EOF
chmod 600 /root/.my.cnf
Bu dosya varsa mysql ve mysqldump komutları otomatik olarak kullanır, script’te şifre yazmanıza gerek kalmaz.
Değişkenleri environment file’dan okumak:
# /etc/backup.env dosyası (chmod 600)
export DB_PASS="guvenli_sifre"
export REMOTE_PASS="uzak_sifre"
export SMTP_PASS="mail_sifre"
Script başında source /etc/backup.env ile yüklersiniz. Bu dosyanın sadece root tarafından okunabilir olduğundan emin olun.
Log Rotasyonu
Loglar zamanla şişer. logrotate ile yönetelim:
cat > /etc/logrotate.d/database_backups << 'EOF'
/opt/backup/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 root root
dateext
dateformat -%Y%m%d
}
EOF
Bu konfigürasyonla loglar 30 gün tutulur, günlük rotate edilir ve sıkıştırılır.
Cron Job İzleme ve Troubleshooting
Cron job’ların çalışıp çalışmadığını kontrol etmek için birkaç pratik yöntem:
# Sistem loglarından cron aktivitesini izle
grep CRON /var/log/syslog | tail -50
# Veya journald kullanan sistemlerde
journalctl -u cron --since "24 hours ago"
# Son çalışma zamanlarını kontrol et
ls -la /opt/backup/mysql/ | head -20
# Tüm backup loglarının özetini al
tail -5 /opt/backup/logs/mysql_backup.log
tail -5 /opt/backup/logs/postgresql_backup.log
tail -5 /opt/backup/logs/sync_remote.log
Cron job çalışmıyorsa kontrol edilecekler:
- Dosya izinleri: Script çalıştırılabilir mi? (
chmod +x) - PATH sorunu: Cron’un PATH’i shell’den farklıdır, script içinde tam yol kullanın
- Çıktı yönlendirme: Hataları göremiyor olabilirsiniz,
2>&1ekleyin - MAILTO değişkeni:
MAILTO=""ile cron output’unu susturabilirsiniz
# Crontab başına eklenebilecek environment ayarları
MAILTO="[email protected]"
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
SHELL=/bin/bash
Gerçek Dünya Senaryosu: E-ticaret Sitesi
Düşünelim ki 50GB MySQL veritabanı olan bir e-ticaret sitesi yönetiyorsunuz. Sipariş verileri kritik, 24/7 işlem var. Bu durumda yaklaşım biraz farklılaşır.
Saatlik incremental yedek için binary log’ları kullanmak gerekir. Önce MySQL’de binary logging aktif olmalı:
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
log_bin = /var/log/mysql/mysql-bin.log
binlog_expire_logs_seconds = 604800
max_binlog_size = 100M
Sonra saatlik binary log yedeklemesi:
# Her saat binary logları yedekle
0 * * * * root mysqlbinlog --read-from-remote-server
--host=localhost
--user=backup_user
--raw
--stop-never
/opt/backup/mysql/binlogs/ 2>>/opt/backup/logs/binlog.log &
Günlük tam yedek gece 03:00’de, saatlik binary log yedekleri ile birlikte herhangi bir noktaya geri dönme (Point-in-Time Recovery) imkanı sağlar.
Sonuç
Veritabanı yedekleme otomasyonu kurduğunuzda rahat uyumak için sağlam bir temel atmış olursunuz. Ama şunu unutmayın: yedek almak işin sadece yarısı. Asıl önemli olan o yedeği geri yükleyebilmek. Her ay bir test restore yapın, sürecin işlediğini doğrulayın.
Bu yazıda anlattığımız sistemi kurduğunuzda sahip olacaklarınızı özetleyelim:
- Otomatik günlük MySQL, PostgreSQL ve MongoDB yedekleri
- Sıkıştırma ile disk tasarrufu
- Belirli gün sonrası eski yedeklerin otomatik silinmesi
- Uzak sunucuya rsync ile kopyalama
- Hata durumunda e-posta bildirimi
- Aylık otomatik restore doğrulaması
- Logrotate ile log yönetimi
Son bir öneri: tüm bu scriptleri Git repository’sinde tutun. Hem versiyon kontrolü olur, hem de yeni bir sunucu kurduğunuzda kolayca deploy edebilirsiniz. İnfrastructure as Code mantığı yedekleme scriptleri için de geçerli.