Systemd Servis Başlamıyor: Adım Adım Hata Analizi

Bir servisin başlamaması kadar sinir bozucu şey az vardır. Özellikle gece 2’de production sunucusundan alarm geldiğinde ve systemctl status çıktısı sana yeşil yeşil “active (running)” yerine kırmızı “failed” gösterdiğinde, soğukkanlı kalmak gerçekten zor. Bu yazıda systemd servis hatalarını sistematik bir şekilde nasıl analiz edeceğini, hangi araçları kullanacağını ve en sık karşılaşılan senaryolarda nasıl çözüm üreteceğini anlatacağım.

Temel Durum Tespiti: İlk Bakış

Bir servisin neden başlamadığını anlamaya çalışırken paniklemeden önce birkaç temel komutu çalıştırman gerekiyor. İlk durağın her zaman systemctl status olması lazım:

systemctl status nginx.service

Bu komut sana servisin son durumunu, son birkaç log satırını ve en önemlisi exit code’unu gösterir. Çıktıda dikkat etmen gereken birkaç alan var:

  • Loaded: Servis dosyasının nerede olduğu ve enable/disable durumu
  • Active: Servisin şu anki durumu ve ne zamandan beri bu durumda olduğu
  • Main PID: Ana process’in PID’i (eğer çalışıyorsa)
  • Status: Servisin kendi raporladığı durum mesajı
  • Process: Önceki çalışma denemelerine ait bilgiler
  • CGroup: Servisin hangi cgroup altında çalıştığı

Çıktıda Result: exit-code veya Result: signal gibi bir şey görüyorsan, bu sana hatanın ne türden olduğunu söyler. exit-code ise servisin bir hata kodu döndürerek kapandığını, signal ise bir sinyal alarak (örneğin SIGKILL) öldürüldüğünü gösterir.

Journalctl ile Derin Dalış

systemctl status sana yüzeysel bilgi verir ama asıl detaylar journald loglarında. En temel kullanım:

journalctl -u nginx.service

Ama bu komut sana tüm geçmişi verir ve çok fazla çıktı üretebilir. Daha pratik yaklaşımlar:

# Son başlatma denemesinden itibaren logları göster
journalctl -u nginx.service --since "10 minutes ago"

# Son boot'tan itibaren servis logları
journalctl -u nginx.service -b

# Gerçek zamanlı takip
journalctl -u nginx.service -f

# Son 50 satır
journalctl -u nginx.service -n 50

# Hata ve üzeri seviye loglar
journalctl -u nginx.service -p err

Bir de çok işe yarayan ama az bilinen bir kullanım var. Sistemin son başarısız boot’undan logları çekmek istediğinde:

# Bir önceki boot'un logları
journalctl -u nginx.service -b -1

# Tüm boot listesi
journalctl --list-boots

Bu özellikle “dün geceden beri servis ayakta değil ama ben sabah fark ettim” durumlarında hayat kurtarıcı.

Exit Code Analizi: Sayılar Ne Anlatıyor

Systemd servis çıktısında gördüğün exit code’lar sana çok şey anlatır. journalctl çıktısında veya systemctl status çıktısında code=exited, status=X/DESCRIPTION formatında görürsün.

Sık karşılaşılanlar:

  • 1/FAILURE: Genel hata, en yaygın olan. Uygulama başlamaya çalışıp başarısız olmuş.
  • 2/INVALIDARGUMENT: Geçersiz argüman, genellikle yanlış konfigürasyon parametresi.
  • 203/EXEC: ExecStart satırında belirtilen binary bulunamıyor veya çalıştırılamıyor.
  • 217/USER: Belirtilen kullanıcı bulunamıyor.
  • 218/CAPABILITIES: Gerekli capabilities mevcut değil.
  • 226/NAMESPACE: Namespace konfigürasyonu başarısız.

Exit code 203 özellikle dikkat çekici çünkü çoğunlukla şunları gösteriyor: binary yolu yanlış, binary executable değil ya da gerekli kütüphaneler eksik.

Yaygın Senaryo 1: Binary Bulunamıyor veya Path Yanlış

En çok karşılaştığım durumlardan biri. Özellikle manuel olarak derlenen yazılımlar veya farklı Linux dağıtımları arasında taşınan servis dosyaları için geçerli.

Bir örnek senaryo: Bir Python uygulaması için servis dosyası hazırladın ama çalışmıyor. journalctl -u myapp.service -b çıktısında şunu görüyorsun:

myapp.service: Control process exited with error code.
myapp.service: Failed with result 'exit-code'.
Failed to start MyApp Application.

Servis dosyasına bakıyorsun:

cat /etc/systemd/system/myapp.service
[Unit]
Description=MyApp Application
After=network.target

[Service]
Type=simple
User=myapp
ExecStart=/usr/local/bin/python3 /opt/myapp/app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

Sorun nerede? Birkaç ihtimal var. Önce binary’nin varlığını kontrol et:

# Python binary kontrol
which python3
ls -la /usr/local/bin/python3

# Uygulamanın varlığı
ls -la /opt/myapp/app.py

# Kullanıcı varlığı
id myapp

# Dosya izinleri
sudo -u myapp /usr/local/bin/python3 /opt/myapp/app.py

Son komut çok önemli. Servisi çalıştıracak kullanıcı ile aynı context’te komutu elle çalıştırmak, hata mesajlarını doğrudan görmen sağlar. Systemd’nin göremediği çevre değişkeni sorunları, dosya izin sorunları burada net olarak ortaya çıkar.

Yaygın Senaryo 2: Konfigürasyon Dosyası Hataları

Nginx, Apache, PostgreSQL gibi servislerde konfigürasyon dosyası hatası en yaygın başlamama sebebi. Bu servislerin çoğu kendi konfigürasyon test mekanizmasına sahip:

# Nginx
nginx -t

# Apache
apache2ctl configtest
# veya
httpd -t

# PostgreSQL konfigürasyon dosyası
sudo -u postgres postgres --describe-config

# HAProxy
haproxy -c -f /etc/haproxy/haproxy.cfg

Systemd servisinin başlamadan önce konfigürasyonu test etmesini de sağlayabilirsin. ExecStartPre direktifi ile:

[Service]
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx -g 'daemon off;'

Bu sayede ExecStartPre başarısız olursa servis hiç başlatılmaya çalışılmaz ve hata mesajı çok daha net olur.

Yaygın Senaryo 3: Bağımlılık ve Sıralama Sorunları

Systemd’nin en güçlü yanlarından biri servis bağımlılıklarını yönetmesi ama aynı zamanda yanlış yapılandırılmış bağımlılıklar ciddi sorunlara yol açabiliyor. Bir uygulama servisi database hazır olmadan başlamaya çalışıyorsa büyük ihtimalle bağlantı hatası alacak.

# Servis bağımlılıklarını görüntüle
systemctl list-dependencies myapp.service

# Hangi servisler bu servise bağımlı
systemctl list-dependencies --reverse myapp.service

# Başlatma sırası grafiği (graphviz gerektirir)
systemd-analyze dot myapp.service | dot -Tsvg > deps.svg

Bağımlılık sorunlarını çözmenin tipik yolu servis dosyasını düzenlemek:

[Unit]
Description=MyApp Application
After=network.target postgresql.service redis.service
Requires=postgresql.service
Wants=redis.service
  • After: Bu servis belirtilen servislerden sonra başlatılır ama onların başarılı olmasına bağlı değildir.
  • Requires: Belirtilen servisler başarısız olursa bu servis de durur.
  • Wants: Belirtilen servisleri başlatmaya çalışır ama başarısız olsalar da bu servis çalışmaya devam eder.
  • BindsTo: Requires’dan daha sıkı, hedef servis herhangi bir nedenle durursa bu servis de durur.

Yaygın Senaryo 4: İzin ve SELinux/AppArmor Sorunları

Bu kategori hem en sinir bozucu hem de en çok gözden kaçan. Servis başlamıyor, loglar belirsiz mesajlar veriyor, her şey doğru görünüyor ama çalışmıyor.

# SELinux reddini kontrol et (CentOS/RHEL/Fedora)
ausearch -m avc -ts recent
ausearch -m avc -c nginx -ts recent

# SELinux audit logları
tail -f /var/log/audit/audit.log | grep denied

# AppArmor kontrolü (Ubuntu/Debian)
cat /var/log/syslog | grep apparmor
aa-status

SELinux için hızlı teşhis ve çözüm:

# Geçici olarak SELinux'u permissive moda al (test için)
setenforce 0

# Servisi başlat ve çalışıyor mu kontrol et
systemctl start myapp.service

# Eğer çalışıyorsa sorun SELinux'ta, policy oluştur
grep myapp /var/log/audit/audit.log | audit2allow -M myapp_policy
semodule -i myapp_policy.pp

# SELinux'u tekrar enforcing'e al
setenforce 1

Dosya context’i de çok önemli. Yanlış SELinux context’e sahip dosyalar servisi başlatamaz:

# Dosyanın SELinux context'ini görüntüle
ls -Z /opt/myapp/app.py

# Doğru context'i ayarla
chcon -R -t bin_t /opt/myapp/
restorecon -R -v /opt/myapp/

Yaygın Senaryo 5: Kaynak Limiti Sorunları

Systemd servislerine kaynak limitleri uygulayabilirsin ama bu limitler bazen beklenmedik şekilde servisi engelliyor. Özellikle miras alınan eski servis dosyalarında bu sorunla sık karşılaşıyorum.

# Servisin aktif limitlerini göster
systemctl show myapp.service | grep -i limit

# Genel sistem limitleri
ulimit -a

# Process'in gerçek limitlerini görmek için (çalışıyorsa)
cat /proc/$(pgrep myapp)/limits

Servis dosyasında yaygın limit direktifleri:

[Service]
# Dosya descriptor limiti
LimitNOFILE=65536

# Maksimum process sayısı
LimitNPROC=4096

# Core dump boyutu
LimitCORE=infinity

# Memory kilitleme
LimitMEMLOCK=infinity

OOM (Out of Memory) killer tarafından öldürülen servisleri tespit etmek için:

dmesg | grep -i "oom|killed process"
journalctl -k | grep -i oom

Systemd-Analyze ile Başlatma Analizi

Servis başlıyor ama çok yavaş başlıyor ve timeout yiyor mu? Ya da genel olarak boot sürecinde neler oluyor anlamak istiyorsun?

# Genel boot süresi özeti
systemd-analyze

# Her servisin boot süresine katkısı
systemd-analyze blame

# Kritik zincir (en uzun başlatma yolu)
systemd-analyze critical-chain

# Belirli bir servis için kritik zincir
systemd-analyze critical-chain myapp.service

Timeout sorunu yaşayan bir servis için TimeoutStartSec değerini artırabilirsin:

[Service]
TimeoutStartSec=120
TimeoutStopSec=30

Ama bu her zaman doğru çözüm değil. Önce neden bu kadar uzun sürdüğünü anlamak gerek. Uzun başlatma sürelerinin yaygın sebepleri:

  • DNS çözümleme sorunları (servis başlarken hostname çözümlemeye çalışıyor)
  • Network servisleri için interface hazır olmadan başlatma girişimi
  • Database bağlantısı için uzun retry döngüleri
  • Büyük dosya sistemlerinin mount edilmesi

Hata Ayıklama için Servis Dosyası Geçici Değişiklik

Bazen sorunu anlamak için servis dosyasına geçici debug seçenekleri eklemen gerekiyor. Bunu yaparken orijinal dosyayı bozmamak için override mekanizmasını kullan:

# Drop-in override dosyası oluştur
systemctl edit myapp.service

Bu komut /etc/systemd/system/myapp.service.d/override.conf dosyasını açar. İçine ekleyebileceklerin:

[Service]
# Tüm çevre değişkenlerini logla
Environment=DEBUG=1
Environment=LOG_LEVEL=debug

# Standart çıktıyı journald'a yönlendir
StandardOutput=journal
StandardError=journal

# Daha fazla systemd debug bilgisi
SystemCallLog=yes

Bazen ExecStart satırını tamamen override etmek de gerekebilir:

[Service]
# Önce mevcut ExecStart'ı temizle
ExecStart=
# Sonra yenisini ekle (debug bayraklarıyla)
ExecStart=/usr/local/bin/myapp --debug --log-file=/tmp/myapp-debug.log

Override dosyalarını görüntülemek ve kaldırmak:

# Override'ları listele
systemctl cat myapp.service

# Override'ı kaldır
systemctl revert myapp.service

Sorunlu Servisi İzole Ederek Test Etme

Gerçek production ortamında bir servisi debug ederken, systemd’nin koyduğu kısıtlamalar olmadan çalıştırmak isteyebilirsin. systemd-run bu iş için mükemmel:

# Servisi interaktif olarak çalıştır
sudo -u myapp /usr/local/bin/myapp --config /etc/myapp/config.yml

# Systemd ile aynı ortamda ama interaktif çalıştır
systemd-run --uid=myapp --gid=myapp --pty /usr/local/bin/myapp

# Belirli bir servisin ortam değişkenlerini kullanarak çalıştır
systemd-run --unit=myapp-test --same-dir --uid=myapp /usr/local/bin/myapp

Ayrıca servisin çevre değişkenlerine bakmak için:

# Çalışan bir servisin çevre değişkenlerini göster
sudo cat /proc/$(pgrep myapp)/environ | tr '' 'n'

# Servis için tanımlanmış çevre değişkenlerini göster
systemctl show myapp.service | grep Environment

Restart Politikaları ve Sonsuz Döngü Tuzağı

Servis başlamıyor ama Restart=always ayarlandıysa systemd sürekli yeniden başlatmaya çalışır. Bu hem log’u dolduran hem de sorunun görülmesini zorlaştıran bir durum. Systemd bunu engellemek için StartLimitIntervalSec ve StartLimitBurst kullanır:

# Servisin restart limitini kontrol et
systemctl show myapp.service | grep -E "StartLimit|Restart"

Servis sonsuz restart döngüsüne girip devre dışı bırakıldıysa:

# Restart sayacını sıfırla
systemctl reset-failed myapp.service

# Sonra tekrar başlatmayı dene
systemctl start myapp.service

Servis dosyasında makul bir restart politikası:

[Service]
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=3

Bu konfigürasyonla servis başarısız olduğunda 5 saniye bekleyip tekrar dener, ama 60 saniyelik pencerede 3 kereden fazla denenirse devre dışı bırakılır.

Gerçek Dünya Senaryosu: PostgreSQL Başlamıyor

Bir müşterinin sunucusunda PostgreSQL servisi başlamıyordu. systemctl status postgresql.service çıktısı:

postgresql.service: Main process exited, code=exited, status=1/FAILURE
Failed to start PostgreSQL Database Server.

İlk adım:

journalctl -u postgresql.service -b --no-pager | tail -30

Çıktıda şunu gördüm:

FATAL: could not open file "pg_hba.conf": Permission denied

Sorun net. Data dizininin ownership’i bozulmuştu:

ls -la /var/lib/postgresql/
# Çıktı: root root yerine olması gereken postgres postgres gösteriyordu

chown -R postgres:postgres /var/lib/postgresql/
chmod 700 /var/lib/postgresql/14/main

systemctl start postgresql.service
systemctl status postgresql.service

Servis ayağa kalktı. Bu sorunun sebebi bir önceki gece yapılan yedekleme scriptinin root olarak çalışması ve dosyaların ownership’ini değiştirmesiydi.

Sistemik Kontrol Listesi

Bir servisi hata ayıklarken kullandığım kişisel kontrol sıram:

  • systemctl status servis.service ile genel duruma bak
  • journalctl -u servis.service -b -p err ile son boot’un hata loglarına bak
  • Exit code’u not al ve ne anlama geldiğini araştır
  • Servis dosyasının sözdizimini kontrol et: systemd-analyze verify /etc/systemd/system/servis.service
  • Binary ve konfigürasyon dosyalarının varlığını ve izinlerini kontrol et
  • Servisi çalıştıracak kullanıcı olarak komutu elle çalıştır
  • SELinux/AppArmor redlerini kontrol et
  • Bağımlılıkların durumunu kontrol et
  • Kaynak limitlerini gözden geçir
  • Konfigürasyon dosyasını uygulamanın kendi test aracıyla doğrula
# Servis dosyası sözdizim kontrolü
systemd-analyze verify /etc/systemd/system/myapp.service

# Tüm başarısız servisleri listele
systemctl --failed

# Belirli bir servisin tüm propertylerini göster
systemctl show myapp.service

Sonuç

Systemd servis hatalarını debug etmek ilk başta karmaşık görünse de aslında oldukça sistematik bir süreç. Paniklemeden, adım adım ilerleyerek neredeyse her sorunu çözebilirsin. journalctl senin en iyi dostun, onu agresif şekilde kullan. Exit code’ları anlamaya çalış, körü körüne servis restart’a girme.

En önemli öğüt: Servisi her zaman çalışacağı kullanıcı context’iyle elle çalıştırmayı dene. Bu tek adım sorunların yüzde ellisini anında açığa çıkarır. Geri kalanı için journald logları ve bağımlılık analizi genellikle yeterli oluyor.

Son olarak, fix uyguladıktan sonra her zaman köklü nedeni anlamaya çalış. “Çalıştı, tamam” deyip geçme. Bir daha neden bu sorunun yaşandığını not al ve gerekirse monitoring veya alerting ekle. Gece 2’deki alarmı azaltmanın en iyi yolu o.

Benzer Konular

Bir yanıt yazın

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