Cron ile Veritabanı Yedekleme Otomasyonu

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ışır
  • 0 /6 : Her 6 saatte bir çalışır
  • 30 1 0 : Her Pazar saat 01:30’da çalışır
  • 0 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>&1 ekleyin
  • 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.

Yorum yapın