2 điểm bởi GN⁺ 2025-08-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nitro là một bộ giám sát tiến trình và hệ thống init siêu nhỏ gọn có thể áp dụng cho cả môi trường nhúng, máy chủ, máy tính để bàn và container
  • Chỉ lưu trạng thái hệ thống trong RAM, nên vẫn hoạt động trơn tru trên hệ thống tệp chỉ đọc, đồng thời cung cấp thiết kế hướng sự kiện nhanh và hiệu quả
  • Cách cấu hình là cấu trúc thư mục script đơn giản, cho phép quản lý dịch vụ mà không cần tệp cấu hình phức tạp hay quy trình build bổ sung
  • Hỗ trợ các tính năng tối ưu cho container và môi trường nhúng như dịch vụ tham số hóa, khởi động lại bền vững, và ghi log đáng tin cậy theo từng dịch vụ
  • Bảo đảm tính linh hoạt và khả năng kiểm soát cao với điều khiển từ xa qua công cụ nitroctl, điều khiển hoạt động dựa trên tín hiệu

Tổng quan

Nitro là một bộ giám sát tiến trình siêu nhỏ gọn có thể dùng làm pid 1 trên Linux

Các lĩnh vực ứng dụng chính như sau

  • init cho các máy Linux với nhiều mục đích khác nhau như nhúng, desktop, máy chủ
  • init của Linux initramfs
  • init trong môi trường container như Docker/Podman/LXC/Kubernetes
  • daemon giám sát chạy không cần đặc quyền trên hệ thống POSIX

Cấu hình sử dụng cấu trúc script dựa trên thư mục, vị trí mặc định là /etc/nitro

Yêu cầu

  • Cần kernel hỗ trợ Unix socket
  • Cần tmpfs hoặc thư mục /run có thể ghi

Ưu điểm so với các hệ thống khác

  • Toàn bộ thông tin trạng thái chỉ được giữ trong RAM, nên có thể chạy trên hệ thống tệp gốc chỉ đọc mà không cần thủ thuật riêng
  • Cung cấp hiệu quả nhờ cơ chế hoạt động hướng sự kiện, không polling
  • Không có cấp phát bộ nhớ động trong lúc chạy
  • Không tiêu tốn file descriptor vô hạn
  • Chỉ cần một binary self-contained duy nhất (có thể thêm binary điều khiển dưới dạng tùy chọn)
  • Không cần chuyển đổi hay biên dịch tệp cấu hình, dịch vụ chỉ là thư mục đơn giản chứa script
  • Hỗ trợ chuỗi khởi động lại dịch vụ và ghi log
  • Vẫn hoạt động bình thường ngay cả khi đồng hồ hệ thống không chính xác
  • Có thể chạy trên FreeBSD thông qua /etc/ttys
  • Có thể tạo binary static siêu nhỏ khi dùng musl libc

Quản lý dịch vụ

  • Mỗi thư mục dịch vụ (mặc định nằm trong /etc/nitro) có thể chứa các tệp sau

    • setup: script (tùy chọn) được chạy trước khi khởi động dịch vụ; chỉ khi kết thúc bình thường (0) thì dịch vụ mới được phép khởi động
    • run: script vận hành dịch vụ; dịch vụ được coi là còn sống miễn là script này chưa kết thúc; nếu chưa triển khai thì được xử lý như dịch vụ one-shot
    • finish: script (tùy chọn) chạy sau khi run kết thúc; nhận trạng thái thoát và giá trị tín hiệu làm đối số
    • log: symbolic link trỏ tới thư mục dịch vụ khác, nối đầu ra của run vào đầu vào của dịch vụ đó bằng pipe (có thể dùng cho chuỗi ghi log)
    • down: nếu tệp này tồn tại thì nitro mặc định sẽ không đưa dịch vụ này lên
    • Nếu tên thư mục kết thúc bằng @ thì sẽ bị bỏ qua và có thể dùng làm dịch vụ tham số hóa
    • Tên dịch vụ phải dưới 64 ký tự và không được chứa ký tự /, ,, hoặc xuống dòng
  • Tiện ích chpst của runit rất hữu ích khi viết script run

Dịch vụ đặc biệt

  • LOG: dịch vụ mặc định để ghi log cho mọi dịch vụ không có liên kết log
  • SYS: SYS/setup được chạy trước khi mọi dịch vụ khởi động, có thể dùng để triển khai thứ tự khởi động dịch vụ
    • SYS/finish: chạy trước khi bước vào giai đoạn tắt toàn bộ
    • SYS/final: chạy sau khi mọi tiến trình đã kết thúc
    • SYS/fatal: được chạy thay vì thoát khi xảy ra lỗi nghiêm trọng (nếu có)
    • SYS/reincarnate: được chạy thay cho shutdown, có thể dùng chẳng hạn để tái triển khai initramfs

Dịch vụ tham số hóa

  • Thư mục dịch vụ kết thúc bằng @ sẽ bị nitro bỏ qua, nhưng vẫn có thể được chỉ định trực tiếp qua symbolic link hoặc lệnh nitroctl
  • Tham số phía sau @ sẽ được truyền làm đối số đầu tiên cho từng script
    • Ví dụ: nếu có symbolic link agetty@/runagetty@tty1 thì sẽ chạy agetty@/run tty1
    • Khi nhập nitroctl up agetty@tty2 thì có thể chạy agetty@/run tty2 (không phụ thuộc việc thư mục có tồn tại hay không)

Chế độ hoạt động

  • Toàn bộ vòng đời gồm ba giai đoạn: khởi động, chạy dịch vụ (giám sát), tắt máy
    • Khởi động: nếu tồn tại dịch vụ đặc biệt SYS thì chạy từ setup, sau đó chạy mọi dịch vụ non-down
    • Khi dịch vụ kết thúc thì sẽ được khởi động lại; tuy nhiên nếu lần khởi động lại gần nhất diễn ra quá nhanh thì sẽ chờ 2 giây
    • Có thể gửi tín hiệu tắt qua nitroctl Reboot hoặc Shutdown
      • Khi đó chuỗi xử lý là SYS/finish → SIGTERM cho mọi dịch vụ (chờ tối đa 7 giây) → SIGKILL → SYS/final → chuỗi tắt
    • Trong trường hợp dùng cho container hoặc bộ giám sát không đặc quyền thì chỉ kết thúc các tiến trình

Điều khiển bằng nitroctl

  • Công cụ CLI nitroctl cho phép điều khiển nitro từ xa

Ví dụ lệnh:

  • list: hiển thị danh sách dịch vụ, trạng thái, PID, uptime, trạng thái thoát gần nhất
  • up/down/start/stop/restart: điều khiển khởi động, dừng, khởi động lại dịch vụ
  • Gửi tín hiệu: p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
  • pidof: in ra PID của dịch vụ được chỉ định
  • rescan: đọc lại thư mục dịch vụ, phản ánh các dịch vụ được thêm hoặc xóa
  • Shutdown/Reboot: tắt hoặc khởi động lại toàn bộ hệ thống

Điều khiển bằng tín hiệu

  • Có thể điều khiển bằng cách gửi trực tiếp tín hiệu tới tiến trình nitro
    • SIGHUP: quét lại dịch vụ (rescan)
    • SIGINT: khởi động lại
    • SIGTERM: tắt (nếu nitro không phải pid 1)

Nitro với vai trò init trên Linux

  • Nitro là binary tự chứa có thể khởi động trực tiếp làm Linux pid 1
  • Có thể mount /dev, /run khi cần; các xử lý khác được thực hiện trong SYS/setup
  • Sự kiện Ctrl-Alt-Del sẽ kích hoạt quá trình khởi động lại một cách có trật tự

Sử dụng Nitro làm init trong container Docker

  • Nitro có thể được build tĩnh để dễ dàng đưa vào container
  • Cần có /run trong container để dùng đường dẫn socket mặc định
  • Nếu bind mount control socket thì có thể điều khiển từ xa bằng nitroctl từ bên ngoài

Nitro trên FreeBSD

  • Có thể để FreeBSD init giám sát nitro bằng cách thêm dòng sau vào /etc/ttys
    /etc/nitro "/usr/local/sbin/nitro" "" on
    

Tác giả

Lời cảm ơn

  • Được phát triển dựa trên việc phân tích chi tiết các hệ thống giám sát tiến trình hiện có như daemontools, freedt, runit, perp, s6

Giấy phép

  • Giấy phép 0BSD (xem chi tiết trong tệp LICENSE)

1 bình luận

 
GN⁺ 2025-08-24
Ý kiến trên Hacker News
  • Muốn xem phần so sánh với runit. runit là một hệ thống init cực kỳ tối giản nhưng gần như đầy đủ. Có nhiều điểm tương đồng như control directory, phụ thuộc không mang tính khai báo, cấu trúc script tương tự, cách tiếp cận logging, v.v. Trang mô tả cũng có nhắc thoáng qua đến runit và khuyến nghị dùng kèm tiện ích chpst. Điểm khác biệt là cấu trúc dùng một service directory để quản lý nhiều tiến trình tương tự nhau đã được tham số hóa (ví dụ: agetty) theo mình là khá hay. Ngoài ra có thể chạy trực tiếp reboot hay shutdown bằng một binary duy nhất (nitroctl). Trong khi đó runit có cấu trúc gồm nhiều binary

    • Năm ngoái khi cho nghỉ hưu những máy chủ cuối cùng còn quản lý tiến trình bằng runit, mình thấy khá tiếc. Khoảng 15 năm trước, khi lần đầu tự viết dịch vụ cho runit, mình từng tin rằng đó là cách tiêu chuẩn để quản lý dịch vụ trên Linux. Sau đó rời xa Linux 5 năm rồi quay lại thì systemd đã trở thành mặc định; mình từng nghe nhiều lời chê, nhưng dần nhận ra phần lớn là sự ác cảm bị bóp méo. Hiện tại mình đang chạy dịch vụ stream camera và dữ liệu nhiệt độ bằng Pi Zero trong một vivarium nuôi bò sát, và việc thiết lập bằng systemd cực kỳ dễ. Trên desktop OpenSuse hay laptop làm việc, mình cũng có thể vận hành nhiều dịch vụ khác nhau rất đơn giản với systemd. Mình thấy rằng “có một tiêu chuẩn” thực ra lại là điều tốt

    • Có một so sánh tối giản khá phù hợp giữa runit và nitro trong bộ slide bài thuyết trình của Leah Neukirchen công bố năm 2024 (PDF)
      https://leahneukirchen.org/talks/#nitroyetanotherinitsy

    • Leah Neukirchen là người hoạt động rất tích cực trong cộng đồng Void Linux. Mình đoán dự án này sẽ gắn khá chặt với Void. Sẽ rất tốt nếu có một bài viết chính thức hơn về cách dùng nitro trên Void

    • Mình thắc mắc liệu việc “không có phụ thuộc mang tính khai báo” có được xem là ưu điểm không. Mình đã nghe nhiều lời chỉ trích systemd với vai trò init, nhưng hiếm khi thấy người ta chỉ trích bản thân thiết kế khai báo. Muốn nghe giải thích chi tiết xem có lý do gì

    • Mình biết đến runit qua Void Linux và đang dùng tốt như một hệ thống init, nhưng cảm thấy tiếc vì UI và tài liệu còn thiếu. Đặc biệt phần cấu hình logging thực sự rất khó. Mình muốn thử một lựa chọn thay thế cũng đơn giản tương tự nhưng có mặc định hợp lý hơn, UI trực quan hơn và tài liệu tốt hơn

  • Mỗi khi thấy ai đó nói về việc chạy một hệ thống init trong container là mình lại băn khoăn. Thực tế có những trường hợp được thiết kế vì nhu cầu thật, nhưng mình thường thấy nó làm mọi thứ trở nên quá phức tạp (đặc biệt trong môi trường Kubernetes và cloud, nơi đáng ra phải thiết kế tách biệt chuẩn hơn). Có cảm giác đây là hiện tượng kiểu “đằng nào mọi người cũng đang dùng thế”, nhưng mình luôn lưỡng lự không biết nên cố “làm tốt hơn” để vấn đề lan rộng hơn, hay cứ để mọi người thất bại thật mạnh với giải pháp cũ thì hơn

    • Mình nghĩ application container nên tuân theo triết lý Unix “chỉ làm tốt một việc”. Nhưng nếu trong container vì lý do nào đó có fork, thì theo mình PID 1 phải là một init thực thụ

    • Theo trải nghiệm của mình trong lĩnh vực robotics, nhiều container thực ra là các hệ thống phức tạp vốn chạy trên bare metal rồi mới được chuyển vào container. Giữa các tiến trình có rất nhiều RPC không có cấu trúc, nên việc tách nhỏ thành nhiều container riêng biệt không mang lại lợi ích lớn. Để chạy nhiều tiến trình bên trong một application container kiểu monolithic, người ta dùng đủ loại lựa chọn như supervisor, runit, systemd, thậm chí cả tmux

    • Mình từng dùng các dịch vụ hosting tính phí theo từng container như Fly.io, Render, Google Cloud Run. Vì giá cả nên khá thường xuyên phải chạy nhiều tiến trình trong một container

  • Tính năng mới của NixOS là modular-services đã được đưa vào Nixpkgs. Điều này sẽ giúp việc port NixOS sang một hệ thống init mới hoặc một kernel mới dễ hơn nhiều, nên mình nghĩ đây là thời điểm tốt để thử các thí nghiệm như nitro

  • Mình muốn so sánh dinit đang dùng trong Chimera Linux với nitro. Lướt nhanh readme thì có vẻ việc quản lý phụ thuộc dịch vụ vẫn chưa được hỗ trợ
    dinit: https://github.com/davmac314/dinit

    • Nitro không xử lý phụ thuộc dịch vụ theo kiểu khai báo. Không thể dùng một lệnh để xem đẹp mắt đồ thị phụ thuộc giữa các dịch vụ. Nhưng nếu chỉ rõ các dịch vụ cần thiết trong script setup, nó sẽ kiểm tra xem dịch vụ đó đã chạy chưa và tự động chờ rồi thử lại. Nếu muốn xem đồ thị phụ thuộc thì chỉ còn cách tự viết script bằng grep hay công cụ tương tự. Ngược lại, khi một dịch vụ chết thì rất dễ quên hạ luôn các dịch vụ phụ thuộc theo chuỗi, và bản thân nitro không có cách thuận tiện nào để phát hiện việc này

    • Mình từng dùng dinit trên Artix Linux và thấy nó thực sự nhẹ, rất ấn tượng
      Artix FAQ: https://artixlinux.org/faq.php

  • Mình thấy các dự án tầng thấp như thế này thật sự rất thú vị. Mình thích việc systemd vượt ra khỏi khuôn khổ SysV·POSIX truyền thống để tận dụng tốt các tính năng chuyên biệt của Linux kernel. Nhưng mình hy vọng đó chưa phải điểm kết thúc, và sẽ còn tiếp tục có những ý tưởng mới cùng các đổi mới khác. Gần đây mình đã tự triển khai một thiết lập cho tự động hóa sản xuất, trong đó Linux kernel được netboot trực tiếp từ firmware UEFI và chỉ nhúng một binary init duy nhất do mình tự viết bằng Go. Việc có thể kiểm soát toàn bộ môi trường OS chỉ bằng mã tự mang theo và ngôn ngữ cấp cao, không phải quản lý đủ loại subprocess cùng vô số file cấu hình văn bản, thật sự rất giải phóng

  • Khoảng 13 năm trước mình từng tự xây dựng một hệ thống init bằng C. Nó đòi hỏi nhiều công sức hơn dự kiến rất nhiều, và được dùng để khởi động nhanh GUI cùng backend trên phần cứng cấu hình thấp. Đó là một bài tập lập trình thú vị, nhưng về sau mình nhận ra có lẽ đã tồn tại những giải pháp tương tự rồi. Một đồng nghiệp trong cùng công ty còn tạo thêm một init khác, nên bản đầu tiên của mình hầu như không có phụ thuộc nào ngoài libc và rất nhẹ, còn phiên bản của đồng nghiệp thì dựa trên libevent và có nhiều tính năng cao cấp hơn

  • Mình thấy hơi vướng khi tên gọi và chức năng bị chồng lấn với AWS Nitro
    https://docs.aws.amazon.com/whitepapers/latest/security-design-of-aws-nitro-system/the-nitro-system-journey.html

    • Chỉ trùng tên thôi, còn init system và hypervisor về bản chất là hoàn toàn khác nhau

    • Mình nghĩ gần như sẽ không gây vấn đề gì. Một bên là hệ thống init mà ai cũng có thể dùng, còn AWS Nitro là một bản fork của KVM chỉ dùng nội bộ trong doanh nghiệp

  • Mình tò mò nitro so với s6 thì thế nào. Gần đây mình thử dựng một hệ thống init cho Docker container bằng s6, nhưng với s6-overlay phải tự tạo khá nhiều file và nó không trực quan như mình tưởng

  • Ở Distrust, chúng tôi đã tự viết một hệ thống init siêu đơn giản bằng rust với chưa tới 500 dòng, và một số khách hàng đang dùng nó trong production cho môi trường enclave có yêu cầu bảo mật bắt buộc. Chúng tôi chỉ dùng rust standard library nên việc audit rất dễ
    https://git.distrust.co/public/nit

    • Trông khá gọn gàng (dù lớn hơn nit 33%), nhưng readme chỉ nói cách build chứ không giải thích giao diện thực tế hay cách nó hoạt động
  • Không thể chỉ định phụ thuộc, không có thiết lập user/group, phải tự sắp xếp thứ tự, không chạy dịch vụ song song, không quản lý tài nguyên. Mình không muốn gọi một hệ thống thiếu những thứ này là init system. Nó chỉ là một process supervisor tối giản mà thôi

    • Thực ra nó làm tốt tất cả những việc đó (theo trải nghiệm của mình, thậm chí còn tốt hơn cả systemd). Mình đã dùng daemontools suốt thời gian dài chứ không phải nitro (và nitro là hậu duệ của nó). Cách dùng cực kỳ dễ, ổn định, và dễ hiểu đến khó tin. Với vấn đề phụ thuộc cũng vậy, phong cách của djb/daemontools kiểu “phần này mỗi người tự lo, đổi lại chúng tôi cung cấp công cụ đơn giản, rẻ và đáng tin cậy” thực tế lại hợp với công việc hơn nhiều