SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-13

ประวัติความเป็นมาของ systemd และเหตุผลที่กลายเป็นมาตรฐาน

เจาะลึกเหตุผลที่ Linux เปลี่ยนจาก SysV init มาใช้ systemd ภายใน 4 ปี วิเคราะห์ข้อจำกัดของ Upstart และ launchd พร้อมสรุปประเด็นโต้แย้งสำคัญที่ยังคงเป็นที่ถกเถียงในปัจจุบัน

เหตุใด systemd จึงเป็นผู้ชนะ

ประวัติของ systemd เริ่มต้นจากสองสิ่งที่ SysV init ไม่สามารถทำได้ SysV init (System V init ซึ่งเป็นระบบเริ่มต้นที่ Linux รับช่วงมาจาก AT&T Unix) ไม่มีวิธีระบุว่าบริการหนึ่งต้องพึ่งพาอะไรบ้าง และไม่มีวิธีทราบว่ากระบวนการใดเป็นของบริการนั้นเมื่อทำงานอยู่ systemd แก้ไขปัญหาทั้งสองประการด้วยฟีเจอร์ของ kernel ที่ shell script เข้าไม่ถึง ได้แก่ control groups สำหรับการติดตามกระบวนการ และการเปิด listening sockets ไว้ล่วงหน้าสำหรับการจัดลำดับ ส่วนที่เหลือของเรื่องราวคือการที่คำตอบทั้งสองประการนี้ขยายตัวไปสู่ส่วนอื่นของ userland ซึ่งเป็นจุดที่เริ่มมีข้อโต้แย้งเกิดขึ้น และข้อโต้แย้งเหล่านั้นหลายประการก็เป็นความจริง

สิ่งที่ SysV init ทำงานจริงในอดีต

บนระบบ SysV นั้น PID 1 (process ID 1 ซึ่งเป็น process แรกที่ kernel เริ่มทำงาน) จะอ่านค่าจาก /etc/inittab เพื่อเลือกระดับการทำงาน (runlevel) และรันสคริปต์ที่เกี่ยวข้องกับระดับนั้น สคริปต์เหล่านี้จะถูกเก็บไว้ใน /etc/init.d/ โดยมี symbolic link ใน /etc/rc3.d/ เป็นตัวกำหนดว่าสคริปต์ใดจะถูกรันและรันตามลำดับใด ดังนั้น /etc/rc3.d/S20nginx จึงชี้ไปยัง /etc/init.d/nginx และถูกเรียกใช้งานพร้อมกับอาร์กิวเมนต์ start

#!/bin/sh
### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
### END INIT INFO

case "$1" in
  start)
    start-stop-daemon --start --quiet --pidfile /run/nginx.pid \
      --exec /usr/sbin/nginx
    ;;
  stop)
    start-stop-daemon --stop --quiet --retry TERM/30/KILL/5 \
      --pidfile /run/nginx.pid
    ;;
esac

ตัวเลข 20 ใน S20nginx เป็นเพียงการระบุตำแหน่ง ไม่ใช่การระบุความสัมพันธ์เชิงพึ่งพา (dependency) มันบอกเพียงว่าสคริปต์นี้จะรันหลังจาก S19 และก่อน S21 แต่มันไม่ได้ระบุเหตุผลไว้ ทำให้ไม่มีระบบใดตรวจสอบได้ และไม่มีระบบใดสามารถรันสคริปต์สองตัวที่ไม่เกี่ยวข้องกันพร้อมกันได้อย่างปลอดภัยหากไม่มีมนุษย์เป็นผู้ยืนยันว่าปลอดภัย

โปรแกรม rc จะรันสคริปต์แต่ละตัวตามลำดับและรอจนกว่าสคริปต์นั้นจะทำงานเสร็จสิ้น หากสคริปต์ใดค้างอยู่ 30 วินาทีเพื่อรอที่อยู่เครือข่าย มันจะทำให้การบูตระบบทั้งหมดหยุดชะงักไป 30 วินาที แม้แต่กับบริการที่ไม่จำเป็นต้องใช้เครือข่ายเลยก็ตาม

ส่วนหัว LSB (Linux Standard Base) ที่อยู่ด้านบนของสคริปต์เหล่านั้นคือความพยายามในการแก้ไขปัญหานี้จากภายใน ต่อมา Debian 6.0 ในปี 2011 ได้กำหนดให้ insserv เป็นค่าเริ่มต้น โดยระบบจะอ่านค่า Required-Start จากทุกสคริปต์เพื่อสร้างกราฟความสัมพันธ์และกำหนดหมายเลข symlink ใหม่ ซึ่งทำให้ Debian สามารถรันสคริปต์ที่เป็นอิสระต่อกันได้พร้อมกันด้วย startpar แม้จะช่วยได้บ้าง แต่ก็ยังไม่สามารถแก้ปัญหาที่ลึกซึ้งกว่านั้นได้ เพราะความสัมพันธ์เชิงพึ่งพายังคงขึ้นอยู่กับการที่สคริปต์ทำงานจบลงเท่านั้น การที่ S20nginx ส่งค่ากลับเป็น 0 หมายความว่าฟังก์ชันใน shell ทำงานเสร็จสิ้นแล้ว แต่มันไม่ได้หมายความว่า nginx กำลังเปิดรับการเชื่อมต่ออยู่จริง

ห้าสิ่งที่สคริปต์ init แก้ไขไม่ได้

  • การเริ่มทำงานแบบขนาน: การเรียงลำดับตามชื่อไฟล์เป็นการกำหนดลำดับแบบตายตัวสำหรับทุกบริการบนเครื่อง ทำให้การบูตระบบช้าเท่ากับผลรวมของเวลาที่ทุกบริการใช้
  • ความพร้อมในการทำงาน: สคริปต์เริ่มทำงานจะจบการทำงานทันทีที่ fork daemon ออกมา ไม่ใช่เมื่อ daemon พร้อมให้บริการคำขอ ทำให้สคริปต์ถัดไปมักจะเริ่มทำงานเร็วเกินไป
  • การกำกับดูแล: daemon จะทำการ fork สองครั้งแล้วให้ parent จบการทำงาน ซึ่งเป็นการตัดขาดจาก terminal และเปลี่ยน parent ไปเป็น PID 1 ทำให้ init เห็นว่า child จบการทำงานและไม่มีความเชื่อมโยงที่เชื่อถือได้กับกระบวนการที่ยังคงทำงานอยู่
  • การเริ่มทำงานตามความต้องการ: inetd (internet super-server) สามารถเรียกใช้ daemon เมื่อมีการเชื่อมต่อเข้ามาได้ แต่เป็นระบบแยกต่างหากที่มีไฟล์คอนฟิกูเรชันของตัวเอง และไม่ได้จัดการเรื่องลำดับการทำงานของบริการอื่น ๆ ในระหว่างบูต
  • การควบคุมทรัพยากร: ไม่มีสิ่งใดในสคริปต์ init ที่สามารถจำกัดหน่วยความจำหรือส่วนแบ่ง CPU ของบริการได้ ulimit ที่ใช้กับกระบวนการเดียวและ nice ที่ส่งผลต่อตัวจัดตารางเวลาเท่านั้น ทำให้กระบวนการลูกที่ทำงานผิดปกติของบริการหนึ่งดูเหมือนกระบวนการทั่วไปบนเครื่อง

ช่องว่างในการกำกับดูแลเป็นปัญหาที่ส่งผลกระทบต่อการทำงานรายวัน ไฟล์ PID เป็นวิธีแก้ปัญหาชั่วคราว: daemon จะเขียน ID ของกระบวนการลงใน /run/nginx.pid และฟังก์ชันหยุดการทำงานจะอ่านไฟล์นั้นกลับมา หาก daemon ถูกสั่ง kill อย่างรุนแรง ไฟล์จะยังคงค้างอยู่ จากนั้น kernel จะนำหมายเลขนั้นไปใช้ใหม่กับงานอื่น และ start-stop-daemon --stop --pidfile จะส่งสัญญาณไปยังสิ่งที่ครอบครองหมายเลขนั้นอยู่ในปัจจุบัน ไฟล์ PID ที่ล้าสมัยคือสาเหตุที่ทำให้สคริปต์ init สั่ง kill กระบวนการผิดตัว

launchd แก้ไขปัญหาเรื่อง socket ได้เป็นรายแรก

Apple ปล่อย launchd ออกมาใน Mac OS X 10.4 เมื่อปี 2005 โดยเขียนขึ้นโดย Dave Zarzycki กระบวนการทำงานเพียงหนึ่งเดียวนี้เข้ามาแทนที่ init, rc, xinetd, crond และ watchdogd

แนวคิดที่ควรค่าแก่การนำมาใช้คือ socket activation โดย launchd จะสร้าง listening socket ทุกตัวขึ้นมาก่อน จากนั้นจึงเริ่มการทำงานของ daemon ต่างๆ หากไคลเอนต์เชื่อมต่อไปยัง daemon ที่ยังไม่ได้เริ่มทำงาน จะไม่ได้รับข้อความปฏิเสธการเชื่อมต่อ เนื่องจากเคอร์เนลจะพักการเชื่อมต่อนั้นไว้ใน backlog queue ของ socket จนกว่า daemon จะเรียกใช้ accept() การกำหนดลำดับการทำงานระหว่าง daemon สองตัวจึงไม่จำเป็นต้องให้มนุษย์เป็นผู้กำหนดอีกต่อไป เพราะ socket จะเป็นตัวจัดการให้เอง

launchd ถูกสร้างขึ้นบน Mach IPC (inter-process communication) ซึ่งเป็นส่วนหนึ่งของ XNU kernel ของ Apple และไม่มีสิ่งที่เทียบเท่าใน Linux การพอร์ตโค้ดจึงเป็นเรื่องที่ไม่สามารถทำได้จริง อย่างไรก็ตาม แนวคิดนี้ก็ได้ถูกนำไปประยุกต์ใช้ในภายหลัง

Upstart เปลี่ยนให้เหตุการณ์ (event) เป็นหน่วยการทำงาน

Upstart ของ Canonical ซึ่งเขียนโดย Scott James Remnant ถูกปล่อยออกมาใน Ubuntu 6.10 เมื่อเดือนตุลาคม 2006 โดย Fedora รุ่น 9 ถึง 14 รวมถึง RHEL 6 และ Chrome OS ต่างก็ใช้งานระบบนี้ มันเข้ามาแทนที่ runlevel ด้วยระบบเหตุการณ์ (event) โดยที่ job จะเป็นตัวกำหนดว่าเหตุการณ์ใดควรเริ่มหรือหยุดการทำงานของมัน

# /etc/init/example.conf
start on filesystem and net-device-up IFACE!=lo
stop on runlevel [!2345]
respawn
respawn limit 10 5
exec /usr/local/bin/exampled

ปัญหา 2 ประการปรากฏขึ้นเมื่อจำนวน job เพิ่มมากขึ้น ประการแรกคือเรื่องทิศทางของความสัมพันธ์ job จะระบุว่า "เริ่มทำงานเมื่อสิ่งนี้เกิดขึ้น" ทำให้ความรู้เรื่องความสัมพันธ์ระหว่างบริการถูกเก็บไว้ในไฟล์ที่ไม่ถูกต้อง กล่าวคือ บริการหนึ่งรู้ว่ามันต้องการอะไร แต่ไม่สามารถรู้ได้ว่าในอนาคตจะมีใครต้องการมันบ้าง การเพิ่มบริการใหม่จึงมักหมายถึงการต้องแก้ไข job เดิมเพื่อให้มันส่งเหตุการณ์ใหม่ๆ ออกมา

ประการที่สองคือเรื่องการติดตามสถานะ Upstart ติดตาม daemon ที่มีการ fork โดยการนับการเรียก fork() ด้วย ptrace ซึ่งคุณต้องกำหนดค่าไว้เป็น expect fork หรือ expect daemon หากคาดเดาจำนวนการ fork ผิดพลาด Upstart จะคอยดูแลกระบวนการที่จบการทำงานไปแล้ว หรือรอคอยการ fork ที่เกิดขึ้นไปแล้ว อาการที่พบคือ initctl start ค้างโดยไม่มีข้อผิดพลาดแจ้งเตือน ซึ่งไฟล์ job ไม่ได้เตรียมช่องทางให้คุณอธิบายสาเหตุของปัญหานี้ได้เลย

นอกจากนี้ Upstart ยังกำหนดให้ผู้ร่วมพัฒนาต้องลงนามในข้อตกลงผู้ร่วมพัฒนาของ Canonical แม้สิ่งนี้จะไม่ใช่ข้อผิดพลาดทางวิศวกรรม แต่ก็มีผลต่อการกำหนดว่าใครบ้างที่จะเข้ามามีส่วนร่วมในการพัฒนาโครงการนี้

การทบทวน PID 1 ใหม่, เมษายน 2010

เมื่อวันที่ 30 เมษายน 2010 Lennart Poettering ได้เผยแพร่บทความในหัวข้อ "Rethinking PID 1" โดยมี Kay Sievers ร่วมงานในโปรเจกต์นี้ด้วย ข้อโต้แย้งดังกล่าวประกอบด้วย 4 ส่วนหลัก:

  • เริ่มต้นบริการให้น้อยลง บริการจำนวนมากสามารถรอจนกว่าจะมีสิ่งใดสิ่งหนึ่งเรียกใช้งานจริง
  • เลิกกำหนดลำดับการทำงานในกรณีที่ socket สามารถบ่งบอกลำดับได้เอง ให้เปิด socket ทั้งหมดในรอบเดียว จากนั้นจึงเริ่มบริการทั้งหมดพร้อมกัน
  • ติดตามกระบวนการทำงานด้วย control groups แทนการใช้ PID files
  • อธิบายบริการในรูปแบบไฟล์ประกาศ (declarative file) เพื่อให้คำอธิบายชุดเดียวสามารถใช้งานได้กับทุก distribution

การปล่อยซอฟต์แวร์รุ่นแรกเกิดขึ้นในปีเดียวกัน Fedora 14 ได้รวม systemd เป็นตัวเลือกในเดือนพฤศจิกายน 2010 และ Fedora 15 ได้กำหนดให้เป็นค่าเริ่มต้นในเดือนพฤษภาคม 2011

เหตุผลที่ cgroups ทำให้การดูแลระบบมีความน่าเชื่อถือ

cgroup (control group) คือฟีเจอร์ของ kernel สำหรับจัดกลุ่ม process ซึ่งถูกรวมเข้ากับ Linux 2.6.24 ในปี 2008 โดย systemd จะนำทุก service ไปไว้ใน cgroup ของตนเอง กระบวนการลูกจะสืบทอด cgroup มาจากกระบวนการแม่ และ process ที่ไม่มีสิทธิ์พิเศษจะไม่สามารถย้ายตัวเองออกจากกลุ่มได้ ดังนั้นการทำ double forking จึงไม่สามารถซ่อนสิ่งใดได้อีกต่อไป PID 1 จะถือครองชุดของ process ที่เป็นของ unit นั้นๆ ไว้อย่างแม่นยำตลอดเวลา การหยุด service จึงหมายถึงการสั่งยุติทุกอย่างที่อยู่ใน cgroup นั้น ซึ่งเป็นสิ่งที่ KillMode=control-group ทำโดยค่าเริ่มต้น

systemctl status จะแสดงกลุ่มดังกล่าวออกมา:

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-08-11 09:14:22 UTC; 3min ago
   Main PID: 1042 (nginx)
      Tasks: 3 (limit: 4653)
     Memory: 6.1M (peak: 7.4M)
     CGroup: /system.slice/nginx.service
             ├─1042 "nginx: master process /usr/sbin/nginx"
             ├─1043 "nginx: worker process"
             └─1044 "nginx: worker process"

บล็อกข้อมูลข้างต้นคือคำตอบทั้งหมดสำหรับปัญหาไฟล์ PID ค้าง (stale PID file) เนื่องจากไม่มีไฟล์ใดให้ต้องค้างอีกต่อไป เพราะรายการดังกล่าวคือสถานะที่อยู่ใน kernel

โครงสร้างแบบเดียวกันนี้ยังรองรับการจำกัดทรัพยากร เนื่องจาก cgroups ถูกสร้างขึ้นเพื่อการบันทึกข้อมูลก่อนที่ใครจะนำมาใช้เพื่อการติดตาม process เสียอีก MemoryMax=, CPUQuota= และ TasksMax= สามารถกำหนดได้ในบรรทัดเดียว การจำกัดหน่วยความจำและ CPU แบบเข้มงวดให้กับ service ในปัจจุบันสามารถทำได้ผ่านไฟล์ drop-in ซึ่งหากย้อนไปในปี 2009 สิ่งนี้ต้องอาศัยการเขียน patch ให้กับ shell script ซึ่งในขณะนั้นยังไม่มีใครทำขึ้นมา

เหตุผลที่ทุก distribution เปลี่ยนมาใช้ในช่วงปี 2011 ถึง 2015

  • Fedora 15, พฤษภาคม 2011
  • openSUSE 12.1, พฤศจิกายน 2011
  • Mageia 2, พฤษภาคม 2012
  • Arch Linux, ค่าเริ่มต้นสำหรับการติดตั้งใหม่ตั้งแต่ตุลาคม 2012
  • RHEL 7, มิถุนายน 2014
  • SLES 12, ตุลาคม 2014
  • Debian 8, เมษายน 2015
  • Ubuntu 15.04, เมษายน 2015

เหตุผลส่วนใหญ่เป็นเรื่องทางเทคนิคทั่วไป ซึ่งเป็นสาเหตุที่ทำให้การเปลี่ยนผ่านเกิดขึ้นอย่างรวดเร็ว

  • unit file หนึ่งไฟล์สามารถทำงานได้บนทุก distribution ดังนั้นโปรเจกต์ต้นทางจึงเริ่มจัดส่งไฟล์ .service มาให้ และ distribution ต่างๆ ก็เลิกดูแล shell script แยกตามแพ็กเกจและตามรุ่น
  • การติดตาม desktop session ย้ายไปใช้ systemd-logind หลังจากที่ ConsoleKit หยุดการพัฒนาไปในช่วงปี 2012 GNOME จำเป็นต้องใช้ logind ดังนั้น distribution ที่ไม่มี systemd จึงต้องหาตัวแทนมาทดแทน ตัวแทนที่ว่านั้นคือ elogind ซึ่งเป็นการแยก logind ของ systemd ออกมาดูแลต่างหาก
  • udev ซึ่งเป็นตัวจัดการอุปกรณ์ ถูกรวมเข้ากับ source tree ของ systemd ในเดือนเมษายน 2012 ทำให้ distribution ที่ใช้ udev ต้องติดตาม repository ของ systemd ไปด้วย Gentoo จึงทำการ fork eudev ออกมาเพื่อตอบสนองต่อการเปลี่ยนแปลงนี้
  • คอนเทนเนอร์ทำให้การติดตาม process ที่เชื่อถือได้และการจำกัดทรัพยากรต่อบริการมีความสำคัญมากขึ้น เนื่องจากทั้งสองอย่างเป็นฟีเจอร์ของ cgroup คำถามที่ว่า supervisor ตัวใดเป็นเจ้าของ process ในคอนเทนเนอร์ยังคงเป็นประเด็นสำคัญเสมอเมื่อคุณ ทำให้ Docker Compose stack กลับมาทำงานหลังรีบูต

การตัดสินใจของ Debian เป็นเหตุการณ์ที่ได้รับความสนใจมากที่สุด คณะกรรมการด้านเทคนิคลงมติในเดือนกุมภาพันธ์ 2014 โดยคะแนนเสียงเสมอกัน และประธานคณะกรรมการ Bdale Garbee ได้ลงคะแนนเสียงชี้ขาดให้เลือกใช้ systemd ไม่กี่วันหลังจากนั้น Ubuntu ประกาศว่าจะเปลี่ยนมาใช้ตาม Debian แทนที่จะใช้ Upstart ต่อไป กลุ่มนักพัฒนา Debian ได้ fork distribution ออกมาเป็น Devuan ในเดือนพฤศจิกายน 2014 และปล่อย Devuan 1.0 ในเดือนพฤษภาคม 2017

ข้อโต้แย้งที่นำเสนออย่างเป็นธรรม

ขอบเขตของระบบ. ปัจจุบันมีหนึ่งโปรเจกต์ที่รวมเอา PID 1, logging daemon, การจัดการ session การล็อกอิน, ตัวจัดการอุปกรณ์, network configuration daemon, DNS (domain name system) resolver, NTP (network time protocol) client, container runner และ boot loader ไว้ด้วยกัน ข้อแก้ต่างที่มักได้ยินว่าสิ่งเหล่านี้เป็นไบนารีแยกส่วนที่คุณไม่จำเป็นต้องติดตั้งนั้นเป็นความจริง แต่ไม่ได้ตอบโจทย์ข้อโต้แย้ง เมื่อ desktop จำเป็นต้องใช้ logind และ logind ถูกแยกออกจากซอร์สทรีของ systemd ทางเลือกก็ไม่ได้เป็นอิสระอีกต่อไป นั่นคือสิ่งที่คำว่า "การผูกติด" (coupling) หมายถึงในข้อถกเถียงนี้ และมันก็ได้เกิดขึ้นจริงแล้ว

บันทึกแบบไบนารี. journald เขียนบันทึกในรูปแบบไบนารีที่มีดัชนีแทนที่จะเป็นข้อความธรรมดา คุณจะได้รับสิ่งที่ข้อความธรรมดาให้ไม่ได้ เช่น การกรองข้อมูลตาม unit และตามระดับความสำคัญ, ฟิลด์ที่มีโครงสร้าง และข้อมูลเมตาที่โปรแกรมผู้ส่งไม่สามารถปลอมแปลงได้ เพราะ journald บันทึก unit และ cgroup ด้วยตัวเอง journalctl -u nginx -p err --since "-1h" ช่วยให้คุณไม่ต้องใช้ grep ร่วมกับ regular expression ของวันที่อีกต่อไป แต่ก็มีต้นทุนที่ต้องจ่ายเช่นกัน บนเครื่องที่ไม่สามารถบูตได้ คุณไม่สามารถอ่าน log ด้วย less จาก rescue shell ได้โดยตรง คุณต้องชี้ /run/log/journal ไปยังดิสก์ที่ mount ไว้แทน:

sudo journalctl --directory /mnt/var/log/journal --boot -1 --priority err

มีกับดักที่สองที่มักทำให้ผู้ใช้พลาดเป็นครั้งแรก journald จะเก็บ log ไว้ใน /run/log/journal ซึ่งเป็นหน่วยความจำ เว้นแต่ว่า /var/log/journal จะมีอยู่จริง บนเครื่องที่ไม่มีไดเรกทอรีดังกล่าว journalctl -b -1 จะไม่มีข้อมูลให้ดูหลังจากรีบูต ซึ่งเป็นช่วงเวลาที่คุณต้องการมันมากที่สุด ให้ตรวจสอบและแก้ไขดังนี้:

journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

journalctl --disk-usage ควรจะแสดงรายการ log ที่ถูกจัดเก็บไว้ภายใต้ /var/log/journal หากคุณต้องการข้อความธรรมดาด้วย ให้ตั้งค่า ForwardToSyslog=yes ใน /etc/systemd/journald.conf และติดตั้ง rsyslog ไว้

ความสามารถในการดีบั๊กการบูต. เมื่อ unit ค้าง คอนโซลจะแสดงเพียงบรรทัดเดียวและไม่มีอะไรเพิ่มเติม:

[  *** ] A start job is running for Wait for Network to be Configured (1min 32s / no limit)

เครื่องมือสำหรับการตรวจสอบเชิงลึกนั้นมีอยู่จริง: systemctl list-jobs ในขณะที่มันค้างอยู่, systemd-analyze blame และ systemd-analyze critical-chain หลังจากนั้น และ systemd.log_level=debug ในบรรทัดคำสั่งของเคอร์เนล ข้อโต้แย้งที่เป็นธรรมคือ init script แบบเดิมสามารถอ่านจากบนลงล่างได้โดยทุกคนที่รู้ sh ในขณะที่ unit ที่ค้างอยู่จำเป็นต้องรู้ว่าต้องใช้คำสั่งใดจากนับสิบคำสั่ง นี่คือต้นทุนที่แท้จริง ซึ่งผู้ดูแลระบบทุกคนต้องจ่ายหนึ่งครั้ง และเป็นสิ่งที่ผู้ดูแลระบบจำนวนมากต้องเผชิญพร้อมกัน

ค่าเริ่มต้นที่เปลี่ยนแปลงสำหรับทุกคน. systemd 230 ในปี 2016 ได้เปลี่ยนค่าเริ่มต้นของ logind ทำให้ process ของผู้ใช้ที่ค้างอยู่ถูกสั่งยุติการทำงานเมื่อ logout ส่งผลให้ session ของ tmux และ screen ที่แยกตัวออกมาต้องจบลงเมื่อ session ที่เริ่มต้นมันสิ้นสุดลง ดิสทริบิวชันต่างๆ ได้ส่งมอบ KillUserProcesses=no ใน /etc/systemd/logind.conf และคำตอบที่ได้รับการสนับสนุนคือ loginctl enable-linger <user> ค่าเริ่มต้นเพียงค่าเดียวในโปรเจกต์เดียวได้เปลี่ยนพฤติกรรมที่ผู้คนนับล้านพึ่งพา ซึ่งนี่คือความหมายในทางปฏิบัติของคำว่า "การรวม userland ไว้ในที่เดียวมากเกินไป"

การพึ่งพาค่าเริ่มต้นคือพื้นผิวการโจมตี. ในเดือนมีนาคม 2024 ช่องโหว่ใน xz-utils มุ่งเป้าไปที่ sshd บน Debian และ Ubuntu โดยที่ OpenSSH ต้นทางไม่ได้เชื่อมโยงกับ libsystemd แต่ดิสทริบิวชันเหล่านั้นได้แพตช์เข้าไปเพื่อให้ sshd สามารถรายงานสถานะความพร้อมไปยัง systemd ได้ และ libsystemd ก็ดึงเอา liblzma เข้ามา ซึ่งเป็นที่ที่ช่องโหว่ซ่อนอยู่ โปรโตคอลความพร้อมนั้นเป็นเพียง datagram เดียวที่ส่งไปยังซ็อกเก็ตที่ระบุใน $NOTIFY_SOCKET ดังนั้นจึงไม่จำเป็นต้องใช้ไลบรารีใดๆ เลย การตอบสนองของ systemd คือการโหลดไลบรารีบีบอัดด้วย dlopen เพื่อไม่ให้มีการเชื่อมโยงโดยค่าเริ่มต้นอีกต่อไป ข้อผิดพลาดในลักษณะเดียวกันนี้เคยเกิดขึ้นในปี 2017 โดยค่า User= ที่ขึ้นต้นด้วยตัวเลขถูกมองว่าไม่ถูกต้อง และ unit จะรันด้วยสิทธิ์ root แทนที่จะล้มเหลว ทำให้การพิมพ์ผิดกลายเป็นการยกระดับสิทธิ์ เวอร์ชันที่ใหม่กว่าจึงปฏิเสธที่จะเริ่ม unit ดังกล่าว

ประวัติศาสตร์ที่ prompt ของ systemctl บนระบบของคุณ

ปัญหาทุกอย่างที่กล่าวมาข้างต้นกลายเป็นคำสั่งในไฟล์ที่คุณสามารถอ่านได้แล้วในปัจจุบัน

  • การบูตแบบลำดับกลายเป็น After= และ Wants= และ systemd-analyze critical-chain จะแสดงให้เห็นว่าสิ่งใดที่ทำให้การบูตของคุณล่าช้าจริง
  • ความพร้อมใช้งานกลายเป็น Type=notify ซึ่งบริการจะเขียน READY=1 ลงใน $NOTIFY_SOCKET เมื่อพร้อมให้บริการแล้ว Type=forking พร้อมด้วย PIDFile= ยังคงมีอยู่สำหรับ daemon รุ่นเก่า และเป็นประเภทที่ล้มเหลวด้วย start operation timed out. Terminating. เมื่อไฟล์ PID ไม่ปรากฏขึ้น
  • การกำกับดูแลกลายเป็น cgroup ดังนั้น Restart=on-failure พร้อมด้วย RestartSec= จึงเข้ามาแทนที่สคริปต์ wrapper และ StartLimitBurst= จะช่วยหยุดวงจรการ crash ที่ทำงานไม่สิ้นสุด
  • inetd กลายเป็นหน่วย .socket ที่วางอยู่ข้างหน่วย .service
  • ulimit กลายเป็น MemoryMax=, CPUQuota= และ TasksMax=
  • บรรทัด su - appuser -c ใน init script กลายเป็น User=, NoNewPrivileges=yes และ ProtectSystem=strict ดังนั้น การรันบริการด้วยผู้ใช้ที่ไม่มีสิทธิ์พิเศษ จึงเป็นรูปแบบเริ่มต้นของ unit แทนที่จะเป็นงานเพิ่มเติม
[Unit]
Description=Example API
Wants=network-online.target
After=network-online.target postgresql.service
Requires=postgresql.service

[Service]
Type=notify
ExecStart=/usr/local/bin/exampled
Restart=on-failure
RestartSec=2
MemoryMax=512M
CPUQuota=50%
TasksMax=128
User=exampled
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
StateDirectory=exampled

[Install]
WantedBy=multi-user.target

บรรทัดหนึ่งในไฟล์นั้นคือข้อผิดพลาดที่ทุกคนเคยทำอย่างน้อยหนึ่งครั้ง Requires=postgresql.service เป็นข้อกำหนด ไม่ใช่การจัดลำดับ: มันระบุว่า unit ของคุณจะล้มเหลวหาก Postgres ล้มเหลว และไม่ได้ระบุว่าต้องเริ่ม Postgres ก่อน หากไม่มี After=postgresql.service ทั้งสองจะเริ่มทำงานพร้อมกัน และบริการของคุณจะพยายามเชื่อมต่อกับพอร์ตที่ยังไม่มีการเปิดรับฟัง ทั้งสองอย่างถูกแยกออกจากกันโดยเจตนา เพราะบางครั้งคุณอาจต้องการอย่างหนึ่งโดยไม่ต้องการอีกอย่างหนึ่ง ProtectSystem=strict จะ mount ระบบไฟล์แบบอ่านอย่างเดียวสำหรับบริการนี้ ซึ่งเป็นเหตุผลว่าทำไม StateDirectory= จึงต้องมีอยู่: มันให้เส้นทางที่เขียนได้หนึ่งเส้นทางแก่บริการภายใต้ /var/lib

จุดที่ชัดเจนที่สุดในการเห็นปี 2005 บนเซิร์ฟเวอร์ปี 2026 คือ SSH บน Ubuntu 24.04 ซึ่งมาพร้อมกับ systemd 255 ณ เดือนสิงหาคม 2026 ssh.service ถูกเปิดใช้งานผ่าน socket โดยค่าเริ่มต้น: ssh.socket จะถือ socket ที่เปิดรับฟังอยู่ และ sshd จะเริ่มทำงานเมื่อมีการเชื่อมต่อเข้ามา ดังนั้น Port 2222 ใน /etc/ssh/sshd_config จึงไม่มีผล เพราะ sshd ไม่ใช่กระบวนการที่เปิดพอร์ตนั้น การเปลี่ยนแปลงต้องทำใน socket unit

sudo systemctl edit ssh.socket
[Socket]
ListenStream=
ListenStream=2222

ค่า ListenStream= ที่ว่างเปล่าจะล้างค่าที่สืบทอดมาจาก unit ที่ติดตั้งมา หากคุณละไว้ คุณจะได้ทั้งสองพอร์ต เพราะ systemd จะผนวกเข้ากับรายการแทนที่จะแทนที่รายการเดิม จากนั้นให้ใช้คำสั่งและตรวจสอบ โดยต้องเปิด SSH session ที่สองทิ้งไว้ตลอดเวลา:

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222

ss ควรแสดง socket หนึ่งรายการบนพอร์ต 2222 ที่เป็นเจ้าของโดย systemd ไม่ใช่โดย sshd นั่นคือการออกแบบแบบ launchd ในอีกยี่สิบปีต่อมาบน VPS ของคุณ หากคุณชอบพฤติกรรมแบบเก่า sudo systemctl disable --now ssh.socket ตามด้วย sudo systemctl enable --now ssh.service จะทำให้คุณได้ sshd ที่ทำงานยาวนานและอ่าน Port จากไฟล์ config ของมันเองอีกครั้ง

รายละเอียดเหล่านี้ที่คุณพบจะขึ้นอยู่กับรุ่นที่คุณใช้งาน ดังนั้นจึงคุ้มค่าที่จะทราบ ความแตกต่างระหว่าง Ubuntu รุ่น LTS และรุ่น interim ก่อนที่คุณจะวางแผนอัปเกรด ในการจัดการเครื่องมากกว่าหนึ่งเครื่อง ความจริงที่ว่าไฟล์ unit เหมือนกันทุกประการคือเหตุผลที่ การจัดการเซิร์ฟเวอร์หลายเครื่องจากที่เดียว กลายเป็นปัญหาด้านการกำหนดค่าแทนที่จะเป็นปัญหาด้าน shell scripting และเมื่อคุณเขียน unit ของคุณเอง คู่ของ service และ timer จะทำงานที่คุณเคยแยกไว้ระหว่าง init script และบรรทัด cron ในปี 2009

FAQ

ทำไมลีนุกซ์ดิสทริบิวชันต่างๆ ถึงเปลี่ยนจาก SysV init มาเป็น systemd?

ด้วยเหตุผลทางวิศวกรรม 2 ประการและเหตุผลด้านการดูแลรักษา 1 ประการ SysV init จัดลำดับบริการตามชื่อไฟล์ซึ่งเป็นเพียงตำแหน่ง ไม่ใช่ความสัมพันธ์เชิงพึ่งพา (dependency) และมันไม่สามารถติดตาม daemon ที่แตกตัว (fork) ออกจากกระบวนการหลักได้ ส่งผลให้ไฟล์ PID ที่ค้างอยู่ส่งสัญญาณฆ่ากระบวนการผิดตัว systemd แก้ปัญหาการจัดลำดับด้วย socket activation และคำสั่งระบุ dependency และแก้ปัญหาการติดตามด้วย control groups ส่วนเหตุผลด้านการดูแลรักษาคือความเร็ว: ไฟล์ unit ไฟล์เดียวสามารถใช้ได้กับทุกดิสทริบิวชัน ดังนั้นโปรเจกต์ต้นทางจึงส่งมอบไฟล์ .service มาให้ และผู้ดูแลดิสทริบิวชันก็ไม่ต้องเขียน shell script แยกสำหรับแต่ละแพ็กเกจอีกต่อไป Fedora 15 เปลี่ยนมาใช้ในเดือนพฤษภาคม 2011 และ Ubuntu 15.04 เป็นดิสทริบิวชันใหญ่รายสุดท้ายที่เปลี่ยนในเดือนเมษายน 2015

systemd เป็นไบนารีขนาดใหญ่ไฟล์เดียวใช่หรือไม่?

ไม่ใช่ ซอร์สโค้ดถูกคอมไพล์ออกมาเป็นโปรแกรมแยกกันหลายตัว PID 1 คือ /usr/lib/systemd/systemd ในขณะที่ journald, logind และ udevd เป็นกระบวนการแยกต่างหากที่มีไบนารีของตัวเอง ให้รัน ls /usr/lib/systemd/ เพื่อดูรายการเหล่านี้บนเครื่องของคุณ ข้อวิจารณ์ที่ยังคงมีอยู่เป็นเรื่องของการผูกติดกันในการปล่อยเวอร์ชัน (release coupling) มากกว่าขนาดของไบนารี โปรแกรมเหล่านี้ถูกปล่อยออกมาพร้อมกันและใช้ interface ภายในร่วมกัน ดิสทริบิวชันจึงมักนำไปใช้เป็นชุด และซอฟต์แวร์อย่าง GNOME ก็เจาะจงเรียกใช้ logind

ฉันยังสามารถรันลีนุกซ์โดยไม่มี systemd ได้หรือไม่?

ได้ Devuan มาพร้อมกับ sysvinit, Gentoo ใช้ OpenRC เป็นค่าเริ่มต้น, Void ใช้ runit, Alpine ใช้ busybox init ร่วมกับ OpenRC และ Slackware ยังคงใช้สคริปต์สไตล์ BSD สิ่งที่ต้องแลกคือภาระงานด้านความเข้ากันได้ ซอฟต์แวร์เดสก์ท็อปที่ต้องการ logind จำเป็นต้องใช้ elogind ซึ่งเป็น logind ของ systemd ที่ถูกดูแลแยกเป็นแพ็กเกจอิสระ และซอฟต์แวร์เซิร์ฟเวอร์จำนวนมากขึ้นเรื่อยๆ ในปัจจุบันส่งมอบมาพร้อมกับไฟล์ .service เท่านั้น ดังนั้นคุณจึงต้องเขียนและดูแลสคริปต์เริ่มต้นด้วยตัวเอง

ทำไม journal ถึงเป็นไบนารีแทนที่จะเป็นไฟล์ข้อความธรรมดา?

เพราะ journald จัดเก็บข้อมูลแบบมีโครงสร้างพร้อมดัชนี ซึ่งช่วยให้สามารถกรองข้อมูลตาม unit, ตามระดับความสำคัญ และตาม metadata ที่โปรแกรมผู้ส่งไม่สามารถปลอมแปลงได้ journald จะบันทึก unit, cgroup และ UID จริงด้วยตัวเองแทนที่จะเชื่อข้อความใน log สิ่งที่ต้องแลกคือคุณต้องใช้ journalctl ในการอ่าน รวมถึงการอ่านจากระบบกู้คืน (rescue system) ซึ่งคุณต้องชี้ไปยังดิสก์ที่ mount ไว้ด้วย journalctl --directory /mnt/var/log/journal หากคุณต้องการไฟล์ข้อความด้วย ให้ตั้งค่า ForwardToSyslog=yes ใน /etc/systemd/journald.conf

อะไรมาแทนที่การแก้ไขสคริปต์ใน /etc/init.d ของฉัน?

ไฟล์ drop-in ห้ามแก้ไข unit ใน /usr/lib/systemd/system/ เพราะการอัปเกรดแพ็กเกจจะเขียนทับไฟล์เดิม ให้รัน sudo systemctl edit nginx.service แล้ว systemd จะสร้าง /etc/systemd/system/nginx.service.d/override.conf ซึ่งจะถูกนำไปรวมกับ unit ของแพ็กเกจ systemctl cat nginx.service จะแสดงผลลัพธ์ที่รวมแล้ว และ systemd-delta จะแสดงรายการการแก้ไขทั้งหมดที่มีในเครื่อง หลังจากแก้ไขด้วยตนเองทุกครั้ง ให้รัน sudo systemctl daemon-reload ไม่เช่นนั้นคำสั่งถัดไปจะแสดงข้อความ Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk.

#systemd#linux#init#sysvinit#history