Restic ile Prometheus ve Grafana Kullanarak Yedekleme İzleme Panosu Kurulumu

Yedekleme işleri çoğu zaman “kur ve unut” mantığıyla yapılır. Restic’i yapılandırırsın, cron job’a eklersin, geceleri rahat uyursun. Ta ki bir gün yedek almadığını fark edene kadar. İşte bu yazıda tam da o sorunu çözeceğiz: Restic yedeklemelerini Prometheus ile izleyip Grafana üzerinde güzel bir pano haline getireceğiz. Artık yedeklerin çalışıp çalışmadığını, ne kadar sürdüğünü ve kaç dosyanın yedeklendiğini tek bakışta görebileceksin.

Neden Yedekleme İzlemesi Önemli?

Bir müşterimin sunucusunda 3 ay boyunca yedek alınmadığını bulduk. Sistem çalışıyordu, cron job oradaydı, ama Restic reposuna erişim sorunu yüzünden her gece sessizce hata verip geçiyordu. Disk dolduğunda, “en azından yedeğimiz var” deyip baktık, yok. Bu tür durumlar için izleme şart.

İzleme panosu sana şunları verir:

  • Son yedek zamanı: Ne zaman son başarılı yedek alındı?
  • Yedek süresi: Her yedek işlemi ne kadar sürdü?
  • Yedeklenen dosya sayısı ve boyut: Veri miktarı beklenmedik şekilde düştü mü?
  • Hata durumu: Başarısız yedekler var mı?
  • Repo büyüme eğrisi: Depolama alanı ne hızla dolduruyor?

Mimari Genel Bakış

Kuracağımız sistem şu parçalardan oluşuyor:

  • Restic: Yedekleme motorumuz
  • restic-exporter veya özel script: Restic metriklerini Prometheus formatına çeviren araç
  • Prometheus: Metrikleri toplayan ve saklayan zaman serisi veritabanı
  • Grafana: Metrikleri görselleştiren pano aracı
  • Alertmanager: (İsteğe bağlı) Uyarı gönderimi için

Akış şu şekilde işliyor: Restic yedek aldıktan sonra bir script çalışıyor, bu script Restic’ten istatistikleri çekip bir .prom dosyasına yazıyor. Prometheus node_exporter üzerindeki textfile collector bu dosyayı okuyor ve metrikleri Prometheus’a taşıyor. Grafana da Prometheus’u veri kaynağı olarak kullanarak panoyu çiziyor.

Gereksinimler ve Kurulum Ortamı

Bu rehberde Ubuntu 22.04 kullanıyorum ama Debian veya RHEL tabanlı sistemlerde de aynı adımlar işe yarar. Şunların kurulu olduğunu varsayıyorum:

  • Restic kurulu ve en az bir repo yapılandırılmış
  • Docker ve Docker Compose kurulu (Prometheus ve Grafana için)
  • sudo yetkisine sahip bir kullanıcı

Eğer Restic kurulu değilse hızlıca kuralım:

sudo apt update && sudo apt install -y restic
# veya binary olarak
wget https://github.com/restic/restic/releases/download/v0.16.4/restic_0.16.4_linux_amd64.bz2
bunzip2 restic_0.16.4_linux_amd64.bz2
sudo mv restic_0.16.4_linux_amd64 /usr/local/bin/restic
sudo chmod +x /usr/local/bin/restic

Prometheus ve Grafana’yı Docker Compose ile Kurmak

Monitoring stack’ini Docker Compose ile ayağa kaldırmak en temiz yol. Önce dizin yapısını oluşturalım:

mkdir -p /opt/monitoring/{prometheus,grafana,alertmanager}
cd /opt/monitoring

Şimdi docker-compose.yml dosyasını oluşturalım:

cat > /opt/monitoring/docker-compose.yml << 'EOF'
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=90d'
      - '--web.enable-lifecycle'
    ports:
      - "9090:9090"
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=SecurePassword123
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    ports:
      - "3000:3000"
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
      - /var/lib/node_exporter/textfile_collector:/var/lib/node_exporter/textfile_collector:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.textfile.directory=/var/lib/node_exporter/textfile_collector'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    ports:
      - "9100:9100"
    networks:
      - monitoring

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge
EOF

Textfile collector dizinini oluşturalım. Bu dizin, Restic metrik scriptimizin çıktısını yazacağı yer:

sudo mkdir -p /var/lib/node_exporter/textfile_collector
sudo chmod 777 /var/lib/node_exporter/textfile_collector

Prometheus yapılandırma dosyasını oluşturalım:

cat > /opt/monitoring/prometheus/prometheus.yml << 'EOF'
global:
  scrape_interval: 60s
  evaluation_interval: 60s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']
    scrape_interval: 30s

  - job_name: 'restic-backup'
    static_configs:
      - targets: ['node-exporter:9100']
    metrics_path: /metrics
    scrape_interval: 300s
EOF

Docker Compose’u başlatalım:

cd /opt/monitoring
docker compose up -d

Restic Metrik Scripti

Asıl sihir burada başlıyor. Restic yedek tamamlandıktan sonra çalışacak bir script yazacağız. Bu script Restic istatistiklerini okuyup Prometheus textfile formatında yazacak.

sudo nano /usr/local/bin/restic-metrics.sh
#!/bin/bash
# Restic Prometheus Metrik Exporter
# Kullanim: ./restic-metrics.sh [repo_adi]

set -euo pipefail

REPO_NAME="${1:-default}"
METRICS_FILE="/var/lib/node_exporter/textfile_collector/restic_${REPO_NAME}.prom"
TEMP_FILE="${METRICS_FILE}.tmp"
TIMESTAMP=$(date +%s)

# Restic ortam degiskenleri - kendi repo bilgilerinle degistir
export RESTIC_REPOSITORY="${RESTIC_REPOSITORY:-/backup/restic-repo}"
export RESTIC_PASSWORD_FILE="${RESTIC_PASSWORD_FILE:-/etc/restic/password.txt}"

# Yedek basarili mi?
BACKUP_SUCCESS=0
BACKUP_DURATION=0
FILES_NEW=0
FILES_CHANGED=0
FILES_UNMODIFIED=0
DATA_ADDED=0
SNAPSHOT_COUNT=0

echo "Restic metrikleri toplanıyor: ${REPO_NAME}"

# Son snapshot bilgilerini al
SNAPSHOTS_JSON=$(restic snapshots --json 2>/dev/null || echo "[]")
SNAPSHOT_COUNT=$(echo "$SNAPSHOTS_JSON" | python3 -c "import json,sys; data=json.load(sys.stdin); print(len(data))" 2>/dev/null || echo "0")

# En son snapshot zamanini al
LAST_SNAPSHOT_TIME=$(echo "$SNAPSHOTS_JSON" | python3 -c "
import json, sys
from datetime import datetime
data = json.load(sys.stdin)
if data:
    last = sorted(data, key=lambda x: x['time'])[-1]
    dt = datetime.fromisoformat(last['time'].replace('Z', '+00:00'))
    print(int(dt.timestamp()))
else:
    print(0)
" 2>/dev/null || echo "0")

# Repo istatistiklerini al
STATS_JSON=$(restic stats --json 2>/dev/null || echo "{}")
TOTAL_SIZE=$(echo "$STATS_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('total_size', 0))" 2>/dev/null || echo "0")
TOTAL_FILE_COUNT=$(echo "$STATS_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('total_file_count', 0))" 2>/dev/null || echo "0")
TOTAL_BLOB_COUNT=$(echo "$STATS_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('total_blob_count', 0))" 2>/dev/null || echo "0")

# Metrikleri yaz
{
cat << METRICS
# HELP restic_backup_snapshot_count Toplam snapshot sayisi
# TYPE restic_backup_snapshot_count gauge
restic_backup_snapshot_count{repo="${REPO_NAME}"} ${SNAPSHOT_COUNT}

# HELP restic_backup_last_success_timestamp Son basarili yedek Unix timestamp
# TYPE restic_backup_last_success_timestamp gauge
restic_backup_last_success_timestamp{repo="${REPO_NAME}"} ${LAST_SNAPSHOT_TIME}

# HELP restic_backup_repo_total_size_bytes Repo toplam boyutu (byte)
# TYPE restic_backup_repo_total_size_bytes gauge
restic_backup_repo_total_size_bytes{repo="${REPO_NAME}"} ${TOTAL_SIZE}

# HELP restic_backup_repo_total_file_count Repo toplam dosya sayisi
# TYPE restic_backup_repo_total_file_count gauge
restic_backup_repo_total_file_count{repo="${REPO_NAME}"} ${TOTAL_FILE_COUNT}

# HELP restic_backup_repo_total_blob_count Repo toplam blob sayisi
# TYPE restic_backup_repo_total_blob_count gauge
restic_backup_repo_total_blob_count{repo="${REPO_NAME}"} ${TOTAL_BLOB_COUNT}

# HELP restic_backup_scrape_timestamp Son metrik toplama zamani
# TYPE restic_backup_scrape_timestamp gauge
restic_backup_scrape_timestamp{repo="${REPO_NAME}"} ${TIMESTAMP}
METRICS
} > "$TEMP_FILE"

mv "$TEMP_FILE" "$METRICS_FILE"
echo "Metrikler yazildi: ${METRICS_FILE}"

Scripte çalıştırma izni verelim:

sudo chmod +x /usr/local/bin/restic-metrics.sh

Restic Yedek Wrapper Scripti

Şimdi hem yedek alan hem de sonunda metrikleri güncelleyen bir wrapper script yazalım. Bu sayede yedek süresi, başarı durumu ve dosya sayısı gibi anlık bilgileri de kaydedebiliriz:

sudo nano /usr/local/bin/restic-backup-with-metrics.sh
#!/bin/bash
# Restic yedek + metrik entegrasyon scripti

set -uo pipefail

REPO_NAME="${1:-webserver}"
BACKUP_PATH="${2:-/var/www}"
METRICS_FILE="/var/lib/node_exporter/textfile_collector/restic_${REPO_NAME}.prom"
TEMP_FILE="${METRICS_FILE}.tmp"
LOG_FILE="/var/log/restic-${REPO_NAME}.log"

export RESTIC_REPOSITORY="${RESTIC_REPOSITORY}"
export RESTIC_PASSWORD_FILE="${RESTIC_PASSWORD_FILE:-/etc/restic/password.txt}"

START_TIME=$(date +%s)
BACKUP_SUCCESS=0
FILES_NEW=0
FILES_CHANGED=0
DATA_ADDED=0
ERROR_MSG=""

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

# Yedek komutunu calistir, JSON cikti al
BACKUP_OUTPUT=$(restic backup "$BACKUP_PATH" 
    --json 
    --exclude-caches 
    --tag "auto,${REPO_NAME}" 
    2>&1) && BACKUP_EXIT=0 || BACKUP_EXIT=$?

END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))

if [ $BACKUP_EXIT -eq 0 ]; then
    BACKUP_SUCCESS=1
    # JSON ciktidan degerleri parse et
    FILES_NEW=$(echo "$BACKUP_OUTPUT" | grep '"message_type":"summary"' | python3 -c "
import json, sys
for line in sys.stdin:
    try:
        d = json.loads(line.strip())
        if d.get('message_type') == 'summary':
            print(d.get('files_new', 0))
            break
    except: pass
" 2>/dev/null || echo "0")

    FILES_CHANGED=$(echo "$BACKUP_OUTPUT" | grep '"message_type":"summary"' | python3 -c "
import json, sys
for line in sys.stdin:
    try:
        d = json.loads(line.strip())
        if d.get('message_type') == 'summary':
            print(d.get('files_changed', 0))
            break
    except: pass
" 2>/dev/null || echo "0")

    DATA_ADDED=$(echo "$BACKUP_OUTPUT" | grep '"message_type":"summary"' | python3 -c "
import json, sys
for line in sys.stdin:
    try:
        d = json.loads(line.strip())
        if d.get('message_type') == 'summary':
            print(int(d.get('data_added', 0)))
            break
    except: pass
" 2>/dev/null || echo "0")

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Yedek basarili. Sure: ${DURATION}s" | tee -a "$LOG_FILE"
else
    ERROR_MSG=$(echo "$BACKUP_OUTPUT" | tail -5 | tr 'n' ' ' | tr '"' "'")
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] HATA: ${ERROR_MSG}" | tee -a "$LOG_FILE"
fi

TIMESTAMP=$(date +%s)

cat > "$TEMP_FILE" << METRICS
# HELP restic_last_backup_success Son yedek basarili mi (1=evet, 0=hayir)
# TYPE restic_last_backup_success gauge
restic_last_backup_success{repo="${REPO_NAME}"} ${BACKUP_SUCCESS}

# HELP restic_last_backup_duration_seconds Son yedek suresi
# TYPE restic_last_backup_duration_seconds gauge
restic_last_backup_duration_seconds{repo="${REPO_NAME}"} ${DURATION}

# HELP restic_last_backup_files_new Son yedekte yeni dosya sayisi
# TYPE restic_last_backup_files_new gauge
restic_last_backup_files_new{repo="${REPO_NAME}"} ${FILES_NEW}

# HELP restic_last_backup_files_changed Son yedekte degisen dosya sayisi
# TYPE restic_last_backup_files_changed gauge
restic_last_backup_files_changed{repo="${REPO_NAME}"} ${FILES_CHANGED}

# HELP restic_last_backup_data_added_bytes Son yedekte eklenen veri boyutu
# TYPE restic_last_backup_data_added_bytes gauge
restic_last_backup_data_added_bytes{repo="${REPO_NAME}"} ${DATA_ADDED}

# HELP restic_last_backup_timestamp Son yedek Unix timestamp
# TYPE restic_last_backup_timestamp gauge
restic_last_backup_timestamp{repo="${REPO_NAME}"} ${TIMESTAMP}
METRICS

mv "$TEMP_FILE" "$METRICS_FILE"

# Detayli repo metriklerini de guncelle
/usr/local/bin/restic-metrics.sh "$REPO_NAME"

exit $BACKUP_EXIT
sudo chmod +x /usr/local/bin/restic-backup-with-metrics.sh

Cron Job Yapılandırması

Yedekleri otomatikleştirmek için cron job ekleyelim:

sudo crontab -e
# Restic ortam degiskenleri
RESTIC_REPOSITORY=sftp:backup-server:/backups/myserver
RESTIC_PASSWORD_FILE=/etc/restic/password.txt

# Web sunucu yedegi - her gece 02:00'de
0 2 * * * /usr/local/bin/restic-backup-with-metrics.sh webserver /var/www >> /var/log/restic-cron.log 2>&1

# Veritabani yedegi - her gece 02:30'da
30 2 * * * /usr/local/bin/restic-backup-with-metrics.sh database /var/backups/db >> /var/log/restic-cron.log 2>&1

# Config dosyalari - her 6 saatte bir
0 */6 * * * /usr/local/bin/restic-backup-with-metrics.sh configs /etc >> /var/log/restic-cron.log 2>&1

# Eski snapshot temizleme - haftada bir Pazar 04:00
0 4 * * 0 restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune >> /var/log/restic-prune.log 2>&1

Grafana Pano Yapılandırması

Grafana’ya http://sunucu-ip:3000 adresinden girelim, admin/SecurePassword123 ile bağlanalım. Önce Prometheus veri kaynağını ekleyelim:

  • Configuration > Data Sources > Add data source
  • Prometheus seçin
  • URL: http://prometheus:9090
  • Save & Test diyerek bağlantıyı doğrulayın

Şimdi birkaç kritik Prometheus sorgusu yazalım. Bunları Grafana panel oluştururken kullanacaksınız:

# Son basarili yedekten bu yana gecen sure (saniye)
time() - restic_backup_last_success_timestamp{repo="webserver"}

# Son 24 saatte yedek basarili mi?
restic_last_backup_success{repo="webserver"} == 1

# Yedek suresi trendi (son 7 gun ortalaması)
avg_over_time(restic_last_backup_duration_seconds{repo="webserver"}[7d])

# Repo boyut artisi (son 24 saat)
increase(restic_backup_repo_total_size_bytes{repo="webserver"}[24h])

# Tum repolar icin son yedek durumu
restic_last_backup_success

# Kac saattir yedek alinmamis?
(time() - restic_backup_last_success_timestamp) / 3600

Alertmanager ile Uyarı Kurulumu

Yedek 25 saatten eski olduğunda mail atmak için Alertmanager yapılandırması:

cat > /opt/monitoring/prometheus/alerts.yml << 'EOF'
groups:
  - name: restic_backup_alerts
    interval: 5m
    rules:
      - alert: ResticBackupTooOld
        expr: (time() - restic_backup_last_success_timestamp) > 90000
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "Yedek çok eski: {{ $labels.repo }}"
          description: "{{ $labels.repo }} reposunun son yedeği {{ $value | humanizeDuration }} önce alındı."

      - alert: ResticBackupFailed
        expr: restic_last_backup_success == 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Yedek başarısız: {{ $labels.repo }}"
          description: "{{ $labels.repo }} için son yedek işlemi başarısız oldu."

      - alert: ResticRepoTooLarge
        expr: restic_backup_repo_total_size_bytes > 107374182400
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "Repo 100GB'ı aştı: {{ $labels.repo }}"
          description: "{{ $labels.repo }} reposu {{ $value | humanize1024 }} boyutuna ulaştı."
EOF

Prometheus config’e alert dosyasını ekleyelim:

# prometheus.yml icindeki global blokunun altina ekle
rule_files:
  - "/etc/prometheus/alerts.yml"

Değişiklikleri uygulamak için Prometheus’u reload edelim:

curl -X POST http://localhost:9090/-/reload

Gerçek Dünya Senaryosu: Birden Fazla Sunucu

Birden fazla sunucuyu izliyorsanız, her sunucuda node-exporter çalıştırıp Prometheus config’e eklemeniz yeterli:

# prometheus.yml scrape_configs bolumune ekle
  - job_name: 'backup-nodes'
    static_configs:
      - targets:
          - 'web-server-01:9100'
          - 'web-server-02:9100'
          - 'db-server-01:9100'
          - 'mail-server:9100'
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        regex: '([^:]+):.*'
        replacement: '$1'

Her sunucuda sadece şunu çalıştırmanız yeterli:

# Uzak sunuculara node-exporter kurulumu
sudo apt install prometheus-node-exporter
sudo systemctl enable --now prometheus-node-exporter
# Textfile collector icin systemd override ekle
sudo systemctl edit prometheus-node-exporter

Kurulumu Test Etmek

Scripti manuel çalıştırıp metriklerin üretildiğini doğrulayalım:

# Test yedek al
export RESTIC_REPOSITORY=/tmp/test-repo
export RESTIC_PASSWORD=testpass123
restic init
echo "test" > /tmp/test-file.txt
/usr/local/bin/restic-backup-with-metrics.sh test /tmp/test-file.txt

# Metrikleri kontrol et
cat /var/lib/node_exporter/textfile_collector/restic_test.prom

# Node exporter uzerinden goruntule
curl -s http://localhost:9100/metrics | grep restic

# Prometheus'ta goruntule
curl -s "http://localhost:9090/api/v1/query?query=restic_last_backup_success" | python3 -m json.tool

Sonuç

Bu kurulumla artık yedeklemelerinizi kör uçuştan çıkarıp görünür bir hale getirdiniz. Grafana panonuza sabah ilk bakışta tüm sunucularınızın yedek durumunu görebilirsiniz. Kritik bir yedek başarısız olduğunda Alertmanager size anında haber verecek.

Birkaç önemli noktayı hatırlatarak bitirelim:

  • Textfile collector dosyalarını düzenli rotate edin: Eski .prom dosyaları metrikleri karıştırabilir
  • Script’lerin hata yönetimini ciddiye alın: Sessiz başarısızlıklar izlemenin amacını ortadan kaldırır
  • Grafana panonuzu ekip arkadaşlarınızla paylaşın: Yedekleme durumu sadece sysadmin’in değil, tüm teknik ekibin görmesi gereken bir bilgi
  • Alertmanager kurallarını iş saatlerinize göre ayarlayın: Gece 3’te false positive alarm almak motivasyonu düşürür

Restic’in --json flag’i bu tür otomasyon senaryoları için son derece kullanışlı. Ürettiği yapılandırılmış çıktıyı doğru parse ettiğinizde, Prometheus üzerinde çok zengin bir metrik seti oluşturabilirsiniz. Zaman içinde bu verilerin birikmesiyle yedek sürelerindeki anormallikler, disk büyüme tahminleri ve kapasite planlaması için de kullanabilirsiniz.

Bir yanıt yazın

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