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
.promdosyaları 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.
