Gece 2’de telefon çalıyor, kritik bir servis çökmüş ve sen saatlerdir haberdar değilsin. Bu senaryo her sistem yöneticisinin kabusu. Neyse ki systemd, tam da bu iş için OnFailure direktifini sunuyor. Bu yazıda OnFailure‘ı nasıl kullanacağını, gerçek dünya senaryolarında nasıl yapılandıracağını ve kurumsal ortamlarda nasıl işe yarar bir bildirim altyapısı kuracağını ele alacağız.
OnFailure Nedir ve Nasıl Çalışır?
Systemd servis dosyalarında [Unit] bölümü altında kullanılan OnFailure direktifi, bir servis başarısız olduğunda otomatik olarak başka bir servis ya da unit’i tetikler. Temel mantığı şu: izlediğin servis failed durumuna düştüğünde, OnFailure ile belirttiğin unit devreye girer.
Bir servisin “başarısız” sayılması için birkaç durum söz konusu olabilir:
- Non-zero exit code: Servis sıfır olmayan bir çıkış kodu döndürmüş
- Sinyal ile sonlanma: Servis bir sinyal tarafından öldürülmüş (örneğin OOM killer)
- Zaman aşımı:
TimeoutStartSecya daTimeoutStopSecdolmuş - Watchdog timeout: Watchdog ping’i zamanında gelmemiş
- Start limit:
StartLimitIntervalSeciçinde çok fazla yeniden başlatma denemesi
Bu durumların tümünde OnFailure tetiklenir. Restart=always veya Restart=on-failure ile birlikte kullanıldığında dikkatli olmak gerekiyor; servis sürekli yeniden başlatılıyorsa ve StartLimitBurst aşılmadıkça failed durumuna geçmiyor, dolayısıyla OnFailure tetiklenmiyor. Bu nüansı aklında tut.
Temel Yapılandırma
En basit haliyle bir OnFailure kurulumu şu şekilde görünür:
# /etc/systemd/system/web-app.service
[Unit]
Description=Web Uygulamamız
OnFailure=notify-failure@%n.service
[Service]
Type=simple
ExecStart=/usr/bin/node /opt/webapp/server.js
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
Buradaki %n bir systemd specifier’ı. Servisin tam adını temsil eder. Yani web-app.service başarısız olursa, [email protected] adlı servis tetiklenir. Bu template (şablon) servis mekanizması sayesinde tek bir bildirim servisiyle tüm servisleri izleyebilirsin.
Bildirim Servisi Oluşturma
Basit E-posta Bildirimi
Şimdi tetiklenecek olan bildirim servisini oluşturalım. Template servis dosyası @ işareti içerir:
# /etc/systemd/system/[email protected]
[Unit]
Description=Servis Başarısızlık Bildirimi - %i
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/notify-failure.sh %i
User=root
Görüldüğü gibi bu bir Type=oneshot servis. Tek bir komut çalıştırır, biter. %i ise template instance adını, yani başarısız olan servisin adını temsil eder.
Şimdi bildirim script’ini yazalım:
#!/bin/bash
# /usr/local/bin/notify-failure.sh
SERVICE_NAME="$1"
HOSTNAME=$(hostname -f)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
RECIPIENT="[email protected]"
SMTP_SERVER="mail.sirket.com"
# Servis durumu hakkında detaylı bilgi topla
SERVICE_STATUS=$(systemctl status "$SERVICE_NAME" 2>&1 | head -50)
JOURNAL_LOGS=$(journalctl -u "$SERVICE_NAME" -n 30 --no-pager 2>&1)
# E-posta içeriği oluştur
MAIL_BODY="KRITIK: $SERVICE_NAME servisi basarisiz oldu!
Sunucu: $HOSTNAME
Zaman: $TIMESTAMP
Servis: $SERVICE_NAME
=== SERVIS DURUMU ===
$SERVICE_STATUS
=== SON LOG SATIRLARI ===
$JOURNAL_LOGS
"
# E-posta gönder
echo "$MAIL_BODY" | mail -s "[ALARM] $SERVICE_NAME - $HOSTNAME sunucusunda basarisiz"
-r "[email protected]"
"$RECIPIENT"
echo "Bildirim gonderildi: $SERVICE_NAME @ $TIMESTAMP"
Script’i çalıştırılabilir hale getirmeyi unutma:
chmod +x /usr/local/bin/notify-failure.sh
systemctl daemon-reload
Gerçek Dünya Senaryoları
Senaryo 1: Kritik Veritabanı Servisi İzleme
Üretim ortamında PostgreSQL çalıştırıyorsun ve bu servis düşerse hem DBA ekibine hem de nöbetçi sistem yöneticisine aynı anda haber vermek istiyorsun:
# /etc/systemd/system/postgresql.service.d/failure-notify.conf
[Unit]
OnFailure=db-critical-alert@%n.service
Mevcut bir servis dosyasını override etmek için drop-in directory kullanmak en temiz yöntem. Böylece paket güncellemelerinde orijinal dosya bozulmaz.
# Drop-in dizinini oluştur
mkdir -p /etc/systemd/system/postgresql.service.d/
# Override dosyasını oluştur
cat > /etc/systemd/system/postgresql.service.d/failure-notify.conf << 'EOF'
[Unit]
OnFailure=db-critical-alert@%n.service
EOF
Veritabanı kritik alert servisi:
# /etc/systemd/system/[email protected]
[Unit]
Description=Veritabanı Kritik Alert - %i
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/db-critical-alert.sh %i
User=root
Environment=ALERT_LEVEL=CRITICAL
Environment=TEAM_WEBHOOK=https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ
[email protected]
[email protected]
#!/bin/bash
# /usr/local/bin/db-critical-alert.sh
SERVICE_NAME="$1"
HOSTNAME=$(hostname -f)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
UPTIME=$(uptime -p)
# Slack webhook bildirimi
send_slack() {
local message="$1"
curl -s -X POST "$TEAM_WEBHOOK"
-H 'Content-type: application/json'
--data "{
"text": "*:rotating_light: KRİTİK VERİTABANI ALARMI :rotating_light:*",
"attachments": [{
"color": "danger",
"fields": [
{"title": "Servis", "value": "$SERVICE_NAME", "short": true},
{"title": "Sunucu", "value": "$HOSTNAME", "short": true},
{"title": "Zaman", "value": "$TIMESTAMP", "short": true},
{"title": "Uptime", "value": "$UPTIME", "short": true},
{"title": "Detay", "value": "$message", "short": false}
]
}]
}" > /dev/null 2>&1
}
# Son hata logu al
LAST_ERROR=$(journalctl -u "$SERVICE_NAME" -n 10 --no-pager -p err 2>&1)
# Hem Slack'e hem e-postaya gönder
send_slack "Servis başarısız oldu. Son hatalar:n$LAST_ERROR"
echo "KRİTİK ALARM - $SERVICE_NAME başarısız oldu.nnSunucu: $HOSTNAMEnZaman: $TIMESTAMPnnSon Hatalar:n$LAST_ERROR" |
mail -s "[KRİTİK] PostgreSQL ÇÖKTÜ - $HOSTNAME"
"$DBA_EMAIL" "$ONCALL_EMAIL"
# Alert log dosyasına kaydet
echo "$TIMESTAMP - $SERVICE_NAME FAILED on $HOSTNAME" >> /var/log/service-failures.log
Senaryo 2: Nginx Web Sunucusu ile Otomatik Kurtarma Girişimi
Bazen sadece bildirim yetmez, önce kurtarma denemesi yapıp sonra bildirim göndermek istersin:
# /etc/systemd/system/nginx.service.d/auto-recover.conf
[Unit]
OnFailure=nginx-recover@%n.service
# /etc/systemd/system/[email protected]
[Unit]
Description=Nginx Otomatik Kurtarma - %i
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/nginx-recover.sh %i
User=root
#!/bin/bash
# /usr/local/bin/nginx-recover.sh
SERVICE_NAME="$1"
HOSTNAME=$(hostname -f)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
MAX_RECOVER_ATTEMPTS=3
RECOVER_LOG="/var/log/nginx-recovery.log"
ALERT_EMAIL="[email protected]"
log() {
echo "[$TIMESTAMP] $1" | tee -a "$RECOVER_LOG"
}
log "Nginx başarısız algılandı. Kurtarma başlatılıyor..."
# Nginx konfigürasyonunu test et
if nginx -t 2>/dev/null; then
log "Nginx konfigürasyonu geçerli, yeniden başlatmayı deniyorum..."
# Manuel yeniden başlatma denemesi
if systemctl start nginx; then
log "Nginx başarıyla yeniden başlatıldı!"
echo "Nginx otomatik kurtarma BAŞARILI - $TIMESTAMP" |
mail -s "[BİLGİ] Nginx Kurtarıldı - $HOSTNAME" "$ALERT_EMAIL"
exit 0
else
log "Manuel yeniden başlatma da başarısız oldu!"
fi
else
log "Nginx konfigürasyonunda HATA var!"
NGINX_TEST_OUTPUT=$(nginx -t 2>&1)
log "Konfigürasyon hatası: $NGINX_TEST_OUTPUT"
# Yedek konfigürasyona geç
if [ -f /etc/nginx/nginx.conf.backup ]; then
log "Yedek konfigürasyona geçiliyor..."
cp /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf
systemctl start nginx && log "Yedek konfigürasyonla başlatıldı!" || log "Yedek konfigürasyonla da başlatılamadı!"
fi
fi
# Her şey başarısız olduysa acil durum bildirimi gönder
JOURNAL_LOGS=$(journalctl -u "$SERVICE_NAME" -n 20 --no-pager 2>&1)
RECOVERY_LOG_CONTENT=$(tail -20 "$RECOVER_LOG")
{
echo "Nginx otomatik kurtarma BAŞARISIZ oldu."
echo "Sunucu: $HOSTNAME"
echo "Zaman: $TIMESTAMP"
echo ""
echo "=== KURTARMA LOGU ==="
echo "$RECOVERY_LOG_CONTENT"
echo ""
echo "=== SERVIS LOGU ==="
echo "$JOURNAL_LOGS"
} | mail -s "[ACİL] Nginx Kurtarılamadı - $HOSTNAME - MÜDAHALE GEREKİYOR" "$ALERT_EMAIL"
Çoklu Bildirim Kanalı Yapılandırması
Kurumsal ortamlarda tek bir bildirim kanalı yetmez. Systemd, OnFailure direktifinde birden fazla unit belirtmene izin verir:
# /etc/systemd/system/production-app.service
[Unit]
Description=Production Uygulama
OnFailure=notify-slack@%n.service notify-email@%n.service notify-pagerduty@%n.service
[Service]
Type=forking
PIDFile=/var/run/production-app.pid
ExecStart=/opt/app/bin/start.sh
ExecStop=/opt/app/bin/stop.sh
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=120
StartLimitBurst=4
[Install]
WantedBy=multi-user.target
Bu yapılandırmada servis düştüğünde Slack, e-posta ve PagerDuty bildirimleri eş zamanlı olarak gönderilir.
OnFailure ile Birlikte Kullanılan Önemli Direktifler
OnFailure‘ı etkili kullanmak için birkaç direktifi birlikte düşünmek gerekiyor:
- StartLimitIntervalSec: Bu süre içinde kaç kez başlatma denemesi yapılacağını belirler. Örneğin
StartLimitIntervalSec=60ile 60 saniye içindeki denemeleri sayar. - StartLimitBurst: Kaç başarısız deneme sonrası servisin
faileddurumuna geçeceğini belirler.StartLimitBurst=3ile 3 denemeden sonra servisfailedolur veOnFailuretetiklenir. - RestartSec: Yeniden başlatmalar arasındaki bekleme süresi.
- Restart: Hangi durumlarda yeniden başlatılacağını belirler.
on-failuredeğeri en yaygın kullanımı. - FailureAction: Servis başarısız olduğunda sistem genelinde bir aksiyon almak için.
poweroff,reboot,exitgibi değerler alabilir. Dikkatli kullan! - OnSuccess:
OnFailure‘ın kardeşi, servis başarıyla tamamlandığında tetiklenir.Type=oneshotservislerle birlikte kullanışlıdır.
Systemd-notify ile Watchdog Entegrasyonu
Daha gelişmiş senaryolarda watchdog mekanizmasıyla OnFailure‘ı birleştirip servisin kilitlendiği durumlarda da bildirim alabilirsin:
# /etc/systemd/system/critical-daemon.service
[Unit]
Description=Kritik Arka Plan Servisi
OnFailure=notify-failure@%n.service
[Service]
Type=notify
ExecStart=/opt/daemon/critical-daemon
WatchdogSec=30
Restart=on-watchdog
RestartSec=5
StartLimitIntervalSec=300
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Bu yapılandırmada daemon 30 saniyede bir systemd’ye sinyal göndermeli. Gönderemezse servis öldürülür, Restart=on-watchdog sayesinde yeniden başlatılmaya çalışılır, başarısız olursa OnFailure devreye girer.
Bildirim Geçmişi ve Raporlama
Uzun vadeli izleme için başarısızlık geçmişini kayıt altına almak önemli. Şu script ile hem anlık bildirim hem de günlük özet rapor üretebilirsin:
#!/bin/bash
# /usr/local/bin/service-failure-tracker.sh
# Hem anlık bildirim hem de geçmiş kaydı tutar
SERVICE_NAME="$1"
FAILURE_DB="/var/lib/service-monitor/failures.log"
REPORT_THRESHOLD=5 # Günde 5'ten fazla arıza varsa haftalık rapor yerine anlık bildirim
HOSTNAME=$(hostname -f)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
DATE_ONLY=$(date '+%Y-%m-%d')
# Veritabanı dizinini oluştur
mkdir -p /var/lib/service-monitor
# Arızayı kayıt et
echo "$TIMESTAMP|$SERVICE_NAME|$HOSTNAME|$(systemctl is-failed $SERVICE_NAME)" >> "$FAILURE_DB"
# Bugünkü arıza sayısını hesapla
TODAY_FAILURES=$(grep "^$DATE_ONLY" "$FAILURE_DB" | grep "|$SERVICE_NAME|" | wc -l)
# Servis logu ve durumunu topla
SERVICE_STATUS=$(systemctl status "$SERVICE_NAME" --no-pager 2>&1)
RECENT_LOGS=$(journalctl -u "$SERVICE_NAME" -n 15 --no-pager --since "1 hour ago" 2>&1)
# Bildirim e-postası oluştur
SUBJECT="[ALARM #$TODAY_FAILURES] $SERVICE_NAME - $HOSTNAME"
if [ "$TODAY_FAILURES" -gt "$REPORT_THRESHOLD" ]; then
SUBJECT="[TEKRARLAYAN ARIZA #$TODAY_FAILURES] $SERVICE_NAME - DIKKAT GEREKIYOR"
fi
{
echo "Servis Adı: $SERVICE_NAME"
echo "Sunucu: $HOSTNAME"
echo "Zaman: $TIMESTAMP"
echo "Bugünkü Toplam Arıza: $TODAY_FAILURES"
echo ""
echo "=== SERVIS DURUMU ==="
echo "$SERVICE_STATUS"
echo ""
echo "=== SON 1 SAATIN LOGLARI ==="
echo "$RECENT_LOGS"
echo ""
echo "=== HAFTALIK ARİZA ÖZETİ ==="
echo "Son 7 gün içinde bu servis için kayıtlı arızalar:"
grep "|$SERVICE_NAME|" "$FAILURE_DB" | tail -20
} | mail -s "$SUBJECT" "[email protected]"
Sık Yapılan Hatalar ve Çözümleri
OnFailure kullanırken karşılaşılan en yaygın sorunlara dikkat etmek gerekiyor:
- Bildirim döngüsü: Bildirim servisi de başarısız olursa ve ona da
OnFailureeklediysen sonsuz döngüye girebilirsin. Bildirim servislerineOnFailureekleme. - Restart ve StartLimitBurst karışıklığı:
Restart=on-failureileStartLimitBurst=3beraber kullanıldığında,OnFailuresadece limit aşıldıktan sonra tetiklenir. Bu beklenen davranış ama bazen şaşırtıcı olabilir. - Ağ bağımlılığı: Bildirim servisi e-posta ya da webhook gönderiyorsa
After=network-online.targeteklemen gerekebilir. Aksi halde ağ hazır olmadan çalışıp başarısız olabilir. - Template servis yanlış adlandırma:
[email protected]dosya adındaki@işaretini unutursan template çalışmaz. - İzin sorunları: Bildirim script’i root olmayan kullanıcıyla çalışıyorsa
journalctlokuma yetkisi olmayabilir.systemd-journalgrubuna ekle ya daUser=rootkullan.
Test Etme
Kurulumu test etmek için kasıtlı olarak başarısız bir servis oluşturabilirsin:
# Test servisi oluştur
cat > /etc/systemd/system/test-failure.service << 'EOF'
[Unit]
Description=Test Başarısızlık Servisi
OnFailure=notify-failure@%n.service
[Service]
Type=oneshot
ExecStart=/bin/false
EOF
systemctl daemon-reload
# Servisi başlat, başarısız olacak ve OnFailure tetiklenecek
systemctl start test-failure.service
# Durumu kontrol et
systemctl status test-failure.service
journalctl -u [email protected] --no-pager
/bin/false her zaman 1 (başarısız) çıkış kodu döner, dolayısıyla test-failure.service hemen failed durumuna geçer ve [email protected]‘i tetikler.
Sonuç
OnFailure direktifi, systemd’nin en pratik ve değer katan özelliklerinden biri. Doğru kurulduğunda gece 2’deki o kabus telefon çalması yerini proaktif bir bildirim sistemine bırakır. Burada anlattıklarımı kısaca özetlemek gerekirse:
OnFailure ile template servis yapısını (@ notasyonu) birleştirerek tek bir bildirim mekanizmasıyla tüm kritik servislerini izleyebilirsin. Drop-in directory kullanarak mevcut servisleri bozmadan konfigürasyon eklemek en güvenli yaklaşım. StartLimitIntervalSec ve StartLimitBurst değerlerini dikkatli ayarlamak, gereksiz alarm yağmurunun önüne geçer.
Bildirim servislerini basit tut, sadece birini değil birden fazla kanalı (e-posta, Slack, PagerDuty) hedefle ve her zaman test et. En iyi alarm sistemi, gerçekten alarm üretmeden önce test edilmiş olandır. Arıza geçmişini loglamak ise kronik sorunları tespit edip kök sebep analizinde zaman kazandırır.
Şimdi systemctl daemon-reload yap ve ilk OnFailure bildirimini kur. Bir sonraki sunucu arızasında saatler sonra değil, dakikalar içinde haberdar olacaksın.