- Nitro es un sistema init y supervisor de procesos ultraligero que puede aplicarse a entornos embebidos, servidores, escritorios y contenedores
- Guarda el estado del sistema solo en RAM, por lo que funciona sin problemas incluso en sistemas de archivos de solo lectura, y ofrece un diseño basado en eventos rápido y eficiente
- Su configuración usa una estructura simple de directorios con scripts, lo que permite administrar servicios sin archivos de configuración complejos ni procesos de compilación adicionales
- Soporta servicios parametrizados, reinicios robustos y logging confiable por servicio, entre otras funciones optimizadas para contenedores y entornos embebidos
- Garantiza alta flexibilidad y control con funciones como control remoto mediante la herramienta nitroctl y control de comportamiento basado en señales
Descripción general
Nitro es un supervisor de procesos ultraligero que también puede usarse como pid 1 en Linux
Sus principales casos de uso son los siguientes
- init para máquinas Linux de distintos usos, como embebidos, escritorios y servidores
- init de initramfs en Linux
- init para entornos de contenedores como Docker/Podman/LXC/Kubernetes
- demonio de supervisión que funciona sin privilegios en sistemas POSIX
La configuración utiliza una estructura de scripts basada en directorios, y la ubicación predeterminada es /etc/nitro
Requisitos
- Se requiere soporte del kernel para sockets Unix
- Se requiere
tmpfso un directorio/runcon escritura habilitada
Ventajas frente a otros sistemas
- Toda la información de estado se mantiene solo en RAM, por lo que funciona en sistemas de archivos raíz de solo lectura sin trucos adicionales
- Proporciona eficiencia con un modo de operación basado en eventos, sin polling
- No hay asignación dinámica de memoria durante la ejecución
- Los descriptores de archivo no se consumen indefinidamente
- Solo se necesita un binario autocontenido (con un binario de control adicional de forma opcional)
- No hace falta convertir ni compilar archivos de configuración; un servicio es simplemente un directorio con scripts
- Soporta reinicio de servicios y cadena de logging
- Funciona correctamente aunque el reloj del sistema no sea preciso
- Puede ejecutarse en FreeBSD mediante
/etc/ttys - Con musl libc se puede crear un binario estático ultrapequeño
Gestión de servicios
-
Cada directorio de servicio (por defecto dentro de
/etc/nitro) puede incluir los siguientes archivossetup: script (opcional) que se ejecuta antes de iniciar el servicio; el servicio solo puede iniciarse si termina correctamente (0)run: script de ejecución del servicio; mientras no termine, el servicio se considera activo; si no está implementado, se trata como un servicio one-shotfinish: script (opcional) que se ejecuta después de que terminerun; recibe como argumentos el estado de salida y el valor de la señallog: enlace simbólico que apunta a otro directorio de servicio; conecta por pipe la salida deruncon la entrada de ese servicio (útil para cadena de logging)down: si este archivo existe, nitro no levanta ese servicio de manera predeterminada- Si el nombre del directorio termina en '@', se ignora y puede usarse como servicio parametrizado
- El nombre del servicio debe tener menos de 64 caracteres y no puede incluir
/,,ni saltos de línea
-
La utilidad
chpstde runit resulta útil al escribir scriptsrun
Servicios especiales
LOG: servicio predeterminado para registrar logs de todos los servicios que no tengan enlacelogSYS:SYS/setupse ejecuta antes de iniciar todos los servicios y permite implementar un arranque ordenado de serviciosSYS/finish: se ejecuta antes de entrar en la fase completa de apagadoSYS/final: se ejecuta después de terminar todos los procesosSYS/fatal: se ejecuta en lugar de salir si ocurre un error fatal (si existe)SYS/reincarnate: se ejecuta en lugar de shutdown y puede usarse, por ejemplo, para reimplementar initramfs
Servicios parametrizados
- Los directorios de servicio que terminan en '@' son ignorados por nitro, pero pueden especificarse directamente mediante enlaces simbólicos o el comando
nitroctl - El parámetro después de '@' se pasa como primer argumento a cada script
- Ejemplo: si existen los enlaces simbólicos
agetty@/runyagetty@tty1, se ejecutaagetty@/run tty1 - Al ingresar
nitroctl up agetty@tty2, puede ejecutarseagetty@/run tty2(sin importar si el directorio existe)
- Ejemplo: si existen los enlaces simbólicos
Modos de operación
- El ciclo de vida completo consta de tres etapas: arranque, ejecución de servicios (supervisión) y apagado
- Arranque: si existe el servicio especial
SYS, se ejecuta desdesetup, y luego se inician todos los servicios que no estén marcados como down - Si un servicio termina, se reinicia; pero si se reinició hace muy poco, espera 2 segundos
- Es posible enviar la señal de apagado con
nitroctl RebootoShutdown- En ese caso, la secuencia es
SYS/finish→ SIGTERM a todos los servicios (espera máxima de 7 segundos) → SIGKILL →SYS/final→ secuencia de salida
- En ese caso, la secuencia es
- En contenedores o en supervisores sin privilegios, solo terminan los procesos
- Arranque: si existe el servicio especial
Control con nitroctl
- La herramienta de línea de comandos nitroctl permite controlar nitro de forma remota
Ejemplos de comandos:
- list: muestra la lista de servicios, estado, PID, uptime y último estado de salida
- up/down/start/stop/restart: controla inicio, detención y reinicio de servicios
- Envío de señales: p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
- pidof: muestra el PID del servicio especificado
- rescan: vuelve a leer el directorio de servicios y refleja servicios agregados o eliminados
- Shutdown/Reboot: apaga o reinicia todo el sistema
Control mediante señales
- Se puede controlar enviando señales directamente al proceso nitro
- SIGHUP: reescaneo de servicios (rescan)
- SIGINT: reinicio
- SIGTERM: apagado (si nitro no es pid 1)
Nitro como init en Linux
- Nitro puede arrancar directamente como pid 1 de Linux al ser un binario autocontenido
- Monta
/devy/runcuando es necesario, y el resto del comportamiento se gestiona enSYS/setup - El evento Ctrl-Alt-Del activa un reinicio ordenado
Uso de Nitro como init en contenedores Docker
- Nitro puede compilarse de forma estática e incluirse fácilmente en un contenedor
- Debe existir
/rundentro del contenedor para poder usar la ruta predeterminada del socket - Si el socket de control se monta con bind mount, se puede controlar de forma remota desde fuera con nitroctl
Nitro en FreeBSD
- Se puede hacer que FreeBSD init supervise nitro agregando la siguiente línea a
/etc/ttys/etc/nitro "/usr/local/sbin/nitro" "" on
Autor
- Leah Neukirchen leah@vuxu.org
Agradecimientos
- Desarrollado a partir de un análisis detallado de sistemas de supervisión de procesos existentes como daemontools, freedt, runit, perp y s6
Licencia
- Licencia 0BSD (consulta el archivo LICENSE para más detalles)
1 comentarios
Comentarios de Hacker News
Me gustaría ver una comparación con runit. runit es un sistema init extremadamente minimalista y aun así casi completo. Tiene muchas similitudes, como el directorio de control, dependencias no declarativas, una estructura de scripts parecida y el enfoque de logging. Incluso en la página de explicación se menciona brevemente a runit y se recomienda usarlo junto con la utilidad chpst. Como diferencia, me parece buena la estructura para gestionar varios procesos similares (por ejemplo, agetty) parametrizados con un solo directorio de servicio. También se puede ejecutar reboot o shutdown directamente con un solo binario (
nitroctl). En cambio, runit usa una estructura de varios binariosEl año pasado retiré los últimos servidores que administraban procesos con runit, y me dio bastante nostalgia. Cuando escribí mis primeros servicios de runit hace unos 15 años, pensaba que esa era la forma estándar de administrar servicios en Linux. Luego me alejé de Linux por 5 años y, al volver, systemd ya era la opción por defecto. Había escuchado muchas críticas, pero con el tiempo entendí que gran parte del rechazo estaba algo distorsionado. Ahora mismo tengo un servicio en un Pi Zero para transmitir cámara y datos de temperatura en un vivarium de reptiles, y configurarlo con systemd fue facilísimo. También pude manejar varios servicios de forma sencilla con systemd en mi escritorio con OpenSuse y en mi laptop de trabajo. Me deja pensando que “tener un estándar quizá sí es algo bueno”
Hay una comparación minimalista bastante adecuada entre runit y nitro en las diapositivas de una charla de Leah Neukirchen publicada en 2024 (PDF)
https://leahneukirchen.org/talks/#nitroyetanotherinitsy
Leah Neukirchen participa activamente en la comunidad de Void Linux. Espero que este proyecto tenga una relación cercana con Void. Me gustaría que escribieran algo más oficial sobre cómo usar nitro en Void
Me pregunto si que “no tenga dependencias declarativas” se considera una ventaja. He escuchado muchas críticas a systemd como init, pero rara vez veo críticas al diseño declarativo en sí. Me gustaría escuchar con detalle por qué
Conocí runit en Void Linux y me ha servido bien como sistema init, pero sí me ha frustrado la falta de UI y de documentación. En particular, configurar el logging fue realmente difícil. Me gustaría probar una alternativa igual de simple, pero con valores por defecto más razonables, una UI más intuitiva y mejor documentación
Cada vez que veo la idea de correr un sistema init dentro de un contenedor, siempre me genera dudas. A veces sí se diseña por necesidad real, pero muchas otras me ha parecido que solo complica demasiado las cosas (sobre todo en entornos de Kubernetes y nube, donde en realidad se debió haber separado mejor el diseño). A veces siento que es más un fenómeno de “como todos lo usan así”. Siempre me queda la duda de si vale la pena “hacerlo mejor” y propagar aún más el patrón, o si sería preferible dejar que la gente fracase con fuerza usando las soluciones existentes
Creo que los contenedores de aplicaciones deberían seguir la filosofía Unix de “hacer una sola cosa y hacerla bien”. Pero si dentro del contenedor se hace
forkpor cualquier motivo, entonces sí creo que debe haber un init de verdad en el PID 1Por lo que he visto en robótica, muchos contenedores son en realidad sistemas complejos que originalmente corrían sobre bare metal y luego se movieron a contenedores. Hay mucho RPC no estructurado entre procesos, así que no siempre tiene sentido partirlos en varios contenedores separados. Para lanzar múltiples procesos dentro de un contenedor de app monolítica, se usan de todo: supervisor, runit, systemd e incluso tmux
He usado hostings que cobran por contenedor, como Fly.io, Render y Google Cloud Run. Por precio, a veces toca correr varios procesos dentro de un mismo contenedor
La nueva función modular-services de NixOS ya fue incluida en Nixpkgs. Eso debería hacer mucho más fácil portar NixOS a un nuevo sistema init o incluso a un kernel nuevo, así que creo que este es un buen momento para experimentar con cosas como nitro
Me gustaría comparar dinit, que usa Chimera Linux, con nitro. Viendo rápidamente el readme, parece que todavía no maneja dependencias de servicios
dinit: https://github.com/davmac314/dinit
Nitro no maneja dependencias de servicios de forma declarativa. No puedes ver bonito el grafo de dependencias entre servicios con un solo comando. Pero si declaras en el script de setup los servicios necesarios, verifica si están levantados y espera automáticamente para reintentar. Si quieres ver el grafo de dependencias, básicamente te toca escribir algo a mano con
grepo similar. En cambio, cuando un servicio cae, es fácil olvidar bajar también en cascada los servicios dependientes, y nitro por sí solo no ofrece una manera cómoda de detectar ese tipo de cosasProbé dinit en Artix Linux y me pareció realmente ligero e impresionante
Artix FAQ: https://artixlinux.org/faq.php
Este tipo de proyectos de bajo nivel siempre me resultan fascinantes. Me gustó que systemd aprovechara bien funciones específicas del kernel de Linux, yendo más allá del marco tradicional de SysV y POSIX. Pero espero que no sea el punto final, y que sigan apareciendo ideas nuevas e innovación. Hace poco armé una configuración de automatización para manufactura que arranca por netboot desde firmware UEFI directo a un kernel Linux, con un solo binario init escrito por mí en Go. Fue realmente liberador controlar todo el entorno del OS solo con código propio y un lenguaje de alto nivel, sin tener que gestionar tantos subprocesos ni montones de archivos de configuración en texto
Hace unos 13 años tuve la experiencia de construir mi propio sistema init en C. Requirió muchísimo más esfuerzo del esperado, y se usó para arrancar rápido una GUI y un backend en hardware de bajo rendimiento. Fue un ejercicio de programación divertido, aunque después caí en cuenta de que quizá ya existían soluciones similares. Un colega incluso hizo otro init dentro de la misma empresa; así que mi primera versión era muy liviana, casi sin dependencias aparte de libc, mientras que la suya estaba basada en libevent y tenía funciones más avanzadas
Me incomoda que el nombre y la función se superpongan con AWS Nitro
https://docs.aws.amazon.com/whitepapers/latest/security-design-of-aws-nitro-system/the-nitro-system-journey.html
Solo comparten el nombre; un sistema init y un hipervisor son cosas fundamentalmente distintas
No creo que vaya a causar problemas. Uno es un sistema init que cualquiera puede usar, y AWS Nitro es un fork de KVM que solo se usa internamente en la empresa
Me da curiosidad cómo se compara nitro con s6. Hace poco armé un sistema init con s6 para un contenedor Docker, pero con s6-overlay tuve que crear muchos archivos manualmente y no me pareció tan intuitivo como esperaba
También vale la pena revisar tini: https://github.com/krallin/tini
En Distrust escribieron su propio sistema init ultraminimalista en Rust, de menos de 500 líneas, y algunos clientes ya lo usan en producción en entornos de enclave donde la seguridad es obligatoria. Usar solo la biblioteca estándar de Rust hizo que auditarlo fuera muy fácil
https://git.distrust.co/public/nit
Sin posibilidad de declarar dependencias, sin configuración de usuario/grupo, con orden manual obligatorio, sin ejecución paralela de servicios y sin gestión de recursos. A algo así no me gustaría llamarlo sistema init. Es solo un supervisor de procesos barebones