- Nitro est un système d’init et superviseur de processus ultra-léger adapté aussi bien à l’embarqué qu’aux serveurs, postes de travail et conteneurs
- Il stocke l’état du système uniquement en RAM, ce qui lui permet de fonctionner sans difficulté sur des systèmes de fichiers en lecture seule, avec une conception rapide et efficace, basée sur les événements
- La configuration repose sur une simple structure de répertoires de scripts, ce qui permet de gérer les services sans fichiers de configuration complexes ni étape de build supplémentaire
- Il prend en charge des services paramétrés, des redémarrages robustes et une journalisation fiable par service, avec des fonctions optimisées pour les conteneurs et les environnements embarqués
- Il garantit une grande souplesse et un contrôle fin, notamment grâce au contrôle à distance via l’outil nitroctl et à un pilotage par signaux
Aperçu
Nitro est un superviseur de processus ultra-léger pouvant aussi être utilisé comme pid 1 sous Linux
Principaux cas d’usage :
- init pour des machines Linux de types variés : embarqué, poste de travail, serveur, etc.
- init de Linux
initramfs - init pour environnements conteneurisés comme Docker/Podman/LXC/Kubernetes
- démon de supervision fonctionnant sans privilèges sur des systèmes POSIX
La configuration utilise une structure de scripts basée sur des répertoires, avec /etc/nitro comme emplacement par défaut
Exigences
- prise en charge des sockets Unix par le noyau
tmpfsou répertoire/runaccessible en écriture
Avantages par rapport aux autres systèmes
- Toutes les informations d’état sont conservées uniquement en RAM, ce qui permet de fonctionner sur un système de fichiers racine en lecture seule sans astuce particulière
- Fonctionnement basé sur les événements, sans polling, pour plus d’efficacité
- Aucune allocation dynamique de mémoire à l’exécution
- Les descripteurs de fichiers ne sont pas consommés indéfiniment
- Un seul binaire autonome est nécessaire (avec, en option, un binaire de contrôle supplémentaire)
- Pas besoin de conversion ou de compilation de fichiers de configuration : un service est simplement un répertoire contenant des scripts
- Prise en charge des chaînes de redémarrage et de journalisation des services
- Fonctionne correctement même si l’horloge système n’est pas précise
- Peut être exécuté via
/etc/ttyssur FreeBSD - Il est possible de produire un binaire statique ultra-compact avec musl libc
Gestion des services
-
Chaque répertoire de service (par défaut dans
/etc/nitro) peut contenir les fichiers suivantssetup: script (optionnel) exécuté avant le démarrage du service ; le service ne peut démarrer que si ce script se termine avec succès (0)run: script d’exécution du service ; tant qu’il ne se termine pas, le service est considéré comme actif ; s’il n’est pas implémenté, le service est traité comme un service one-shotfinish: script (optionnel) exécuté après la fin derun, recevant en argument l’état de sortie et la valeur du signallog: lien symbolique vers un autre répertoire de service ; la sortie derunest reliée par pipe à l’entrée de ce service (utile pour les chaînes de journalisation)down: si ce fichier existe, nitro ne démarre pas ce service par défaut- si le nom du répertoire se termine par
@, il est ignoré et peut servir de service paramétré - le nom du service doit faire moins de 64 caractères et ne peut pas contenir
/,,ni de saut de ligne
-
L’utilitaire
chpstde runit est utile pour écrire les scriptsrun
Services spéciaux
LOG: service par défaut pour enregistrer les journaux de tous les services sans lienlogSYS:SYS/setupest exécuté avant le démarrage de tous les services, ce qui permet de mettre en place un démarrage ordonné des servicesSYS/finish: exécuté avant l’entrée dans la phase d’arrêt globaleSYS/final: exécuté après l’arrêt de tous les processusSYS/fatal: exécuté à la place de l’arrêt en cas d’erreur fatale, s’il existeSYS/reincarnate: exécuté à la place du shutdown, utile par exemple pour une réimplémentation d’initramfs
Services paramétrés
- Les répertoires de service se terminant par
@sont ignorés par nitro, mais peuvent être ciblés directement via un lien symbolique ou la commandenitroctl - Le paramètre placé après
@est transmis comme premier argument à chaque script- Exemple : s’il existe des liens symboliques
agetty@/runetagetty@tty1, alorsagetty@/run tty1est exécuté - Avec
nitroctl up agetty@tty2, il est possible d’exécuteragetty@/run tty2(que le répertoire existe ou non)
- Exemple : s’il existe des liens symboliques
Modes de fonctionnement
- Le cycle de vie complet comprend trois étapes : démarrage, exécution des services (supervision), arrêt
- Démarrage : si le service spécial
SYSexiste, sonsetupest exécuté en premier, puis tous les services non marquésdownsont lancés - Lorsqu’un service se termine, il est redémarré ; si les redémarrages récents sont trop rapprochés, une attente de 2 secondes est appliquée
nitroctl RebootouShutdownpermet d’envoyer le signal d’arrêt- Dans ce cas :
SYS/finish→SIGTERMà tous les services (attente maximale de 7 secondes) →SIGKILL→SYS/final→ séquence d’arrêt
- Dans ce cas :
- Dans le cas d’un conteneur ou d’un superviseur sans privilèges, seuls les processus sont arrêtés
- Démarrage : si le service spécial
Contrôle via nitroctl
- L’outil CLI nitroctl permet de contrôler nitro à distance
Exemples de commandes :
- list : affiche la liste des services, leur état, leur PID, leur uptime et leur dernier état de sortie
- up/down/start/stop/restart : contrôle du démarrage, de l’arrêt et du redémarrage des services
- envoi de signaux : p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
- pidof : affiche le PID du service demandé
- rescan : relit les répertoires de services et prend en compte les services ajoutés ou supprimés
- Shutdown/Reboot : arrêt ou redémarrage de l’ensemble du système
Contrôle par signaux
- Il est possible de contrôler nitro en envoyant directement des signaux au processus nitro
- SIGHUP : rescannage des services (
rescan) - SIGINT : redémarrage
- SIGTERM : arrêt (si nitro n’est pas pid 1)
- SIGHUP : rescannage des services (
Nitro comme init sous Linux
- Nitro peut démarrer directement comme pid 1 sous Linux sous forme de binaire autonome
- Il monte
/devet/runsi nécessaire, et les autres opérations sont gérées dansSYS/setup - L’événement Ctrl-Alt-Del déclenche un redémarrage ordonné
Utiliser Nitro comme init dans un conteneur Docker
- Nitro peut être compilé statiquement et intégré facilement dans un conteneur
/rundoit exister dans le conteneur pour utiliser le chemin de socket par défaut- Si la socket de contrôle est montée par bind mount, un contrôle à distance via nitroctl depuis l’extérieur est possible
Nitro sur FreeBSD
- En ajoutant la ligne suivante dans
/etc/ttys, il est possible de faire superviser nitro par l’init de FreeBSD/etc/nitro "/usr/local/sbin/nitro" "" on
Auteur
- Leah Neukirchen leah@vuxu.org
Remerciements
- Développé à partir d’une analyse détaillée de systèmes existants de supervision de processus comme daemontools, freedt, runit, perp et s6
Licence
- Licence 0BSD (voir le fichier LICENSE pour les détails)
1 commentaires
Commentaires sur Hacker News
J’aimerais voir une comparaison avec runit. runit est un système d’init extrêmement minimal tout en étant presque complet. Il y a beaucoup de similarités, comme le répertoire de contrôle, des dépendances non déclaratives, une organisation de scripts similaire, ou encore l’approche de la journalisation. La page de présentation mentionne aussi brièvement runit et recommande d’utiliser l’utilitaire chpst avec. Côté différences, je trouve intéressante la structure qui permet de gérer plusieurs processus similaires (par ex.
agetty) de façon paramétrée à partir d’un seul répertoire de service. On peut aussi exécuter directementrebootoushutdownvia un binaire unique (nitroctl). À l’inverse, runit repose sur plusieurs binairesJ’ai eu un petit pincement au cœur quand j’ai retiré l’an dernier les derniers serveurs où je gérais les processus avec runit. Quand j’ai écrit mes premiers services runit moi-même il y a environ 15 ans, je pensais que c’était la façon standard de gérer les services sous Linux. Puis, après avoir quitté Linux pendant 5 ans avant d’y revenir, systemd était devenu la norme. J’en avais souvent entendu du mal, mais j’ai fini par comprendre qu’il y avait aussi beaucoup d’hostilité déformée. Aujourd’hui, je fais tourner sur un Pi Zero, dans un vivarium pour reptiles, des services de streaming de caméra et de données de température, et la configuration avec systemd a été extrêmement simple. J’ai aussi pu exploiter facilement divers services avec systemd sur un desktop OpenSuse et sur mon portable de travail. Je me dis qu’« avoir un standard, c’est finalement une bonne chose »
Il existe une comparaison minimaliste pertinente entre runit et nitro dans les slides de présentation (PDF) de Leah Neukirchen publiées en 2024
https://leahneukirchen.org/talks/#nitroyetanotherinitsy
Leah Neukirchen est très active dans la communauté Void Linux. Je m’attends à ce que ce projet soit étroitement lié à Void. J’aimerais qu’il y ait un article un peu plus officiel sur la manière d’utiliser nitro dans Void
Je me demande si l’absence de dépendances « déclaratives » est présentée comme un avantage. J’ai souvent entendu des critiques de systemd en tant qu’init, mais il est rare d’entendre une critique du modèle déclaratif lui-même. J’aimerais bien avoir une explication détaillée des raisons
J’ai découvert runit via Void Linux et je l’utilise bien comme système d’init, mais son interface et sa documentation m’ont laissé sur ma faim. La configuration de la journalisation, en particulier, a été vraiment difficile. J’aimerais essayer une alternative tout aussi simple, mais avec des valeurs par défaut plus raisonnables, une interface plus intuitive et une meilleure documentation
Chaque fois que je vois quelqu’un proposer d’exécuter un système d’init dans un conteneur, ça me laisse perplexe. Parfois c’est conçu ainsi par nécessité réelle, mais j’ai souvent vu que cela rendait les choses inutilement trop complexes (surtout dans des environnements Kubernetes ou cloud, où il aurait fallu mieux séparer l’architecture dès le départ). J’ai l’impression qu’il y a aussi un effet « puisque tout le monde fait comme ça ». Du coup, je me demande toujours s’il vaut mieux « faire mieux » tout en laissant le problème se répandre, ou bien laisser les gens échouer spectaculairement avec les solutions existantes
Je pense qu’un conteneur applicatif devrait suivre la philosophie Unix : « faire une seule chose et bien la faire ». Mais si, pour une raison quelconque, le conteneur fait des
fork, alors il devrait y avoir un vrai init en PID 1Dans la robotique, d’après ce que j’ai vu, beaucoup de conteneurs sont en fait des systèmes complexes auparavant exécutés sur bare metal puis déplacés dans des conteneurs. Il y a beaucoup de RPC non structurées entre processus, donc il n’y a pas vraiment d’intérêt à tout découper en multiples conteneurs séparés. Pour lancer plusieurs processus à l’intérieur d’un conteneur d’application monolithique, on voit coexister toutes sortes d’options : supervisor, runit, systemd, jusqu’à tmux
J’ai déjà utilisé des hébergements facturés au conteneur, comme Fly.io, Render ou Google Cloud Run. À cause du prix, il faut parfois lancer plusieurs processus dans un seul conteneur
La nouvelle fonctionnalité modular-services de NixOS a été intégrée à Nixpkgs. Cela devrait rendre beaucoup plus facile le portage de NixOS vers un nouveau système d’init ou un nouveau noyau, donc je pense que c’est un bon moment pour expérimenter avec quelque chose comme nitro
J’aimerais comparer nitro avec dinit, utilisé par Chimera Linux. En parcourant rapidement le readme, on dirait que la gestion des dépendances de services n’est pas encore prise en charge
dinit: https://github.com/davmac314/dinit
Nitro ne gère pas les dépendances de services de façon déclarative. Il est impossible d’obtenir avec une simple commande une jolie vue du graphe de dépendances entre services. En revanche, si les services nécessaires sont mentionnés dans le script de setup, il vérifie qu’ils sont bien démarrés et attend automatiquement puis réessaie si besoin. Pour voir le graphe des dépendances, il n’y a guère d’autre choix que d’écrire soi-même des scripts avec
grepou autre. À l’inverse, il est facile d’oublier d’arrêter correctement en cascade les services dépendants lorsqu’un service meurt, et nitro n’offre pas vraiment de moyen pratique pour détecter ce type de problèmeJ’ai utilisé dinit sur Artix Linux, et c’était vraiment léger et impressionnant
FAQ Artix : https://artixlinux.org/faq.php
Je trouve ces projets de bas niveau vraiment passionnants. J’ai apprécié que systemd sache bien exploiter des fonctionnalités spécifiques au noyau Linux au-delà du cadre traditionnel SysV/POSIX. Mais j’espère que ce n’est pas une fin en soi, et que de nouvelles idées et innovations continueront d’apparaître. Récemment, j’ai mis en place pour de l’automatisation industrielle un système qui démarre un noyau Linux directement en netboot depuis le firmware UEFI, avec pour seul composant embarqué un binaire d’init monolithique que j’ai moi-même écrit en Go. Le fait de contrôler tout l’environnement OS uniquement avec du code maison et un langage de haut niveau, sans avoir à gérer toutes sortes de sous-processus ni une multitude de fichiers de configuration texte, donnait un sentiment de liberté assez incroyable
Il y a environ 13 ans, j’ai moi-même construit un système d’init en C. Cela a demandé bien plus d’efforts que prévu, et il servait à démarrer rapidement une interface GUI et un backend sur du matériel peu performant. C’était un exercice de programmation amusant, mais j’ai réalisé plus tard qu’il existait peut-être déjà des solutions similaires. Un collègue a même créé un autre init dans la même entreprise : ma première version était très légère, avec quasiment aucune dépendance en dehors de libc, tandis que la sienne, basée sur libevent, avait des fonctionnalités plus avancées
Le chevauchement de nom et de fonction avec AWS Nitro me gêne
https://docs.aws.amazon.com/whitepapers/latest/security-design-of-aws-nitro-system/the-nitro-system-journey.html
Seul le nom se recoupe ; un système d’init et un hyperviseur sont fondamentalement des choses complètement différentes
Je ne pense pas qu’il y aura vraiment de problème. L’un est un système d’init utilisable par tout le monde, tandis qu’AWS Nitro est un fork de KVM utilisé uniquement en interne par une entreprise
Je me demande comment nitro se compare à s6. J’ai récemment essayé de mettre en place un système d’init avec s6 dans un conteneur Docker, mais avec s6-overlay il fallait créer soi-même beaucoup de fichiers, et ce n’était pas aussi intuitif que je l’espérais
tini mérite aussi un coup d’œil : https://github.com/krallin/tini
Chez Distrust, nous avons écrit nous-mêmes en rust un système d’init ultra-simple de moins de 500 lignes, et certains clients l’utilisent déjà en production dans des environnements d’enclave à fortes exigences de sécurité. Nous n’avons utilisé que la bibliothèque standard rust, ce qui a rendu l’audit très facile
https://git.distrust.co/public/nit
Pas de déclaration de dépendances, pas de configuration utilisateur/groupe, ordre à définir manuellement, pas de lancement parallèle des services, pas de gestion des ressources. Je préférerais qu’on n’appelle pas « système d’init » un système auquel tout cela manque. C’est juste un process supervisor bare-bones