- 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
nitroctlund 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
tmpfsoder 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/ttysausgeführt werden - Mit musl libc lassen sich ultrakleine statische Binärdateien erstellen
Dienstverwaltung
-
Jedes Dienstverzeichnis (standardmäßig unter
/etc/nitro) kann die folgenden Dateien enthaltensetup: (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 behandeltfinish: (optionales) Skript, das nach dem Ende vonrunausgeführt wird; Exit-Status und Signalwert werden als Argumente übergebenlog: symbolischer Link auf ein anderes Dienstverzeichnis; die Ausgabe vonrunwird 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 vonrun-Skripten nützlich
Besondere Dienste
LOG: Standarddienst zum Protokollieren aller Dienste ohnelog-LinkSYS:SYS/setupwird vor dem Start aller Dienste ausgeführt und kann für geordnete Startabläufe verwendet werdenSYS/finish: wird vor dem Eintritt in die globale Beendigungsphase ausgeführtSYS/final: wird ausgeführt, nachdem alle Prozesse beendet wurdenSYS/fatal: wird bei fatalen Fehlern statt eines Beendens ausgeführt, falls vorhandenSYS/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 Befehlnitroctldirekt angesprochen werden - Der Parameter nach
@wird als erstes Argument an jedes Skript übergeben- Beispiel: Wenn symbolische Links
agetty@/runundagetty@tty1existieren, wirdagetty@/run tty1ausgeführt - Mit
nitroctl up agetty@tty2kannagetty@/run tty2ausgeführt werden, unabhängig davon, ob das Verzeichnis existiert
- Beispiel: Wenn symbolische Links
Betriebsmodi
- Der gesamte Lebenszyklus besteht aus drei Phasen: Booten, Dienstausführung (Supervision) und Beenden
- Booten: Wenn der besondere Dienst
SYSexistiert, wird absetupausgeführt; danach werden alle nicht aufdowngesetzten Dienste gestartet - Wenn ein Dienst endet, wird er neu gestartet; war der letzte Neustart jedoch sehr kurz zuvor, wird 2 Sekunden gewartet
- Mit
nitroctl RebootoderShutdownkann ein Beendigungssignal ausgelöst werden- Dann folgt
SYS/finish→ SIGTERM an alle Dienste (maximal 7 Sekunden warten) → SIGKILL →SYS/final→ Beendigungssequenz
- Dann folgt
- Für Container oder unprivilegierte Supervisoren werden nur die Prozesse beendet
- Booten: Wenn der besondere Dienst
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
/devund/runwerden bei Bedarf gemountet; weitere Abläufe werden inSYS/setupbehandelt- 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
/runmuss 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/ttyskann nitro von FreeBSD init überwacht werden/etc/nitro "/usr/local/sbin/nitro" "" on
Autor
- Leah Neukirchen leah@vuxu.org
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
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 manrebootodershutdowndirekt über ein einzelnes Binary (nitroctl) ausführen. runit besteht dagegen aus mehreren BinariesAls 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
forkverwendet wird, sollte PID 1 meiner Meinung nach ein echtes init seinAus 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 wiegrepzu 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 erkennenIch 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-overlayaber viele Dateien selbst anlegen, und es war nicht so intuitiv, wie ich erwartet hattetini sollte man sich auch ansehen: https://github.com/krallin/tini
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
nit), aber im Readme steht nur, wie man es baut; die eigentliche Schnittstelle oder Funktionsweise wird nicht erklärtKeine 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