2 คะแนน โดย GN⁺ 2025-08-24 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Nitro คือ ตัวควบคุมดูแลโปรเซสและระบบ init ขนาดจิ๋วมาก ที่ใช้งานได้กับทั้งระบบฝังตัว เซิร์ฟเวอร์ เดสก์ท็อป และคอนเทนเนอร์
  • เก็บสถานะของระบบไว้ใน RAM เท่านั้น จึงทำงานได้อย่างราบรื่นแม้บนระบบไฟล์แบบอ่านอย่างเดียว พร้อมมอบ สถาปัตยกรรมแบบ event-driven ที่รวดเร็วและมีประสิทธิภาพ
  • รูปแบบการตั้งค่าคือโครงสร้างไดเรกทอรีสคริปต์ที่เรียบง่าย จึงจัดการบริการได้โดยไม่ต้องมีไฟล์ตั้งค่าที่ซับซ้อนหรือขั้นตอน build เพิ่มเติม
  • รองรับ บริการแบบพารามิเตอร์ การรีสตาร์ตที่ทนทาน และความสามารถด้าน logging ที่เชื่อถือได้สำหรับแต่ละบริการ รวมถึง ฟีเจอร์ที่เหมาะกับคอนเทนเนอร์และสภาพแวดล้อมแบบฝังตัว
  • รับประกัน ความยืดหยุ่นและการควบคุมระดับสูง ด้วย การควบคุมจากระยะไกลผ่านเครื่องมือ nitroctl และการควบคุมการทำงานด้วยสัญญาณ

ภาพรวม

Nitro คือ ตัวควบคุมดูแลโปรเซสขนาดจิ๋วมาก ที่สามารถใช้เป็น pid 1 บน Linux ได้

ขอบเขตการใช้งานหลักมีดังนี้

  • init สำหรับเครื่อง Linux หลากหลายประเภท เช่น ระบบฝังตัว เดสก์ท็อป และเซิร์ฟเวอร์
  • init ของ Linux initramfs
  • init สำหรับสภาพแวดล้อมคอนเทนเนอร์ เช่น Docker/Podman/LXC/Kubernetes
  • เดมอน supervision ที่ทำงานได้โดยไม่ต้องใช้สิทธิ์บนระบบ POSIX

การตั้งค่าใช้โครงสร้างสคริปต์แบบอิงไดเรกทอรี โดยตำแหน่งเริ่มต้นคือ /etc/nitro

ข้อกำหนด

  • ต้องการการรองรับ Unix socket ของเคอร์เนล
  • ต้องมี tmpfs หรือ ไดเรกทอรี /run ที่เขียนได้

ข้อดีเมื่อเทียบกับระบบอื่น

  • ข้อมูลสถานะทั้งหมด ถูกเก็บไว้ใน RAM เท่านั้น จึงทำงานได้บน root filesystem แบบอ่านอย่างเดียวโดยไม่ต้องใช้เทคนิคพิเศษ
  • ให้ประสิทธิภาพด้วย รูปแบบการทำงานแบบ event-driven ที่ไม่มีการ polling
  • ไม่มีการจัดสรรหน่วยความจำแบบไดนามิก ระหว่างรันไทม์
  • ไม่มีการใช้ file descriptor เพิ่มขึ้นอย่างไม่จำกัด
  • ต้องการเพียง ไบนารีแบบ self-contained ตัวเดียว (อาจมีไบนารีควบคุมเพิ่มเป็นตัวเลือก)
  • ไม่ต้องแปลงหรือคอมไพล์ไฟล์ตั้งค่า บริการเป็นเพียง ไดเรกทอรีธรรมดาที่มีสคริปต์อยู่ภายใน
  • รองรับ การรีสตาร์ตบริการและสายโซ่ logging
  • ทำงานได้ตามปกติแม้นาฬิการะบบจะไม่แม่นยำ
  • สามารถรันผ่าน /etc/ttys บน FreeBSD ได้
  • สามารถสร้าง static binary ขนาดเล็กมากได้เมื่อใช้ musl libc

การจัดการบริการ

  • แต่ละไดเรกทอรีบริการ (ค่าเริ่มต้นอยู่ภายใน /etc/nitro) อาจมีไฟล์ต่อไปนี้

    • setup: สคริปต์ (ทางเลือก) ที่รันก่อนเริ่มบริการ จะเริ่มบริการได้ก็ต่อเมื่อจบแบบปกติ (0) เท่านั้น
    • run: สคริปต์สำหรับการทำงานของบริการ ตราบใดที่ยังไม่จบจะถือว่าบริการยังทำงานอยู่ หากยังไม่ถูกติดตั้งจะถือเป็นบริการแบบ one-shot
    • finish: สคริปต์ (ทางเลือก) ที่รันหลัง run จบลง โดยส่งสถานะการจบและค่าสัญญาณเป็นอาร์กิวเมนต์
    • log: symbolic link ที่ชี้ไปยังไดเรกทอรีบริการอื่น โดยจะ pipe เอาต์พุตของ run ไปเป็นอินพุตของบริการนั้น (ใช้ทำ logging chain ได้)
    • down: หากไฟล์นี้มีอยู่ nitro จะไม่ยกบริการนี้ขึ้นโดยค่าเริ่มต้น
    • หากชื่อไดเรกทอรีลงท้ายด้วย '@' จะถูกมองข้ามและนำไปใช้เป็นบริการแบบพารามิเตอร์ได้
    • ชื่อบริการต้องยาวน้อยกว่า 64 ตัวอักษร และห้ามมีอักขระ /, ,, ขึ้นบรรทัดใหม่
  • ยูทิลิตี chpst ของ runit มีประโยชน์ในการเขียนสคริปต์ run

บริการพิเศษ

  • LOG: บริการค่าเริ่มต้นสำหรับบันทึก log ของทุกบริการที่ไม่มีลิงก์ log
  • SYS: SYS/setup จะถูกรันก่อนเริ่มทุกบริการ จึงใช้สร้างลำดับการเริ่มบริการได้
    • SYS/finish: รันก่อนเข้าสู่ขั้นตอนปิดระบบทั้งหมด
    • SYS/final: รันหลังจากทุกโปรเซสถูกปิดแล้ว
    • SYS/fatal: รันแทนการปิดเมื่อเกิดข้อผิดพลาดร้ายแรง (ถ้ามี)
    • SYS/reincarnate: รันแทน shutdown และอาจใช้สำหรับการสร้าง initramfs ขึ้นใหม่ เป็นต้น

บริการแบบพารามิเตอร์

  • ไดเรกทอรีบริการที่ลงท้ายด้วย '@' จะถูก nitro มองข้าม แต่สามารถระบุใช้งานได้โดยตรงผ่าน symbolic link หรือคำสั่ง nitroctl
  • พารามิเตอร์ที่ต่อท้ายหลัง '@' จะถูกส่งเป็นอาร์กิวเมนต์ตัวแรกให้แต่ละสคริปต์
    • ตัวอย่าง: หากมี symbolic link agetty@/run และ agetty@tty1 จะมีการรัน agetty@/run tty1
    • เมื่อป้อน nitroctl up agetty@tty2 ก็สามารถรัน agetty@/run tty2 ได้ (ไม่ขึ้นกับว่ามีไดเรกทอรีอยู่หรือไม่)

โหมดการทำงาน

  • วงจรชีวิตทั้งหมดประกอบด้วย 3 ขั้นตอนคือ บูต รันบริการ (supervision) และปิดระบบ
    • บูต: หากมีบริการพิเศษ SYS จะรันตั้งแต่ setup ก่อน แล้วจึงรันทุกบริการที่ไม่ใช่ down
    • หากบริการจบลงจะถูกรีสตาร์ตใหม่ แต่ถ้าเพิ่งรีสตาร์ตไปไม่นานจะรอ 2 วินาที
    • สามารถส่งสัญญาณปิดระบบได้ด้วย nitroctl Reboot หรือ Shutdown
      • ในกรณีนี้จะเป็นลำดับ SYS/finish → ส่ง SIGTERM ให้ทุกบริการ (รอได้สูงสุด 7 วินาที) → SIGKILL → SYS/final → ลำดับการปิด
    • หากใช้กับคอนเทนเนอร์หรือ supervisor ที่ไม่ใช้สิทธิ์ จะปิดเฉพาะโปรเซสเท่านั้น

การควบคุมด้วย nitroctl

  • เครื่องมือ CLI nitroctl ใช้ควบคุม nitro จากระยะไกลได้

ตัวอย่างคำสั่ง:

  • list: แสดงรายการบริการ สถานะ PID uptime และสถานะการจบล่าสุด
  • up/down/start/stop/restart: ควบคุมการเริ่ม·หยุด·รีสตาร์ตบริการ เป็นต้น
  • การส่งสัญญาณ: p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
  • pidof: แสดง PID ของบริการที่ระบุ
  • rescan: อ่านไดเรกทอรีบริการใหม่ และสะท้อนบริการที่ถูกเพิ่มหรือลบ
  • Shutdown/Reboot: ปิดหรือรีบูตทั้งระบบ

การควบคุมผ่านสัญญาณ

  • สามารถควบคุมได้ด้วยการส่งสัญญาณตรงไปยังโปรเซส nitro
    • SIGHUP: สแกนบริการใหม่ (rescan)
    • SIGINT: รีบูต
    • SIGTERM: ปิดระบบ (หาก nitro ไม่ใช่ pid 1)

Nitro ในฐานะ init บน Linux

  • Nitro เป็นไบนารีแบบบรรจุครบในตัวและสามารถบูตเป็น Linux pid 1 ได้โดยตรง
  • จะเมานต์ /dev, /run เมื่อต้องการ และให้จัดการการทำงานอื่น ๆ ใน SYS/setup
  • อีเวนต์ Ctrl-Alt-Del จะทริกเกอร์การรีบูตอย่างเป็นระเบียบ

การใช้ Nitro เป็น init ในคอนเทนเนอร์ Docker

  • Nitro สามารถ build แบบ static และใส่ลงในคอนเทนเนอร์ได้อย่างง่ายดาย
  • ต้องมี /run อยู่ในคอนเทนเนอร์เพื่อใช้พาธซ็อกเก็ตเริ่มต้น
  • หากทำ bind mount ให้ control socket ก็จะควบคุมจากภายนอกด้วย nitroctl จากระยะไกลได้

Nitro บน FreeBSD

  • สามารถให้ FreeBSD init ดูแล nitro ได้ด้วยการเพิ่มบรรทัดต่อไปนี้ใน /etc/ttys
    /etc/nitro "/usr/local/sbin/nitro" "" on
    

ผู้เขียน

คำขอบคุณ

  • พัฒนาขึ้นจากการวิเคราะห์อย่างละเอียดของ ระบบควบคุมดูแลโปรเซส ที่มีอยู่เดิม เช่น daemontools, freedt, runit, perp, s6 เป็นต้น

สัญญาอนุญาต

  • สัญญาอนุญาต 0BSD (ดูรายละเอียดในไฟล์ LICENSE)

1 ความคิดเห็น

 
GN⁺ 2025-08-24
ความคิดเห็นจาก Hacker News
  • อยากเห็นการเปรียบเทียบกับ runit เพราะ runit เป็น init system ที่มินิมอลสุด ๆ แต่ก็เกือบครบถ้วนมาก มีหลายอย่างคล้ายกัน เช่น control directory, dependency ที่ไม่เป็นแบบ declarative, โครงสร้างสคริปต์คล้ายกัน, แนวทางด้าน logging แม้แต่ในหน้าคำอธิบายก็พูดถึง runit อยู่เล็กน้อยและแนะนำให้ใช้ร่วมกับยูทิลิตี chpst จุดที่ต่างกันคือโครงสร้างที่ใช้ service directory เดียวเพื่อจัดการหลายโปรเซสที่คล้ายกันโดยใส่พารามิเตอร์ได้ (เช่น agetty) ซึ่งผมคิดว่าดี อีกอย่างคือสามารถสั่ง reboot หรือ shutdown ได้ตรงผ่านไบนารีเดียว (nitroctl) ในขณะที่ runit แยกเป็นหลายไบนารี

    • ตอนปลดระวางเซิร์ฟเวอร์ชุดสุดท้ายที่ยังใช้ runit จัดการโปรเซสเมื่อปีที่แล้ว รู้สึกเสียดายมาก ตอนที่เขียน service ของ runit เองครั้งแรกเมื่อราว 15 ปีก่อน ผมเชื่อว่านี่แหละคือวิธีมาตรฐานในการจัดการ service บน Linux หลังจากนั้นผมห่างจาก Linux ไป 5 ปี พอกลับมาก็พบว่า systemd กลายเป็นค่าเริ่มต้นไปแล้ว เคยได้ยินคำวิจารณ์แย่ ๆ มาหลายครั้ง แต่ภายหลังก็พบว่าความรู้สึกต่อต้านจำนวนมากค่อนข้างบิดเบือน ทุกวันนี้ผมรัน service สำหรับสตรีมกล้องและข้อมูลอุณหภูมิบน Pi Zero ใน vivarium ของสัตว์เลื้อยคลาน และการตั้งค่าด้วย systemd นั้นง่ายมาก บนเดสก์ท็อป OpenSuse และโน้ตบุ๊กทำงานก็จัดการ service ต่าง ๆ ด้วย systemd ได้ง่ายเช่นกัน เลยรู้สึกว่า “การมีมาตรฐานก็นับว่าเป็นเรื่องดี”

    • มีการเปรียบเทียบแบบมินิมอลที่เหมาะสมระหว่าง runit กับ nitro อยู่ในสไลด์นำเสนอ (PDF) ของ Leah Neukirchen ที่เผยแพร่ในปี 2024
      https://leahneukirchen.org/talks/#nitroyetanotherinitsy

    • Leah Neukirchen เป็นคนที่มีบทบาทมากในชุมชน Void Linux เลยคาดว่าโปรเจกต์นี้จะเชื่อมโยงกับ Void อย่างใกล้ชิด อยากให้มีบทความอย่างเป็นทางการมากกว่านี้เกี่ยวกับวิธีใช้ nitro บน Void

    • สงสัยว่าเรื่อง “ไม่มี dependency แบบ declarative” นี่ถูกมองว่าเป็นข้อดีหรือเปล่า ผมเคยได้ยินคำวิจารณ์ systemd ในฐานะ init มามาก แต่แทบไม่ค่อยเห็นใครวิจารณ์แนวทาง declarative design โดยตรง อยากฟังเหตุผลแบบละเอียดว่าทำไม

    • ผมรู้จัก runit ผ่าน Void Linux และใช้งานเป็น init system ได้ดี แต่รู้สึกเสียดายที่ UI และเอกสารยังไม่ดีพอ โดยเฉพาะการตั้งค่า logging ที่ยากมาก อยากลองทางเลือกที่เรียบง่ายคล้ายกัน แต่มีค่าเริ่มต้นที่สมเหตุสมผลกว่า UI ที่เข้าใจง่ายกว่า และเอกสารที่ดีกว่า

  • ทุกครั้งที่เห็นคนพูดถึงการรัน init system ในคอนเทนเนอร์ ผมมักลังเลเสมอ บางครั้งมันก็เป็นการออกแบบตามความจำเป็นจริง แต่หลายครั้งกลับดูเหมือนทำให้ระบบซับซ้อนเกินไป (โดยเฉพาะในสภาพแวดล้อม Kubernetes และคลาวด์ ที่จริงควรแยกส่วนให้ดีกว่านี้ตั้งแต่ต้น) เหมือนปรากฏการณ์ “ไหน ๆ ทุกคนก็ใช้แบบนี้กัน” มากกว่า เลยยังไม่แน่ใจว่าการพยายาม “ทำมันให้ดีขึ้น” แล้วช่วยให้ปัญหาแพร่หลายต่อไปนั้นดีกว่า หรือควรปล่อยให้คนล้มเหลวกับโซลูชันเดิมแบบชัด ๆ ไปเลยจะดีกว่า

    • ผมคิดว่าคอนเทนเนอร์ของแอปพลิเคชันควรยึดตามปรัชญา Unix คือ “ทำอย่างเดียวแต่ทำให้ดี” แต่ถ้าคอนเทนเนอร์มีการ fork ไม่ว่าด้วยเหตุผลใด ผมมองว่าควรมี init ของจริงอยู่ที่ PID 1

    • จากที่เจอในสาย robotics คอนเทนเนอร์จำนวนมากเป็นเพียงการย้ายระบบซับซ้อนที่เดิมรันบน bare metal เข้ามาอยู่ในคอนเทนเนอร์ ระบบพวกนี้มี RPC แบบไร้โครงสร้างระหว่างโปรเซสเยอะมาก จึงไม่ได้มีประโยชน์มากนักที่จะหั่นเป็นหลายคอนเทนเนอร์แยก ภายในคอนเทนเนอร์แอปแบบ monolithic ถ้าจะรันหลายโปรเซส ก็มีตัวเลือกสารพัดที่ใช้กันอยู่ ตั้งแต่ supervisor, runit, systemd ไปจนถึง tmux

    • ผมเคยใช้โฮสติ้งที่คิดค่าบริการเป็นรายคอนเทนเนอร์ เช่น Fly.io, Render, Google Cloud Run บางครั้งเพราะเรื่องราคา จึงจำเป็นต้องรันหลายโปรเซสในคอนเทนเนอร์เดียว

  • ฟีเจอร์ใหม่ของ NixOS อย่าง modular-services ถูกนำเข้า Nixpkgs แล้ว การพอร์ต NixOS ไปยัง init system ใหม่หรือเคอร์เนลใหม่จะง่ายขึ้นมาก เลยคิดว่านี่เป็นช่วงเวลาที่ดีในการทดลองอะไรแบบ nitro

  • อยากเห็นการเปรียบเทียบระหว่าง dinit ที่ใช้ใน Chimera Linux กับ nitro พอไล่อ่าน readme อย่างเร็ว ๆ แล้วดูเหมือนว่ายังไม่มีการจัดการ service dependency
    dinit: https://github.com/davmac314/dinit

    • Nitro ไม่จัดการ service dependency แบบ declarative คุณจึงไม่สามารถดูกราฟ dependency ระหว่าง service แบบสวย ๆ ได้ด้วยคำสั่งเดียว แต่ถ้าระบุ service ที่ต้องใช้ไว้ใน setup script มันจะเช็กว่า service นั้นขึ้นหรือยัง แล้วรอและ retry ให้อัตโนมัติ ถ้าอยากดูกราฟ dependency ก็แทบไม่มีทางนอกจากเขียนสคริปต์เองด้วยอะไรอย่าง grep ในทางกลับกัน เวลา service ตาย ก็ลืมได้ง่ายว่าควรปิด service ที่พึ่งพามันลงตามไปด้วย และตัว nitro เองก็ไม่มีวิธีที่สะดวกในการตรวจจับเรื่องนี้

    • ผมเคยใช้ dinit บน Artix Linux มันเบาและน่าประทับใจมาก
      Artix FAQ: https://artixlinux.org/faq.php

  • เวลาเห็นโปรเจกต์ระดับล่างแบบนี้ทีไร ผมรู้สึกว่าน่าสนใจมาก systemd มีจุดที่ดีตรงมันก้าวข้ามกรอบ SysV·POSIX แบบดั้งเดิม และใช้ประโยชน์จากความสามารถเฉพาะของ Linux kernel ได้อย่างดี แต่ผมก็หวังว่านั่นจะไม่ใช่จุดจบ และอยากเห็นไอเดียใหม่ ๆ กับนวัตกรรมเกิดขึ้นต่อไป ไม่นานมานี้ผมเพิ่งทำระบบสำหรับงาน automation ในการผลิต ที่บูต Linux kernel ผ่าน netboot ตรงจาก UEFI firmware แล้วฝังเพียงไบนารี init ตัวเดียวที่เขียนเองด้วย Go การควบคุมสภาพแวดล้อมของ OS ได้ทั้งหมดด้วยโค้ดที่ดึงมาเองและภาษาระดับสูง โดยไม่ต้องจัดการ subprocess จำนวนมากและไฟล์ config แบบข้อความสารพัด ทำให้รู้สึกเป็นอิสระมาก

  • เมื่อราว 13 ปีก่อน ผมเคยสร้าง init system เองด้วย C ต้องใช้ความพยายามมากกว่าที่คาดไว้มาก และนำไปใช้เพื่อบูต GUI กับ backend ให้เร็วบนฮาร์ดแวร์สเปกต่ำ มันเป็นแบบฝึกหัดการเขียนโปรแกรมที่สนุก แต่ภายหลังก็เริ่มตระหนักว่าอาจมีโซลูชันคล้าย ๆ กันอยู่แล้ว เพื่อนร่วมงานคนหนึ่งในบริษัทเดียวกันก็ไปสร้าง init อีกตัวขึ้นมา ทำให้เวอร์ชันแรกของผมเบามากและแทบไม่มี dependency นอกจาก libc ขณะที่ของเพื่อนใช้ libevent เป็นฐานและมีฟีเจอร์ขั้นสูงกว่า

  • ผมกังวลที่ชื่อมันไปทับทั้งชื่อและหน้าที่กับ AWS Nitro
    https://docs.aws.amazon.com/whitepapers/latest/security-design-of-aws-nitro-system/the-nitro-system-journey.html

    • ชื่ออาจซ้ำกัน แต่ init system กับ hypervisor เป็นคนละอย่างกันโดยสิ้นเชิง

    • ผมว่าแทบจะไม่เกิดปัญหาอะไร ตัวหนึ่งเป็น init system ที่ใคร ๆ ก็ใช้ได้ ส่วน AWS Nitro เป็น KVM fork ที่ใช้ภายในบริษัทเท่านั้น

  • สงสัยว่าเทียบกับ s6 แล้ว nitro เป็นอย่างไร ช่วงหลังผมเพิ่งลองตั้งค่า init system ใน Docker container ด้วย s6 แต่กับ s6-overlay ต้องสร้างไฟล์เองหลายไฟล์ และไม่ได้รู้สึกว่าเข้าใจง่ายอย่างที่คิด

  • ที่ Distrust เราเคยเขียน init system แบบเรียบง่ายมาก ๆ ด้วย rust ให้สั้นกว่า 500 บรรทัด และตอนนี้มีลูกค้าบางรายใช้งานจริงในสภาพแวดล้อม enclave ที่ต้องการความปลอดภัยสูง เราใช้แค่ rust standard library จึงทำให้การ audit ง่ายมาก
    https://git.distrust.co/public/nit

    • ดูสะอาดดี (แต่ใหญ่กว่า nit อยู่ 33%) อย่างไรก็ตาม readme มีแค่วิธี build และไม่ได้อธิบาย interface หรือวิธีทำงานจริงเอาไว้
  • ระบุ dependency ไม่ได้, ไม่มีการตั้งค่า user/group, ต้องกำหนดลำดับเอง, ไม่มีการรัน service แบบขนาน, ไม่มีการจัดการทรัพยากร ถ้าขาดสิ่งเหล่านี้ไป ผมไม่อยากเรียกระบบแบบนี้ว่า init system เลย มันเป็นแค่ bare-bones process supervisor

    • ในทางปฏิบัติ มันทำทั้งหมดนี้ได้ดีมากจริง ๆ (ถึงขั้นมีประสบการณ์ว่าดีกว่า systemd ด้วยซ้ำ) ผมใช้ daemontools มานานมากแทนที่จะใช้ nitro (และ nitro ก็เป็นผู้สืบทอดแนวทางนั้น) วิธีใช้งานง่ายจนน่าเหลือเชื่อ เสถียร และทำความเข้าใจได้ง่าย ส่วนเรื่อง dependency นั้น แนวคิดแบบ djb/daemontools ที่ว่า “เรื่องนั้นแต่ละคนจัดการกันเอง เราแค่ให้เครื่องมือที่เรียบง่าย ราคาถูก และเชื่อถือได้” กลับดูใช้งานจริงได้มากกว่า