กลับไปที่บล็อก

Systemd service ไม่ยอมสตาร์ต? วิธีแก้แบบตรงจุด

14 กันยายน 2569

systemd service ที่ไม่ยอมสตาร์ตแทบไม่เคยลึกลับเลย รัน systemctl status <unit> แล้วอ่าน 50 บรรทัดสุดท้ายของ journal หน่วยนั้นด้วย journalctl -u <unit> -n 50 --no-pager — ระหว่างสองคำสั่งนี้ ปกติจะมีหนึ่งในห้าสาเหตุถูกเรียกชื่อออกมาตรง ๆ: พาธพัง, ไบนารีหาย, สิทธิ์ผิด, การปฏิเสธจาก SELinux/AppArmor หรือไวยากรณ์ไฟล์ unit ผิด เหตุผลการล้มอยู่ใน log วิธีแก้ด้านล่างเป็นแค่การจับคู่รูปแบบกับมัน

ดูได้อย่างไรว่า systemd service ล้มเพราะอะไร?

สถานะก่อน แล้วค่อย journal:

systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager

status ให้สถานะ (inactive (dead), failed (exit-code), activating (auto-restart)) กับบรรทัด log สุดท้ายไม่กี่บรรทัด journal ให้เรื่องราวเต็ม: stdout, stderr และข้อร้องเรียนของ systemd เองที่มีต่อ unit

ถ้า unit เคยล้มก่อนหน้าและคุณต้องการบันทึกของรอบนั้น เพิ่ม -b สำหรับรอบบูตปัจจุบันหรือ --since today:

journalctl -u myapp.service -b --no-pager

ชุดเครื่องมือเต็ม — รอบบูต ระดับความรุนแรง การติดตาม output สด — อยู่ที่สรุปคำสั่ง journalctl นี่คือความจำกล้ามเนื้อชุดเดิม

อีกหนึ่งคู่ที่ควรรู้:

systemctl list-units --failed
systemctl reset-failed myapp.service

อันแรกแสดงทุก unit สีแดงในเครื่อง อันที่สองเคลียร์สถานะ failed หลังคุณแก้เสร็จ — เป็นเรื่องความสวยงาม แต่มันทำให้หน้า status ไม่ร้องห่มร้องไห้โดยไม่มีไฟ

สาเหตุที่พบบ่อยที่สุดคืออะไร?

หลัง log เรียกอาการแล้ว สาเหตุแทบจะเป็นหนึ่งในห้านี้:

log พูดว่าสาเหตุที่น่าจะเป็นวิธีแก้
status=203/EXECพาธ ExecStart= ผิดหรือ interpreter หายพาธแบบ absolute, chmod +x, เช็ก shebang
status=203/EXEC กับสคริปต์สคริปต์จบบรรทัดแบบ CRLF หรือ shebang เพี้ยนdos2unix script.sh แก้บรรทัดแรก
status=1/FAILURE ไม่มี log จากแอปขาด working directory หรือตัวแปรสภาพแวดล้อมตั้ง WorkingDirectory= เพิ่ม Environment=
Permission denieduser อ่านไฟล์หรือ bind พอร์ตไม่ได้แก้ ownership; พอร์ตต่ำกว่า 1024 ต้อง AmbientCapabilities=CAP_NET_BIND_SERVICE หรือ root
Unit is maskedมีคนรัน systemctl masksystemctl unmask myapp.service
แก้ไฟล์ unit “แล้วไม่เกิดอะไรขึ้น”daemon ยังไม่ได้ reloadsystemctl daemon-reload

ตระกูล 203/EXEC สมควรได้รับการกล่าวถึงเป็นพิเศษเพราะมันคือตัวกินบ่ายทั้งบ่าย systemd ไม่ใช้เชลล์ของคุณสั่งรัน ExecStart= แปลว่า:

# ผิด — ไม่มีเชลล์ ~ ไม่เคย expand ไม่มีการหาใน PATH
ExecStart=~/app/run.sh

# ถูก
ExecStart=/opt/app/run.sh

และสคริปต์เองต้อง executable และขึ้นด้วย shebang จริง (#!/bin/bash หรือ #!/usr/bin/env bash) สคริปต์ที่รันจากเทอร์มินัลคุณลื่นไหลแต่ใต้ systemd ล้มด้วย 203 เกือบทุกครั้งเป็นหนึ่งใน: ไม่ executable, จบบรรทัดแบบ CRLF, shebang ชี้ไปไหนไม่เป็น หรือพาธแบบ relative

ทำไมสตาร์ตมือได้แต่สตาร์ตตอนบูตไม่ได้?

บั๊กลำดับคลาสสิก ถ้า log โชว์ความล้มเหลวหลังบูตทันทีแต่ unit สตาร์ตได้ปกติเมื่อคุณสั่ง systemctl start ด้วยมือ service ของคุณกำลังแพ้การแข่งขัน — มันวิ่งไปหาเน็ต ดิสก์ที่ mount หรือฐานข้อมูลก่อนสิ่งเหล่านั้นจะมีตัวตน

วิธีแก้คือประกาศ dependency แทนการเดา:

[Unit]
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.toml

After= จัดลำดับการเริ่ม Wants= ทำให้ systemd ดึง dependency ขึ้นมาจริง ๆ network-online.target ทำงานได้ต่อเมื่อเซอร์วิสรอเน็ตถูกเปิดใช้ใน distro ของคุณ เช็กด้วย systemctl is-enabled NetworkManager-wait-online.service (หรือตัวเทียบของ systemd-networkd) และราว ExecStartPre= คือวิธีให้มันล้มดัง ๆ พร้อมข้อความอ่านได้ แทน stack trace

อีกรูปแบบหนึ่ง: เซอร์วิสสตาร์ตตอนบูตแต่ตายทันที หาสิ่งที่สภาพแวดล้อมแบบโต้ตอบของคุณมีแต่ตอนบูตไม่มี — PATH ต่างกัน virtualenv หรือ HOME ตั้งที่ต้องใช้อย่างชัดเจน:

[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app

ทำไม service ผมไม่เขียน log ลง journal?

ถ้า journalctl -u ไม่เห็นอะไรเลย เช็กสามอย่างตามลำดับ:

  1. StandardOutput= กับ StandardError= ใน unit — ต้องเป็น journal (ค่าเริ่มต้น) หรือ journal+console อาจมีคนตั้งเป็น null
  2. แอปเขียนลงไฟล์แทน stdout systemd จับเฉพาะ stdout/stderr ผู้เขียน log ไฟล์จะ bypass journal ทั้งหมด ชี้แอปไปที่ stdout หรือไม่ก็ไปอ่านไฟล์
  3. ขีดจำกัดการเก็บทำรายการเก่าหาย: journalctl --disk-usage และ SystemMaxUse= ใน /etc/systemd/journald.conf ถ้า journal กำลังขัดขวางอยู่

สำหรับ debug การสตาร์ตเอง ไม่มีอะไรชนะการโยนเชลล์ลงไปในบริบทช่วงบูต:

[Service]
ExecStart=/bin/bash -c 'exec /opt/app/bin/server 2>&1'

หรือกับความลึกลับของแท้ รันคำสั่ง ExecStart= ตัวเป๊ะด้วย user ของ unit ในเชลล์ — ความต่างของสภาพแวดล้อมส่วนใหญ่ปรากฏตัวภายในสิบวินาทีแรก

ทำให้มัน restart เองหลังพังอย่างไร?

นโยบายดีฟอลต์คือ Restart=no: เซอร์วิสที่พังจะนอนตายไป แล้วคุณจะรู้ตัวจากผู้ใช้ แก้เป็นรายเซอร์วิส:

[Service]
Restart=on-failure
RestartSec=5
การตั้งค่าrestart เมื่อ
no (ดีฟอลต์)ไม่เคย
on-failureexit ไม่ใช่ศูนย์ สัญญาณ หมดเวลา
alwaysexit อะไรก็ตาม แม้แต่แบบสะอาด
on-watchdogเฉพาะหมดเวลาของ watchdog

จับคู่ Restart=on-failure กับตัวกำกับอัตราการสตาร์ต เพื่อกันลูปพังไม่ให้ตอกเครื่อง: StartLimitIntervalSec= กับ StartLimitBurst= ในส่วน [Unit] ล้มห้าครั้งในหกสิบวินาทีควรเรียกคนมาดู ไม่ใช่หมุน CPU

ถ้าคุณกำลังต่องานนี้เป็นงานตามตารางแทน daemon ชั่งน้ำหนักcron vs systemd timerก่อน — timer ให้ทั้ง log ลง journal และการเรียง dependency ฟรี ซึ่งเป็นสิ่งที่บทความนี้กำลังไขว่คว้ามาตลอด

เช็กลิสต์ 60 วินาที

  1. systemctl status <unit> — อ่านสถานะกับบรรทัดท้าย
  2. journalctl -u <unit> -n 50 --no-pager — หา error ตัวจริง
  3. 203/EXEC? แก้พาธ shebang สิทธิ์ Permission denied? แก้ user กับ ownership ของไฟล์
  4. ล้มเฉพาะตอนบูต? เพิ่ม After=/Wants=network-online.target และราว ExecStartPre=
  5. แก้ไฟล์ unit แล้ว? systemctl daemon-reload && systemctl restart <unit>
  6. เพิ่ม Restart=on-failure ให้การพังครั้งถัดไปประกาศตัวเองแทนการซ่อน

ช่วงเวลา “systemd ซับซ้อน” ส่วนใหญ่ย่อเหลือประโยคเดียวคือ error อยู่ใน journal มาตลอด อ่านมันก่อนแก้ไฟล์ unit ไม่ใช่หลังจากนั้น

— mrsaynothing

— mrsaynothing

บันทึกหน้างานเรื่อง AI, Linux และ self-hosted

คุยต่อโพสต์นี้บน dev.to dev.to ↗

รับวิธีแก้ฉบับถัดไปทางอีเมล

อีเมลหนึ่งฉบับต่อหนึ่งโพสต์ แก้เสร็จแล้วไปต่อ

self-hosted · ไม่มีบุคคลที่สาม · ยกเลิกได้ในคลิกเดียว

นี่คืออะไร?

Quantization ของ GGUF: ควรเลือกระดับไหน

ถ้าอ่านแล้วชอบ — ผมสร้างงานแบบนี้เป็นอาชีพ จ้างผม