SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

วิธีเลือก Type= ใน systemd service ให้ถูกต้องและแม่นยำ

แก้ปัญหา systemd แสดงสถานะ active ทั้งที่ daemon หยุดทำงานไปแล้ว เรียนรู้วิธีเลือก Type= simple, forking, notify หรือ oneshot เพื่อให้ระบบติดตาม main PID ได้อย่างถูกต้องแม่นยำ

เหตุใด systemd จึงรายงานว่า unit ทำงานอยู่ทั้งที่ process ตายไปแล้ว

service unit ของ systemd จะคงสถานะเป็น active ตราบเท่าที่ process ที่ systemd ระบุว่าเป็น main process ยังคงทำงานอยู่ โดยค่าในส่วน [Service] ของ Type= จะเป็นตัวกำหนดว่า process ใดคือ main process หากเลือกค่าไม่ถูกต้อง systemd จะเฝ้าดูเพียง shell wrapper หรือ parent process ที่ทำงานเพียงชั่วคราว ในขณะที่ daemon ที่คุณต้องการใช้งานจริงอาจตายไปแล้วภายใน unit เดียวกัน ดังนั้น unit จึงรายงานสถานะตามความเป็นจริงของ process ที่ถูกสั่งให้เฝ้าดู

การเปลี่ยนนโยบายการ restart จะไม่ช่วยแก้ปัญหานี้ เนื่องจาก Restart= จะทำงานก็ต่อเมื่อ main process จบการทำงานลงเท่านั้น ดังนั้น Restart=always จึงไม่มีทางทำงานตราบใดที่ main PID (process identifier) ยังคงเป็นของ process ที่ยังทำงานอยู่ คุณต้องแก้ไข Type= ให้ถูกต้องก่อน ส่วนสิ่งที่ systemd จะดำเนินการหลังจาก main process จบการทำงานจริงๆ แล้วนั้น เป็นการตัดสินใจในส่วนอื่น ซึ่งครอบคลุมอยู่ใน คู่มือการใช้งาน Restart= และ RestartSec=

Type= ตัดสินใจเรื่องอะไรบ้าง

ทุกค่าของ Type= จะตอบคำถามสองข้อพร้อมกัน คือ systemd จะพิจารณาว่า unit นี้เริ่มทำงานเมื่อใด และกระบวนการ (process) ใดคือกระบวนการหลัก

คำตอบแรกควบคุมลำดับการทำงาน (ordering) unit ที่ระบุชื่อ unit ของคุณไว้ใน After= จะรอจนกว่า systemd จะเรียก unit ของคุณว่าเริ่มทำงานแล้ว ส่วน Type= ที่รายงานสถานะว่า "started" เร็วเกินไป จะทำให้ unit ที่ขึ้นต่อกันเริ่มทำงานก่อนที่บริการของคุณจะพร้อมตอบสนอง

คำตอบที่สองควบคุมการกำกับดูแล (supervision) systemd จะนำทุกกระบวนการที่ unit สร้างขึ้นไปไว้ใน cgroup (control group) ซึ่งเป็นฟีเจอร์ของ kernel ที่ใช้จัดกลุ่มกระบวนการเพื่อให้สามารถจำกัดทรัพยากรและสั่งยุติการทำงานพร้อมกันได้ cgroup คือวิธีที่ systemctl stop ใช้ในการล้างข้อมูล โดย KillMode= จะตั้งค่าเริ่มต้นไว้ที่ control-group ดังนั้นการสั่งหยุด unit จึงเป็นการส่งสัญญาณไปยังทุกกระบวนการที่อยู่ภายในนั้น ส่วน main PID จะมีความเฉพาะเจาะจงกว่า คือเป็นกระบวนการเดียวที่หากยุติลงจะถือว่า unit นั้นจบการทำงาน และสถานะการออก (exit status) ของกระบวนการนี้จะกลายเป็นผลลัพธ์ของ unit นั้น การเข้าใจผิดว่า cgroup คือ main PID คือจุดเริ่มต้นของความสับสนในเรื่องนี้

Type=simple รายงานว่าเริ่มทำงานก่อนที่ไฟล์ binary จะรันจริง

Type=simple คือค่าเริ่มต้นเมื่อตั้งค่า ExecStart= ไว้และไม่มีทั้ง Type= หรือ BusName= อยู่ systemd จะสร้างกระบวนการทำงานขึ้นมา ถือว่า unit เริ่มทำงานทันที และนับกระบวนการนั้นเป็น PID หลัก ส่งผลให้ unit ลำดับถัดไปเริ่มทำงานทันที ก่อนที่ไฟล์ binary ของบริการจะถูกเรียกใช้งานจริงเสียอีก

รายละเอียดสุดท้ายนี้อธิบายถึงเหตุการณ์ที่มักสร้างความประหลาดใจบ่อยครั้ง หากมีการพิมพ์ path ใน ExecStart= ผิดพลาด งานเริ่มต้น (start job) จะยังคงแสดงสถานะว่าสำเร็จ และความล้มเหลวจะปรากฏขึ้นในภายหลังเมื่อการเรียกใช้งานไม่สำเร็จ systemd จะบันทึกกรณีดังกล่าวด้วย exit code 203 ซึ่งตารางของ systemd เองระบุว่าเป็น EXEC และนิยามว่าเป็นความล้มเหลวในการเรียกใช้งานไฟล์ binary ของบริการ ดังนั้นการที่ systemctl start ส่งค่ากลับโดยไม่มีข้อผิดพลาด จึงไม่ได้เป็นเครื่องพิสูจน์ว่าไฟล์ binary ของคุณมีอยู่จริง

ให้ใช้ simple สำหรับโปรแกรมที่ทำงานใน foreground และไม่มีการย้ายตัวเองไปทำงานใน background ซึ่งครอบคลุมถึง daemon สมัยใหม่ส่วนใหญ่และเกือบทุกโปรแกรมที่คุณเขียนขึ้นเอง

Type=exec รอให้โปรแกรมเริ่มทำงานจริง

Type=exec คือ simple ที่เพิ่มขั้นตอนขึ้นมาอีกหนึ่งขั้น systemd จะถือว่า unit เริ่มทำงานก็ต่อเมื่อทั้งการ fork และการรัน binary สำเร็จเท่านั้น หากหา binary ไม่พบหรือ User= ไม่สามารถแก้ไขได้ งานการเริ่มระบบจะล้มเหลวทันที แทนที่จะรายงานว่าสำเร็จแล้วค่อยไปล้มเหลวเงียบๆ ในภายหลัง

Type=exec เริ่มมีใน systemd 240 ดังนั้น distribution ของเซิร์ฟเวอร์ในปัจจุบันทุกตัวจึงรองรับ Ubuntu 24.04 มาพร้อมกับ systemd 255 และ Debian 13 มาพร้อมกับ systemd 257 ณ เดือนสิงหาคม 2026 ตรวจสอบเวอร์ชันของคุณได้ด้วย systemctl --version

สิ่งที่ต้องแลกคือขั้นตอนการซิงโครไนซ์ที่เพิ่มขึ้นหนึ่งขั้นในตอนเริ่มต้น แต่สิ่งที่ได้รับคือสถานะการออก (exit status) ที่ตรงไปตรงมาของ systemctl start สำหรับโปรแกรมที่ทำงานเบื้องหน้า (foreground) ให้เลือกใช้ exec แทน simple

Type=forking และปัญหาการสูญเสีย main PID

Type=forking แจ้งให้ systemd ทราบว่ากระบวนการใน ExecStart= จะทำการ fork child process ออกมาแล้วจบการทำงานลงโดยเจตนา systemd จะรอให้กระบวนการแรกนั้นจบลงก่อน แล้วจึงถือว่า unit นั้นเริ่มทำงานเสร็จสิ้น ส่วน child process ที่เหลืออยู่คือ daemon พฤติกรรมนี้สืบทอดมาจากยุค SysV ซึ่งไม่มีระบบคอยกำกับดูแล daemon หลังจาก init script ทำงานเสร็จสิ้น และ PID file เป็นเพียงหลักฐานเดียวที่ระบุว่ามีอะไรทำงานอยู่ ซึ่งเป็นข้อจำกัดที่เป็นหัวใจสำคัญของ เหตุผลที่ systemd เข้ามาแทนที่ init scripts

ความยากอยู่ที่การระบุตัวตน เนื่องจากกระบวนการที่ systemd สั่งเริ่มทำงานได้จบไปแล้ว systemd จึงต้องหาให้พบว่ากระบวนการใดที่ยังคงอยู่คือกระบวนการหลัก ให้ตั้งค่า PIDFile= ไปยังไฟล์ที่ daemon เขียนขึ้น ซึ่งปกติจะเป็น path ภายใต้ /run แล้ว systemd จะอ่าน PID จากไฟล์นั้น นอกจากนี้ systemd จะตรวจสอบว่า PID ในไฟล์ดังกล่าวเป็นของกระบวนการที่อยู่ใน service นี้จริงหรือไม่ เพื่อให้มั่นใจว่าไฟล์เก่าที่ระบุถึงกระบวนการที่ไม่เกี่ยวข้องจะไม่ถูกนำมาใช้งาน

หากไม่มี PIDFile= ค่า GuessMainPID= จะถูกนำมาใช้ ซึ่งค่าเริ่มต้นคือ yes การคาดเดานี้จะเชื่อถือได้ก็ต่อเมื่อ service นั้นทำงานเหลือเพียงกระบวนการเดียวเท่านั้น คู่มือระบุข้อจำกัดไว้อย่างชัดเจนว่า หาก daemon ประกอบด้วยกระบวนการมากกว่าหนึ่ง การคาดเดาอาจผิดพลาดและระบบตรวจจับความล้มเหลวจะหยุดทำงาน นอกจากนี้ unit อาจลงเอยด้วย main PID เป็น 0 ซึ่งหมายความว่า systemd ไม่มีกระบวนการใดให้กำกับดูแลเลย

daemon ส่วนใหญ่ที่ใช้วิธี fork มักจะมีสวิตช์สำหรับสั่งให้ทำงานใน foreground ให้ใช้สวิตช์นั้นร่วมกับ Type=exec แล้วลบแถว PIDFile= ออก การลดจำนวนส่วนประกอบที่ซับซ้อนลงจะช่วยลดโอกาสที่จะเกิดปัญหาการสูญเสีย PID ได้

Type=oneshot สำหรับงานที่ทำงานจนเสร็จสิ้น

Type=oneshot คาดหวังให้กระบวนการทำงานและจบการทำงานลง systemd จะถือว่า unit เริ่มทำงานก็ต่อเมื่อกระบวนการนั้นจบลงแล้วเท่านั้น ซึ่งทำให้ oneshot เป็นรูปแบบที่เหมาะสมสำหรับงานใดๆ ที่ unit อื่นต้องรอให้เสร็จสิ้น นอกจากนี้ยังเป็นค่าเริ่มต้นโดยนัยเมื่อ unit ไม่ได้ระบุทั้ง Type= และ ExecStart= ไว้

มีพฤติกรรมสองอย่างที่เฉพาะเจาะจงสำหรับ oneshot คือเป็น type เดียวที่ยอมรับบรรทัด ExecStart= ได้มากกว่าหนึ่งบรรทัด และบรรทัดเหล่านั้นจะทำงานตามลำดับ นอกจากนี้การตั้งค่าเวลาเริ่มต้น (start timeout) จะถูกปิดใช้งานโดยค่าเริ่มต้น ดังนั้น oneshot ที่ค้างอยู่จะรอไปเรื่อยๆ เว้นแต่คุณจะตั้งค่า TimeoutStartSec= ด้วยตนเอง

หลังจากกระบวนการจบลง unit จะกลับสู่สถานะ inactive แต่ RemainAfterExit=yes จะคงสถานะไว้เป็น active โดยไม่มีกระบวนการใดๆ ทำงานอยู่เลย นี่คือเวอร์ชันที่ตั้งใจให้เป็นของอาการที่กล่าวไว้ที่ด้านบนของหน้านี้ และเป็นสิ่งที่ถูกต้องเมื่อหน้าที่ของ unit คือการทิ้งสถานะไว้เบื้องหลังแทนที่จะคงให้บางอย่างทำงานอยู่ เช่น การโหลดชุดกฎ firewall หรือการเริ่มทำงานของ container stack นี่คือรูปแบบเบื้องหลังของ Docker Compose stack ที่กลับมาทำงานใหม่หลังรีบูต ซึ่ง unit จะรันคำสั่ง compose แล้วจบการทำงานลง แต่ยังคงสถานะ active ไว้เพราะ container ที่มันเริ่มทำงานนั้นยังคงทำงานอยู่หลังจากที่ตัว unit จบไปแล้ว นอกจากนี้ unit แบบ oneshot ยังเป็นสิ่งที่ schedule เรียกใช้งาน ซึ่งเป็นอีกครึ่งหนึ่งของ การรันงานบน systemd timer แทนการใช้ cron

Type=notify ช่วยให้ service แจ้งสถานะเมื่อพร้อมทำงาน

Type=notify เป็นการย้ายการตัดสินใจไปไว้ที่ตัว service เอง โดย systemd จะคงสถานะการเริ่มทำงานไว้จนกว่า process จะส่ง READY=1 ผ่าน Unix socket ซึ่งได้รับ path มาในตัวแปร environment NOTIFY_SOCKET สำหรับ C interface นั้นคือ sd_notify(3) ซึ่งปัจจุบันมี server จำนวนมากรองรับการทำงานนี้แล้ว

นี่คือคำตอบที่แม่นยำสำหรับคำถามที่ว่า "บริการเริ่มทำงานแล้วหรือยัง" เนื่องจาก simple และ exec จะรายงานว่าเริ่มทำงานแล้วตั้งแต่ก่อนที่ service จะอ่าน configuration หรือเปิด listening socket ทำให้ unit ที่ขึ้นต่อกันอาจเริ่มทำงานเร็วเกินไปและล้มเหลวในการเชื่อมต่อครั้งแรก ในขณะที่ notify จะรายงานว่าเริ่มทำงานแล้วในจังหวะที่ service ยืนยันด้วยตัวเองว่าพร้อมใช้งาน

systemd จะยอมรับข้อความดังกล่าวจาก main process เท่านั้น ซึ่งเป็นความหมายของ NotifyAccess=main และ Type=notify ก็สื่อถึงเรื่องนี้เช่นกัน หากข้อความถูกส่งมาจาก child process หรือ helper process ให้ตั้งค่า NotifyAccess=all สำหรับ shell script สามารถเรียกใช้ systemd-notify --ready ได้ แต่เนื่องจากเป็นการทำงานของ process ระยะสั้นที่แยกออกมา จึงจำเป็นต้องใช้ NotifyAccess=all และ systemd อาจไม่สามารถระบุแหล่งที่มาของข้อความได้หากผู้ส่งจบการทำงานไปแล้ว ดังนั้น service ที่สื่อสารผ่าน protocol นี้ได้โดยตรงจึงมีความน่าเชื่อถือมากกว่า

มีการตั้งค่าที่เกี่ยวข้องอีก 2 รายการที่ควรทราบ คือ Type=notify-reload ซึ่งมีให้ใช้งานตั้งแต่ systemd 253 เป็นต้นไป โดยจะขยายการทำ handshake นี้ไปถึงการ reload ด้วย ทำให้คำสั่ง systemctl reload จะรอจนกว่า service จะรายงานว่าการ reload เสร็จสิ้น แทนที่จะคืนค่าทันทีหลังจากส่งสัญญาณ ส่วน WatchdogSec= เป็นการกำหนดให้ service ที่ใช้การแจ้งสถานะต้องส่งข้อความ keep-alive ตามช่วงเวลาที่กำหนด หาก systemd ไม่ได้รับข้อความภายในเวลาที่กำหนดจะถือว่า service นั้นล้มเหลว

Type=dbus และ Type=idle

Type=dbus จะรอจนกว่าบริการจะลงทะเบียนชื่อบน D-Bus ซึ่งเป็น message bus ที่บริการของระบบและเดสก์ท็อปใช้สื่อสารกัน การตั้งค่านี้จำเป็นต้องมี BusName= และจะกลายเป็นค่าเริ่มต้นทันทีที่มีการตั้งค่า BusName= ให้ใช้ค่านี้เฉพาะกับบริการที่มีการลงทะเบียนชื่อบน bus จริงๆ เท่านั้น

Type=idle ทำงานคล้ายกับ simple แต่จะหน่วงเวลาการรันโปรแกรมไว้จนกว่างานที่ค้างอยู่ในคิวจะถูกประมวลผลเสร็จสิ้น โดยมีระยะเวลาจำกัดสูงสุดที่ 5 วินาที ค่านี้มีไว้เพื่อป้องกันไม่ให้ข้อความที่แสดงบนคอนโซลระหว่างการบูตปะปนกับข้อความสถานะของระบบ ไม่ใช่เครื่องมือสำหรับจัดการลำดับการทำงาน (ordering tool) และไม่ควรนำไปใช้กับบริการทั่วไป

เหตุใด wrapper script จึงทำให้ systemd ติดอยู่ที่ PID ที่ไม่ถูกต้อง

นี่คือรูปแบบที่ทำให้เกิดอาการดังกล่าว

[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 บันทึก shell ไว้เป็น PID หลัก shell จะยังคงทำงานอยู่ตราบเท่าที่ exporter ทำงานในเบื้องหน้า หาก server หยุดทำงาน shell จะไม่รับรู้ ดังนั้น PID หลักจึงยังคงทำงานอยู่ สถานะของ unit จึงยังคงเป็น active และ Restart= ก็ไม่มีสิ่งใดให้ดำเนินการ ทั้งสองกระบวนการยังคงอยู่ใน cgroup ของ unit ตลอดเวลา ดังนั้น systemctl stop จึงยังคงล้างข้อมูลได้อย่างถูกต้อง สิ่งที่ล้มเหลวคือการกำกับดูแล (supervision) ไม่ใช่การล้างข้อมูล

วิธีแก้ไขขึ้นอยู่กับจำนวนกระบวนการที่ทำงานยาวนานใน unit นั้นจริงๆ

หากมีเพียงกระบวนการเดียว ให้แทนที่ shell ด้วยกระบวนการนั้น

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

exec จะแทนที่ shell ด้วยโปรแกรมที่ระบุและรักษา PID เดิมไว้ ดังนั้น PID ที่ systemd บันทึกไว้จึงกลายเป็นของ daemon โดยตรง วิธีที่ดีกว่าคือการลบ wrapper ออก Environment= และ EnvironmentFile= สามารถจัดการตัวแปรต่างๆ ได้ และ ExecStartPre= สามารถจัดการขั้นตอนการตั้งค่าได้ ดังนั้น systemd จึงสามารถเรียกใช้ daemon ได้โดยตรงและทราบ PID ของมันโดยโครงสร้าง

หากมีสองกระบวนการ จะไม่มี PID เดียวที่แสดงถึง unit นั้น ให้แยกออกเป็นสอง unit และกำหนดลำดับด้วย After= และ Wants= การจัดรูปแบบให้หนึ่ง unit ต่อหนึ่งกระบวนการคือสิ่งที่ systemd กำกับดูแลได้ดี และเป็นวิธีเดียวที่แต่ละกระบวนการจะได้รับพฤติกรรมการรีสตาร์ทเป็นของตนเอง

การเปลี่ยนแปลงของ ExitType=cgroup

ExitType= ถูกเพิ่มเข้ามาใน systemd 250 ค่าเริ่มต้นคือ main ซึ่งหมายความว่า unit จะถูกถือว่าหยุดทำงานเมื่อกระบวนการหลัก (main process) จบลง แต่ด้วย ExitType=cgroup unit จะยังคงถูกถือว่าทำงานอยู่ตราบเท่าที่ยังมีกระบวนการใดๆ ใน cgroup นั้นทำงานอยู่

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

การตั้งค่านี้ช่วยแก้ปัญหาเฉพาะอย่างหนึ่ง คือในกรณีที่ตัวเรียกใช้งาน (launcher) เริ่มทำงานจริงแล้วจบการทำงานลง ภายใต้ ExitType=main นั้น systemd จะถือว่า unit หยุดทำงานและสั่งฆ่ากระบวนการที่เหลืออยู่ทั้งหมด แต่ด้วย ExitType=cgroup ตัว unit จะติดตามสถานะของทั้งกลุ่มแทน

ต้องทำความเข้าใจให้ชัดเจนว่าสิ่งนี้ไม่ได้แก้ปัญหาอะไรบ้าง ExitType=cgroup จะรักษา unit ให้ทำงานอยู่ตราบเท่าที่มีอย่างน้อยหนึ่งกระบวนการทำงาน ดังนั้น unit ที่ดูแล daemon สองตัวจะยังคงทำงานอยู่แม้ว่าตัวหนึ่งจะตายไป มันช่วยแก้ปัญหาในกรณีของตัวเรียกใช้งานเท่านั้น ไม่ได้เปลี่ยน unit ให้กลายเป็นตัวควบคุม (supervisor) ของกระบวนการอิสระหลายตัว นอกจากนี้ ExitType= ยังไม่สามารถใช้ร่วมกับ Type=oneshot ได้

cgroup ยังเป็นจุดที่ใช้ในการคำนวณทรัพยากร ดังนั้นขีดจำกัดต่างๆ เช่น MemoryMax= และ CPUQuota= จะมีผลกับทุกกระบวนการที่ unit นั้นสร้างขึ้น ไม่ว่า Type= จะระบุเกี่ยวกับ main PID ไว้อย่างไรก็ตาม รายละเอียดในส่วนนี้อยู่ใน การจำกัดหน่วยความจำและ CPU ของบริการด้วย systemd

วิธีค้นหา process ที่ systemd กำลังเฝ้าติดตามอยู่จริง

ให้ดำเนินการตามขั้นตอนต่อไปนี้กับ unit ที่คุณกำลังแก้ไขปัญหา โดยเริ่มจากการอ่านสิ่งที่ systemd โหลดขึ้นมา จากนั้นดูสิ่งที่ระบบติดตามอยู่ แล้วเปรียบเทียบกับตาราง process

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

systemctl cat จะแสดงไฟล์ unit พร้อมกับ drop-in ทุกรายการที่เกี่ยวข้อง เพื่อให้คุณได้อ่านสิ่งที่ systemd โหลดจริงแทนที่จะเป็นไฟล์ที่คุณจำได้ว่าเคยแก้ไข systemctl show จะแสดงค่าที่มีผลใช้งานจริง รวมถึงค่าเริ่มต้นที่คุณไม่ได้ระบุไว้ในไฟล์ ให้จดบันทึกค่าของ MainPID ไว้ก่อนดำเนินการต่อ

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

systemd-cgls จะแสดงรายการ process ทั้งหมดที่อยู่ใน cgroup ของ unit นั้น บรรทัด ps จะอธิบายถึง process เดียวที่ systemd กำลังควบคุมดูแลอยู่ ให้พิจารณาทั้งสองส่วนประกอบกัน หาก MainPID เป็น 0 หมายความว่า systemd ไม่มี process ใดให้เฝ้าติดตาม หาก MainPID ชี้ไปยัง shell ในขณะที่ cgroup ยังมี daemon ของคุณอยู่ แสดงว่าเป็นกรณี wrapper ที่กล่าวถึงข้างต้น หาก cgroup มีจำนวน process มากกว่าที่คาดไว้ แสดงว่ามีการใช้ launcher หรือ forking daemon เข้ามาเกี่ยวข้อง

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

systemctl status จะแสดงบรรทัดสถานะและโครงสร้าง cgroup พร้อมกัน ซึ่งมักจะตอบคำถามทั้งสองข้อได้ในคราวเดียว journalctl -u ที่จำกัดขอบเขตเฉพาะการบูตปัจจุบันด้วย -b จะแสดงเหตุการณ์การเริ่มและหยุดทำงานที่ systemd บันทึกไว้สำหรับ unit นั้น พร้อมกับ exit code ที่ตรวจพบ หาก daemon เขียน log ลงในไฟล์ของตัวเองแทนที่จะเป็น journal ให้ตรวจสอบไฟล์นั้นด้วย เพราะ systemd สามารถบันทึกได้เฉพาะสิ่งที่ส่งมาถึงตัวมันเท่านั้น

เมื่อคุณแก้ไข Type= ให้ทำการ reload และ restart เสมอ

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

systemd-analyze verify จะทำการตรวจสอบไฟล์และรายงานการตั้งค่าที่ไม่สามารถยอมรับได้ daemon-reload จะสั่งให้ systemd อ่านไฟล์ unit จากดิสก์ใหม่อีกครั้ง การเปลี่ยนแปลงใน Type= จะไม่มีผลกับ unit ที่กำลังทำงานอยู่ ดังนั้นการ restart จึงเป็นสิ่งที่จำเป็น ไม่ใช่ทางเลือก

จากนั้นให้ทดสอบการเปลี่ยนแปลง โดยนำ PID ของ process ที่คุณต้องการตรวจสอบจาก systemd-cgls แล้วทำการ kill process นั้น จากนั้นรัน systemctl is-active app.service ทันที หาก Type= ถูกต้อง unit จะเปลี่ยนสถานะออกจาก active หากสถานะยังคงเป็น active อยู่ แสดงว่า systemd ยังคงเฝ้าติดตาม process อื่นที่เหลืออยู่

คุณควรใช้ systemd service Type= แบบใด

  • โปรแกรมที่ทำงานใน foreground: Type=exec
  • โปรแกรมที่รองรับการแจ้งเตือนสถานะพร้อมใช้งาน: Type=notify และ notify-reload หากโปรแกรมนั้นยืนยันการโหลดซ้ำ (reload) ด้วย
  • daemon ที่ต้องการย้ายตัวเองไปทำงานใน background: Type=forking ร่วมกับ PIDFile= หรือใช้สวิตช์สำหรับรันใน foreground ของโปรแกรมนั้นร่วมกับ Type=exec
  • สคริปต์ที่ทำงานจนเสร็จแล้วจบการทำงาน: Type=oneshot และใช้ RemainAfterExit=yes เมื่อต้องการให้สถานะคงอยู่หลังจากสคริปต์จบลง
  • ตัวเรียกใช้งาน (launcher) ที่จบการทำงานในขณะที่กระบวนการลูกยังคงทำงานอยู่: Type=simple ร่วมกับ ExitType=cgroup

หากคุณไม่แน่ใจว่า daemon ของบุคคลที่สามต้องใช้แบบใด ให้ตรวจสอบไฟล์ unit ที่มาพร้อมกับแพ็กเกจก่อน การรันคำสั่ง systemctl cat บน unit ที่มาพร้อมกับ distribution จะแสดงค่า Type= ที่ผู้พัฒนาเลือกไว้ ซึ่งเป็นค่าที่ผ่านการทดสอบจากผู้ใช้งานจำนวนมากแล้ว

FAQ

ทำไม systemd unit ของฉันยังคงสถานะ active ทั้งที่ process ตายไปแล้ว?

เพราะ process ที่ systemd ถือว่าเป็น process หลักยังคงทำงานอยู่ systemd จะเฝ้าดู PID เพียงตัวเดียวต่อหนึ่ง service ตามที่กำหนดใน Type= ไม่ใช่ทุก process ใน cgroup ของ unit นั้น สาเหตุที่พบบ่อยคือการใช้ wrapper script ที่เริ่มด้วย Type=simple โดย shell จะกลายเป็น PID หลัก ดังนั้น unit จึงยังคงสถานะ active แม้ daemon ที่ shell เรียกขึ้นมาใน background จะทำงานเสร็จสิ้นไปแล้ว ให้รัน systemctl show -p MainPID app.service จากนั้นแสดงรายการ cgroup ของ unit ด้วย systemd-cgls --unit=app.service แล้วเปรียบเทียบค่าทั้งสอง

Type=simple และ Type=exec ต่างกันอย่างไร?

Type=simple จะถือว่า unit เริ่มทำงานทันทีที่ systemd สร้าง process ขึ้นมา ก่อนที่ binary จะถูกประมวลผลจริง ดังนั้นหากระบุ path ใน ExecStart= ผิด ก็จะยังคงได้รับสถานะว่า start job สำเร็จก่อนที่จะตามมาด้วยความล้มเหลวในภายหลัง ส่วน Type=exec จะรอจนกว่าการประมวลผลจะสำเร็จจริง ดังนั้นความล้มเหลวจะถูกรายงานโดยตัว start job เอง ทั้งสองแบบถือว่า process เดียวกันเป็น PID หลัก โดย Type=exec ต้องการ systemd เวอร์ชัน 240 ขึ้นไป

ฉันยังจำเป็นต้องใช้ PIDFile= เมื่อใช้ Type=forking หรือไม่?

จำเป็น หาก daemon มีการเขียนไฟล์ดังกล่าว หากไม่มี systemd จะหันไปใช้ GuessMainPID= ซึ่งเป็นการคาดเดาและเชื่อถือได้เฉพาะกับ service ที่ทำงานเป็น process เดียวเท่านั้น เมื่อการคาดเดาผิดพลาดหรือไม่สามารถทำได้ การตรวจจับความล้มเหลวและการรีสตาร์ทอัตโนมัติสำหรับ unit นั้นจะหยุดทำงาน ให้ชี้ PIDFile= ไปยัง path ที่ daemon เขียนไฟล์จริง ซึ่งโดยปกติจะอยู่ภายใต้ /run

ควรใช้ RemainAfterExit=yes เมื่อใด?

ใช้เมื่อจุดประสงค์ของ unit คือการเปลี่ยนสถานะของระบบ ไม่ใช่การรักษาให้ process ทำงานอยู่ตลอดเวลา unit ประเภท Type=oneshot ที่โหลดกฎ firewall หรือเริ่ม container stack จะทำงานเสร็จสิ้นและจบการทำงานทันที หากไม่มี RemainAfterExit=yes ตัว unit จะเปลี่ยนเป็นสถานะ inactive ซึ่งทำให้ systemctl stop ไม่มีสิ่งใดให้หยุดทำงานและไม่มีวิธีเรียกใช้การล้างข้อมูลใน ExecStop= ได้ แต่เมื่อใช้คำสั่งนี้ unit จะคงสถานะ active ไว้แม้ไม่มี process ใดเหลืออยู่ ซึ่งเป็นพฤติกรรมที่ต้องการในกรณีนี้

การเปลี่ยน Type= จำเป็นต้องรัน daemon-reload หรือไม่?

จำเป็น และต้องรีสตาร์ท unit นั้นด้วย systemctl daemon-reload จะทำให้ systemd อ่านไฟล์ unit บนดิสก์ใหม่ แต่ instance ที่กำลังทำงานอยู่จะยังคงใช้ Type= ที่เริ่มมาตั้งแต่ต้น ให้รัน sudo systemctl daemon-reload และตามด้วย sudo systemctl restart app.service ก่อนทำการทดสอบ มิฉะนั้นคุณจะยังคงเฝ้าดูพฤติกรรมการควบคุมแบบเดิมอยู่