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

Cara Pilih Type= systemd yang Betul untuk Servis

Servis systemd anda tunjuk status aktif tetapi daemon telah mati? Ketahui cara memilih Type= yang tepat seperti simple, forking, atau notify untuk mengesan PID utama sebenar.

Mengapa systemd melaporkan unit sebagai aktif sedangkan proses telah mati

Unit servis systemd kekal active selagi proses yang ditetapkan oleh systemd sebagai proses utama masih hidup, dan Type= dalam bahagian [Service] menentukan proses yang mana satu. Jika nilai yang salah dipilih, systemd akhirnya memantau pembalut shell (shell wrapper) atau proses induk yang berumur pendek, sementara daemon yang anda perlukan mati di dalam unit yang sama. Unit tersebut melaporkan perkara yang benar tentang proses yang diarahkan untuk dipantau.

Menukar polisi mulakan semula tidak akan membantu dalam situasi ini. Restart= bertindak apabila proses utama keluar, jadi Restart=always tidak akan dicetuskan selagi PID (pengecam proses) utama masih dimiliki oleh sesuatu yang sedang berjalan. Betulkan Type= terlebih dahulu. Tindakan systemd selepas proses utama benar-benar keluar adalah keputusan berasingan, yang dibincangkan dalam panduan untuk Restart= dan RestartSec=.

Apakah yang ditentukan oleh Type=

Setiap nilai Type= menjawab dua soalan serentak. Bilakah systemd menganggap unit ini telah bermula, dan proses manakah yang merupakan proses utama.

Jawapan pertama mengawal urutan. Unit yang menamakan unit anda dalam After= akan menunggu sehingga systemd mengisytiharkan unit anda telah bermula. Type= yang melaporkan "started" terlalu awal menyebabkan unit yang bergantung berjalan sebelum servis anda bersedia untuk melayaninya.

Jawapan kedua mengawal penyeliaan. systemd meletakkan setiap proses yang dijana oleh unit ke dalam cgroup (control group), iaitu ciri kernel yang mengumpulkan proses supaya ia boleh dihadkan dan ditamatkan secara bersama. cgroup ialah cara systemctl stop melakukan pembersihan: KillMode= secara lalai ditetapkan kepada control-group, jadi menghentikan unit akan menghantar isyarat kepada setiap proses di dalamnya. PID utama adalah lebih khusus. Ia merupakan proses tunggal yang mana penamatannya akan menamatkan unit tersebut, dan status keluar proses itu menjadi hasil bagi unit berkenaan. Membaca cgroup seolah-olah ia adalah PID utama merupakan punca kekeliruan tersebut.

Type=simple melaporkan servis telah bermula sebelum binari dijalankan

Type=simple ialah tetapan lalai apabila ExecStart= ditetapkan dan tiada Type= mahupun BusName= disertakan. systemd mencipta proses tersebut, menganggap unit itu telah bermula serta-merta, dan melayan proses tersebut sebagai PID utama. Unit susulan akan bermula dengan segera, walaupun sebelum binari servis sempat dilaksanakan.

Perincian terakhir itu menjelaskan satu situasi yang sering mengejutkan. Kesilapan menaip pada laluan ExecStart= masih menghasilkan tugasan mula yang berjaya, dan kegagalan hanya muncul seketika kemudian apabila pelaksanaan gagal. systemd merekodkan kes tersebut dengan kod keluar 203, yang dalam jadualnya sendiri dinamakan EXEC dan ditakrifkan sebagai kegagalan untuk melaksanakan binari servis. Oleh itu, systemctl start yang kembali tanpa ralat tidak membuktikan binari anda wujud.

Gunakan simple untuk program yang kekal di latar depan dan tidak pernah mengalihkan dirinya ke latar belakang. Ini merangkumi kebanyakan daemon moden dan hampir apa sahaja yang anda tulis sendiri.

Type=exec menunggu program untuk benar-benar bermula

Type=exec adalah simple dengan satu langkah tambahan. systemd menganggap unit telah bermula hanya selepas kedua-dua proses fork dan pelaksanaan binari berjaya. Binari yang tiada atau User= yang tidak dapat diselesaikan kini menyebabkan tugasan permulaan gagal, bukannya melaporkan kejayaan dan gagal secara senyap seketika kemudian.

Type=exec diperkenalkan dalam systemd 240, jadi setiap pengedaran pelayan semasa memilikinya. Ubuntu 24.04 menyertakan systemd 255 dan Debian 13 menyertakan systemd 257, setakat Ogos 2026. Semak versi anda dengan systemctl --version.

Kosnya ialah satu langkah penyelarasan tambahan semasa permulaan. Kelebihannya ialah status keluar yang tepat daripada systemctl start. Untuk program latar depan, utamakan exec berbanding simple.

Type=forking, dan bagaimana PID utama boleh hilang

Type=forking memberitahu systemd bahawa proses dalam ExecStart= akan melakukan fork kepada proses anak dan kemudian keluar dengan sengaja. systemd menunggu proses pertama itu keluar dan hanya selepas itu menganggap unit tersebut telah dimulakan. Proses anak yang ditinggalkan ialah daemon.

Kesukarannya adalah pada identiti. Proses yang dilancarkan oleh systemd telah tiada, jadi systemd perlu menentukan proses mana yang merupakan proses utama. Tetapkan PIDFile= kepada fail yang ditulis oleh daemon, biasanya laluan di bawah /run, dan systemd akan membaca PID daripada fail tersebut. systemd juga menyemak sama ada PID dalam fail itu merujuk kepada proses yang memang milik perkhidmatan ini, supaya fail lama yang menamakan proses yang tidak berkaitan akan ditolak dan tidak dipercayai.

Tanpa PIDFile=, GuessMainPID= akan digunakan, dan nilai lalainya ialah yes. Tekaan ini hanya boleh dipercayai apabila perkhidmatan tersebut stabil dalam satu proses tunggal. Manual menyatakan hadnya dengan jelas: jika daemon terdiri daripada lebih daripada satu proses, tekaan tersebut boleh menjadi salah, dan pengesanan kegagalan akan berhenti berfungsi. Unit juga boleh berakhir dengan PID utama 0, yang bermaksud systemd tidak mempunyai apa-apa untuk diselia.

Kebanyakan daemon yang melakukan fork juga mempunyai suis yang mengekalkan proses di latar depan (foreground). Gunakan suis tersebut dengan Type=exec dan padamkan baris PIDFile=. Kurang komponen yang bergerak bermakna kurang risiko untuk kehilangan PID.

Type=oneshot for work that finishes

Type=oneshot expects the process to run and exit. systemd calls the unit started only after it has exited, which makes oneshot the right shape for anything another unit must wait for. It is also the implied default when a unit specifies neither Type= nor ExecStart=.

Two behaviours are specific to oneshot. It is the only type that accepts more than one ExecStart= line, and those lines run in order. Its start timeout is also disabled by default, so a oneshot that hangs waits forever unless you set TimeoutStartSec= yourself.

After the process exits, the unit returns to inactive. RemainAfterExit=yes keeps it active with no process running at all. That is the deliberate version of the symptom at the top of this page, and it is correct when the unit's job was to leave state behind rather than to keep something running: loading a firewall ruleset, or bringing up a container stack. It is the pattern behind a Docker Compose stack that comes back after a reboot, where the unit runs the compose command, exits, and stays active because the containers it started outlive it. A oneshot unit is also what a schedule triggers, which is the other half of running a job on a systemd timer instead of cron.

Type=notify membolehkan servis menyatakan bila ia sudah bersedia

Type=notify memindahkan keputusan tersebut kepada servis. systemd menahan tugasan permulaan sehingga proses menghantar READY=1 melalui soket Unix yang laluannya diterima dalam pemboleh ubah persekitaran NOTIFY_SOCKET. Antara muka C adalah sd_notify(3), dan banyak pelayan sudah menyokongnya.

Ini adalah jawapan yang tepat bagi soalan "adakah ia sudah bermula". simple dan exec melaporkan servis telah bermula sebelum servis membaca konfigurasinya atau membuka soket pendengarannya, jadi unit yang bergantung boleh bermula terlalu awal dan gagal pada sambungan pertamanya. notify melaporkan servis telah bermula pada saat servis itu sendiri menyatakan ia sudah bersedia.

systemd menerima mesej tersebut daripada proses utama sahaja, itulah maksud NotifyAccess=main, dan Type=notify membayangkan perkara tersebut. Jika mesej datang daripada proses anak atau pembantu, tetapkan NotifyAccess=all. Skrip shell boleh memanggil systemd-notify --ready, tetapi ia berjalan sebagai proses berasingan yang singkat hayatnya, jadi ia memerlukan NotifyAccess=all dan systemd mungkin tidak dapat mengaitkan mesej yang penghantarnya sudah pun tamat. Servis yang menggunakan protokol itu sendiri adalah lebih boleh dipercayai.

Dua tetapan berkaitan yang perlu diketahui. Type=notify-reload, yang tersedia sejak systemd 253, melanjutkan jabat tangan yang sama kepada muat semula (reloads), jadi systemctl reload akan kembali apabila servis melaporkan muat semula telah selesai dan bukannya kembali sebaik sahaja isyarat dihantar. WatchdogSec= meminta servis pemberitahuan untuk menghantar mesej keep-alive pada selang masa tertentu, dan systemd akan menganggap tarikh akhir yang terlepas sebagai kegagalan.

Type=dbus dan Type=idle

Type=dbus menunggu sehingga servis mengambil nama pada D-Bus, iaitu bas mesej yang digunakan oleh servis sistem dan desktop untuk berkomunikasi antara satu sama lain. Ia memerlukan BusName=, dan ia menjadi lalai sebaik sahaja BusName= ditetapkan. Gunakannya hanya untuk servis yang benar-benar mendaftarkan nama bas.

Type=idle berkelakuan seperti simple, tetapi ia melengahkan pelaksanaan program sehingga tugasan yang beratur telah dihantar, dengan had masa lima saat. Ia wujud supaya output konsol semasa but tidak bercampur dengan mesej status. Ia bukan alat penyusunan, dan ia tidak sesuai digunakan pada servis biasa.

Mengapa skrip pembungkus menyebabkan systemd tersalah mengesan PID

Berikut adalah bentuk yang menghasilkan simptom asal.

[Service]
Type=simple
ExecStart=/opt/app/run.sh
#!/bin/bash
export APP_ENV=production
/opt/app/bin/server --config /etc/app.yaml &
/opt/app/bin/exporter --port 9101

systemd merekodkan shell sebagai PID utama. Shell kekal aktif sementara exporter berjalan di latar depan. Jika server mati, shell tidak menyedarinya, jadi PID utama masih aktif, unit masih dalam keadaan active, dan Restart= tidak mempunyai apa-apa untuk diambil tindakan. Kedua-dua proses berada dalam cgroup unit sepanjang masa, jadi systemctl stop masih melakukan pembersihan dengan betul. Penyeliaan yang terjejas, bukan pembersihan.

Penyelesaiannya bergantung pada berapa banyak proses yang berjalan lama dalam unit tersebut.

Jika hanya ada satu, gantikan shell dengan proses tersebut.

#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yaml

exec menggantikan shell dengan program yang dinamakan dan mengekalkan PID yang sama, jadi PID yang direkodkan oleh systemd kini milik daemon tersebut. Lebih baik lagi, buang pembungkus itu. Environment= dan EnvironmentFile= membawa pemboleh ubah, dan ExecStartPre= membawa langkah penyediaan, jadi systemd boleh melancarkan daemon secara terus dan mengetahui PID-nya melalui pembinaan.

Jika terdapat dua, tiada PID tunggal yang mewakili unit tersebut. Pecahkan kepada dua unit dan susun mengikut urutan dengan After= dan Wants=. Satu unit bagi setiap proses adalah aturan yang diselia dengan baik oleh systemd, dan ia merupakan satu-satunya cara setiap proses mendapat gelagat mulakan semula (restart) yang tersendiri.

Apakah perubahan ExitType=cgroup

ExitType= telah ditambah dalam systemd 250. Nilai lalai ialah main: unit dianggap berhenti apabila proses utama keluar. Dengan ExitType=cgroup, unit dianggap berjalan selagi mana-mana proses dalam cgroupnya masih hidup.

[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcher

Ini menyelesaikan satu masalah khusus. Pelancar yang memulakan kerja sebenar dan kemudian keluar akan menyebabkan systemd menganggap unit tersebut telah berhenti dan mematikan proses yang masih berjalan di bawah ExitType=main. Dengan ExitType=cgroup, unit tersebut mengikuti keseluruhan kumpulan sebaliknya.

Fahami perkara yang tidak diselesaikan oleh tetapan ini. ExitType=cgroup mengekalkan unit aktif selagi sekurang-kurangnya satu proses masih hidup, jadi unit yang memegang dua daemon akan kekal aktif selepas salah satu daripadanya mati. Ia membetulkan kes pelancar. Ia tidak mengubah satu unit menjadi penyelia bagi beberapa proses bebas. ExitType= juga tidak boleh digabungkan dengan Type=oneshot.

Cgroup juga merupakan tempat perakaunan sumber dilakukan, jadi had seperti MemoryMax= dan CPUQuota= terpakai kepada setiap proses yang dijana oleh unit tersebut, tidak kira apa yang Type= nyatakan tentang PID utama. Bahagian tersebut diterangkan dalam mengehadkan memori dan CPU servis dengan systemd.

Cara mencari proses yang sebenarnya dipantau oleh systemd

Lakukan langkah ini pada unit yang sedang anda nyahpepijat, mengikut urutan. Baca apa yang dimuatkan oleh systemd, kemudian baca apa yang dijejakinya, dan bandingkan dengan jadual proses.

systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.service

systemctl cat mencetak fail unit bersama setiap drop-in yang terpakai kepadanya, supaya anda membaca apa yang dimuatkan oleh systemd dan bukannya fail yang anda ingat pernah disunting. systemctl show mencetak nilai berkesan, termasuk nilai lalai yang tidak pernah anda tulis. Perhatikan nilai MainPID sebelum meneruskan.

systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"

systemd-cgls menyenaraikan setiap proses dalam cgroup unit tersebut. Baris ps menerangkan proses tunggal yang diselia oleh systemd. Baca kedua-duanya bersama-sama. MainPID bernilai 0 bermakna systemd tidak mempunyai proses untuk dipantau. MainPID yang merujuk kepada shell sementara cgroup juga mengandungi daemon anda adalah kes wrapper di atas. Cgroup dengan lebih banyak proses daripada yang dijangkakan bermakna terdapat pelancar atau daemon forking yang terlibat.

systemctl status app.service
journalctl -u app.service -b

systemctl status mencetak baris status dan pepohon cgroup bersama-sama, jadi ia sering menjawab kedua-dua soalan serentak. journalctl -u yang dihadkan kepada but semasa dengan -b menunjukkan peristiwa mula dan henti yang direkodkan oleh systemd untuk unit tersebut, berserta kod keluar yang dilihatnya. Jika daemon menulis kepada fail lognya sendiri dan bukannya journal, baca fail tersebut juga, kerana systemd hanya boleh merekodkan apa yang sampai kepadanya.

Apabila anda menukar Type=, muat semula dan mulakan semula.

systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service

systemd-analyze verify menghuraikan fail dan melaporkan tetapan yang tidak dapat diterimanya. daemon-reload menyebabkan systemd membaca semula fail unit daripada cakera. Type= yang diubah tidak terpakai pada unit yang sedang berjalan, jadi mulakan semula adalah wajib, bukan pilihan.

Kemudian uji perubahan tersebut. Ambil PID proses yang sebenarnya anda perlukan daripada systemd-cgls dan tamatkan proses tersebut. Jalankan systemctl is-active app.service sejurus selepas itu. Jika Type= adalah betul, unit tersebut akan keluar daripada status aktif. Jika ia kekal aktif, systemd masih memantau sesuatu yang lain.

Jenis Type= servis systemd yang manakah perlu anda gunakan

  • Program yang kekal di latar hadapan: Type=exec.
  • Program yang menyokong pemberitahuan kesediaan: Type=notify, dan notify-reload jika ia turut mengesahkan muat semula (reload).
  • Daemon yang berkeras untuk bergerak ke latar belakang: Type=forking dengan PIDFile=, atau suis latar hadapan daemon tersebut dengan Type=exec.
  • Skrip yang melakukan kerja dan kemudian tamat: Type=oneshot, ditambah dengan RemainAfterExit=yes apabila tujuannya adalah untuk meninggalkan status (state) di belakang.
  • Pelancar (launcher) yang tamat manakala proses anaknya terus berjalan: Type=simple dengan ExitType=cgroup.

Jika anda tidak pasti jenis yang diperlukan oleh daemon pihak ketiga, baca fail unit yang dibekalkan terlebih dahulu. Menjalankan systemctl cat pada unit yang dibekalkan oleh pengedar menunjukkan Type= yang dipilih oleh pihak hulu (upstream), dan pilihan tersebut telah diuji oleh lebih ramai orang berbanding pilihan anda.

FAQ

Mengapakah unit systemd saya kekal aktif apabila proses telah mati?

Kerana proses yang dianggap oleh systemd sebagai proses utama masih hidup. systemd memantau satu PID bagi setiap servis, yang dipilih berdasarkan Type=, bukannya setiap proses dalam cgroup unit tersebut. Skrip pembungkus (wrapper script) yang dimulakan dengan Type=simple biasanya menjadi punca: shell tersebut adalah PID utama, jadi unit kekal aktif apabila daemon yang dilancarkan oleh shell di latar belakang telah tamat. Jalankan systemctl show -p MainPID app.service, kemudian senaraikan cgroup unit tersebut dengan systemd-cgls --unit=app.service, dan bandingkan kedua-duanya.

Apakah perbezaan antara Type=simple dan Type=exec?

Type=simple menganggap unit telah bermula sebaik sahaja systemd mencipta proses tersebut, sebelum binari dilaksanakan, jadi laluan yang salah dalam ExecStart= masih memberikan tugasan permulaan yang berjaya diikuti oleh kegagalan. Type=exec menunggu sehingga pelaksanaan berjaya, supaya kegagalan dilaporkan oleh tugasan permulaan itu sendiri. Kedua-duanya menganggap proses yang sama sebagai PID utama. Type=exec memerlukan systemd 240 atau lebih baharu.

Adakah saya masih memerlukan PIDFile= dengan Type=forking?

Ya, pada bila-bila masa daemon menulis fail tersebut. Tanpanya, systemd akan kembali kepada GuessMainPID=, yang merupakan satu tekaan dan hanya boleh dipercayai untuk servis yang menetap dalam satu proses tunggal. Apabila tekaan itu salah atau mustahil, pengesanan kegagalan dan permulaan semula automatik akan berhenti berfungsi untuk unit tersebut. Halakan PIDFile= ke laluan tepat yang ditulis oleh daemon, biasanya di bawah /run.

Bilakah saya perlu menggunakan RemainAfterExit=yes?

Apabila tujuan unit tersebut adalah untuk mengubah keadaan sistem dan bukannya mengekalkan proses supaya terus berjalan. Unit Type=oneshot yang memuatkan peraturan firewall atau memulakan timbunan kontena akan tamat sebaik sahaja tugasnya selesai, dan tanpa RemainAfterExit=yes unit tersebut menjadi tidak aktif, yang menyebabkan systemctl stop tidak mempunyai apa-apa untuk dihentikan dan tiada cara untuk menjalankan pembersihan ExecStop=. Dengan tetapan ini, unit kekal aktif tanpa sebarang proses, dan itu adalah tujuan yang diingini di sini.

Adakah menukar Type= memerlukan daemon-reload?

Ya, dan juga perlu memulakan semula unit tersebut. systemctl daemon-reload menyebabkan systemd membaca semula fail unit pada cakera, tetapi instans yang sedang berjalan mengekalkan Type= yang digunakan semasa ia bermula. Jalankan sudo systemctl daemon-reload dan kemudian sudo systemctl restart app.service sebelum melakukan ujian, jika tidak, anda masih memantau gelagat penyeliaan yang lama.