Systemd ile Otomatik Yeniden Başlatma Yapılandırması

Prodüksiyonda bir servis çöktüğünde saat gece 3’tür ve telefon çalmaya başlar. Bunu yaşamış her sysadmin bilir: manuel müdahale gerektiren her kesinti, hem stres hem de downtime demektir. Systemd’nin otomatik yeniden başlatma mekanizması tam da bu sorunu çözmek için tasarlanmış, ve doğru yapılandırıldığında gerçekten hayat kurtarır. Bu yazıda systemd servislerini nasıl otomatik olarak yeniden başlatacağınızı, hangi senaryolarda hangi ayarların kullanılacağını ve prodüksiyonda karşılaşabileceğiniz edge case’leri ele alacağız.

Otomatik Yeniden Başlatmanın Temel Mantığı

Systemd, bir servisin çökmesini veya durmasını tespit ettiğinde ne yapacağını [Service] bölümündeki yönergelerle belirler. Bu mekanizma basit görünse de arkasında oldukça sofistike bir state machine var.

Bir servis şu durumlara düşebilir:

  • failed: Servis beklenmedik şekilde sonlandı (exit code != 0)
  • exited: Servis temiz şekilde çıktı (exit code = 0)
  • killed: Bir sinyal tarafından öldürüldü (SIGKILL, SIGTERM vs.)
  • watchdog: Watchdog timeout aşıldı

Restart= direktifi, bu durumların hangilerinde systemd’nin servisi yeniden başlatacağını kontrol eder.

Restart= Direktifi ve Değerleri

En temel yapılandırma şu şekilde görünür:

[Service]
Restart=on-failure
RestartSec=5s

Ama Restart= direktifi birden fazla değer alabilir, ve hangisini seçtiğiniz büyük fark yaratır:

  • no: Hiçbir durumda yeniden başlatma yapma (varsayılan)
  • always: Her durumda yeniden başlat (temiz çıkış dahil)
  • on-success: Sadece temiz çıkışta (exit code 0) yeniden başlat
  • on-failure: Başarısız çıkış, sinyal veya watchdog timeout durumunda yeniden başlat
  • on-abnormal: Sinyal veya watchdog durumlarında yeniden başlat, temiz çıkışta başlatma
  • on-watchdog: Sadece watchdog timeout durumunda yeniden başlat
  • on-abort: Temizlenmemiş sinyal (abort) durumunda yeniden başlat

Prodüksiyonda en sık kullanılan iki değer on-failure ve always‘dir. Web servisleri, API’ler ve daemon’lar için genellikle on-failure yeterlidir. Ancak bazen servisiniz işini bitirip temiz çıkış yapıyorsa ama siz onun sürekli çalışmasını istiyorsanız (örneğin bir cron benzeri yapı kuruyorsanız) always kullanmanız gerekebilir.

Gerçek Bir Servis Dosyası Oluşturmak

Teoriden pratiğe geçelim. Bir Node.js API servisi için örnek bir unit dosyası:

# /etc/systemd/system/myapi.service
[Unit]
Description=My Production API Service
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapi
ExecStart=/usr/bin/node /opt/myapi/server.js
Restart=on-failure
RestartSec=10s
StartLimitIntervalSec=60s
StartLimitBurst=3
Environment=NODE_ENV=production
Environment=PORT=3000
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Dosyayı oluşturduktan sonra:

sudo systemctl daemon-reload
sudo systemctl enable myapi.service
sudo systemctl start myapi.service
sudo systemctl status myapi.service

Burada dikkat edilmesi gereken nokta StartLimitIntervalSec ve StartLimitBurst kombinasyonudur. Bu örnek şunu söylüyor: “60 saniye içinde 3 kezden fazla yeniden başlatmaya çalışırsan vazgeç.” Bu olmadan sonsuz döngüye girebilir ve sistemi boğabilirsiniz.

StartLimitIntervalSec ve StartLimitBurst

Bu iki direktif birlikte çalışır ve “restart storm” olarak bilinen durumu engeller. Bir servis kodunda ciddi bir bug varsa ve her başlatmada hemen çöküyorsa, systemd sonsuz bir döngüye girip sistemi yavaşlatabilir.

[Unit]
Description=Critical Background Worker
After=network.target

[Service]
Type=forking
User=worker
ExecStart=/usr/local/bin/background-worker --daemon
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=120s
StartLimitBurst=5

Bu yapılandırma şunu sağlar: 120 saniye içinde 5 kezden fazla başlatma denemesi yapılırsa servis failed state’e düşer ve daha fazla deneme yapılmaz. Bu noktada sysadmin’in manuel müdahale etmesi gerekir.

Peki ya servis limit’e takılmışsa ve siz yeniden denemek istiyorsanız?

# Limit sayacını sıfırla ve servisi tekrar başlat
sudo systemctl reset-failed myapi.service
sudo systemctl start myapi.service

RestartSec: Bekleme Süresi Stratejisi

RestartSec değeri küçük bir detay gibi görünse de prodüksiyonda büyük önem taşır. Çok küçük bir değer seçerseniz, servis ve bağımlılıkları (veritabanı bağlantısı, ağ vs.) tam olarak hazırlanmadan yeniden başlatma denemesi yapılır ve yine başarısız olur.

Birkaç pratik senaryo:

  • Basit daemon’lar: RestartSec=5s genellikle yeterli
  • Veritabanı bağımlı servisler: RestartSec=15s ile bağlantı hazırlanması için zaman tanıyın
  • Karmaşık başlatma süreci olan servisler: RestartSec=30s güvenli bir seçim
[Service]
Restart=on-failure
RestartSec=15s

Zaman birimi olarak s (saniye), min (dakika), ms (milisaniye) kullanabilirsiniz.

ExecStartPre ve Bağımlılık Kontrolü

Bazen servisiniz başlamadan önce bazı koşulların sağlandığından emin olmak istersiniz. Örneğin veritabanının gerçekten hazır olup olmadığını kontrol etmek:

[Unit]
Description=Django Application Server
After=network.target postgresql.service redis.service
Requires=postgresql.service

[Service]
Type=simple
User=django
WorkingDirectory=/opt/django-app
ExecStartPre=/bin/bash -c 'until pg_isready -h localhost -p 5432; do sleep 2; done'
ExecStartPre=/opt/django-app/venv/bin/python manage.py migrate --noinput
ExecStart=/opt/django-app/venv/bin/gunicorn config.wsgi:application --bind 0.0.0.0:8000
Restart=on-failure
RestartSec=10s
StartLimitIntervalSec=180s
StartLimitBurst=4
Environment=DJANGO_SETTINGS_MODULE=config.settings.production
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Bu yapılandırmada ExecStartPre ile önce PostgreSQL’in gerçekten hazır olup olmadığını kontrol ediyoruz. After=postgresql.service yeterli değildir çünkü systemd servisin aktif olduğunu görür ama PostgreSQL’in bağlantı kabul etmeye hazır olduğunu garanti etmez.

Watchdog ile Gelişmiş Sağlık Kontrolü

Systemd’nin watchdog mekanizması, servislerinizin sadece “çalışıyor” değil, “gerçekten sağlıklı çalışıyor” olduğunu kontrol etmenizi sağlar. Bu özellikle birkaç saniyede yanıt vermeyi bırakmış ama process’i hala ayakta olan servisler için kritik önem taşır.

Watchdog kullanmak için servis kodunuzun sd_notify protokolünü desteklemesi gerekir. Python için örnek:

# Python servis kodu içinde watchdog ping
import systemd.daemon
import time

systemd.daemon.notify('READY=1')

while True:
    # İş mantığınız burada
    do_work()
    # Watchdog'a "hala iyiyim" sinyali gönder
    systemd.daemon.notify('WATCHDOG=1')
    time.sleep(30)

Unit dosyasında watchdog’u etkinleştirmek için:

[Unit]
Description=Python Worker with Watchdog
After=network.target

[Service]
Type=notify
User=worker
ExecStart=/opt/worker/venv/bin/python /opt/worker/main.py
WatchdogSec=90s
Restart=on-watchdog
RestartSec=5s
NotifyAccess=main

WatchdogSec=90s şunu söylüyor: “Servis 90 saniye içinde watchdog ping’i göndermezse, onu ölü kabul et ve yeniden başlat.” Bu, freeze olan servisleri otomatik olarak kurtarmanın en temiz yoludur.

Override Dosyaları ile Yapılandırma Yönetimi

Sistem paketleriyle birlikte gelen servisleri (nginx, postgresql, redis gibi) doğrudan düzenlemek iyi bir pratik değildir. Paket güncellemesi geldiğinde değişikliklerinizin üzerine yazılabilir. Bunun yerine drop-in override dosyaları kullanın:

# Override dizinini oluştur
sudo systemctl edit nginx.service

Bu komut otomatik olarak /etc/systemd/system/nginx.service.d/override.conf dosyasını açar. İçine yazacaklarınız orijinal unit dosyasının üstüne gelir:

[Service]
Restart=always
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=5

Değişikliği uygulamak için:

sudo systemctl daemon-reload
sudo systemctl restart nginx.service
# Yapılandırmayı doğrula
sudo systemctl cat nginx.service

systemctl cat komutu orijinal dosya ve tüm override’ları birleştirilmiş halde gösterir. Bu, gerçekte hangi yapılandırmanın aktif olduğunu görmek için çok kullanışlıdır.

Loglama ve Hata Ayıklama

Otomatik yeniden başlatmalar gerçekleştiğinde ne olduğunu takip etmek için journald ile entegrasyon hayati önem taşır.

Bir servisin yeniden başlatma geçmişini görmek için:

# Son 100 log satırı
sudo journalctl -u myapi.service -n 100

# Gerçek zamanlı log takibi
sudo journalctl -u myapi.service -f

# Belirli bir zaman aralığı
sudo journalctl -u myapi.service --since "2024-01-15 08:00:00" --until "2024-01-15 12:00:00"

# Sadece hata seviyesindeki mesajlar
sudo journalctl -u myapi.service -p err

Servisin kaç kez yeniden başladığını görmek için:

systemctl show myapi.service --property=NRestarts

Bu bilgi monitoring sistemlerinize beslenmesi gereken kritik bir metriktir. Eğer NRestarts değeri hızla artıyorsa, bir şeyler ciddi biçimde yanlış gidiyor demektir.

Prodüksiyon Senaryosu: Yüksek Kullanılabilirlik Gerektiren Bir API

Gerçek dünyadan bir senaryo düşünelim. Bir e-ticaret platformunun ödeme API’si var ve bu API’nin uptime’ı doğrudan para kaybıyla ilişkili. İşte bu senaryo için optimize edilmiş bir yapılandırma:

# /etc/systemd/system/payment-api.service
[Unit]
Description=Payment Processing API
Documentation=https://docs.internal/payment-api
After=network-online.target postgresql.service redis.service
Wants=network-online.target
Requires=postgresql.service redis.service
StartLimitIntervalSec=300s
StartLimitBurst=10

[Service]
Type=simple
User=payment-api
Group=payment-api
WorkingDirectory=/opt/payment-api
EnvironmentFile=/etc/payment-api/production.env
ExecStartPre=/opt/payment-api/scripts/pre-start-check.sh
ExecStart=/opt/payment-api/venv/bin/gunicorn 
    --workers 4 
    --bind unix:/run/payment-api/gunicorn.sock 
    --access-logfile /var/log/payment-api/access.log 
    --error-logfile /var/log/payment-api/error.log 
    payment.wsgi:application
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10s
TimeoutStartSec=60s
TimeoutStopSec=30s
RuntimeDirectory=payment-api
RuntimeDirectoryMode=0750
StandardOutput=journal
StandardError=journal
SyslogIdentifier=payment-api

[Install]
WantedBy=multi-user.target

Bu yapılandırmada birkaç önemli nokta var:

  • StartLimitBurst=10 ile 5 dakika içinde 10 deneme hakkı tanındı. Ödeme servisi için daha toleranslı bir limit belirlendi.
  • TimeoutStartSec=60s ile servisin başlaması için 60 saniye zaman tanındı (gunicorn worker’ların ayağa kalkması zaman alabilir).
  • TimeoutStopSec=30s ile servisin durması için 30 saniye verildi (devam eden ödeme işlemlerinin tamamlanması için).
  • RuntimeDirectory ile /run altında geçici dizin otomatik oluşturuldu.

Monitoring ile Entegrasyon

Otomatik yeniden başlatma harika bir güvenlik ağı olsa da körü körüne güvenmek tehlikelidir. Prometheus ve Alertmanager gibi araçlarla entegrasyon kurarak yeniden başlatmaları izleyin.

node_exporter’ın systemd collector’ı etkinleştirilmişse, Prometheus’ta şöyle bir alert rule yazabilirsiniz:

# alerts/systemd.yml
groups:
  - name: systemd
    rules:
      - alert: ServiceFrequentRestarts
        expr: rate(node_systemd_unit_start_total{name="payment-api.service"}[15m]) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.name }} servisi sık sık yeniden başlıyor"
          description: "Son 15 dakikada 0.1'den fazla yeniden başlatma/dk tespit edildi"

Aynı zamanda bir bash script ile basit bir monitoring da kurabilirsiniz:

#!/bin/bash
# /opt/scripts/check-service-restarts.sh

SERVICE="payment-api.service"
THRESHOLD=5
ALERT_EMAIL="[email protected]"

RESTARTS=$(systemctl show "$SERVICE" --property=NRestarts | cut -d= -f2)

if [ "$RESTARTS" -gt "$THRESHOLD" ]; then
    echo "UYARI: $SERVICE servisi $RESTARTS kez yeniden başlatıldı!" | 
    mail -s "[ALERT] Servis Yeniden Başlatma Uyarısı" "$ALERT_EMAIL"
    logger -t service-monitor "ALERT: $SERVICE has restarted $RESTARTS times"
fi

Bu scripti cron’a ekleyin:

# /etc/cron.d/service-monitor
*/15 * * * * root /opt/scripts/check-service-restarts.sh

Yaygın Hatalar ve Çözümleri

Yıllar içinde en sık yapılan hataları sıralayayım:

  • After= ve Requires= karıştırmak: After= sadece sıralama yapar, başarısız bağımlılığa rağmen servisi başlatmaya çalışır. Requires= ise bağımlılık başarısız olursa servisi de başlatmaz.
  • Type=simple kullanırken PID yönetimi: Servisiniz bir fork yapıyorsa mutlaka Type=forking kullanın, aksi halde systemd yanlış process’i izler.
  • Çok agresif RestartSec: 1-2 saniyelik değerler genellikle çok kısadır. Bağımlılıkların toparlanması için zaman tanıyın.
  • StartLimitIntervalSec ve StartLimitBurst atlayarak limitsiz döngü: Bu iki direktifi atlarsanız çöken bir servis sistemi kilitleyebilir.
  • Override yerine orijinal dosyayı düzenlemek: Paket güncellemelerinde değişiklikleriniz kaybolur.

Sonuç

Systemd’nin otomatik yeniden başlatma mekanizması, doğru yapılandırıldığında prodüksiyon sistemlerinizin dayanıklılığını ciddi ölçüde artırır. Ancak bu özelliği körü körüne uygulamak yerine her servisin ihtiyacına göre özelleştirmek gerekir. Bir ödeme API’si ile bir arka plan log işleyicisinin yeniden başlatma stratejisi aynı olmaz.

En önemli takeaway’ler şunlardır: StartLimitIntervalSec ve StartLimitBurst değerlerini her zaman belirleyin, RestartSec ile bağımlılıklarınızın toparlanması için yeterli süre tanıyın, kritik servisler için watchdog mekanizmasını kullanın ve tüm yeniden başlatmaları monitoring sisteminizle izleyin. Otomatik yeniden başlatma bir son savunma hattıdır, sorunları gizlememek için yeniden başlatma sıklığını mutlaka takip edin ve altta yatan nedenleri düzeltin.

Yorum yapın