SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Mengapa systemd Tidak Me-restart Service Anda

Restart= hanya memantau proses utama. Pelajari mengapa proses anak yang mati dalam cgroup tidak terlihat, serta cara kerja Type=, batas restart, dan journal.

Jawaban singkat: kebijakan restart systemd memantau satu proses

Kebijakan restart systemd memantau satu proses untuk setiap unit: proses utama. Restart= membaca status keluar dari proses tersebut dan tidak membaca apa pun yang lain. Control group suatu unit dapat berisi dua puluh proses. Salah satunya dapat berhenti, tetapi unit tetap active (running) karena proses utama masih berjalan. Dari sudut pandang systemd, tidak ada yang gagal. Karena itu, tidak ada proses yang di-restart.

systemd tetap mengetahui proses lainnya. systemd menghentikan proses tersebut saat unit dihentikan, menghitung penggunaan memorinya terhadap batas unit, menerapkan kuota CPU unit kepadanya, dan menampilkannya di systemctl status. Namun, systemd tidak pernah membaca status keluar proses tersebut. Logika restart dan cgroup adalah dua hal yang berbeda. Sebagian besar panduan ini membahas kesenjangan di antara keduanya.

Apa yang ditampung cgroup dan apa yang dibaca logika restart

Cgroup (control group) adalah objek kernel yang memiliki sekumpulan proses. Setiap unit service memiliki satu cgroup yang namanya sama dengan unit tersebut. Proses tidak dapat keluar dari cgroup itu. Proses anak mewarisi cgroup dari proses induknya, dan proses tanpa hak istimewa tidak dapat memindahkan dirinya ke tempat lain. Karena itu, systemd dapat membersihkan daemon yang melakukan fork dua kali. Skrip init lama tidak dapat melakukannya secara andal.

Perhatikan kedua fakta berikut secara berdampingan:

systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Restart -p RestartUSec myapp.service

systemd-cgls mencantumkan setiap proses dalam unit. MainPID adalah satu-satunya angka yang dibaca oleh kebijakan restart. Jika kedua hal tersebut tidak sesuai dengan model mental Anda, ketidaksesuaian itulah masalahnya. MainPID=0 lebih buruk daripada PID yang salah: ini berarti systemd sama sekali tidak melacak apa pun, sehingga nilai Restart= tidak akan pernah memicu tindakan apa pun.

Ada satu pengecualian nyata terhadap aturan proses utama. Jika kernel out-of-memory killer menghentikan proses apa pun di dalam cgroup unit, systemd dapat melihatnya karena systemd memantau file memory.events milik cgroup tersebut. OOMPolicy= menentukan tindakan berikutnya. Nilai defaultnya adalah stop: seluruh unit dihentikan, hasilnya dicatat sebagai oom-kill, dan hal itu dihitung sebagai kegagalan, sehingga Restart=on-failure terpicu. Journal mencatatnya dengan jelas.

myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Failed with result 'oom-kill'.

Jadi, proses anak yang dihentikan karena kekurangan memori memang dapat menghentikan unit, sedangkan proses anak yang berhenti karena segmentation fault tidak. Jika Anda menetapkan batas memori pada unit, baca cara MemoryMax dan CPUQuota diterapkan pada cgroup unit sebelum menyesuaikan kebijakan restart, karena kedua fitur tersebut bertemu di sini dan tidak di tempat lain.

Cara Type= memilih proses utama

Type= dalam bagian [Service] tidak hanya mengatur urutan start-up. Parameter ini menentukan PID (process ID) yang menjadi MainPID. Dengan demikian, parameter ini juga menentukan apa yang dapat dilihat oleh Restart=.

  • Type=simple adalah nilai default. Proses yang di-fork oleh systemd dari ExecStart= menjadi proses utama. systemd langsung menandai unit sebagai started, sebelum mengetahui apakah exec benar-benar berhasil. Kesalahan ketik pada path binary membuat start job berhasil, lalu Main process exited, code=exited, status=203/EXEC sesaat kemudian.
  • Type=exec berperilaku seperti simple, tetapi start job menunggu hingga exec berhasil. Dengan demikian, kesalahan ketik di atas menghasilkan kegagalan start yang sebenarnya. Parameter ini memerlukan systemd 240 atau yang lebih baru, yang tersedia pada setiap distribution yang masih didukung. Pilih parameter ini daripada simple.
  • Type=forking mengharapkan proses dari ExecStart= melakukan fork untuk membuat daemon di latar belakang, lalu keluar. systemd menunggu proses induk keluar, kemudian mencari daemon yang sebenarnya. Berikan PIDFile=. Tanpa parameter tersebut, GuessMainPID= (aktif secara default) hanya berfungsi jika tepat satu proses tersisa dalam cgroup. Jika dua proses tersisa, MainPID tetap 0.
  • Type=notify berarti service memanggil sd_notify(3) dan mengirim READY=1 saat service sudah dapat menangani network traffic. Service juga dapat mengirim MAINPID= untuk menyerahkan proses lain kepada systemd agar dipantau. NotifyAccess= secara default bernilai main, sehingga notifikasi yang dikirim oleh proses anak diabaikan dan journal mencantumkan PID asal notifikasi tersebut.
  • Type=oneshot tidak memiliki proses utama yang bertahan. Unit menjadi inactive segera setelah ExecStart= selesai, kecuali Anda menetapkan RemainAfterExit=yes. Restart=always dan Restart=on-success ditolak di sini, dengan pesan Service has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing.. Nilai lain, termasuk on-failure, diterima.

Dua error Type=forking perlu diingat karena masing-masing dapat menghasilkan unit yang tampak rusak tanpa alasan yang terlihat:

myapp.service: Can't open PID file /run/myapp.pid (yet?) after start: No such file or directory
myapp.service: New main PID 4711 does not belong to service, and PID file is not owned by root. Refusing.

Error pertama berarti daemon menulis file PID di lokasi lain atau menulisnya setelah systemd melakukan pemeriksaan. Error kedua berarti file PID mencantumkan proses di luar cgroup unit. systemd menolak mengambil alih proses tersebut karena file PID yang dapat ditulis dapat digunakan untuk membuat systemd mengirim signal ke proses apa pun pada server.

Mengapa skrip wrapper menyembunyikan berhentinya proses turunannya

Berikut bentuk konfigurasi yang menimbulkan pertanyaan pada judul.

#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait

Unit tersebut adalah Type=simple, sehingga proses utamanya adalah shell. wait tanpa argumen hanya mengembalikan hasil setelah semua proses turunan berhenti. Hentikan worker, lalu shell tetap menunggu proses web. Akibatnya, shell tidak berhenti, MainPID tidak selesai, dan Restart= tidak pernah diperiksa. cgroup kini menampung satu proses lebih sedikit, systemctl status menampilkan pohon proses yang lebih pendek, dan unit tetap berstatus active (running). systemd tidak memantau perubahan pada pohon tersebut.

Versi kedua dari kesalahan yang sama lebih sulit terlihat:

ExecStart=/bin/sh -c 'export APP_ENV=production; /usr/local/bin/myapp'

Proses utamanya adalah shell, bukan myapp. Saat systemctl stop, systemd mengirim SIGTERM ke proses utama. Shell yang sedang menunggu proses turunan foreground tidak meneruskan sinyal tersebut. Proses penghentian kemudian berlangsung selama TimeoutStopSec, yaitu 90 detik secara default, lalu berakhir seperti ini:

myapp.service: State 'stop-sigterm' timed out. Killing.
myapp.service: Killing process 4711 (myapp) with signal SIGKILL.

Solusinya adalah exec. Tulis exec /usr/local/bin/myapp agar shell digantikan oleh program. Dengan demikian, MainPID adalah program tersebut dan sinyal dapat diterimanya. Lebih baik lagi, hapus shell dan gunakan Environment= atau EnvironmentFile= dalam unit. Perhatikan bahwa bug ini tidak terlihat ketika string -c hanya berisi satu perintah, karena bash dan dash mengoptimalkan kasus tersebut menjadi exec langsung. Tambahkan perintah kedua ke dalam string tersebut, lalu shell akan tetap berjalan di depan program Anda.

Reproduksi pada VPS pengujian dalam dua menit

Simpan wrapper di atas sebagai /usr/local/bin/two-children.sh, jadikan file tersebut executable dengan chmod +x, lalu ganti kedua path program dengan sleep 3600. Arahkan sebuah unit ke file tersebut menggunakan Type=simple dan Restart=on-failure, lalu jalankan systemctl daemon-reload dan start unit tersebut. Jalankan systemd-cgls --unit two-children.service dan catat tiga PID: shell dan kedua proses turunannya. Hentikan salah satu proses turunan dengan sudo kill <pid>. Periksa kembali unit tersebut. Pohon proses kini lebih pendek satu proses, statusnya tetap active (running), dan journal tidak menampilkan informasi baru. Sekarang jalankan sudo kill -9 <shell pid>. Unit gagal, proses turunan yang masih berjalan dibersihkan karena KillMode=control-group adalah nilai default, dan journal menampilkan Scheduled restart job, restart counter is at 1.

Kosakata lengkap Restart= dan kapan on-failure lebih tepat daripada always

Restart= menerima salah satu dari tujuh nilai. Perbedaan di antara nilai-nilai tersebut ditentukan oleh status keluar yang dianggap bersih. systemd menganggap kode keluar 0, setiap kode yang tercantum dalam SuccessExitStatus=, serta sinyal SIGHUP, SIGINT, SIGTERM, dan SIGPIPE sebagai keluar bersih. Selain itu, termasuk SIGKILL dan SIGSEGV, dianggap tidak bersih.

  • no adalah nilai default. Unit tidak pernah memulai ulang dirinya sendiri. Karena itu, unit tanpa baris Restart= berhenti saat crash pertama dan tetap berhenti.
  • on-success memulai ulang unit hanya setelah keluar secara bersih.
  • on-failure memulai ulang unit setelah kode keluar non-zero, sinyal tidak bersih, timeout saat start atau stop, atau watchdog kedaluwarsa.
  • on-abnormal memulai ulang unit setelah sinyal tidak bersih, timeout, atau watchdog kedaluwarsa, tetapi tidak pernah setelah kode keluar non-zero biasa.
  • on-abort memulai ulang unit hanya setelah sinyal tidak bersih, yang berarti crash.
  • on-watchdog memulai ulang unit hanya saat WatchdogSec= kedaluwarsa.
  • always memulai ulang unit setelah semua kondisi di atas, termasuk keluar bersih dengan status 0.

on-failure adalah nilai default yang tepat untuk daemon yang berjalan terus-menerus. Nilai ini memulihkan layanan setelah crash, tetapi membiarkan exit 0 yang disengaja. always cocok untuk program yang keluar secara bersih karena alasan di luar kendalinya, misalnya klien tunnel yang mengembalikan 0 saat endpoint jarak jauh terputus. Kekurangan always adalah dapat menyembunyikan bug. Service yang start, membaca file konfigurasi yang rusak, mencatat error, lalu keluar dengan kode 0 akan terus mengulang proses tersebut. Satu-satunya tandanya adalah penghitung restart yang terus meningkat.

SuccessExitStatus= mengubah batas antara keluar bersih dan tidak bersih. Borg keluar dengan kode 1 untuk peringatan dan kode 2 untuk error. Karena itu, unit backup tanpa SuccessExitStatus=1 ditandai gagal setiap kali melewati satu file yang tidak dapat dibaca. RestartPreventExitStatus= mencantumkan kode yang mencegah restart bahkan dengan always. Ini adalah cara yang tepat bagi program untuk menyatakan bahwa program tersebut tidak boleh dijalankan kembali. RestartForceExitStatus= melakukan kebalikannya. Pekerjaan backup sebaiknya ditempatkan dalam unit Type=oneshot yang dijalankan oleh timer, bukan dalam loop restart. Pasangan service dan timer yang menjalankan pekerjaan sesuai jadwal adalah pola yang dapat digunakan.

Ada satu peringatan saat melakukan pengujian. Menghentikan service dengan kill <pid> biasa mengirimkan SIGTERM, yang termasuk dalam daftar keluar bersih. Karena itu, Restart=on-failure dengan benar tidak melakukan apa pun, lalu Anda menyimpulkan bahwa konfigurasi rusak. Gunakan kill -9 <pid> atau systemctl kill -s SIGKILL myapp.service sebagai gantinya. Ingat juga bahwa tidak ada nilai Restart= yang dijalankan setelah systemctl stop, atau ketika unit dihentikan karena dependensi BindsTo= atau PartOf= berhenti tersedia. Pekerjaan stop bukanlah kegagalan.

RestartSec dan nilai default 100 milidetik

RestartSec= adalah jeda antara saat unit berhenti dan saat systemd menjalankannya kembali. Nilai defaultnya adalah 100 milidetik. Periksa nilai yang benar-benar dimuat oleh unit Anda:

systemctl show -p RestartUSec -p StartLimitIntervalUSec -p StartLimitBurst myapp.service

Unit yang tidak menetapkan nilai ini akan menampilkan RestartUSec=100ms. Nilai default tersebut sesuai untuk service yang mengalami crash sekali lalu kembali berjalan. Namun, nilai itu tidak sesuai untuk service yang sama sekali tidak dapat start, karena lima kali restart dapat terjadi dalam waktu kurang dari setengah detik. Kondisi inilah yang memicu batas laju yang dijelaskan berikutnya. Untuk service yang menunggu database, mount, atau rute jaringan, tetapkan RestartSec=5s atau nilai yang lebih besar.

Per Agustus 2026, systemd 254 dan versi yang lebih baru juga menyediakan RestartSteps= dan RestartMaxDelaySec=. Keduanya meningkatkan jeda dari RestartSec= hingga mencapai batas maksimum selama jumlah percobaan tersebut. Ubuntu 24.04 menyertakan systemd 255 dan mendukung keduanya. Debian 12 menyertakan systemd 252 dan tidak mendukungnya. Jeda yang meningkat adalah pilihan yang tepat ketika dependensi mungkin tidak tersedia dalam waktu lama.

Apa arti sebenarnya dari "start request repeated too quickly"

Inilah kondisi yang membuat pembaca mengira systemd menyerah secara acak. Sebenarnya, systemd menggunakan penghitung. Aturannya: jika sebuah unit dijalankan lebih dari StartLimitBurst= kali dalam StartLimitIntervalSec=, systemd menolak untuk menjalankannya lagi dan menetapkan statusnya sebagai failed. Nilai defaultnya adalah 5 kali dalam 10 detik.

Journal menampilkan urutannya:

myapp.service: Scheduled restart job, restart counter is at 5.
myapp.service: Start request repeated too quickly.
myapp.service: Failed with result 'start-limit-hit'.
Failed to start myapp.service - My application.

dan systemctl start menunjukkan perintah perbaikan yang sudah dituliskan:

Job for myapp.service failed because start of the service was attempted too often. See "systemctl status myapp.service" and "journalctl -xeu myapp.service" for details. To force a start use "systemctl reset-failed myapp.service" followed by "systemctl start myapp.service" again.

systemctl reset-failed myapp.service menghapus penghitung dan status failed. Tidak ada cara lain yang melakukan hal tersebut, sehingga systemctl start biasa akan tetap ditolak sampai Anda menjalankan perintah itu. Start manual juga dihitung dalam batas tersebut. Jadi, beberapa kali menjalankan systemctl restart dengan tidak sabar saat mengedit file konfigurasi dapat memicu kondisi ini tanpa adanya crash sama sekali.

Bagian yang sering menyesatkan: start-limit-hit tidak pernah menjelaskan alasan service gagal. Pesan itu hanya menyatakan bahwa service gagal berulang kali dalam waktu singkat. Alasan sebenarnya ada pada baris journal sebelumnya.

Kedua pengaturan tersebut harus ditempatkan di bagian [Unit]. Anda akan menemukan contoh yang menempatkannya di [Service], yang diterima oleh systemd versi lama. Di situlah kebingungan bermula. Tulis pengaturan tersebut di [Unit], lalu tanyakan kepada systemd nilai yang dimuatnya dengan systemctl show, karena hanya nilai yang dimuat yang berlaku.

[Unit]
Description=My application
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=exec
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10s

Dengan konfigurasi tersebut, unit akan mencoba 5 kali dalam jendela waktu lima menit sebelum systemd menyerah. StartLimitIntervalSec=0 menonaktifkan batas tersebut sepenuhnya. Anda harus memahami konsekuensinya: service yang tidak pernah dapat start akan mencoba ulang tanpa batas dan menulis pesan ke journal setiap kali mencoba. Nilai default untuk seluruh mesin berada di /etc/systemd/system.conf sebagai DefaultStartLimitIntervalSec= dan DefaultStartLimitBurst=.

Ada satu pengaturan terkait yang perlu diperhatikan. StartLimitAction= menentukan tindakan saat batas tercapai. Pengaturan ini menerima nilai seperti reboot, reboot-force, dan poweroff. Nilai defaultnya adalah none, yang membuat unit gagal dan membiarkan mesin tetap berjalan. Pada VPS remote, poweroff berarti mesin akan tetap mati sampai Anda membuka konsol milik provider.

Perbaikan satu: satu proses per unit

Ini adalah jawaban yang tepat dalam hampir semua kasus. Jika dua program harus berjalan, tulis dua unit. Dengan demikian, masing-masing unit memiliki proses utama yang jelas, status keluar yang jelas, dan kebijakan restart sendiri. Anda juga mendapatkan log, batas resource, dan penghitung restart yang terpisah. Ini diperlukan saat terjadi masalah pada pukul tiga pagi.

Nyatakan hubungan antarunit dalam file unit, bukan dalam shell script.

  • After= hanya mengatur urutan start-up. Opsi ini tidak menjelaskan apa pun tentang kegagalan.
  • Requires= menjalankan unit lain secara bersamaan dengan unit ini dan menghentikan unit ini jika unit lain dihentikan secara eksplisit.
  • BindsTo= adalah Requires= ditambah kasus yang penting: unit ini berhenti jika unit lain berhenti karena alasan apa pun, termasuk crash. Pasangkan dengan After=. Jika tidak, urutannya tidak ditentukan.
  • PartOf= meneruskan penghentian dan restart ke unit-unit di bawahnya, sehingga systemctl restart myapp.target menjangkau setiap unit yang menjadi PartOf= unit tersebut.
  • Upholds= (systemd 249 dan yang lebih baru, yaitu Ubuntu 22.04 dan yang lebih baru) memastikan unit yang disebutkan tetap berjalan. Jika unit tersebut berhenti, systemd akan menjalankannya lagi. Opsi ini tetap tunduk pada batas laju start yang sama seperti unit lainnya.

Contoh worker yang tidak boleh berjalan tanpa API server dan selalu dijaga tetap aktif oleh systemd selama API aktif:

# /etc/systemd/system/myapp-api.service
[Unit]
Description=myapp API server
Wants=network-online.target
After=network-online.target
Upholds=myapp-worker.service

[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp serve
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/myapp-worker.service
[Unit]
Description=myapp background worker
BindsTo=myapp-api.service
After=myapp-api.service
StartLimitIntervalSec=120
StartLimitBurst=5

[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp worker
Restart=on-failure
RestartSec=5s

Worker tidak memiliki bagian [Install] dan tidak pernah diaktifkan secara manual. Unit API memanggilnya melalui Upholds=, sehingga systemctl enable --now myapp-api.service adalah satu-satunya perintah yang perlu Anda jalankan. Muat ulang konfigurasi dan periksa hasil konfigurasi pasangan unit tersebut menurut systemd:

sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/myapp-worker.service
systemctl list-dependencies myapp-api.service

systemd-analyze verify tidak menampilkan apa pun jika file sudah benar. Setiap output menunjukkan masalah, biasanya berupa key systemd yang tidak dikenali pada bagian tempat Anda menuliskannya, atau dependensi pada unit yang tidak ada.

Perbaikan dua: Type=notify, sehingga systemd mengetahui lebih dari sekadar PID

Jika program mendukung protokol notifikasi systemd, gunakan protokol tersebut. Dengan Type=notify, service memberi tahu systemd saat sudah siap. Dengan demikian, pengurutan dependensi didasarkan pada kondisi nyata, bukan asumsi. Service juga dapat mengirim MAINPID= untuk mengarahkan systemd ke proses yang relevan, bukan ke proses peluncur.

WatchdogSec= adalah bagian yang sepadan dengan upayanya. Jika opsi ini diaktifkan, service harus mengirim WATCHDOG=1 melalui sd_notify(3) setidaknya sesering itu. Jika pesan tersebut berhenti, systemd menghentikan service dengan SIGABRT dan menandainya sebagai gagal. Dengan demikian, Restart=on-failure atau Restart=on-watchdog dapat menjalankannya kembali. Ini adalah satu-satunya cara bawaan untuk me-restart proses yang masih hidup tetapi macet. Kebijakan status keluar tidak akan pernah dapat mendeteksi kondisi tersebut.

[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/local/bin/myapp serve
WatchdogSec=30s
Restart=on-failure
RestartSec=5s

Peristiwa watchdog muncul dalam journal sebagai myapp.service: Watchdog timeout (limit 30s)!, lalu diikuti oleh penghentian proses. Jika unit justru tetap berada dalam activating (start) hingga TimeoutStartSec habis, READY=1 tidak pernah tiba. Penyebabnya adalah program tidak mendukung protokol tersebut, atau NotifyAccess=main menolak notifikasi yang berasal dari proses anak. journal melaporkan kedua PID tersebut.

Untuk software yang menyediakan endpoint pemeriksaan kesehatan HTTP tetapi tidak mendukung sd_notify, pilihan yang tepat adalah menggunakan unit timer kecil untuk menguji endpoint tersebut dan memanggil systemctl restart, atau membiarkan runtime container melakukan pengujian. Inilah fungsi healthcheck Compose dan perilaku restart-nya.

Perbaikan tiga: supervisor di dalam unit, hanya jika tidak ada pilihan lain

Beberapa software memang dirilis sebagai kumpulan proses di balik launcher yang tidak dapat Anda pisahkan. Dalam kasus ini, jalankan supervisor di dalam unit dan terima konsekuensinya: systemd memantau supervisor, supervisor memantau semua proses lainnya, dan kebijakan restart kini berada di dua file.

Bentuk yang umum adalah container runtime. Unit docker compose atau podman persis menggunakan pola ini, dengan kebijakan restart untuk setiap container dinyatakan dalam file Compose, sedangkan unit systemd hanya memastikan runtime tetap berjalan. Jika struktur Anda seperti itu, unit yang menjalankan stack Compose saat boot menunjukkan versi yang berfungsi, termasuk alasan Type=oneshot dengan RemainAfterExit=yes biasanya tepat dalam kasus tersebut.

cgroup tetap bekerja untuk Anda. Semua proses yang dijalankan supervisor tetap berada di dalam cgroup unit, sehingga MemoryMax=, CPUQuota=, dan pembersihan saat dihentikan tetap mencakup seluruh hierarki proses. Hanya keputusan restart yang didelegasikan.

Apa pun supervisor yang Anda pilih, jangan menetapkan Restart=always pada unit luar dan kebijakan restart agresif di dalamnya tanpa pertimbangan. Dua lapisan logika restart, masing-masing dengan backoff sendiri, dapat membuat service terus gagal dan dimulai ulang selama beberapa menit. Journal juga tidak akan menjelaskan penyebabnya.

ExitType=cgroup bukan berarti “restart saat proses apa pun berhenti”

ExitType= (systemd 250 dan yang lebih baru, sehingga Ubuntu 24.04 dan Debian 12 sudah memilikinya) adalah pengaturan yang ditemukan orang saat mencari solusi untuk masalah ini. Namun, pengaturan tersebut justru bekerja berlawanan dengan yang disiratkan namanya. Nilai default, ExitType=main, berarti service dianggap berhenti saat proses utama keluar. ExitType=cgroup berarti service dianggap tetap berjalan sampai proses terakhir dalam cgroup keluar.

Jadi, ExitType=cgroup membuat unit lebih tidak sensitif terhadap satu proses yang berhenti, bukan lebih sensitif. Pengaturan ini tepat untuk program yang melakukan fork terhadap worker sebenarnya lalu menghentikan proses induknya tanpa menulis file PID, sehingga Type=forking tidak dapat menemukan daemon tersebut. Pengaturan ini tidak tepat untuk kegagalan yang dijelaskan di sini.

Tidak ada nilai Restart= yang berarti “restart unit saat proses apa pun dalam cgroup berhenti”. Jika memerlukan perilaku tersebut, Anda harus menggunakan satu proses untuk setiap unit. Jika program tidak dapat dipisah dan Anda mengendalikan wrapper script, pilihan yang paling mendekati adalah wait -n, yang segera mengembalikan hasil saat child pertama keluar:

#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait -n
exit 1

Jika ada child yang berhenti, wrapper kini ikut berhenti dengan status non-zero, sehingga Restart=on-failure akan bekerja. Ini adalah kompromi, bukan perbaikan. Anda tetap mendapatkan satu penghitung restart untuk dua program, satu aliran log, dan tidak memiliki cara untuk me-restart bagian yang gagal secara terpisah.

Cara memeriksa apa yang sebenarnya terjadi

Empat perintah, dalam urutan ini.

systemctl status myapp.service
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Result -p ExecMainStatus myapp.service
journalctl -u myapp.service -b -o short-precise

systemctl status menampilkan status, PID utama, dan pohon cgroup dalam satu layar. Unit yang sehat menampilkan Active: active (running) dengan baris Main PID: yang mencantumkan proses yang diharapkan. Jika pohon di bagian bawah mencantumkan proses yang tidak Anda kenali, atau tidak mencantumkan proses yang Anda kenali, jawabannya sudah jelas.

systemd-cgls --unit menampilkan pohon yang sama tanpa pemotongan. Hal ini mulai penting ketika sebuah unit menjalankan lebih dari beberapa proses.

systemctl show menampilkan fakta dalam format yang dapat dibaca mesin. NRestarts= adalah penghitung restart. Ini adalah cara tercepat untuk membedakan service yang telah restart empat puluh kali dari service yang sudah berjalan sejak boot. Result= menyimpan alasan kegagalan terakhir: exit-code, signal, timeout, oom-kill, watchdog, atau start-limit-hit. ExecMainStatus= adalah status keluar mentah dari proses utama terakhir.

Journal menyimpan urutannya. Berikut tiga baris yang perlu dicari:

myapp.service: Main process exited, code=exited, status=1/FAILURE
myapp.service: Failed with result 'exit-code'.
myapp.service: Scheduled restart job, restart counter is at 1.

code=exited, status=N berarti program memilih untuk mengembalikan N. Jadi, masalahnya ada pada program atau konfigurasinya. code=killed, signal=SEGV berarti program mengalami crash. code=killed, signal=TERM biasanya berarti ada sesuatu yang meminta program tersebut berhenti. Ini bukan kegagalan dan tidak akan memicu Restart=on-failure. code=dumped berarti program meninggalkan file core. File tersebut dapat ditampilkan oleh coredumpctl list jika systemd-coredump terpasang.

Untuk lebih dari satu mesin, NRestarts adalah nilai yang perlu dikumpulkan secara terjadwal. Unit yang penghitungnya meningkat setiap hari mengalami kegagalan setiap hari, meskipun tidak ada yang menyadarinya. Setelah Anda mengelola dua atau tiga server, cara konsisten menjalankan satu perintah di setiap server mengubah dugaan tersebut menjadi laporan.

FAQ

Mengapa systemctl menyatakan service saya aktif padahal prosesnya sudah berhenti?

systemd melacak satu proses per unit service, yaitu proses utama, dan Restart= hanya membaca status keluarnya proses tersebut. Semua proses lain yang dijalankan unit berada dalam cgroup yang sama. systemd akan menghentikan proses-proses tersebut ketika unit berhenti, tetapi tidak pernah memantau apakah proses itu berhenti sendiri. Jalankan systemctl show -p MainPID myapp.service dan bandingkan jumlahnya dengan systemd-cgls --unit myapp.service. Jika proses yang berhenti muncul dalam pohon tersebut tetapi bukan MainPID, systemd bekerja tepat seperti yang dirancang. Solusinya adalah menjalankan satu proses per unit, dengan hubungan yang ditulis sebagai BindsTo= dan Upholds= antarsatuan tersebut.

Apa arti "start request repeated too quickly"?

Artinya, unit telah dijalankan lebih dari StartLimitBurst= kali dalam StartLimitIntervalSec=. Nilai default-nya adalah 5 kali dalam 10 detik, sehingga systemd berhenti mencoba. Ini adalah batas laju dan tidak menjelaskan penyebab service gagal, jadi baca baris journal di atas pesan tersebut. Hapus statusnya dengan systemctl reset-failed myapp.service, lalu perbaiki kegagalan yang mendasarinya. Jika service menunggu sesuatu yang memerlukan waktu lama untuk berjalan, naikkan RestartSec=, karena jeda default 100 milidetik menghabiskan kelima percobaan dalam waktu kurang dari satu detik.

Sebaiknya saya menggunakan Restart=always atau Restart=on-failure?

Gunakan on-failure untuk hampir semua kasus. Opsi ini me-restart service setelah crash, keluar dengan kode bukan nol, timeout, atau watchdog terpicu, tetapi membiarkan exit 0 yang disengaja. Gunakan always hanya jika program keluar dengan bersih karena alasan di luar kendalinya, misalnya client yang mengembalikan 0 ketika peer-nya terputus. Dampak always adalah service yang membaca konfigurasi rusak, mencatat satu error, lalu keluar dengan kode 0 akan terus berulang tanpa batas. Satu-satunya gejala yang terlihat adalah NRestarts terus meningkat di systemctl show.

Mengapa mematikan proses secara manual tidak memicu restart?

Karena systemd menganggap SIGHUP, SIGINT, SIGTERM, dan SIGPIPE sebagai penghentian yang bersih, sedangkan kill <pid> biasa mengirim SIGTERM. Dengan Restart=on-failure, penghentian yang bersih bukan merupakan kegagalan. Karena itu, tidak ada yang di-restart dan konfigurasi tampak rusak padahal sebenarnya tidak. Uji dengan kill -9 <pid> atau systemctl kill -s SIGKILL myapp.service, yang merupakan penghentian tidak bersih dan memang memicu kebijakan tersebut. Aturan yang sama menjelaskan mengapa systemctl stop tidak pernah bertentangan dengan kebijakan restart Anda.

Di mana StartLimitIntervalSec dan StartLimitBurst harus ditempatkan?

Di bagian [Unit]. Materi lama dan versi systemd lama menempatkannya di [Service], sehingga contoh yang disalin dapat berbeda satu sama lain. Jangan menebak bagian mana yang didukung oleh versi Anda. Setelah systemctl daemon-reload, tanyakan kepada systemd apa yang dimuatnya dengan systemctl show -p StartLimitBurst -p StartLimitIntervalUSec myapp.service, dan gunakan angka tersebut sebagai acuan. systemd-analyze verify /etc/systemd/system/myapp.service mendeteksi key yang sama sekali tidak dikenali systemd dan tidak mencetak apa pun jika file tersebut bersih.

#systemd#restart#service-unit#cgroups#reliability