Systemd, Linux sistemlerinde servisleri ve diğer sistem kaynaklarını “unit” adı verilen tanımlarla yöneten init ve servis yöneticisidir. Bir uygulamayı .service unit’i olarak tanımladığınızda systemd uygulamayı başlatabilir, durumunu izleyebilir, başarısız olduğunda yeniden çalıştırabilir ve günlüklerini journal üzerinden sunabilir.
Systemd kullanan yaygın dağıtımlarda sistem yöneticisi PID 1 olarak çalışır. Servis yönetimi bunun yalnızca bir bölümüdür; unit türleri arasında şunlar bulunur:
- servisler (
.service) - soketler (
.socket) - zamanlayıcılar (
.timer) - bağlama noktaları (
.mount) - aygıtlar (
.device) - hedefler (
.target)
Bu rehberde /home/my-app altında bir Python uygulamasını my-app.service adıyla çalıştıracağız.
Unit dosyasındaki bölümler
Bir servis unit’i genellikle [Unit], [Service] ve [Install] bölümlerinden oluşur. Direktiflerin hangi bölüme ait olduğu önemlidir.
[Unit]
systemd.unit kılavuzunda tanımlanan genel unit ilişkileri burada yer alır:
- Description=:
systemctl statusgibi çıktılarda gösterilen kısa açıklamadır. - Documentation=: Unit ile ilgili belge URI’lerini listeler.
- Wants=: Başka unit’lerin aynı işlem kapsamında etkinleştirilmesi için daha zayıf bir gereksinim ilişkisi kurar.
- Requires=: Daha güçlü bir gereksinim ilişkisi kurar. Gereksinim başarısızlığının bu servisi nasıl etkileyeceğini tasarlarken dikkatli kullanılmalıdır.
- Before= / After=: Yalnızca başlatma ve durdurma sırasını belirler; adı geçen unit’i kendiliğinden başlatmaz.
- OnFailure=: Unit başarısız olduğunda etkinleştirilecek unit’leri belirtir.
Önemli ayrım şudur: bağımlılık ve sıralama birbirinden ayrıdır. After=postgresql.service, PostgreSQL’i işlem kapsamına eklemez; yalnızca iki unit de başlatılacaksa bu servisin PostgreSQL’den sonra başlamasını söyler. PostgreSQL’in de etkinleştirilmesi gerekiyorsa Wants= veya gerçekten zorunluysa Requires= ilişkisi ayrıca yazılır.
Benzer biçimde Before=test.service, mevcut unit’in test.service’den önce çalışmasını ister; test.service’i önce başlatmak anlamına gelmez.
[Service]
Servise özgü çalışma ayarları systemd.service kılavuzunda tanımlanır:
- User= / Group=: Sürecin hangi kullanıcı ve grup kimliğiyle çalışacağını belirler.
- WorkingDirectory=: Uygulamanın çalışma dizinidir.
- ExecStart=: Başlatılacak yürütülebilir dosyayı ve argümanlarını belirtir.
- Type=: Systemd’nin servisin başlamış sayılacağı anı nasıl belirleyeceğini tanımlar.
- Restart=: Süreç beklenmedik biçimde çıktığında uygulanacak yeniden başlatma politikasını belirler.
- TimeoutStopSec=: Durdurma sırasında zorlayıcı sonlandırmaya geçmeden önce beklenecek süreyi sınırlar.
User=, Group= ve ExecStart= genel [Unit] ayarları değildir; [Service] bölümüne yazılmalıdır.
[Install]
Bu bölüm, systemctl enable çalıştırıldığında unit’in hangi hedefe bağlanacağını belirtir. Uzun süre çalışan sistem servisleri için sıklıkla WantedBy=multi-user.target kullanılır.
Çalışan bir service örneği
Aşağıdaki unit, sanal ortam içindeki Python yorumlayıcısını doğrudan çalıştırır:
[Unit]
Description=My application service
Documentation=https://www.aliyilmaz.co/
Wants=network-online.target
After=network-online.target
[Service]
Type=exec
User=my-app
Group=my-app
WorkingDirectory=/home/my-app
ExecStart=/home/my-app/venv/bin/python -u /home/my-app/app.py
EnvironmentFile=/etc/my-app.env
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
[Install]
WantedBy=multi-user.target
Bu örnek için my-app kullanıcısı, dizinler, sanal ortam ve /etc/my-app.env dosyası önceden oluşturulmuş olmalıdır. Uygulamanın ağ tamamen hazır olmadan da başlayabilmesi durumunda network-online.target ilişkisine ihtiyacınız olmayabilir; gereksiz bağımlılıkları eklemeyin.
Type=exec, uzun çalışan basit servislerde systemd’nin kullanıcı veya yürütülebilir dosya gibi başlangıç hatalarını çağırana yansıtmasını sağlar. Kullanılan systemd sürümünde veya uygulamanın çalışma modelinde farklı bir Type= gerekebilir; özellikle fork eden daemon’lar ve hazır olma bildirimi yapan servisler için ilgili kılavuzu izleyin.
Systemd varsayılan durdurma davranışında ana sürece SIGTERM gönderip tanımlı süreyi bekleyebildiği için bu örnekte yapay bir ExecStop=/bin/kill ... satırına gerek yoktur. Ayrıca varsayılan cgroup davranışını KillMode=process ile daraltmak çocuk süreçleri geride bırakabileceğinden genel bir şablon olarak kullanılmamalıdır.
Unit dosyasını güvenli biçimde oluşturma
Sabit ve açık bir dosya adıyla düzenleyiciyi açın:
sudoedit /etc/systemd/system/my-app.service
Unit içeriğini kaydettikten sonra sözdizimini kontrol edebilirsiniz:
sudo systemd-analyze verify /etc/systemd/system/my-app.service
Ardından systemd yöneticisinin dosyaları yeniden okumasını sağlayın ve servisi hem açılışa ekleyip hem de hemen başlatın:
sudo systemctl daemon-reload
sudo systemctl enable --now my-app.service
enable tek başına servisi hemen başlatmaz; yalnızca bir sonraki uygun açılış/target işlemi için bağlantıları oluşturur. systemctl üzerindeki --now seçeneği enable işlemiyle birlikte başlatmayı da ister.
Durum ve günlükleri inceleme
Servisin güncel durumunu görüntüleyin:
systemctl status my-app.service
Son 100 journal kaydını alın:
journalctl -u my-app.service -n 100 --no-pager
Yeni kayıtları canlı izlemek için:
journalctl -u my-app.service -f
journalctl filtresindeki -u, seçilen unit ve systemd tarafından o unit ile ilişkilendirilen iletileri gösterir. Yetki yapılandırmasına göre sistem günlüğünü okumak için sudo gerekebilir.
Unit dosyasını daha sonra değiştirirseniz yeni tanımı yüklemek ve çalışan servise uygulamak için:
sudo systemctl daemon-reload
sudo systemctl restart my-app.service
Önce systemctl status, ardından journalctl -u çıktısını kontrol ederek yeni sürecin sağlıklı başladığını doğrulayın.
Wrapper script ne zaman gerekir?
Uygulamayı ExecStart= ile doğrudan çalıştırmak genellikle daha iyidir. Böylece systemd gerçek uygulama sürecini ana PID olarak takip eder; çıkış kodu ve sinyal davranışı araya gereksiz bir shell girmeden görünür.
Başlangıç öncesinde shell’e özgü hazırlık adımları gerçekten gerekiyorsa bir wrapper script kullanılabilir. Script’in son komutunda exec kullanmak, shell sürecini uygulamayla değiştirerek sinyallerin ve çıkış kodunun doğru sürece ulaşmasını kolaylaştırır:
#!/usr/bin/env bash
set -euo pipefail
# Yalnızca uygulamaya özgü zorunlu hazırlık adımları burada yapılır.
exec /home/my-app/venv/bin/python -u /home/my-app/app.py
Sanal ortamı source ile etkinleştirmek zorunlu değildir; sanal ortamın Python yürütülebilir dosyasını doğrudan çağırmak daha sade ve tekrarlanabilir bir unit üretir. Uygulama standart çıktıya ve standart hataya yazdığında systemd bunları varsayılan olarak journal’a aktarabilir.