Bacula ile Kubernetes Persistent Volume Yedekleme Stratejisi
Kubernetes ortamlarında veri yönetimi, geleneksel sistemlere kıyasla çok daha fazla dikkat gerektiriyor. Pod’lar gelip geçici olsa da Persistent Volume’lerdeki veriler kalıcı olması gerekiyor. Peki bu verileri güvenilir şekilde nasıl yedekleyeceğiz? İşte burada Bacula devreye giriyor. Açık kaynaklı, kurumsal düzeyde yeteneklere sahip bu yedekleme sistemi, doğru yapılandırıldığında Kubernetes PV’lerini oldukça etkin şekilde koruyabiliyor.
Neden Bacula ve Kubernetes Birlikteliği?
Kubernetes, kendi başına bir yedekleme çözümü sunmuyor. Velero gibi Kubernetes’e özgü araçlar mevcut olsa da mevcut altyapısında zaten Bacula kullanan bir ekip için ayrı bir sistem kurmak hem maliyet hem de operasyonel yük açısından mantıklı değil. Bacula’nın modüler yapısı, özelleştirilebilir betik desteği ve güçlü zamanlama özellikleri sayesinde Kubernetes PV yedeklemelerini mevcut yedekleme iş akışınıza entegre edebilirsiniz.
Gerçek dünya senaryosuna bakacak olursak: Bir e-ticaret şirketinde PostgreSQL veritabanı Kubernetes üzerinde çalışıyor, veriler PVC’lerde saklanıyor. Geceleri yapılan Bacula yedeklemeleri hem bu PV’leri hem de fiziksel sunuculardaki verileri kapsıyor. Tek konsol, tek raporlama, tek sorumluluk. Bu senaryo hem operasyonel karmaşıklığı azaltıyor hem de mevcut Bacula lisans yatırımınızı değerlendirmenizi sağlıyor.
Mimari Genel Bakış
Bacula ile Kubernetes PV yedekleme stratejisi temel olarak iki yaklaşım üzerine inşa ediliyor:
- Doğrudan Volume Mount Yaklaşımı: Bacula File Daemon’ı Kubernetes node’larında çalıştırarak PV dosya sistemlerine doğrudan erişim
- Sidecar Container Yaklaşımı: Her Pod’un yanına Bacula FD içeren bir sidecar container ekleyerek uygulama tutarlılığını sağlama
Her iki yaklaşımın da avantajları ve dezavantajları var. Biz bu yazıda her ikisini de ele alacağız ancak production ortamlar için sidecar yaklaşımını önereceğiz çünkü uygulama tutarlılığı kritik önem taşıyor.
Ön Gereksinimler ve Ortam Hazırlığı
Başlamadan önce aşağıdakilere ihtiyacınız olacak:
- Çalışan bir Bacula Director, Storage Daemon ve Catalog (en az Bacula 9.x, tercihen 11.x)
- Kubernetes cluster (1.20+)
- kubectl erişimi ve gerekli RBAC yetkileri
- Bacula FD binary’sinin container image’a eklenebilmesi için Docker build ortamı
Önce Bacula Director’da Kubernetes node’larını veya pod’larını temsil edecek Client tanımlarını yapılandırmamız gerekiyor.
# Bacula Director konfigürasyon dosyasını düzenle
vim /etc/bacula/bacula-dir.conf
# Kubernetes PV yedeklemesi için Client tanımı
Client {
Name = k8s-postgres-fd
Address = 10.0.1.45 # Bacula FD'nin dinleyeceği IP
FDPort = 9102
Catalog = MyCatalog
Password = "guclu-sifre-buraya"
File Retention = 30 days
Job Retention = 6 months
AutoPrune = yes
}
# Kubernetes PV'leri için ayrı bir FileSet tanımı
FileSet {
Name = "K8S-PersistentVolumes"
Include {
Options {
signature = MD5
compression = GZIP
noatime = yes
}
# NFS veya hostPath PV mount noktaları
File = /mnt/k8s-pvs/postgres-data
File = /mnt/k8s-pvs/redis-data
File = /mnt/k8s-pvs/elasticsearch-data
}
Exclude {
File = /mnt/k8s-pvs/postgres-data/pg_wal
File = /mnt/k8s-pvs/postgres-data/postmaster.pid
}
}
Node Üzerinde Bacula File Daemon Kurulumu
İlk yaklaşım olan node üzerinde doğrudan FD çalıştırma yöntemi için Kubernetes worker node’larına Bacula File Daemon kurulumu yapıyoruz.
# Worker node'larda Bacula FD kurulumu (Debian/Ubuntu için)
apt-get update && apt-get install -y bacula-fd
# bacula-fd.conf konfigürasyonu
cat > /etc/bacula/bacula-fd.conf << 'EOF'
FileDaemon {
Name = k8s-node1-fd
FDport = 9102
WorkingDirectory = /var/lib/bacula
Pid Directory = /run/bacula
Maximum Concurrent Jobs = 5
Plugin Directory = /usr/lib/bacula
}
Director {
Name = bacula-dir
Password = "guclu-sifre-buraya"
}
Messages {
Name = Standard
director = bacula-dir = all, !skipped, !restored
}
EOF
# Servisi başlat
systemctl enable bacula-fd
systemctl start bacula-fd
# PV mount noktalarının erişilebilir olduğunu doğrula
ls -la /mnt/k8s-pvs/
Bu yaklaşımda kritik bir adım var: Kubernetes PV’lerinin node üzerindeki fiziksel konumlarını tespit etmek.
# PVC'lerin fiziksel konumlarını bulmak için
kubectl get pv -o json | jq '.items[] | {name: .metadata.name, path: .spec.hostPath.path, nfs: .spec.nfs}'
# Belirli bir PVC için node bilgisi
kubectl get pod postgres-0 -o jsonpath='{.spec.nodeName}'
kubectl describe pod postgres-0 | grep -A 5 "Volumes:"
# PV mount noktasını node üzerinde doğrula
kubectl get pvc postgres-data-pvc -o jsonpath='{.spec.volumeName}'
# Çıktı: pvc-abc123def456
# Node üzerinde fiziksel yolu bul
find /var/lib/kubelet/pods -name "*.mount" 2>/dev/null | head -20
ls /var/lib/kubelet/plugins/kubernetes.io/
Sidecar Container Yaklaşımı (Önerilen Yöntem)
Production ortamlar için sidecar yaklaşımı çok daha güvenilir. Bunun temel sebebi uygulama tutarlılığı. Veritabanı dump’ı almadan önce dosyaları yedeklemek tutarsız bir backup üretebilir.
Önce Bacula FD içeren bir container image oluşturalım:
# Dockerfile.bacula-fd
FROM ubuntu:22.04
RUN apt-get update &&
apt-get install -y bacula-fd bacula-common &&
apt-get clean &&
rm -rf /var/lib/apt/lists/*
# Özel başlangıç betiği
COPY bacula-fd-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/bacula-fd-entrypoint.sh
EXPOSE 9102
ENTRYPOINT ["/usr/local/bin/bacula-fd-entrypoint.sh"]
# bacula-fd-entrypoint.sh
#!/bin/bash
set -e
# Ortam değişkenlerinden konfigürasyon oluştur
cat > /etc/bacula/bacula-fd.conf << EOF
FileDaemon {
Name = ${FD_NAME:-pod-fd}
FDport = 9102
WorkingDirectory = /var/lib/bacula
Pid Directory = /run
Maximum Concurrent Jobs = 3
}
Director {
Name = ${DIRECTOR_NAME:-bacula-dir}
Password = "${FD_PASSWORD}"
}
Messages {
Name = Standard
director = ${DIRECTOR_NAME:-bacula-dir} = all, !skipped, !restored
}
EOF
mkdir -p /var/lib/bacula /run/bacula
echo "Bacula FD başlatılıyor: ${FD_NAME}"
exec /usr/sbin/bacula-fd -f -c /etc/bacula/bacula-fd.conf
Şimdi bu image’ı kullanan bir Kubernetes Deployment oluşturalım:
# postgres-with-bacula-sidecar.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: production
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
# Bacula FD Sidecar
- name: bacula-fd
image: registry.example.com/bacula-fd:11.0
env:
- name: FD_NAME
value: "k8s-postgres-fd"
- name: DIRECTOR_NAME
value: "bacula-dir"
- name: FD_PASSWORD
valueFrom:
secretKeyRef:
name: bacula-secret
key: fd-password
ports:
- containerPort: 9102
name: bacula-fd
volumeMounts:
- name: postgres-data
mountPath: /backup/postgres-data
readOnly: true
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
Uygulama Tutarlılığı için Pre/Post Betikler
Yedekleme öncesi ve sonrası çalıştırılacak betikler, uygulama tutarlılığının anahtarı. Bacula’nın ClientRunBeforeJob ve ClientRunAfterJob direktifleri tam da bu iş için var.
# /etc/bacula/scripts/postgres-pre-backup.sh
#!/bin/bash
# PostgreSQL için yedekleme öncesi betik
LOG_FILE="/var/log/bacula/pre-backup.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] PostgreSQL pre-backup başlatılıyor..." >> $LOG_FILE
# Pod adını ve namespace'i bul
POD_NAME=$(kubectl get pods -n production -l app=postgres
-o jsonpath='{.items[0].metadata.name}')
if [ -z "$POD_NAME" ]; then
echo "[$TIMESTAMP] HATA: PostgreSQL pod bulunamadı!" >> $LOG_FILE
exit 1
fi
# PostgreSQL checkpoint al (WAL'ı temizle ve tutarlı durum sağla)
kubectl exec -n production $POD_NAME -c postgres --
psql -U postgres -c "CHECKPOINT;" >> $LOG_FILE 2>&1
# pg_start_backup ile yedekleme moduna gir
kubectl exec -n production $POD_NAME -c postgres --
psql -U postgres -c "SELECT pg_start_backup('bacula_backup', true);"
>> $LOG_FILE 2>&1
echo "[$TIMESTAMP] PostgreSQL backup modu aktif, yedekleme başlayabilir." >> $LOG_FILE
exit 0
# /etc/bacula/scripts/postgres-post-backup.sh
#!/bin/bash
# PostgreSQL için yedekleme sonrası betik
LOG_FILE="/var/log/bacula/post-backup.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] PostgreSQL post-backup başlatılıyor..." >> $LOG_FILE
POD_NAME=$(kubectl get pods -n production -l app=postgres
-o jsonpath='{.items[0].metadata.name}')
# Backup modundan çık
kubectl exec -n production $POD_NAME -c postgres --
psql -U postgres -c "SELECT pg_stop_backup();" >> $LOG_FILE 2>&1
echo "[$TIMESTAMP] PostgreSQL normal operasyon moduna döndü." >> $LOG_FILE
# Yedekleme başarı durumunu kontrol et ve bildirim gönder
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] Yedekleme başarıyla tamamlandı." >> $LOG_FILE
# Opsiyonel: Slack veya mail bildirimi
# curl -X POST -H 'Content-type: application/json'
# --data '{"text":"Postgres PV backup tamamlandı"}'
# $SLACK_WEBHOOK_URL
else
echo "[$TIMESTAMP] UYARI: pg_stop_backup hatası!" >> $LOG_FILE
exit 1
fi
exit 0
Bacula Director’da bu betikleri kullanan Job tanımı:
# bacula-dir.conf içinde Job tanımı
Job {
Name = "K8S-Postgres-PV-Backup"
Type = Backup
Client = k8s-postgres-fd
FileSet = "K8S-PersistentVolumes"
Schedule = "WeeklyCycleAfterMidnight"
Storage = FileStorage
Messages = Standard
Pool = K8S-PV-Pool
Priority = 10
Write Bootstrap = "/var/lib/bacula/%c.bsr"
# Uygulama tutarlılığı için betikler
ClientRunBeforeJob = "/etc/bacula/scripts/postgres-pre-backup.sh"
ClientRunAfterJob = "/etc/bacula/scripts/postgres-post-backup.sh"
# Yedekleme başarısız olursa post betiği yine de çalıştır
RunAfterFailedJob = "/etc/bacula/scripts/postgres-post-backup.sh"
}
Schedule {
Name = "WeeklyCycleAfterMidnight"
Run = Full 1st sun at 01:05
Run = Differential 2nd-5th sun at 01:05
Run = Incremental mon-sat at 01:05
}
Pool {
Name = K8S-PV-Pool
Pool Type = Backup
Recycle = yes
AutoPrune = yes
Volume Retention = 30 days
Maximum Volume Bytes = 50G
Maximum Volumes = 20
Label Format = "K8S-PV-"
}
RBAC ve Güvenlik Yapılandırması
Kubernetes ortamında Bacula’nın çalışabilmesi için doğru RBAC tanımları şart.
# bacula-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: bacula-backup
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: bacula-backup-role
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["get", "list", "create"]
- apiGroups: [""]
resources: ["persistentvolumes", "persistentvolumeclaims"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["statefulsets", "deployments"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: bacula-backup-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: bacula-backup-role
subjects:
- kind: ServiceAccount
name: bacula-backup
namespace: production
# RBAC'ı uygula
kubectl apply -f bacula-rbac.yaml
# Bacula'nın kullanacağı kubeconfig oluştur
kubectl create token bacula-backup -n production --duration=8760h
> /etc/bacula/k8s-token
# Kubeconfig dosyasını oluştur
kubectl config set-credentials bacula-user
--token=$(cat /etc/bacula/k8s-token)
kubectl config set-context bacula-context
--cluster=production-cluster
--user=bacula-user
Restore Prosedürü ve Testler
Yedekleme kadar önemli olan restore sürecini de otomatize etmeliyiz. Her yedeklemenin geri yüklenebilir olduğunu periyodik olarak test etmek kritik öneme sahip.
# restore-test.sh - Haftalık otomatik restore testi
#!/bin/bash
TEST_NAMESPACE="backup-test"
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
LOG_FILE="/var/log/bacula/restore-test-${TIMESTAMP}.log"
echo "Restore testi başlıyor: $TIMESTAMP" | tee -a $LOG_FILE
# Test namespace oluştur
kubectl create namespace $TEST_NAMESPACE 2>/dev/null || true
# Bacula restore job'u başlat
bconsole << 'BCON'
restore
5
mark *
done
yes
quit
BCON
# Restore edilen verileri doğrula
sleep 300 # Restore'un tamamlanmasını bekle
# PostgreSQL veri bütünlüğünü kontrol et
TEST_POD=$(kubectl get pods -n $TEST_NAMESPACE -l app=postgres-restore
-o jsonpath='{.items[0].metadata.name}')
if kubectl exec -n $TEST_NAMESPACE $TEST_POD --
pg_dump -U postgres myapp > /dev/null 2>&1; then
echo "BASARILI: Restore test geçti - $TIMESTAMP" | tee -a $LOG_FILE
# Bildirim gönder
else
echo "HATA: Restore test başarısız - $TIMESTAMP" | tee -a $LOG_FILE
# Acil bildirim gönder
exit 1
fi
# Test namespace'i temizle
kubectl delete namespace $TEST_NAMESPACE
echo "Restore testi tamamlandı." | tee -a $LOG_FILE
Restore testini Bacula tarafında da zamanlamak için:
# bacula-dir.conf içine restore test job'u ekle
Job {
Name = "K8S-Postgres-Restore-Test"
Type = Restore
Client = k8s-postgres-fd
FileSet = "K8S-PersistentVolumes"
Storage = FileStorage
Messages = Standard
Pool = K8S-PV-Pool
Where = /tmp/bacula-restore-test
RunScript {
Command = "/etc/bacula/scripts/restore-test.sh"
RunsOnClient = yes
RunsWhen = After
FailJobOnError = yes
}
}
İzleme ve Uyarı Entegrasyonu
Yedekleme durumunu izlemek için Bacula’nın Events tablosunu sorgulayan basit bir izleme betiği hazırlayalım:
#!/bin/bash
# bacula-k8s-monitor.sh
BACULA_DB="bacula"
DB_HOST="localhost"
DB_USER="bacula"
ALERT_EMAIL="[email protected]"
# Son 24 saatteki başarısız job'ları kontrol et
FAILED_JOBS=$(mysql -h $DB_HOST -u $DB_USER $BACULA_DB -se
"SELECT Job.Name, Job.StartTime, Job.JobStatus
FROM Job
WHERE Job.Name LIKE 'K8S%'
AND Job.StartTime > DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND Job.JobStatus NOT IN ('T', 'W')
ORDER BY Job.StartTime DESC;")
if [ -n "$FAILED_JOBS" ]; then
echo "UYARI: Kubernetes PV yedekleme hataları tespit edildi!" |
mail -s "[KRITIK] Bacula K8S Backup Hatasi" $ALERT_EMAIL << EOF
Son 24 saat içinde başarısız olan K8S yedekleme job'ları:
$FAILED_JOBS
Lütfen Bacula konsolunu kontrol ediniz.
EOF
fi
# PV kapasitesi kontrolü
kubectl get pv --no-headers | awk '{
if ($5 == "Released" || $5 == "Failed")
print "UYARI: PV durumu sorunlu - " $1 " : " $5
}'
# Son başarılı full backup kontrolü
LAST_FULL=$(mysql -h $DB_HOST -u $DB_USER $BACULA_DB -se
"SELECT MAX(StartTime) FROM Job
WHERE Name = 'K8S-Postgres-PV-Backup'
AND Level = 'F'
AND JobStatus = 'T';")
echo "Son başarılı full backup: $LAST_FULL"
Sık Karşılaşılan Sorunlar ve Çözümleri
Pratik deneyimlerimizden derlediğimiz sorun ve çözüm listesi:
- “Connection refused on port 9102”: Sidecar container’da network policy sorunu olabilir.
kubectl execile container içinden Bacula Director’a telnet atarak bağlantıyı test edin. NetworkPolicy kurallarını gözden geçirin.
- “Permission denied on /var/lib/kubelet”: Node tabanlı yaklaşımda kubelet dizinine doğrudan erişim kısıtlanmış olabilir. FSGroup ve supplementalGroups ayarlarını Pod security context’inde düzenleyin.
- “Inconsistent backup data”: Pre-backup betiğinin çalışmadığını gösterir.
ClientRunBeforeJobçıkış kodunu kontrol edin; betik 0 dışında bir değer döndürürse Bacula job’u durdurmaz, sadece uyarı verir.Abort Job On Error = yesdirektifini Job tanımına ekleyin.
- “PVC bound to different node after pod restart”: ReadWriteOnce PVC’ler pod farklı bir node’a schedule edilirse mount edilemez. Bu durumda yedekleme de çalışmaz. Node affinity kuralları ekleyerek pod’un her zaman aynı node’da çalışmasını sağlayın.
- Incremental backup boyutu beklenenden büyük: PostgreSQL WAL dosyaları sürekli değiştiği için Exclude direktifleriyle
pg_waldizinini FileSet’ten çıkarın. WAL arşivini ayrı bir job ile yedekleyin.
Kapasite Planlama Notları
Kubernetes PV yedeklemelerinde kapasite planlaması yaparken şu faktörleri göz önünde bulundurun:
- Değişim oranı (change rate): Veritabanı workload’una bağlı olarak günlük değişim oranı toplam veri boyutunun yüzde 10 ile 30’u arasında değişebilir. Incremental yedekleme boyutlarını ilk iki haftada ölçerek Pool tanımınızdaki Maximum Volume Bytes değerini buna göre ayarlayın.
- Retention politikası: Production ortamda minimum 30 günlük, kritik veriler için 90 günlük retention önerilir. Volume Retention değerini iş gereksinimlerinize göre belirleyin.
- Deduplication: Aynı base image’dan türeyen birden fazla PV varsa, Bacula’nın deduplication özelliği depolama alanından önemli tasarruf sağlar. Deduplication = yes direktifini FileSet’e ekleyin.
- Ağ bant genişliği: Sidecar yaklaşımında Bacula FD ile Storage Daemon arasındaki trafik cluster iç ağını kullanır. Büyük full backup’lar için gece saatlerini tercih edin ve Maximum Bandwidth Per Job direktifiyle bant genişliğini sınırlandırın.
Sonuç
Bacula ile Kubernetes Persistent Volume yedeklemesi, doğru yapılandırıldığında kurumsal düzeyde güvenilir bir çözüm sunuyor. Sidecar container yaklaşımı, uygulama tutarlılığı açısından node tabanlı yönteme kıyasla çok daha sağlam bir temel oluşturuyor. Pre ve post backup betikleri sayesinde PostgreSQL gibi veritabanları tutarlı bir şekilde yedeklenebiliyor.
Bu stratejinin en büyük avantajı mevcut Bacula altyapınızla bütünleşmesi. Ayrı bir araç öğrenmek, ayrı lisans maliyeti, ayrı bir yedekleme politikası yönetmek yerine Kubernetes PV’lerinizi diğer tüm sistemlerinizle birlikte tek bir konsoldan yönetebiliyorsunuz. Restore testlerini otomatize etmek ve izleme entegrasyonunu kurmak, “yedekleme var mı?” sorusunu “yedekleme çalışıyor mu?” sorusuna dönüştürüyor ki bu fark operasyonel olgunluk açısından son derece önemli.
Bir sonraki adım olarak Bacula’nın plugin altyapısını kullanarak Kubernetes CSI snapshot’larını doğrudan Bacula kataloğuna entegre etmeyi düşünebilirsiniz. Bu konu başlı başına bir yazıyı hak ediyor.
