Bacula ile PostgreSQL Veritabanı Yedekleme

PostgreSQL veritabanlarını yedeklemek, çoğu sysadmin’in aklında sürekli bir “acaba yeterince güvenli miyim?” soru işareti bırakır. Özellikle production ortamında gigabyte’larla ifade edilen veritabanlarını yönetirken hem güvenilir hem de merkezi bir yedekleme çözümüne ihtiyaç duyarsınız. Bacula, tam da bu noktada devreye giriyor. Açık kaynaklı, kurumsal düzeyde özellikler sunan bu yedekleme sistemiyle PostgreSQL veritabanlarınızı hem tutarlı hem de otomatik bir şekilde yedekleyebilirsiniz. Bu yazıda gerçek dünya senaryoları üzerinden Bacula ile PostgreSQL yedekleme sürecini baştan sona ele alacağız.

Neden Bacula + PostgreSQL Kombinasyonu?

Birçok ekip PostgreSQL yedeklemeleri için basit cron + pg_dump kombinasyonuyla yoluna devam eder. Bu yaklaşım küçük ortamlar için işe yarar, ancak onlarca sunucu ve terabayt’lık verilerle uğraştığınızda merkezi yönetim hayat kurtarır. Bacula’nın sunduğu avantajlar şunlardır:

  • Merkezi yönetim: Tüm yedekleme işlerinizi tek bir konsoldan takip edersiniz
  • Katalog kaydı: Her yedeklemenin nerede, ne zaman ve nasıl alındığını Bacula kataloğunda saklarsınız
  • Artımlı/diferansiyel yedekleme: Tam yedek almak zorunda kalmadan değişen verileri yedekleyebilirsiniz
  • Şifreleme ve sıkıştırma: Verileriniz hem güvenli hem de daha az yer kaplar
  • Uyarı sistemi: Başarısız yedeklemelerde anında haberdar olursunuz

Mimari Genel Bakış

Bacula, üç ana bileşenden oluşur. PostgreSQL yedekleme senaryomuzda bu bileşenler şöyle dağılır:

  • Bacula Director (bacula-dir): Yedekleme işlerini planlayan ve yöneten ana bileşen. Genellikle ayrı bir yönetim sunucusunda çalışır.
  • Bacula Storage Daemon (bacula-sd): Yedek verilerini fiziksel olarak depolayan bileşen. NAS, tape veya disk üzerinde çalışabilir.
  • Bacula File Daemon (bacula-fd): Yedeklenecek sunucuda (PostgreSQL sunucusu) çalışan ajan. Verileri Director’ın talebiyle Storage Daemon’a gönderir.

PostgreSQL yedeklemelerinde kritik nokta şudur: Veritabanını tutarlı bir şekilde yedeklemek için dosyaları doğrudan kopyalamak yerine pg_dump veya pg_basebackup araçlarını kullanmanız gerekir. Bacula bu süreci “Before/After Job” script’leriyle yönetir.

Ön Hazırlık ve Kurulum

PostgreSQL Sunucusunda Hazırlık

Öncelikle PostgreSQL sunucusunda yedekleme için kullanacağımız bir kullanıcı ve geçici dump dizini oluşturalım:

# Yedekleme için özel PostgreSQL kullanıcısı oluştur
sudo -u postgres psql -c "CREATE USER backup_user WITH PASSWORD 'güçlü_şifre_buraya';"
sudo -u postgres psql -c "GRANT pg_read_all_data TO backup_user;"

# Eğer PostgreSQL 14 öncesi sürüm kullanıyorsanız
sudo -u postgres psql -c "GRANT SELECT ON ALL TABLES IN SCHEMA public TO backup_user;"
sudo -u postgres psql -c "ALTER USER backup_user WITH SUPERUSER;"

# Geçici dump dizini oluştur
sudo mkdir -p /var/backup/postgresql/dumps
sudo chown bacula:bacula /var/backup/postgresql/dumps
sudo chmod 750 /var/backup/postgresql/dumps

PostgreSQL bağlantı bilgilerini güvenli bir şekilde saklamak için .pgpass dosyasını yapılandıralım:

# bacula kullanıcısı için .pgpass dosyası oluştur
sudo -u bacula bash -c 'echo "localhost:5432:*:backup_user:güçlü_şifre_buraya" > ~/.pgpass'
sudo -u bacula chmod 600 ~/.pgpass

Bacula File Daemon Kurulumu (PostgreSQL Sunucusu)

# Debian/Ubuntu için
sudo apt-get update
sudo apt-get install -y bacula-client

# RHEL/CentOS/Rocky Linux için
sudo dnf install -y bacula-client

# File Daemon servisini başlat ve otomatik başlatmayı etkinleştir
sudo systemctl enable bacula-fd
sudo systemctl start bacula-fd

Yedekleme Script’lerinin Hazırlanması

Bu bölüm tüm sürecin can damarı. PostgreSQL veritabanlarını tutarlı bir şekilde yedeklemek için Bacula’nın job öncesi ve sonrası çalıştıracağı script’leri hazırlayacağız.

Pre-Backup Script (pg_dump ile)

#!/bin/bash
# /etc/bacula/scripts/pg_backup_before.sh
# Bacula job'u başlamadan önce PostgreSQL dump alır

set -e

BACKUP_DIR="/var/backup/postgresql/dumps"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/bacula/pg_backup.log"
PG_USER="backup_user"
PG_HOST="localhost"

# Log fonksiyonu
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "PostgreSQL yedekleme başlıyor..."

# Eski dump'ları temizle (2 günden eski)
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +2 -delete
log "Eski dump dosyaları temizlendi"

# Tüm veritabanlarını listele ve yedekle
DATABASES=$(sudo -u postgres psql -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres';" | tr -d ' ')

for DB in $DATABASES; do
    DUMP_FILE="$BACKUP_DIR/${DB}_${TIMESTAMP}.sql.gz"
    log "Veritabanı yedekleniyor: $DB"
    
    sudo -u postgres pg_dump 
        --host="$PG_HOST" 
        --username="$PG_USER" 
        --format=plain 
        --no-password 
        --verbose 
        "$DB" | gzip > "$DUMP_FILE"
    
    if [ $? -eq 0 ]; then
        log "Başarılı: $DB -> $DUMP_FILE"
    else
        log "HATA: $DB yedeklenemedi!"
        exit 1
    fi
done

# Global objeler (roller, tablespace'ler) için ayrı dump
sudo -u postgres pg_dumpall 
    --globals-only 
    --no-password | gzip > "$BACKUP_DIR/globals_${TIMESTAMP}.sql.gz"

log "PostgreSQL yedekleme tamamlandı"
exit 0

Script’e çalıştırma izni verelim:

sudo chmod +x /etc/bacula/scripts/pg_backup_before.sh
sudo chown bacula:bacula /etc/bacula/scripts/pg_backup_before.sh

Post-Backup Script

#!/bin/bash
# /etc/bacula/scripts/pg_backup_after.sh
# Bacula job'u tamamlandıktan sonra temizlik yapar

BACKUP_DIR="/var/backup/postgresql/dumps"
LOG_FILE="/var/log/bacula/pg_backup.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "Post-backup temizlik başlıyor..."

# Bugünkü dump'ları koru, 3 günden eskileri sil
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +3 -delete
log "Temizlik tamamlandı"

# Disk kullanımını logla
DISK_USAGE=$(du -sh "$BACKUP_DIR" | cut -f1)
log "Backup dizini boyutu: $DISK_USAGE"

exit 0

Bacula Director Konfigürasyonu

Director sunucusunda PostgreSQL yedekleme job’unu tanımlamalıyız. Konfigürasyon genellikle /etc/bacula/bacula-dir.conf dosyasında veya include edilen ayrı bir dosyada yapılır.

Job ve FileSet Tanımlaması

# /etc/bacula/conf.d/postgresql-backup.conf

# PostgreSQL sunucusu için Client tanımı
Client {
  Name = postgresql-server-fd
  Address = 192.168.1.50        # PostgreSQL sunucusunun IP adresi
  FDPort = 9102
  Catalog = MyCatalog
  Password = "fd_password_buraya"
  File Retention = 30 days
  Job Retention = 6 months
  AutoPrune = yes
}

# PostgreSQL Dump dosyalarını içerecek FileSet
FileSet {
  Name = "PostgreSQL-Dumps"
  Include {
    Options {
      Signature = MD5
      Compression = GZIP
      OneFS = no
    }
    File = /var/backup/postgresql/dumps
  }
  Exclude {
    File = /var/backup/postgresql/dumps/*.tmp
  }
}

# Haftalık tam yedekleme job'u
Job {
  Name = "PostgreSQL-Full-Backup"
  Type = Backup
  Level = Full
  Client = postgresql-server-fd
  FileSet = "PostgreSQL-Dumps"
  Schedule = "PostgreSQL-Weekly"
  Storage = File-Storage
  Pool = PostgreSQL-Full-Pool
  Messages = Standard
  Priority = 10
  
  # Job başlamadan önce dump script'ini çalıştır
  ClientRunBeforeJob = "/etc/bacula/scripts/pg_backup_before.sh"
  
  # Job bittikten sonra temizlik yap
  ClientRunAfterJob = "/etc/bacula/scripts/pg_backup_after.sh"
  
  # İş başarısız olursa e-posta gönder
  RunAfterFailedJob = "/etc/bacula/scripts/notify_failure.sh PostgreSQL-Full-Backup"
}

# Günlük artımlı yedekleme job'u
Job {
  Name = "PostgreSQL-Incremental-Backup"
  Type = Backup
  Level = Incremental
  Client = postgresql-server-fd
  FileSet = "PostgreSQL-Dumps"
  Schedule = "PostgreSQL-Daily"
  Storage = File-Storage
  Pool = PostgreSQL-Incr-Pool
  Messages = Standard
  Priority = 10
  ClientRunBeforeJob = "/etc/bacula/scripts/pg_backup_before.sh"
  ClientRunAfterJob = "/etc/bacula/scripts/pg_backup_after.sh"
}

# Zamanlama tanımları
Schedule {
  Name = "PostgreSQL-Weekly"
  Run = Full sun at 02:00
}

Schedule {
  Name = "PostgreSQL-Daily"
  Run = Incremental mon-sat at 03:00
}

# Full yedeklemeler için Pool
Pool {
  Name = PostgreSQL-Full-Pool
  Pool Type = Backup
  Recycle = yes
  AutoPrune = yes
  Volume Retention = 30 days
  Maximum Volume Bytes = 50G
  Maximum Volumes = 10
  Label Format = "PG-Full-"
}

# Artımlı yedeklemeler için Pool
Pool {
  Name = PostgreSQL-Incr-Pool
  Pool Type = Backup
  Recycle = yes
  AutoPrune = yes
  Volume Retention = 14 days
  Maximum Volume Bytes = 20G
  Maximum Volumes = 20
  Label Format = "PG-Incr-"
}

Konfigürasyonu Test Etme

# Director konfigürasyonunu syntax kontrolü yap
sudo bacula-dir -tc /etc/bacula/bacula-dir.conf

# Eğer hata yoksa Director'ı yeniden başlat
sudo systemctl restart bacula-dir

# File Daemon bağlantısını test et
echo "status client=postgresql-server-fd" | sudo bconsole

Gerçek Dünya Senaryosu: Büyük Veritabanı Yedekleme

Diyelim ki 500GB büyüklüğünde bir e-ticaret veritabanınız var ve geleneksel pg_dump yaklaşımı çok yavaş kalıyor. Bu durumda pg_basebackup ile fiziksel yedekleme yaklaşımını kullanabilirsiniz. Ancak bunun için PostgreSQL’de WAL arşivlemeyi etkinleştirmeniz gerekir.

# postgresql.conf içine eklenecek ayarlar
# sudo nano /etc/postgresql/15/main/postgresql.conf

wal_level = replica
archive_mode = on
archive_command = 'cp %p /var/backup/postgresql/wal_archive/%f'
archive_timeout = 300  # 5 dakikada bir WAL segmenti zorla

Büyük veritabanları için optimize edilmiş yedekleme script’i:

#!/bin/bash
# /etc/bacula/scripts/pg_basebackup_before.sh
# Büyük veritabanları için pg_basebackup kullanan script

set -e

BACKUP_DIR="/var/backup/postgresql/basebackup"
LOG_FILE="/var/log/bacula/pg_backup.log"
RETENTION_DAYS=3

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Eski basebackup'ları temizle
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +$RETENTION_DAYS -exec rm -rf {} ;
log "Eski base backup'lar temizlendi"

# pg_basebackup ile fiziksel yedekleme al
log "pg_basebackup başlıyor..."
sudo -u postgres pg_basebackup 
    --host=localhost 
    --username=backup_user 
    --pgdata="$BACKUP_DIR/$(date +%Y%m%d)" 
    --format=tar 
    --gzip 
    --compress=9 
    --checkpoint=fast 
    --progress 
    --verbose 2>> "$LOG_FILE"

if [ $? -eq 0 ]; then
    log "pg_basebackup başarıyla tamamlandı"
    # Backup boyutunu logla
    BACKUP_SIZE=$(du -sh "$BACKUP_DIR/$(date +%Y%m%d)" | cut -f1)
    log "Backup boyutu: $BACKUP_SIZE"
else
    log "HATA: pg_basebackup başarısız!"
    exit 1
fi

exit 0

Yedekten Geri Yükleme

Yedekleme kadar önemli olan geri yükleme sürecini de test etmelisiniz. Hiç restore etmediğiniz bir yedek, yedek sayılmaz!

Belirli Bir Veritabanını Geri Yükleme

Önce Bacula konsolundan yedek dosyalarını geri yükleyin:

# bconsole'dan restore işlemi başlatın
sudo bconsole

# Konsol içinde:
# restore client=postgresql-server-fd
# Ardından restore etmek istediğiniz job'u seçin

Dosyalar geri yüklendikten sonra PostgreSQL’e aktarın:

#!/bin/bash
# /etc/bacula/scripts/pg_restore.sh
# Kullanım: ./pg_restore.sh <veritabani_adi> <dump_dosyasi>

DB_NAME="$1"
DUMP_FILE="$2"
RESTORE_LOG="/var/log/bacula/pg_restore.log"

if [ -z "$DB_NAME" ] || [ -z "$DUMP_FILE" ]; then
    echo "Kullanim: $0 <veritabani_adi> <dump_dosyasi>"
    exit 1
fi

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$RESTORE_LOG"
}

log "Restore başlıyor: $DB_NAME <- $DUMP_FILE"

# Eğer veritabanı zaten varsa yeniden oluştur
sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${DB_NAME}_restore;"
sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME}_restore;"

# Gzip'li dump'ı geri yükle
gunzip -c "$DUMP_FILE" | sudo -u postgres psql 
    --host=localhost 
    --username=postgres 
    --dbname="${DB_NAME}_restore" 
    --echo-errors 2>> "$RESTORE_LOG"

if [ $? -eq 0 ]; then
    log "Restore başarıyla tamamlandı: ${DB_NAME}_restore"
    log "Dogrulama icin baglanti: psql -U postgres -d ${DB_NAME}_restore"
else
    log "HATA: Restore basarisiz oldu!"
    exit 1
fi

exit 0

İzleme ve Uyarı Sistemi

Yedekleme işlerini izlemek için basit ama etkili bir kontrol mekanizması kuralım:

#!/bin/bash
# /etc/bacula/scripts/check_backup_health.sh
# Cron ile her sabah çalıştırın: 0 8 * * * /etc/bacula/scripts/check_backup_health.sh

BACKUP_DIR="/var/backup/postgresql/dumps"
ALERT_EMAIL="[email protected]"
MAX_AGE_HOURS=26  # 26 saatten eski dump varsa uyar

log_and_alert() {
    local MESSAGE="$1"
    echo "$MESSAGE"
    echo "$MESSAGE" | mail -s "UYARI: PostgreSQL Backup Sorunu" "$ALERT_EMAIL"
}

# Son dump dosyasının yaşını kontrol et
LATEST_DUMP=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f -printf '%T@ %pn' | sort -n | tail -1 | awk '{print $2}')

if [ -z "$LATEST_DUMP" ]; then
    log_and_alert "KRITIK: Hiç dump dosyası bulunamadı! Backup çalışmıyor olabilir."
    exit 1
fi

# Dosya yaşını hesapla
FILE_AGE_HOURS=$(( ($(date +%s) - $(stat -c %Y "$LATEST_DUMP")) / 3600 ))

if [ "$FILE_AGE_HOURS" -gt "$MAX_AGE_HOURS" ]; then
    log_and_alert "UYARI: En son PostgreSQL backup $FILE_AGE_HOURS saat önce alınmış. Kontrol edin!"
    exit 1
fi

# Dump dosyası boyutunu kontrol et (boş dump alarm versin)
DUMP_SIZE=$(stat -c %s "$LATEST_DUMP")
if [ "$DUMP_SIZE" -lt 1024 ]; then
    log_and_alert "UYARI: Son dump dosyası çok küçük ($DUMP_SIZE bytes). Dump başarısız olmuş olabilir!"
    exit 1
fi

echo "Backup kontrolü tamam. En son dump: $LATEST_DUMP ($FILE_AGE_HOURS saat önce)"
exit 0

Sık Yapılan Hatalar ve Çözümleri

Pratikte karşılaştığım en yaygın sorunlar ve çözümleri şunlardır:

  • Permission denied hataları: bacula kullanıcısının sudo -u postgres komutunu şifresiz çalıştırabilmesi için /etc/sudoers dosyasına bacula ALL=(postgres) NOPASSWD: /usr/bin/pg_dump, /usr/bin/pg_dumpall, /usr/bin/pg_basebackup satırını ekleyin.
  • ClientRunBeforeJob çalışmıyor: Script’in tam yolu belirtildiğinden ve çalıştırma izninin olduğundan emin olun. Bacula, bacula kullanıcısı olarak bu script’i çalıştırır.
  • Dump sırasında “could not connect to server” hatası: .pgpass dosyasının izinlerini chmod 600 ile ayarlayın ve bacula kullanıcısına ait olduğunu chown bacula:bacula ile doğrulayın.
  • Büyük dump dosyaları Bacula’yı yavaşlatıyor: Pool konfigürasyonunda Spool Data = yes seçeneğini etkinleştirin. Bu, verilerin önce diske yazılıp ardından Storage Daemon’a gönderilmesini sağlar.
  • Artımlı yedeklemeler beklenen boyutta değil: FileSet’te Accurate = yes kullanın. Bu sayede Bacula dosya değişikliklerini daha doğru tespit eder.
  • “Before job” script hatası job’u durdurmaz: Script’inizin hata durumunda exit 1 ile çıktığından emin olun. Bacula, sıfırdan farklı bir exit kodu gördüğünde job’u durdurur.

Konfigürasyonu Doğrulama ve İlk Çalıştırma

Her şeyi yapılandırdıktan sonra ilk manuel çalıştırma ile sistemi test edin:

# bconsole'a bağlan
sudo bconsole

# Job'u manuel olarak çalıştır
*run job=PostgreSQL-Full-Backup

# Job durumunu takip et
*status dir

# Tamamlanan job'ların logunu gör
*list jobs

# Belirli bir job'un detaylarını incele
*list joblog jobid=<job_id>

Sonuç

Bacula ile PostgreSQL yedekleme kurulumu başlangıçta karmaşık görünebilir, ancak bir kez doğru yapılandırdığınızda size inanılmaz bir rahatlık sağlar. Merkezi yönetim, otomatik temizlik, esnek zamanlama ve detaylı loglama ile artık “yedek alındı mı acaba?” diye sabah 3’te uyanmazsınız.

Şu noktaları aklınızda tutun: yedekleme script’lerinizi düzenli test edin, restore süreçlerini en az yılda bir tatbikat yaparak doğrulayın ve izleme mekanizmanızın gerçekten çalıştığından emin olun. Bir disaster recovery planının en zayıf halkası, hiç test edilmemiş restore sürecidir.

Bu yapıyı production’a almadan önce staging ortamında en az bir tam backup-restore döngüsü gerçekleştirmenizi şiddetle öneririm. Bacula’nın kataloğu sayesinde hangi veritabanının hangi volumeda, hangi tarihte yedeklendiğini her zaman takip edebilir ve ihtiyaç anında dakikalar içinde geri yükleyebilirsiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir