Jenkins Backup ve Restore Stratejileri: Verilerinizi Koruyun

Jenkins kurulumunuzu emekle yapılandırdınız, pipeline’larınızı yazdınız, job’larınızı düzenlediniz ve her şey mükemmel çalışıyor. Sonra bir gün sunucu diskinde kritik bir bozulma yaşanıyor ya da yanlışlıkla yapılan bir güncelleme her şeyi mahvediyor. İşte tam bu noktada “acaba backup almış mıydım?” sorusu aklınıza geliyor. Jenkins için sağlam bir backup ve restore stratejisi kurmak, sadece iyi bir uygulama değil, production ortamında zorunluluktur.

Jenkins’in Dosya Yapısını Anlamak

Backup stratejisi oluşturmadan önce neyi yedekleyeceğimizi bilmemiz gerekiyor. Jenkins verilerinin tamamı JENKINS_HOME dizininde tutulur. Bu dizin genellikle /var/lib/jenkins ya da /opt/jenkins olarak bulunur, ama kuruluma göre farklılık gösterebilir.

# Jenkins home dizinini bulmak
echo $JENKINS_HOME
# ya da
systemctl show jenkins | grep JENKINS_HOME
# ya da doğrudan servis dosyasına bakın
cat /etc/default/jenkins | grep JENKINS_HOME

JENKINS_HOME altında kritik olan dizinler şunlardır:

  • jobs/: Tüm job tanımlarınız ve build geçmişleri burada
  • config.xml: Ana Jenkins konfigürasyonu
  • credentials.xml: Tüm credential’larınız (şifreli olsa da backup’a dahil edin)
  • plugins/: Yüklü plugin’ler
  • users/: Kullanıcı hesapları ve API token’ları
  • nodes/: Agent node konfigürasyonları
  • secrets/: Şifreleme anahtarları (bu dizin olmadan credential’ları çözemezsiniz)
  • fingerprints/: Artifact parmak izleri
  • updates/: Plugin güncelleme bilgileri

Şunu özellikle vurgulamak istiyorum: secrets/ dizini olmadan yedeklediğiniz credential’lar tamamen işe yaramaz hale gelir. Çünkü Jenkins, credential’ları bu dizindeki anahtarlarla şifreler.

Basit Bash Script ile Manuel Backup

En temel yaklaşımdan başlayalım. Karmaşık araçlara geçmeden önce ne yaptığımızı anlamalıyız.

#!/bin/bash
# jenkins-backup.sh

JENKINS_HOME="/var/lib/jenkins"
BACKUP_DIR="/backup/jenkins"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="jenkins_backup_${DATE}"
LOG_FILE="/var/log/jenkins-backup.log"

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

# Backup dizini oluştur
mkdir -p "${BACKUP_DIR}"

log "Jenkins backup başlıyor: ${BACKUP_NAME}"

# Jenkins servisini durdurmak zorunda değiliz ama tutarlılık için
# önerilen yol safe-restart ile beklemek
# Alternatif: quiet mode açıp mevcut build'lerin bitmesini beklemek

# Kritik dizinleri yedekle
tar -czf "${BACKUP_DIR}/${BACKUP_NAME}.tar.gz" 
    --exclude="${JENKINS_HOME}/workspace" 
    --exclude="${JENKINS_HOME}/caches" 
    --exclude="${JENKINS_HOME}/logs" 
    --exclude="${JENKINS_HOME}/.git" 
    -C "$(dirname ${JENKINS_HOME})" 
    "$(basename ${JENKINS_HOME})"

if [ $? -eq 0 ]; then
    log "Backup başarıyla tamamlandı: ${BACKUP_DIR}/${BACKUP_NAME}.tar.gz"
    # Boyutu logla
    SIZE=$(du -sh "${BACKUP_DIR}/${BACKUP_NAME}.tar.gz" | cut -f1)
    log "Backup boyutu: ${SIZE}"
else
    log "HATA: Backup başarısız!"
    exit 1
fi

# Eski backup'ları temizle (30 günden eski)
find "${BACKUP_DIR}" -name "jenkins_backup_*.tar.gz" -mtime +30 -delete
log "30 günden eski backup'lar temizlendi"

Bu script’i crontab’a ekleyerek otomatikleştirebilirsiniz:

# crontab -e ile ekleyin
# Her gün gece 02:00'de çalıştır
0 2 * * * /opt/scripts/jenkins-backup.sh

# Her 6 saatte bir çalıştır
0 */6 * * * /opt/scripts/jenkins-backup.sh

Workspace ve Build Geçmişini Hariç Tutmak

Prodüksiyonda çalışan bir Jenkins sunucusunda JENKINS_HOME kolayca 50-100GB’ı geçebilir. Bunun büyük çoğunluğu workspace’ler ve eski build loglarıdır. Bunları yedeklemeye gerek yoktur.

# Sadece kritik dosyaları yedekleyen daha seçici bir yaklaşım
tar -czf "${BACKUP_DIR}/${BACKUP_NAME}.tar.gz" 
    --exclude="${JENKINS_HOME}/workspace/*" 
    --exclude="${JENKINS_HOME}/caches/*" 
    --exclude="${JENKINS_HOME}/logs/*" 
    --exclude="${JENKINS_HOME}/jobs/*/builds/*/archive" 
    --exclude="${JENKINS_HOME}/jobs/*/builds/*/log" 
    --exclude="${JENKINS_HOME}/jobs/*/htmlreports" 
    --exclude="${JENKINS_HOME}/war" 
    --exclude="${JENKINS_HOME}/.cache" 
    -C "$(dirname ${JENKINS_HOME})" 
    "$(basename ${JENKINS_HOME})"

Eğer build geçmişini de saklamak istiyorsanız ama disk alanından tasarruf etmek istiyorsanız, job konfigürasyonlarında “Discard old builds” ayarını aktif edin. Job başına maksimum 10-20 build tutmak genellikle yeterlidir.

ThinBackup Plugin ile Otomatik Backup

Jenkins’in en popüler backup plugin’i olan ThinBackup, arayüzden yönetilebilen güçlü bir çözüm sunar. Ama dikkatli olun, plugin’e tamamen bağımlı olmak bazı senaryolarda sorun çıkarabilir.

ThinBackup kurulumundan sonra Manage Jenkins > ThinBackup menüsünden konfigürasyon yapabilirsiniz. Script tabanlı yapılandırma tercih edenler için Groovy ile de ayarlayabilirsiniz:

// Jenkins Script Console'dan ThinBackup konfigürasyonu
import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl

def thinBackup = ThinBackupPluginImpl.get()
thinBackup.setBackupPath('/backup/jenkins/thinbackup')
thinBackup.setFullBackupSchedule('0 2 * * 0')    // Her Pazar gece 02:00
thinBackup.setDiffBackupSchedule('0 2 * * 1-6')  // Hafta içi gece 02:00
thinBackup.setNrMaxStoredFull(4)                  // Maksimum 4 tam yedek
thinBackup.setExcludedFilesRegex('.*workspace.*')
thinBackup.setWaitForIdle(true)
thinBackup.save()

println "ThinBackup konfigürasyonu kaydedildi"

Jenkins Configuration as Code (JCasC) ile Backup Yaklaşımı

Modern Jenkins deployment’larında en sağlam yaklaşım, konfigürasyonu kod olarak yönetmektir. JCasC plugin’i kullanarak tüm Jenkins konfigürasyonunuzu YAML dosyasına dönüştürebilirsiniz.

# jenkins.yaml - JCasC konfigürasyon dosyası örneği
jenkins:
  systemMessage: "Production Jenkins - DevOps Team"
  numExecutors: 2
  mode: NORMAL
  
  securityRealm:
    local:
      allowsSignup: false
      users:
        - id: "admin"
          password: "${ADMIN_PASSWORD}"
  
  authorizationStrategy:
    roleBased:
      roles:
        global:
          - name: "admin"
            permissions:
              - "Overall/Administer"
            assignments:
              - "admin"

  clouds:
    - kubernetes:
        name: "kubernetes"
        serverUrl: "https://kubernetes.default"
        namespace: "jenkins"

tool:
  git:
    installations:
      - name: "Default"
        home: "git"

unclassified:
  location:
    url: "https://jenkins.sirketiniz.com/"
    adminAddress: "[email protected]"

Bu yaklaşımın güzelliği şu: Konfigürasyonunuz Git’te yaşıyor. Backup derdiniz yok, versiyon kontrolünüz var.

Restore İşlemi

Asıl test yedek almak değil, o yedeği geri yükleyebilmektir. Restore işlemini düzenli olarak test edin.

#!/bin/bash
# jenkins-restore.sh

BACKUP_FILE="$1"
JENKINS_HOME="/var/lib/jenkins"
JENKINS_USER="jenkins"

if [ -z "$BACKUP_FILE" ]; then
    echo "Kullanım: $0 /backup/jenkins/jenkins_backup_20240115_020000.tar.gz"
    exit 1
fi

if [ ! -f "$BACKUP_FILE" ]; then
    echo "HATA: Backup dosyası bulunamadı: $BACKUP_FILE"
    exit 1
fi

echo "Restore işlemi başlıyor..."
echo "Backup dosyası: $BACKUP_FILE"

# Jenkins servisini durdur
echo "Jenkins servisi durduruluyor..."
systemctl stop jenkins

# Mevcut konfigürasyonu yedekle (güvenlik için)
TEMP_BACKUP="/tmp/jenkins_pre_restore_$(date +%Y%m%d_%H%M%S)"
echo "Mevcut konfigürasyon geçici olarak yedekleniyor: $TEMP_BACKUP"
cp -r "$JENKINS_HOME" "$TEMP_BACKUP"

# JENKINS_HOME'u temizle (workspace'i koru)
echo "Jenkins home temizleniyor..."
find "$JENKINS_HOME" -mindepth 1 -maxdepth 1 
    ! -name 'workspace' 
    -exec rm -rf {} +

# Backup'ı geri yükle
echo "Backup geri yükleniyor..."
tar -xzf "$BACKUP_FILE" -C "$(dirname $JENKINS_HOME)" --strip-components=1

# İzinleri düzelt
echo "İzinler düzenleniyor..."
chown -R "$JENKINS_USER:$JENKINS_USER" "$JENKINS_HOME"
chmod 700 "$JENKINS_HOME/secrets"

# Jenkins servisini başlat
echo "Jenkins servisi başlatılıyor..."
systemctl start jenkins

# Servisin başlamasını bekle
echo "Jenkins'in hazır olması bekleniyor..."
for i in {1..30}; do
    if curl -sf http://localhost:8080/login > /dev/null 2>&1; then
        echo "Jenkins başarıyla başladı!"
        break
    fi
    echo "Bekleniyor... ($i/30)"
    sleep 10
done

echo "Restore işlemi tamamlandı."
echo "Geçici backup: $TEMP_BACKUP (doğrulama sonrası silebilirsiniz)"

Uzak Sunucuya Backup Gönderme

Backup’ı aynı sunucuda tutmak, sunucu tamamen giderse işe yaramaz. Backup’ları uzak bir konuma göndermeliyiz.

#!/bin/bash
# jenkins-backup-remote.sh

JENKINS_HOME="/var/lib/jenkins"
LOCAL_BACKUP_DIR="/backup/jenkins"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="jenkins_backup_${DATE}.tar.gz"
REMOTE_SERVER="backup-server.sirket.local"
REMOTE_USER="backup"
REMOTE_DIR="/data/jenkins-backups"
S3_BUCKET="s3://sirket-jenkins-backups"

# Lokal backup oluştur
tar -czf "${LOCAL_BACKUP_DIR}/${BACKUP_NAME}" 
    --exclude="${JENKINS_HOME}/workspace" 
    --exclude="${JENKINS_HOME}/caches" 
    -C "$(dirname ${JENKINS_HOME})" 
    "$(basename ${JENKINS_HOME})"

# SCP ile uzak sunucuya gönder
echo "Uzak sunucuya gönderiliyor..."
scp -i /etc/jenkins/.ssh/backup_key 
    "${LOCAL_BACKUP_DIR}/${BACKUP_NAME}" 
    "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_DIR}/"

# AWS S3'e yükle (aws cli kurulu olmalı)
if command -v aws &> /dev/null; then
    echo "S3'e yükleniyor..."
    aws s3 cp "${LOCAL_BACKUP_DIR}/${BACKUP_NAME}" 
        "${S3_BUCKET}/${BACKUP_NAME}" 
        --storage-class STANDARD_IA
    
    # S3'te 90 günden eski dosyaları lifecycle policy ile silin
    # ya da manuel:
    # aws s3 ls ${S3_BUCKET} | awk '{print $4}' | head -n -90 | xargs -I{} aws s3 rm "${S3_BUCKET}/{}"
fi

# Lokal eski backup'ları temizle (7 gün)
find "${LOCAL_BACKUP_DIR}" -name "jenkins_backup_*.tar.gz" -mtime +7 -delete

echo "Backup ve transfer tamamlandı: ${BACKUP_NAME}"

Docker Tabanlı Jenkins için Backup

Eğer Jenkins’i Docker container olarak çalıştırıyorsanız, yaklaşım biraz farklı:

#!/bin/bash
# docker-jenkins-backup.sh

CONTAINER_NAME="jenkins"
BACKUP_DIR="/backup/jenkins"
DATE=$(date +%Y%m%d_%H%M%S)

# Volume mount noktasını bul
JENKINS_VOLUME=$(docker inspect "$CONTAINER_NAME" 
    --format='{{range .Mounts}}{{if eq .Destination "/var/jenkins_home"}}{{.Source}}{{end}}{{end}}')

echo "Jenkins volume: $JENKINS_VOLUME"

# Container'ı durdurma, çalışırken backup al
# Ama önce quiet mode'a al
docker exec "$CONTAINER_NAME" 
    curl -X POST http://localhost:8080/quietDown 
    --user "admin:${JENKINS_API_TOKEN}"

# Aktif build'lerin bitmesini bekle
sleep 30

# Backup al
tar -czf "${BACKUP_DIR}/jenkins_docker_${DATE}.tar.gz" 
    --exclude="${JENKINS_VOLUME}/workspace" 
    --exclude="${JENKINS_VOLUME}/caches" 
    -C "$(dirname $JENKINS_VOLUME)" 
    "$(basename $JENKINS_VOLUME)"

# Quiet mode'u kapat
docker exec "$CONTAINER_NAME" 
    curl -X POST http://localhost:8080/cancelQuietDown 
    --user "admin:${JENKINS_API_TOKEN}"

echo "Docker Jenkins backup tamamlandı"

Kubernetes’te Jenkins Backup (PVC Snapshot)

Kubernetes üzerinde Jenkins çalıştırıyorsanız, en temiz yol PersistentVolumeClaim snapshot’ı almaktır:

# jenkins-volume-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: jenkins-backup-20240115
  namespace: jenkins
spec:
  volumeSnapshotClassName: csi-aws-vsc
  source:
    persistentVolumeClaimName: jenkins-home-pvc
# Snapshot oluştur
kubectl apply -f jenkins-volume-snapshot.yaml

# Snapshot durumunu kontrol et
kubectl get volumesnapshot -n jenkins

# Snapshot'tan restore için yeni PVC oluştur
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-home-restored
  namespace: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  dataSource:
    name: jenkins-backup-20240115
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
EOF

Backup Doğrulama Script’i

Backup aldınız, peki işe yarar mı? Düzenli doğrulama yapmanız şart:

#!/bin/bash
# jenkins-backup-verify.sh

BACKUP_FILE="$1"
TEST_RESTORE_DIR="/tmp/jenkins-backup-test-$(date +%s)"

echo "Backup doğrulama başlıyor: $BACKUP_FILE"

# Arşiv bütünlüğünü kontrol et
echo "1. Arşiv bütünlüğü kontrol ediliyor..."
if tar -tzf "$BACKUP_FILE" > /dev/null 2>&1; then
    echo "   TAMAM: Arşiv bütünlüğü sağlam"
else
    echo "   HATA: Arşiv bozuk!"
    exit 1
fi

# Test dizinine aç
echo "2. Test restore deneniyor..."
mkdir -p "$TEST_RESTORE_DIR"
tar -xzf "$BACKUP_FILE" -C "$TEST_RESTORE_DIR"

# Kritik dosyaların varlığını kontrol et
echo "3. Kritik dosyalar kontrol ediliyor..."

CRITICAL_FILES=(
    "config.xml"
    "credentials.xml"
    "secrets/master.key"
    "secrets/hudson.util.Secret"
)

RESTORE_HOME=$(find "$TEST_RESTORE_DIR" -name "config.xml" -maxdepth 3 | head -1 | xargs dirname)

for file in "${CRITICAL_FILES[@]}"; do
    if [ -f "${RESTORE_HOME}/${file}" ]; then
        echo "   TAMAM: ${file} mevcut"
    else
        echo "   UYARI: ${file} bulunamadı!"
    fi
done

# Job sayısını kontrol et
JOB_COUNT=$(find "${RESTORE_HOME}/jobs" -name "config.xml" 2>/dev/null | wc -l)
echo "4. Yedeklenen job sayısı: $JOB_COUNT"

# Plugin sayısını kontrol et
PLUGIN_COUNT=$(find "${RESTORE_HOME}/plugins" -name "*.jpi" 2>/dev/null | wc -l)
echo "5. Yedeklenen plugin sayısı: $PLUGIN_COUNT"

# Temizlik
rm -rf "$TEST_RESTORE_DIR"

echo "Doğrulama tamamlandı."

Gerçek Dünya Senaryosu: Felaketten Dönüş

Geçen yıl bir müşterimin production Jenkins sunucusu, yapılan bir kernel güncellemesinin ardından bozuk dosya sistemiyle açılmaz hale geldi. Elimizde şanslı ki üç günlük backup vardı. Restore süreci şöyle işledi:

İlk adım olarak yeni bir sunucu ayağa kaldırdık ve aynı işletim sistemi versiyonunu, aynı Java versiyonunu ve aynı Jenkins versiyonunu kurduk. Bu önemli bir nokta: Farklı Jenkins versiyonuna restore etmeye çalışırsanız ciddi sorunlarla karşılaşabilirsiniz. Özellikle downgrade kesinlikle önerilmez.

İkinci adımda servisi başlatmadan backup’ı JENKINS_HOME üzerine açtık. Üçüncü adımda secrets/ dizininin doğru izinlere sahip olduğunu (chmod 700) doğruladık. Dördüncü adımda jenkins kullanıcısının tüm dosyalara sahipliğini kontrol ettik. Son olarak servisi başlattık ve tüm job’ların, pipeline’ların ve credential’ların yerli yerinde olduğunu gördük.

Toplam recovery süresi: 45 dakika. Backup olmasa en az 2 gün sürerdi.

Backup Stratejisi Önerileri

Sıfırdan bir strateji oluştururken şu noktaları göz önünde bulundurun:

  • 3-2-1 kuralı: 3 kopya, 2 farklı medya, 1 off-site
  • RPO (Recovery Point Objective): Ne kadar veri kaybı kabul edilebilir? Günlük backup ile maksimum 24 saatlik veri kaybı riski var
  • RTO (Recovery Time Objective): Sistemi kaç saatte ayağa kaldırabilirsiniz?
  • Backup’larınızı en az ayda bir test edin, tercihen her hafta
  • Jenkins versiyonunu backup metadata’sına kaydedin
  • Backup başarısız olduğunda alert alın, sessiz başarısızlık en tehlikeli senaryodur
  • Credential backup’larını ayrı bir güvenli lokasyonda saklayın
  • JCasC kullanıyorsanız Git repo’nuzun backup’ını da ihmal etmeyin

Sonuç

Jenkins backup stratejisi tek bir çözüme indirgenecek bir konu değil. Küçük bir ekip için günlük cron ile S3’e backup yeterli olabilirken, büyük bir CI/CD altyapısında JCasC + PVC snapshot + düzenli doğrulama kombinasyonu daha uygun olacaktır.

En önemli nokta şu: Backup almak sürecin yarısı, restore edebilmek diğer yarısı. “Felaket anında” ilk kez restore denemesi yapmak yerine, düzenli tatbikatlarla sürecinizi test edin. Sunucular yalnız gece yarısı bozulur ve o gece panik yaşamamak için bugünden hazırlıklı olun.

Workspace ve build log’larını dışarıda bırakarak, gerçekten önemli olan konfigürasyon dosyalarına odaklanın. Disk alanını verimli kullanın, doğrulama script’lerini cron’a ekleyin ve uyarılarınızı kurun. Jenkins’iniz ne kadar kritikse, backup stratejiniz o kadar sağlam olmalı.

Bir yanıt yazın

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