2 puntos por GN⁺ 2025-08-24 | 1 comentarios | Compartir por WhatsApp
  • 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 tmpfs o un directorio /run con 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 archivos

    • setup: 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-shot
    • finish: script (opcional) que se ejecuta después de que termine run; recibe como argumentos el estado de salida y el valor de la señal
    • log: enlace simbólico que apunta a otro directorio de servicio; conecta por pipe la salida de run con 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 chpst de runit resulta útil al escribir scripts run

Servicios especiales

  • LOG: servicio predeterminado para registrar logs de todos los servicios que no tengan enlace log
  • SYS: SYS/setup se ejecuta antes de iniciar todos los servicios y permite implementar un arranque ordenado de servicios
    • SYS/finish: se ejecuta antes de entrar en la fase completa de apagado
    • SYS/final: se ejecuta después de terminar todos los procesos
    • SYS/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@/run y agetty@tty1, se ejecuta agetty@/run tty1
    • Al ingresar nitroctl up agetty@tty2, puede ejecutarse agetty@/run tty2 (sin importar si el directorio existe)

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 desde setup, 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 Reboot o Shutdown
      • 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 contenedores o en supervisores sin privilegios, solo terminan los procesos

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 /dev y /run cuando es necesario, y el resto del comportamiento se gestiona en SYS/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 /run dentro 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

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

 
GN⁺ 2025-08-24
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 binarios

    • El 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 fork por cualquier motivo, entonces sí creo que debe haber un init de verdad en el PID 1

    • Por 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 grep o 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 cosas

    • Probé 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

  • 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

    • Se ve limpio (aunque es 33% más grande que nit), pero en el readme solo aparece cómo compilarlo; no explica la interfaz real ni cómo funciona
  • 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

    • En la práctica, sí resuelve bien todo eso (incluso, en mi experiencia, mejor que systemd). Durante mucho tiempo he usado daemontools, del cual nitro es heredero, y no nitro en sí. Es increíblemente fácil de usar, estable y fácil de entender. Respecto al problema de las dependencias, el estilo de djb/daemontools de “eso lo resuelve cada quien por su lado; nosotros solo damos herramientas simples, baratas y confiables” me parece mucho más práctico en el trabajo real