systemd ile Service Yönetmek: systemctl, Unit Override'ları, Timer'lar ve Cron
Bir service'in reboot'tan sonra kalkması için start yetmez, systemctl enable gerekir; Restart=always yalnızca çalışan service'in process'i ölünce devreye girer (gerçek reboot denemesiyle). Bookstore.Api'yi systemctl ile yönetmeyi, unit dosyalarının yerini, Nginx'e systemctl edit ile drop-in override eklemeyi, After/Wants/Requires farkını, systemd-analyze'ı, Persistent=true'lu gece yedeği timer'ını ve Ubuntu 24.04'te cron'un gerçek environment tuzaklarını gösteriyoruz.
Sürüm notu: Bu yazıdaki komutlar Ubuntu Server 24.04.5 LTS (systemd 255, cron 3.0, Nginx 1.24) çalıştıran bir test VM'inde; kod .NET SDK 10.0.402 ve Microsoft.Extensions.Hosting.Systemd 10.0.15 ile derlenip ASP.NET Core 10.0.15 runtime'ıyla çalıştırılmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) API'si için bir systemd unit dosyası yazdın, sunucuya koydun ve başlattın. Her şey yolunda görünüyor:
$ sudo systemctl daemon-reload
$ sudo systemctl start bookstore-api
$ systemctl is-active bookstore-api
active
$ curl -s http://127.0.0.1:5000/health; echo
{"status":"Healthy"}
Gece sunucu bir kernel güncellemesi için yeniden başlıyor. Sabah Bookstore.Api çalışıyor mu? Restart=always satırı da unit dosyasında var. Cevap bir sonraki bölümde, gerçek bir reboot'tan sonra.
Dördüncü yazıda nohup ile başlattığımız bir process'in reboot'tan sonra kalkmadığını, çökünce yeniden başlamadığını gördük. Bu yazıda Bookstore'u systemd'ye devrediyoruz: systemctl ile günlük yönetim, unit dosyalarının yerleri, paket unit'lerini değiştirmeden drop-in override, dependency'ler, gece yedeği için systemd timer ve cron. Unit dosyasının satır satır anlamı ve hardening ayarları Web Server'lar ve Hosting serisinin systemd yazısında; burada o unit'i bu serideki yollara uyarladık ve yönetimine odaklanıyoruz.
Unit Dosyası ve systemctl ile Günlük Yönetim
Uygulamanın systemd'ye "hazırım" diyebilmesi için Type=notify kullanıyoruz; bunun için Bookstore.Api'ye Microsoft.Extensions.Hosting.Systemd paketini ve tek bir satırı ekledik:
builder.Services.AddSystemd();
Unit dosyası repoda deploy/linux/systemd/bookstore-api.service'te duruyor:
[Unit]
Description=Bookstore API (ASP.NET Core on Kestrel)
After=network.target
[Service]
Type=notify
User=bookstore
Group=bookstore
WorkingDirectory=/opt/bookstore/current
ExecStart=/usr/bin/dotnet /opt/bookstore/current/Bookstore.Api.dll
Restart=always
RestartSec=10
TimeoutStopSec=30
SyslogIdentifier=bookstore-api
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://127.0.0.1:5000
Environment=DOTNET_NOLOGO=true
EnvironmentFile=/etc/bookstore/secrets.env
[Install]
WantedBy=multi-user.target
Üçüncü yazıdaki bookstore kullanıcısı, ikinci yazıdaki current symlink'i ve 640 izinli secrets.env burada birleşiyor. EnvironmentFile'ı systemd root olarak okuyup değişkenleri process'e verdiği için uygulamanın dosyayı kendisinin okuyabilmesi gerekmiyor. Durumu görmek için status:
$ sudo systemctl status bookstore-api --no-pager -n 4
● bookstore-api.service - Bookstore API (ASP.NET Core on Kestrel)
Loaded: loaded (/etc/systemd/system/bookstore-api.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-11-14 15:10:31 UTC; 10s ago
Main PID: 1140 (dotnet)
Tasks: 19 (limit: 1106)
Memory: 53.1M (peak: 53.3M)
CPU: 3.756s
CGroup: /system.slice/bookstore-api.service
└─1140 /usr/bin/dotnet /opt/bookstore/current/Bookstore.Api.dll
Nov 14 15:10:31 bookstore-dev bookstore-api[1140]: Microsoft.Hosting.Lifetime[14] Now listening on: http://127.0.0.1:5000
Nov 14 15:10:31 bookstore-dev bookstore-api[1140]: Microsoft.Hosting.Lifetime[0] Application started. Hosting environment: Production; Content root path: /opt/bookstore/releases/2026-11-14-4
Nov 14 15:10:31 bookstore-dev systemd[1]: Started bookstore-api.service - Bookstore API (ASP.NET Core on Kestrel).
Nov 14 15:10:31 bookstore-dev bookstore-api[1140]: Bookstore.Api[0] Bookstore.Api started, PID 1140
Okuma sırası: Loaded satırı unit dosyasının nereden geldiğini ve enable edilip edilmediğini, Active şu anki durumu ve ne zamandan beri olduğunu, CGroup service'e ait bütün process'leri, alttaki satırlar journal'ın son kayıtlarını gösterir. Log satırları sudo olmadan görünmüyor; journal'ı okumak için kullanıcının adm ya da systemd-journal grubunda olması gerekiyor (altıncı yazı). Günlük kullandığın komutlar:
start,stop,restart: service'i başlat, durdur (önceSIGTERM, süre dolarsaSIGKILL), durdurup başlat.reload: process'i durdurmadan configuration'ı yeniden okut; yalnızca unit'teExecReload=varsa çalışır.enable,disable,enable --now: açılışta başlasın ya da başlamasın;--nowaynı anda başlatır da.is-active,is-enabled,is-failed: script'lerde ve health check'lerde tek kelimelik cevap.systemctl --failed: çökmüş bütün unit'ler; sunucuya girince ilk bakılacak yerlerden biri.daemon-reload: unit dosyalarını diskten yeniden oku; dosyayı her değiştirdiğinde gerekli.
reload farkını iki service'te gördüm:
$ sudo systemctl reload bookstore-api
Failed to reload bookstore-api.service: Job type reload is not applicable for unit bookstore-api.service.
$ sudo systemctl reload nginx; echo "exit=$?"
exit=0
$ sudo -u deploy sudo -n /usr/bin/systemctl restart bookstore-api; echo "exit=$?"
exit=0
Nginx'in unit'inde ExecReload var: nginx -s reload çalıştırıyor, bu da master process'e SIGHUP gönderiyor; Bookstore'unkinde yok, çünkü dördüncü yazıda gördüğümüz gibi .NET SIGHUP'ta kapanıyor. .NET'te configuration değişikliği için restart kullanılır. Son satır üçüncü yazıdaki sudoers kuralının çalıştığını gösteriyor: deploy kullanıcısı yalnızca bu service'i yeniden başlatabiliyor.
enable ve Reboot Sonrası
Tahminin cevabı. Unit'i start ile başlatıp VM'i yeniden başlattım:
$ uptime
15:10:24 up 1 min, 1 user, load average: 5.27, 1.82, 0.65
$ systemctl status bookstore-api --no-pager
○ bookstore-api.service - Bookstore API (ASP.NET Core on Kestrel)
Loaded: loaded (/etc/systemd/system/bookstore-api.service; disabled; preset: enabled)
Active: inactive (dead)
$ systemctl is-enabled bookstore-api
disabled
$ sudo systemctl enable --now bookstore-api
Created symlink /etc/systemd/system/multi-user.target.wants/bookstore-api.service → /etc/systemd/system/bookstore-api.service.
Uygulama kalkmadı. start yalnızca "şimdi başlat" der; açılışta başlaması için enable gerekir. Restart=always ise yalnızca çalışan bir service'in process'i ölünce devreye girer, reboot'la ilgisi yok. enable'ın yaptığı şey çıktıda görünüyor: [Install] bölümündeki WantedBy=multi-user.target yüzünden multi-user.target.wants/ altına bir symlink oluşturdu. Açılışta systemd normal çok kullanıcılı sisteme (multi-user.target) ulaşırken bu klasördeki her şeyi de başlatıyor. Status satırındaki preset: enabled ise dağıtımın varsayılan önerisi; dosyayı kendin koyduğunda hiçbir şey otomatik enable edilmez. Bundan sonraki reboot'ta service kendiliğinden kalktı.
Unit Dosyalarının Yeri ve Drop-in Override
$ systemctl show -p FragmentPath -p DropInPaths nginx bookstore-api
FragmentPath=/usr/lib/systemd/system/nginx.service
DropInPaths=
FragmentPath=/etc/systemd/system/bookstore-api.service
DropInPaths=
Paketlerin getirdiği unit'ler /usr/lib/systemd/system/'da durur (eski kaynaklardaki /lib/systemd/system aynı yer, ikinci yazıda gördüğümüz gibi /lib artık bir symlink). Bu VM'de orada 401 dosya var ve paket güncellendiğinde hepsi üzerine yazılır. Yöneticinin unit'leri ve değişiklikleri /etc/systemd/system/'da durur ve öncelik ondadır. Bookstore'un unit'i bizim olduğu için doğrudan oraya koyduk.
Bir paketin unit'ini değiştirmek istediğinde dosyayı /usr/lib'de düzenleme, bir sonraki apt upgrade değişikliğini siler. Bunun yerine drop-in kullan: yalnızca değiştirmek istediğin satırları içeren küçük bir dosya. Nginx'in unit'inde çökünce yeniden başlatma yok; bunu önce bir deneyle gördüm, sonra drop-in ekledim:
$ sudo kill -9 $(systemctl show -p MainPID --value nginx)
$ systemctl is-active nginx
failed
$ sudo systemctl start nginx
$ sudo systemctl edit nginx
Successfully installed edited file '/etc/systemd/system/nginx.service.d/override.conf'.
$ systemctl cat nginx --no-pager | tail -5
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
Restart=on-failure
RestartSec=5s
systemctl edit bir editör açar; dosyada iki yorum satırı arasındaki kısım drop-in'in içeriği olur, ben oraya üç satır yazdım. Kaydedince systemd dosyayı /etc/systemd/system/nginx.service.d/override.conf'a yazdı ve daemon-reload'u kendisi yaptı. systemctl cat paketin dosyasını ve altına eklenen bütün drop-in'leri sırayla gösterir; bir service'in gerçekte hangi ayarlarla çalıştığını görmenin en kısa yolu. Aynı çöküşü tekrarladım:
$ sudo kill -9 $(systemctl show -p MainPID --value nginx)
$ systemctl is-active nginx; systemctl show -p NRestarts nginx
active
NRestarts=1
$ sudo journalctl -u nginx --no-pager -n 6 -o short | cut -c1-160
Nov 14 15:13:19 bookstore-dev systemd[1]: nginx.service: Killing process 2343 (nginx) with signal SIGKILL.
Nov 14 15:13:19 bookstore-dev systemd[1]: nginx.service: Killing process 2344 (nginx) with signal SIGKILL.
Nov 14 15:13:19 bookstore-dev systemd[1]: nginx.service: Failed with result 'signal'.
Nov 14 15:13:25 bookstore-dev systemd[1]: nginx.service: Scheduled restart job, restart counter is at 1.
Nov 14 15:13:25 bookstore-dev systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Nov 14 15:13:25 bookstore-dev systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.
Bu sefer systemd 5 saniye bekleyip Nginx'i yeniden başlattı. Drop-in'de bir ayarı yeniden yazmak öncekinin yerine geçer; ama ExecStart= gibi birden fazla değer alabilen satırlarda önce boş bir ExecStart= ile listeyi temizlemen gerekir, yoksa systemd ikinci bir komut eklediğini sanır ve service'i başlatmaz; denediğimde journal'a Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing. yazdı. Drop-in'i geri almak için sudo systemctl revert nginx.
Dependency'ler: After, Wants ve Requires
Unit'ler arasındaki ilişkiler iki ayrı soruya cevap verir: sıra ve bağımlılık. After= yalnızca sıradır: "ikisi birden başlatılacaksa beni ondan sonra başlat". Kendi başına diğer unit'i başlatmaz. Wants= zayıf bağımlılıktır: "beni başlatırken onu da başlat, başlamazsa da ben devam ederim". Requires= güçlü bağımlılıktır: o açıkça durdurulur ya da yeniden başlatılırsa ben de durdurulur ya da yeniden başlatılırım; After= ile birlikte yazıldıysa o başlayamadığında ben de başlatılmam. Genellikle Wants ya da Requires, After ile birlikte yazılır.
$ systemctl show -p After -p Wants -p Requires -p WantedBy bookstore-api
Requires=system.slice sysinit.target -.mount
Wants=
WantedBy=multi-user.target
After=sysinit.target -.mount systemd-journald.socket system.slice basic.target network.target
Bizim yazdığımız yalnızca After=network.target; gerisini systemd varsayılan olarak ekledi. network.target yalnızca ağ yönetiminin ayağa kalktığını söyler, IP adresinin hazır olduğunu değil. Açılışta uzak bir veritabanına bağlanıp bağlantı kuramayınca çöken bir uygulamanın sebebi çoğu zaman budur. Gerçekten ağın hazır olmasını bekleyen bir unit, yedek service'inde yaptığımız gibi Wants=network-online.target ve After=network-online.target'ı birlikte yazar. Uygulamanın kendisinin de bağlantı hatasında retry yapması en sağlam çözüm; bunu External Service Integration ve Resilience serisi anlatıyor.
Açılış sırasında neyin neyi beklediğini ve ne kadar sürdüğünü systemd-analyze gösterir. "Reboot sonrası service neden geç kalktı" sorusunun cevabı genelde buradadır:
$ systemd-analyze blame --no-pager | head -6
21.648s fwupd-refresh.service
20.276s dev-vda1.device
18.812s cloud-config.service
17.134s snapd.seeded.service
15.931s snapd.service
10.879s udisks2.service
$ systemd-analyze critical-chain bookstore-api.service --no-pager
bookstore-api.service +2.418s
└─basic.target @52.560s
└─sockets.target @52.557s
└─snapd.socket @52.488s +65ms
└─sysinit.target @52.326s
└─cloud-init.service @44.695s +7.566s
└─systemd-networkd-wait-online.service @42.922s +1.625s
...
blame her unit'in başlarken harcadığı süreyi, critical-chain ise bir unit'e giden yoldaki bekleme zincirini gösterir: @ unit'in hazır olduğu an, + başlama süresi. Benim test VM'imde süreler uzun; normal bir VM'de bunlar saniyeler mertebesinde olur, ama okuma şekli aynı.
systemd Timer ile Gece Yedeği
Bookstore'un veritabanının her gece yedeğini almak istiyoruz. Yedek script'inin kendisi Bash Scripting serisinin konusu; burada /usr/local/bin/bookstore-db-backup (repoda deploy/linux/bin/ altında) şimdilik tek satır yazan bir yer tutucu ve bizi yalnızca ne zaman ve nasıl çalıştığı ilgilendiriyor. Timer iki dosyadan oluşur: işi yapan bir service ve onu tetikleyen bir timer.
# /etc/systemd/system/bookstore-db-backup.service
[Unit]
Description=Nightly BookstoreDb backup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=bookstore
Group=bookstore
ExecStart=/usr/local/bin/bookstore-db-backup
# /etc/systemd/system/bookstore-db-backup.timer
[Unit]
Description=Run the BookstoreDb backup every night
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
Type=oneshot "işini yapıp çıkan bir komut" demek. Service'in [Install] bölümü yok, çünkü onu biz değil timer başlatıyor; enable edilen timer. OnCalendar her gün 02:30'u, RandomizedDelaySec bunun üstüne 0 ile 10 dakika arası rastgele bir gecikmeyi tarif ediyor; aynı saatte yedek alan çok sayıda sunucunun aynı anda veritabanına ya da depolamaya yüklenmesini önler. Persistent=true son tetiklenme zamanını diske yazar; sunucu 02:30'da kapalıysa timer açılışta kaçırılan çalıştırmayı hemen yapar. cron'da bunun karşılığı yok.
$ sudo systemctl enable --now bookstore-db-backup.timer
Created symlink /etc/systemd/system/timers.target.wants/bookstore-db-backup.timer → /etc/systemd/system/bookstore-db-backup.timer.
$ systemctl list-timers bookstore-db-backup.timer --no-pager
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sun 2026-11-15 02:37:36 UTC 11h - - bookstore-db-backup.timer bookstore-db-backup.service
1 timers listed.
Pass --all to see loaded but inactive timers, too.
$ systemd-analyze calendar "*-*-* 02:30:00" --iterations 2
Normalized form: *-*-* 02:30:00
Next elapse: Sun 2026-11-15 02:30:00 UTC
From now: 11h left
Iteration #2: Mon 2026-11-16 02:30:00 UTC
From now: 1 day 11h left
$ systemd-analyze calendar "Mon..Fri 08:00" "*:0/15"
Original form: Mon..Fri 08:00
Normalized form: Mon..Fri *-*-* 08:00:00
Next elapse: Mon 2026-11-16 08:00:00 UTC
From now: 1 day 16h left
Original form: *:0/15
Normalized form: *-*-* *:00/15:00
Next elapse: Sat 2026-11-14 15:15:00 UTC
From now: 1min 5s left
Sıradaki çalışma 02:37:36: 02:30 artı rastgele gecikme. systemd-analyze calendar bir ifadeyi yazmadan önce denemenin en kolay yolu; hafta içi sabah sekiz ve on beş dakikada bir gibi ifadeleri normalleştirip sonraki zamanı veriyor. Saatler sunucunun saat dilimine göre; bu sunucu UTC (saat dilimi farkı olmayan evrensel saat) kullanıyor. Gece yarısını beklemeden test etmek için service'i elle başlat:
$ sudo systemctl start bookstore-db-backup.service
$ sudo journalctl -u bookstore-db-backup.service --no-pager -o short | cut -c1-160
Nov 14 15:13:54 bookstore-dev systemd[1]: Starting bookstore-db-backup.service - Nightly BookstoreDb backup...
Nov 14 15:13:54 bookstore-dev bookstore-db-backup[2643]: backup started for BookstoreDb at 2026-11-14T15:13:54Z
Nov 14 15:13:54 bookstore-dev systemd[1]: bookstore-db-backup.service: Deactivated successfully.
Nov 14 15:13:54 bookstore-dev systemd[1]: Finished bookstore-db-backup.service - Nightly BookstoreDb backup.
Script'in çıktısı ayrıca bir log dosyasına yönlendirmeye gerek kalmadan journal'a düştü, çalışma zamanı ve sonucu da orada. Timer ile cron arasındaki farklar şöyle:
| Konu | systemd timer | cron |
|---|---|---|
| Log | journal'da, journalctl -u ile | Yönlendirmezsen e-posta ya da kaybolur |
| Kaçırılan çalışma | Persistent=true ile açılışta çalışır | Çalışmaz |
| Environment | Unit'te açıkça yazılır | Çok az değişken; shell profili okunmaz |
| Üst üste binme | Önceki çalışma sürüyorsa yenisi başlamaz | Aynı iş ikinci kez başlayabilir |
| Kurulum | İki dosya | Tek satır |
cron: crontab, İfadeler ve Environment Tuzağı
cron hâlâ her yerde ve tek satırlık işler için pratik. Her kullanıcının kendi crontab'ı var ve crontab -e ile düzenlenir; crontab -l listeler, crontab -r siler (soru sormadan, dikkat). Bir satır beş zaman alanı ve komuttan oluşur: dakika, saat, ayın günü, ay, haftanın günü. 30 2 * * * her gün 02:30, */15 * * * * on beş dakikada bir, 0 8 * * 1-5 hafta içi 08:00 demek.
cron'un klasik tuzağı, komutun senin shell'indeki ortamda değil, çok daha yalın bir ortamda çalışması. Bunu bir crontab'la denedim; satırları crontab -e ile ekledim:
$ crontab -l
* * * * * env > /tmp/cron-env.txt
* * * * * bookstore-report >> /tmp/cron-report.log 2>&1
* * * * * /home/ada/bookstore-tools/count-books.sh >> /tmp/cron-count.log 2>&1
* * * * * cd /home/ada/bookstore-tools && ./count-books.sh >> /tmp/cron-count.log 2>&1
$ cat /tmp/cron-env.txt
HOME=/home/ada
LOGNAME=ada
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
LANG=C.UTF-8
SHELL=/bin/sh
PWD=/home/ada
$ cat /tmp/cron-report.log
/bin/sh: 1: bookstore-report: not found
$ cat /tmp/cron-count.log
/home/ada/bookstore-tools/count-books.sh: line 2: data/books.csv: No such file or directory
3
Ortamda yalnızca altı değişken var ve komutu bash değil /bin/sh çalıştırıyor. Eski kaynaklar cron'un PATH'inin (komutların arandığı klasör listesi) yalnızca /usr/bin:/bin olduğunu yazar; Ubuntu 24.04'te durum farklı: /etc/pam.d/cron'daki pam_env satırı /etc/environment'ı okuyor ve PATH oradan geliyor, /usr/local/bin dahil. Bu yüzden /usr/local/bin'e koyduğumuz yedek script'i cron'dan sorunsuz çalıştı. Ama ~/.profile ve ~/.bashrc hiç okunmuyor: shell'de ~/.local/bin/bookstore-report'u bulan PATH eklemesi, .bashrc'deki export'lar ve dotnet tool'ların ~/.dotnet/tools'u cron'da yok, komut "not found" ile düştü. Çalışma klasörü de ev klasörü: data/books.csv'yi kendi klasörüne göre okuyan script mutlak path ile çağrılınca dosyayı bulamadı, önce cd yapan satır ise 3 kitabı saydı.
Kural: cron'da her şeyi absolute path ile yaz, script'in kendi içinde de ya mutlak path kullan ya da kendi klasörüne cd yap, ihtiyaç duyulan değişkenleri crontab'ın başında ya da script'in içinde açıkça tanımla ve çıktıyı mutlaka bir yere yönlendir. Sistem genelindeki işler için /etc/cron.d/'ye konan dosyalarda altıncı alan komutu çalıştıracak kullanıcıdır. Bu kuralların çoğunu kendiliğinden çözdüğü için yeni işlerde systemd timer'ı tercih ediyoruz.
Üretimde Dikkat
Unit dosyalarını, drop-in'leri ve timer'ları repoda tut ve sunucuya deploy'un bir parçası olarak kopyala. Bookstore için bu deploy/linux/systemd/ klasörü. Sunucuda elle systemctl edit ile yapılan bir değişiklik, sunucu yeniden kurulduğunda kaybolan ve kimsenin hatırlamadığı bir değişiklik olur. Elle değişiklik yaptıysan systemctl cat çıktısını repodaki dosyayla karşılaştır.
Restart=always bir çökme döngüsünü gizleyebilir. Uygulama açılışta her seferinde çöküyorsa systemd onu 10 saniyede bir yeniden başlatır ve dışarıdan bakan "service ayakta" sanabilir. systemd varsayılan olarak 10 saniyede 5'ten fazla başlatma denemesi olursa vazgeçip service'i failed yapar (StartLimitIntervalSec, StartLimitBurst); ama bizim unit'te RestartSec=10 olduğu için denemeler arası 10 saniye ve bu sınıra hiç ulaşılmıyor, döngü sonsuza kadar sürüyor. systemctl show -p NRestarts ile kaç kez yeniden başladığını izle ve bunu monitoring'e bağla. Çökme döngüsünü teşhis etmek on ikinci yazıdaki playbook'un ilk vakası.
Sık Düşülen Tuzaklar
Unit dosyasını değiştirip daemon-reload'u unutmak. RestartSec'i 10'dan 5'e indirip yalnızca restart ettim:
$ sudo sed -i "s/^RestartSec=10/RestartSec=5/" /etc/systemd/system/bookstore-api.service
$ sudo systemctl restart bookstore-api
Warning: The unit file, source configuration file or drop-ins of bookstore-api.service changed on disk. Run 'systemctl daemon-reload' to reload units.
$ systemctl show -p RestartUSec bookstore-api
RestartUSec=10s
$ sudo systemctl daemon-reload && systemctl show -p RestartUSec bookstore-api
RestartUSec=5s
systemd uyardı ama restart'ı yine yaptı, eski ayarla. Uyarı bir deploy script'inin çıktısında kaybolursa "değiştirdim ama işe yaramadı" diye saatler geçebilir. Unit dosyasını değiştiren her adımın arkasından daemon-reload gelsin; systemctl edit bunu kendisi yapar.
start'ı enable sanmak. Yukarıda gördüğümüz gibi, reboot'tan sonra service yok. Yeni bir service kurduktan sonra systemctl is-enabled ile kontrol et, en iyisi baştan enable --now kullan.
cron'da shell'deki ortamı varsaymak. Shell'de çalışan komutun cron'da çalışacağını varsayma; crontab satırını bir kez env -i HOME=$HOME LOGNAME=$USER PATH=/usr/bin:/bin sh -c '...' gibi yalın bir ortamda dene ya da bir dakikalık bir zamanlamayla çalıştırıp çıktısına bak.
Paket unit'ini /usr/lib/systemd/system'de düzenlemek. Bir sonraki paket güncellemesi değişikliği sessizce siler. Her zaman drop-in.
Küçük Bir Deneme
★☆☆ bookstore-api.service'i kendi VM'ine kur, önce yalnızca start ile başlat ve VM'i yeniden başlat. Sonra enable edip tekrar dene ve multi-user.target.wants altındaki symlink'e bak.
★★☆ systemctl edit bookstore-api ile bir drop-in oluştur ve Environment=Logging__LogLevel__Default=Warning ekle. systemctl cat ile iki dosyayı birlikte gör, service'i yeniden başlat ve journal'daki log satırlarının azaldığını kontrol et. Sonra systemctl revert ile geri al.
★★★ Yedek timer'ını kur, OnCalendar'ı iki dakika sonrasına ayarla ve VM'i o dakikadan önce kapat. Dakika geçtikten sonra VM'i aç ve Persistent=true sayesinde işin açılışta çalıştığını journalctl -u bookstore-db-backup ile doğrula. Aynı işi cron ile kurup karşılaştır.
Bookstore artık systemd'nin elinde: açılışta kalkıyor, çökünce yeniden başlıyor ve gece yedeği zamanında çalışıyor. Bütün bunların çıktısı journal'a gidiyor. Bir sonraki yazıda log okumaya başlıyoruz: journalctl'ın filtreleri, /var/log'daki klasik dosyalar ve log diskini doldurmamak için logrotate.