2 Punkte von GN⁺ 2025-08-24 | 1 Kommentare | Auf WhatsApp teilen
  • Nitro ist ein ultrakompakter Prozess-Supervisor und Init-System, das sich für Embedded-Systeme, Server, Desktops und Container gleichermaßen eignet
  • Es speichert den Systemzustand ausschließlich im RAM und funktioniert daher problemlos auch mit schreibgeschützten Dateisystemen; zugleich bietet es ein schnelles und effizientes ereignisbasiertes Design
  • Die Konfiguration erfolgt über eine einfache skriptbasierte Verzeichnisstruktur, sodass sich Dienste ohne komplexe Konfigurationsdateien oder zusätzliche Build-Schritte verwalten lassen
  • Es unterstützt parametrisierte Dienste, robuste Neustarts sowie zuverlässiges Logging pro Dienst und bietet damit für Container- und Embedded-Umgebungen optimierte Funktionen
  • Mit Remote-Steuerung über das Tool nitroctl und signalbasierter Ablaufsteuerung gewährleistet es hohe Flexibilität und Kontrolle

Überblick

Nitro ist ein ultrakompakter Prozess-Supervisor, der unter Linux auch als pid 1 verwendet werden kann

Wichtige Einsatzbereiche sind:

  • Init für Linux-Maschinen verschiedenster Art, etwa Embedded, Desktop und Server
  • Init für Linux-initramfs
  • Init für Container-Umgebungen wie Docker/Podman/LXC/Kubernetes
  • Ein Supervisions-Daemon für POSIX-Systeme, der ohne Privilegien läuft

Die Konfiguration verwendet eine verzeichnisbasierte Skriptstruktur; der Standardpfad ist /etc/nitro

Anforderungen

  • Unterstützung für Unix-Sockets im Kernel erforderlich
  • tmpfs oder ein beschreibbares /run-Verzeichnis erforderlich

Vorteile gegenüber anderen Systemen

  • Alle Statusinformationen bleiben ausschließlich im RAM, sodass es auch auf schreibgeschützten Root-Dateisystemen ohne besondere Tricks funktioniert
  • Ereignisbasiertes Arbeiten ohne Polling sorgt für Effizienz
  • Keine dynamische Speicherallokation zur Laufzeit
  • Datei-Deskriptoren werden nicht unbegrenzt verbraucht
  • Es wird nur eine in sich geschlossene Binärdatei benötigt (optional zusätzlich eine Steuer-Binärdatei)
  • Keine Umwandlung oder Kompilierung von Konfigurationsdateien nötig; ein Dienst ist einfach ein Verzeichnis mit Skripten
  • Unterstützung für Dienst-Neustarts und Logging-Ketten
  • Funktioniert auch dann korrekt, wenn die Systemuhr nicht exakt ist
  • Kann unter FreeBSD über /etc/ttys ausgeführt werden
  • Mit musl libc lassen sich ultrakleine statische Binärdateien erstellen

Dienstverwaltung

  • Jedes Dienstverzeichnis (standardmäßig unter /etc/nitro) kann die folgenden Dateien enthalten

    • setup: (optionales) Skript, das vor dem Start des Dienstes ausgeführt wird; der Dienst startet nur bei erfolgreichem Ende (0)
    • run: Skript für den eigentlichen Dienstbetrieb; solange es nicht endet, gilt der Dienst als aktiv; falls nicht implementiert, wird der Dienst als One-Shot-Dienst behandelt
    • finish: (optionales) Skript, das nach dem Ende von run ausgeführt wird; Exit-Status und Signalwert werden als Argumente übergeben
    • log: symbolischer Link auf ein anderes Dienstverzeichnis; die Ausgabe von run wird an die Eingabe dieses Dienstes weitergeleitet (für Logging-Ketten nutzbar)
    • down: wenn diese Datei existiert, startet nitro diesen Dienst standardmäßig nicht
    • Endet der Verzeichnisname auf @, wird er ignoriert und kann als parametrisierter Dienst verwendet werden
    • Dienstnamen müssen kürzer als 64 Zeichen sein und dürfen kein /, , oder Zeilenumbrüche enthalten
  • Das chpst-Utility von runit ist beim Schreiben von run-Skripten nützlich

Besondere Dienste

  • LOG: Standarddienst zum Protokollieren aller Dienste ohne log-Link
  • SYS: SYS/setup wird vor dem Start aller Dienste ausgeführt und kann für geordnete Startabläufe verwendet werden
    • SYS/finish: wird vor dem Eintritt in die globale Beendigungsphase ausgeführt
    • SYS/final: wird ausgeführt, nachdem alle Prozesse beendet wurden
    • SYS/fatal: wird bei fatalen Fehlern statt eines Beendens ausgeführt, falls vorhanden
    • SYS/reincarnate: wird statt eines Shutdowns ausgeführt und kann z. B. für eine Re-Implementierung von initramfs genutzt werden

Parametrisierte Dienste

  • Dienstverzeichnisse, die auf @ enden, werden von nitro ignoriert, können aber per symbolischem Link oder über den Befehl nitroctl direkt angesprochen werden
  • Der Parameter nach @ wird als erstes Argument an jedes Skript übergeben
    • Beispiel: Wenn symbolische Links agetty@/run und agetty@tty1 existieren, wird agetty@/run tty1 ausgeführt
    • Mit nitroctl up agetty@tty2 kann agetty@/run tty2 ausgeführt werden, unabhängig davon, ob das Verzeichnis existiert

Betriebsmodi

  • Der gesamte Lebenszyklus besteht aus drei Phasen: Booten, Dienstausführung (Supervision) und Beenden
    • Booten: Wenn der besondere Dienst SYS existiert, wird ab setup ausgeführt; danach werden alle nicht auf down gesetzten Dienste gestartet
    • Wenn ein Dienst endet, wird er neu gestartet; war der letzte Neustart jedoch sehr kurz zuvor, wird 2 Sekunden gewartet
    • Mit nitroctl Reboot oder Shutdown kann ein Beendigungssignal ausgelöst werden
      • Dann folgt SYS/finish → SIGTERM an alle Dienste (maximal 7 Sekunden warten) → SIGKILL → SYS/final → Beendigungssequenz
    • Für Container oder unprivilegierte Supervisoren werden nur die Prozesse beendet

Steuerung mit nitroctl

  • Das CLI-Tool nitroctl ermöglicht die Fernsteuerung von nitro

Beispiele für Befehle:

  • list: Ausgabe von Dienstliste, Status, PID, Uptime und letztem Exit-Status
  • up/down/start/stop/restart: Dienste starten, anhalten, neu starten usw.
  • Signale senden: p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
  • pidof: Ausgabe der PID des angegebenen Dienstes
  • rescan: Dienstverzeichnisse neu einlesen und hinzugefügte bzw. entfernte Dienste übernehmen
  • Shutdown/Reboot: gesamtes System herunterfahren bzw. neu starten

Steuerung über Signale

  • Der nitro-Prozess kann auch durch direkt gesendete Signale gesteuert werden
    • SIGHUP: Dienste neu einlesen (rescan)
    • SIGINT: Neustart
    • SIGTERM: Beenden (wenn nitro nicht pid 1 ist)

Nitro als Init unter Linux

  • Nitro ist eine in sich geschlossene Binärdatei und kann unter Linux direkt als pid 1 booten
  • /dev und /run werden bei Bedarf gemountet; weitere Abläufe werden in SYS/setup behandelt
  • Ctrl-Alt-Del-Ereignisse lösen einen geordneten Neustart aus

Verwendung von Nitro als Init in Docker-Containern

  • Nitro kann statisch gebaut und einfach in Container eingebunden werden
  • /run muss im Container vorhanden sein, damit der Standardpfad für den Socket verwendet werden kann
  • Wenn der Kontroll-Socket per Bind-Mount eingebunden wird, ist eine externe Fernsteuerung mit nitroctl möglich

Nitro unter FreeBSD

  • Durch Hinzufügen der folgenden Zeile in /etc/ttys kann nitro von FreeBSD init überwacht werden
    /etc/nitro "/usr/local/sbin/nitro" "" on
    

Autor

Danksagung

  • Entwickelt auf Grundlage einer detaillierten Analyse bestehender Prozess-Supervisionssysteme wie daemontools, freedt, runit, perp und s6

Lizenz

  • 0BSD-Lizenz (Details siehe Datei LICENSE)

1 Kommentare

 
GN⁺ 2025-08-24
Hacker-News-Kommentare
  • Ich würde gerne einen Vergleich mit runit sehen. runit ist ein extrem minimalistisches und zugleich nahezu vollständiges init-System. Es gibt viele Gemeinsamkeiten, etwa das Control Directory, nicht-deklarative Abhängigkeiten, eine ähnliche Skriptstruktur und den Logging-Ansatz. Auch auf der Beschreibungsseite wird runit kurz erwähnt, zusammen mit der Empfehlung, das chpst-Utility zu verwenden. Als Unterscheidungsmerkmal finde ich die Struktur gut, mit der sich mehrere ähnliche Prozesse (z. B. agetty) über ein einziges Service-Verzeichnis parametrisiert verwalten lassen. Außerdem kann man reboot oder shutdown direkt über ein einzelnes Binary (nitroctl) ausführen. runit besteht dagegen aus mehreren Binaries

    • Als ich im letzten Jahr die letzten Server ausgemustert habe, auf denen Prozesse mit runit verwaltet wurden, war ich ziemlich wehmütig. Als ich vor etwa 15 Jahren zum ersten Mal selbst runit-Services geschrieben habe, dachte ich, das sei die Standardmethode, um unter Linux Services zu verwalten. Nach fünf Jahren ohne Linux kam ich zurück und stellte fest, dass systemd zum Standard geworden war. Ich hatte oft Schlechtes darüber gehört, merkte aber nach und nach, dass viel davon überzogene Abneigung war. Derzeit betreibe ich in einem Reptilien-Vivarium auf einem Pi Zero einen Service zum Streamen von Kamera- und Temperaturdaten, und das mit systemd einzurichten war ausgesprochen einfach. Auch auf meinem OpenSuse-Desktop und meinem Arbeitslaptop konnte ich mit systemd verschiedene Services unkompliziert betreiben. Ich denke inzwischen: „Einen Standard zu haben, ist eigentlich etwas Gutes“

    • Einen passenden minimalistischen Vergleich zwischen runit und nitro gibt es in Leah Neukirchens Vortragsslides (PDF) von 2024
      https://leahneukirchen.org/talks/#nitroyetanotherinitsy

    • Leah Neukirchen ist in der Void-Linux-Community sehr aktiv. Ich vermute, dass dieses Projekt eng mit Void verbunden sein wird. Ich würde mir wünschen, dass es etwas offizieller einen Artikel dazu gibt, wie man nitro in Void verwendet

    • Ich frage mich, ob „keine deklarativen Abhängigkeiten“ als Vorteil gemeint ist. Ich habe viele Kritiken an systemd als init gehört, aber nur selten Kritik am deklarativen Design selbst. Ich würde gern genauer hören, warum das so gesehen wird

    • Ich bin über Void Linux zu runit gekommen und nutze es als init-System gern, fand aber UI und Dokumentation unzureichend. Vor allem das Logging zu konfigurieren war wirklich schwierig. Ich würde gern eine ähnlich einfache Alternative ausprobieren, aber mit sinnvolleren Defaults, einer intuitiveren UI und besserer Dokumentation

  • Immer wenn ich lese, dass man in Containern ein init-System betreiben soll, bin ich hin- und hergerissen. Manchmal ist das tatsächlich aus echten Anforderungen heraus so entworfen, aber oft wirkt es eher unnötig kompliziert gemacht, besonders in Kubernetes- und Cloud-Umgebungen, wo man die Trennung eigentlich sauberer hätte gestalten sollen. Es wirkt manchmal wie ein Fall von „alle machen es eben so“, und ich bin nie sicher, ob es besser ist, das Problem mit dem Anspruch „wir machen es besser“ weiterzuverbreiten, oder ob man Leute lieber mit bestehenden Lösungen spektakulär scheitern lassen sollte

    • Ich finde, Application-Container sollten der Unix-Philosophie „eine Sache gut machen“ folgen. Wenn in einem Container aber aus irgendeinem Grund fork verwendet wird, sollte PID 1 meiner Meinung nach ein echtes init sein

    • Aus meiner Erfahrung in der Robotik sind viele Container ursprünglich komplexe Systeme, die auf Bare Metal liefen und später in Container verlagert wurden. Zwischen den Prozessen gibt es oft unstrukturierte RPCs, sodass es wenig Mehrwert bringt, sie in viele separate Container zu zerlegen. Um in einem monolithischen App-Container mehrere Prozesse zu starten, sind supervisor, runit, systemd und sogar tmux allesamt gebräuchliche Optionen

    • Ich habe Hosting-Angebote wie Fly.io, Render und Google Cloud Run genutzt, bei denen pro Container abgerechnet wird. Wegen der Preise muss man dort oft mehrere Prozesse in einem Container betreiben

  • Das neue NixOS-Feature modular-services wurde in Nixpkgs aufgenommen. Dadurch dürfte es deutlich einfacher werden, NixOS auf ein neues init-System oder einen neuen Kernel zu portieren. Deshalb ist jetzt meiner Meinung nach ein guter Zeitpunkt, mit Experimenten wie nitro zu spielen

  • Ich würde gern dinit, das in Chimera Linux verwendet wird, mit nitro vergleichen. Beim schnellen Überfliegen des Readme wirkt es so, als gäbe es noch kein Service-Abhängigkeitsmanagement
    dinit: https://github.com/davmac314/dinit

    • Nitro behandelt Service-Abhängigkeiten nicht deklarativ. Man kann also nicht mit einem einzigen Befehl hübsch den Abhängigkeitsgraphen zwischen Services anzeigen. Wenn man aber im setup-Skript die benötigten Services angibt, prüft es, ob sie laufen, und wartet bzw. versucht es automatisch erneut. Wenn man den Abhängigkeitsgraphen sehen will, bleibt einem nur, selbst ein Skript mit Dingen wie grep zu schreiben. Umgekehrt vergisst man leicht, abhängige Services korrekt mit herunterzufahren, wenn ein Service ausfällt, und nitro selbst bietet keine bequeme Möglichkeit, so etwas zu erkennen

    • Ich habe dinit unter Artix Linux verwendet, und es war wirklich leichtgewichtig und beeindruckend
      Artix FAQ: https://artixlinux.org/faq.php

  • Solche Low-Level-Projekte finde ich immer sehr spannend. Ich mochte an systemd, dass es den traditionellen SysV-/POSIX-Rahmen hinter sich gelassen und Linux-kernelspezifische Funktionen gut genutzt hat. Ich hoffe aber, dass es nicht das Ende der Entwicklung ist und weiterhin neue Ideen und Innovationen auftauchen. Vor Kurzem habe ich selbst in der Fertigungsautomatisierung ein Setup umgesetzt, bei dem direkt aus der UEFI-Firmware ein Linux-Kernel per netboot gestartet wird, der nur ein einziges selbst in Go geschriebenes init-Binary eingebettet hat. Es fühlte sich sehr befreiend an, die gesamte OS-Umgebung nur mit eigenem Code und einer High-Level-Sprache zu steuern, ohne all die diversen Subprozesse und unzähligen Text-Konfigurationsdateien verwalten zu müssen

  • Ich habe vor etwa 13 Jahren selbst ein init-System in C gebaut. Das war sehr viel aufwendiger als erwartet und wurde genutzt, um GUI und Backend auf leistungsschwacher Hardware schnell zu booten. Es war eine unterhaltsame Programmierübung, aber später wurde mir klar, dass es womöglich bereits ähnliche Lösungen gab. Ein Kollege baute in derselben Firma noch ein anderes init, weshalb meine erste Version fast ohne Abhängigkeiten außer libc sehr leichtgewichtig war, während seine Version auf libevent basierte und deutlich mehr fortgeschrittene Funktionen bot

  • Mich stört, dass sich Name und Funktion mit AWS Nitro überschneiden
    https://docs.aws.amazon.com/whitepapers/latest/security-design-of-aws-nitro-system/the-nitro-system-journey.html

    • Der Name überschneidet sich nur, init-System und Hypervisor sind grundsätzlich völlig verschieden

    • Ich glaube kaum, dass das Probleme verursachen wird. Das eine ist ein init-System, das jeder nutzen kann, AWS Nitro dagegen ist ein KVM-Fork, der nur intern im Unternehmen verwendet wird

  • Ich würde gern wissen, wie nitro im Vergleich zu s6 ist. Ich habe vor Kurzem in einem Docker-Container mit s6 ein init-System aufgebaut, musste mit s6-overlay aber viele Dateien selbst anlegen, und es war nicht so intuitiv, wie ich erwartet hatte

  • Bei Distrust haben wir in Rust selbst ein extrem simples init-System mit weniger als 500 Zeilen geschrieben, das einige Kunden in sicherheitskritischen Enclave-Umgebungen produktiv einsetzen. Wir haben nur die Rust-Standardbibliothek verwendet, was Audits sehr einfach gemacht hat
    https://git.distrust.co/public/nit

    • Sieht sauber aus (wenn auch 33 % größer als nit), aber im Readme steht nur, wie man es baut; die eigentliche Schnittstelle oder Funktionsweise wird nicht erklärt
  • Keine Abhängigkeitsangaben, keine Benutzer-/Gruppenkonfiguration, Reihenfolge muss manuell festgelegt werden, keine parallele Ausführung von Services, kein Ressourcenmanagement. So etwas würde ich nicht als init-System bezeichnen. Das ist nur ein Barebones-Process-Supervisor

    • Tatsächlich erledigt es all das sehr gut, nach meiner Erfahrung sogar besser als systemd. Ich habe statt nitro lange Zeit nur daemontools verwendet, von denen nitro ein Nachfolger ist. Die Benutzung ist unglaublich einfach, stabil und leicht zu verstehen. Auch beim Thema Abhängigkeiten ist der djb-/daemontools-Stil „darum kümmert ihr euch selbst, dafür bekommt ihr einfache, günstige und zuverlässige Werkzeuge“ in der Praxis viel sinnvoller