- Nitro é um sistema init e supervisor de processos ultracompacto que pode ser usado em embarcados, servidores, desktops e contêineres
- Armazena o estado do sistema apenas na RAM, funcionando sem dificuldade mesmo em sistemas de arquivos somente leitura, e oferece um design rápido e eficiente baseado em eventos
- A forma de configuração usa uma estrutura simples de diretórios com scripts, permitindo gerenciar serviços sem arquivos de configuração complexos nem processos de build adicionais
- Suporta serviços parametrizados, reinicialização robusta e logging confiável por serviço, entre outros recursos otimizados para contêineres e ambientes embarcados
- Garante alta flexibilidade e controle com controle remoto via a ferramenta nitroctl e controle de comportamento baseado em sinais
Visão geral
Nitro é um supervisor de processos ultracompacto que também pode ser usado como pid 1 no Linux
As principais áreas de uso são:
- init para máquinas Linux de vários tipos, como embarcados, desktops e servidores
- init do Linux initramfs
- init para ambientes de contêineres como Docker/Podman/LXC/Kubernetes
- daemon de supervisão que funciona sem privilégios em sistemas POSIX
A configuração usa uma estrutura de scripts baseada em diretórios, e o local padrão é /etc/nitro
Requisitos
- suporte a sockets Unix no kernel
tmpfsou um diretório/rungravável
Vantagens em relação a outros sistemas
- Todas as informações de estado ficam apenas na RAM, então funciona em sistemas de arquivos raiz somente leitura sem truques extras
- Operação baseada em eventos, sem polling, para maior eficiência
- Sem alocação dinâmica de memória durante a execução
- Não consome descritores de arquivo indefinidamente
- Requer apenas um binário autocontido (com um binário de controle adicional como opção)
- Não é necessário converter nem compilar arquivos de configuração; um serviço é apenas um diretório simples com scripts
- Suporte a cadeias de reinício de serviços e de logging
- Funciona normalmente mesmo que o relógio do sistema não esteja correto
- Pode ser executado no FreeBSD via
/etc/ttys - Com musl libc, é possível gerar um binário estático ultracompacto
Gerenciamento de serviços
-
Cada diretório de serviço (por padrão dentro de
/etc/nitro) pode conter os seguintes arquivossetup: script (opcional) executado antes do início do serviço; o serviço só pode iniciar se terminar com sucesso (0)run: script de execução do serviço; enquanto não terminar, o serviço é considerado ativo; se não for implementado, é tratado como um serviço one-shotfinish: script (opcional) executado após o término derun, recebendo como argumentos o status de saída e o valor do sinallog: link simbólico apontando para outro diretório de serviço, conectando a saída derunà entrada desse serviço por pipe (permitindo usar cadeias de logging)down: se este arquivo existir, o nitro não sobe esse serviço por padrão- Se o nome do diretório terminar com
@, ele será ignorado e poderá ser usado como serviço parametrizado - O nome do serviço deve ter menos de 64 caracteres e não pode conter
/,,nem quebras de linha
-
O utilitário
chpstdo runit é útil ao escrever scriptsrun
Serviços especiais
LOG: serviço padrão para registrar logs de todos os serviços que não têm linklogSYS:SYS/setupé executado antes de todos os serviços, permitindo implementar inicialização ordenada de serviçosSYS/finish: executado antes de entrar na etapa de desligamento geralSYS/final: executado após o encerramento de todos os processosSYS/fatal: executado no lugar do encerramento em caso de erro fatal (se existir)SYS/reincarnate: executado no lugar de shutdown, podendo ser usado, por exemplo, para reimplementar initramfs
Serviços parametrizados
- Diretórios de serviço terminados em
@são ignorados pelo nitro, mas podem ser especificados diretamente por link simbólico ou pelo comandonitroctl - O parâmetro após
@é passado como primeiro argumento para cada script- Exemplo: se existirem os links simbólicos
agetty@/runeagetty@tty1, entãoagetty@/run tty1será executado - Ao executar
nitroctl up agetty@tty2, é possível rodaragetty@/run tty2(independentemente da existência do diretório)
- Exemplo: se existirem os links simbólicos
Modos de operação
- Todo o ciclo de vida é composto por três etapas: boot, execução de serviços (supervisão) e encerramento
- Boot: se o serviço especial
SYSexistir, ele é executado a partir desetup, e depois todos os serviços non-down são iniciados - Quando um serviço termina, ele é reiniciado; porém, se a reinicialização mais recente tiver ocorrido muito rapidamente, há uma espera de 2 segundos
- É possível enviar um sinal de desligamento com
nitroctl RebootouShutdown- Nesse caso:
SYS/finish→ SIGTERM para todos os serviços (espera máxima de 7 segundos) → SIGKILL →SYS/final→ sequência de encerramento
- Nesse caso:
- Em contêineres ou quando usado como supervisor sem privilégios, apenas os processos são encerrados
- Boot: se o serviço especial
Controle com nitroctl
- A ferramenta de linha de comando nitroctl permite controlar o nitro remotamente
Exemplos de comandos:
- list: mostra lista de serviços, estado, PID, uptime e último status de saída
- up/down/start/stop/restart: controla início, parada e reinício de serviços
- envio de sinais: p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
- pidof: imprime o PID do serviço especificado
- rescan: relê o diretório de serviços e aplica adições/remoções
- Shutdown/Reboot: desliga ou reinicia todo o sistema
Controle por sinais
- Também é possível controlar enviando sinais diretamente ao processo nitro
- SIGHUP: reescaneia serviços (rescan)
- SIGINT: reinicializa
- SIGTERM: encerra (se o nitro não for pid 1)
Nitro como init no Linux
- Nitro é um binário autocontido que pode inicializar diretamente como pid 1 no Linux
- Monta
/deve/runquando necessário, e o restante do comportamento é tratado emSYS/setup - O evento Ctrl-Alt-Del aciona uma reinicialização ordenada
Usando Nitro como init em contêiner Docker
- Nitro pode ser compilado estaticamente e incluído facilmente em um contêiner
/runprecisa existir no contêiner para usar o caminho padrão do socket- Se o socket de controle for montado com bind mount, é possível fazer controle remoto externo com
nitroctl
Nitro no FreeBSD
- É possível fazer o init do FreeBSD supervisionar o nitro adicionando a seguinte linha em
/etc/ttys/etc/nitro "/usr/local/sbin/nitro" "" on
Autor
- Leah Neukirchen leah@vuxu.org
Agradecimentos
- Desenvolvido com base em uma análise detalhada de sistemas existentes de supervisão de processos como daemontools, freedt, runit, perp e s6
Licença
- Licença 0BSD (consulte o arquivo LICENSE para mais detalhes)
1 comentários
Comentários do Hacker News
Gostaria de ver uma comparação com o runit. O runit é um sistema init extremamente minimalista e ainda assim quase completo. Há muitas semelhanças, como o diretório de controle, dependências não declarativas, uma estrutura de scripts parecida e a abordagem de logging. A página de explicação também menciona brevemente o runit e recomenda usar o utilitário
chpstjunto. Como diferencial, acho boa a estrutura de gerenciar vários processos semelhantes (por exemplo,agetty) parametrizando-os a partir de um único diretório de serviço. Também é possível executarrebootoushutdowndiretamente com um único binário (nitroctl). Já o runit usa uma estrutura com vários bináriosSenti bastante falta dele quando aposentei, no ano passado, os últimos servidores que eu gerenciava com runit. Quando escrevi meus próprios serviços para runit pela primeira vez, cerca de 15 anos atrás, acreditei que aquele era o jeito padrão de gerenciar serviços no Linux. Depois fiquei uns 5 anos longe do Linux e, quando voltei, o systemd já era o padrão. Eu já tinha ouvido muitas críticas, mas aos poucos percebi que havia muito ressentimento distorcido. Hoje estou rodando serviços de streaming de câmera e dados de temperatura em um Pi Zero, em um vivário de répteis, e foi extremamente fácil configurar isso com systemd. Também consegui operar vários serviços com simplicidade usando systemd no meu desktop OpenSuse e no notebook de trabalho. Acabei pensando que “ter um padrão é, na verdade, uma coisa boa”
Há uma boa comparação minimalista entre runit e nitro nos slides da apresentação de Leah Neukirchen, publicados em 2024 (PDF)
https://leahneukirchen.org/talks/#nitroyetanotherinitsy
Leah Neukirchen é uma pessoa bastante ativa na comunidade do Void Linux. Imagino que este projeto vá ficar intimamente ligado ao Void. Gostaria que escrevessem algo mais oficial sobre como usar o nitro no Void
Fico curioso se o fato de “não ter dependências declarativas” é visto como uma vantagem. Já ouvi muitas críticas ao systemd como init, mas é raro ver críticas ao próprio design declarativo. Gostaria de ouvir em detalhe quais seriam os motivos
Conheci o runit no Void Linux e o uso bem como sistema init, mas sinto falta de UI e de documentação. Especialmente a configuração de logging foi realmente difícil. Gostaria de experimentar uma alternativa igualmente simples, mas com padrões mais sensatos, uma UI mais intuitiva e documentação melhor
Sempre fico em dúvida quando vejo essa ideia de rodar um sistema init dentro de contêiner. Em alguns casos isso realmente é projetado por necessidade, mas com frequência me parece que a coisa acaba ficando complexa demais (principalmente em Kubernetes e em ambientes de nuvem, onde uma arquitetura mais separada talvez devesse ter sido feita de forma mais adequada). Às vezes parece ser só o fenômeno de “já que todo mundo usa assim”, e eu sempre fico na dúvida se faz sentido tentar “fazer melhor” e espalhar ainda mais o problema, ou se seria melhor deixar as pessoas fracassarem feio com as soluções existentes
Acho que contêineres de aplicação deveriam seguir a filosofia Unix de “fazer uma coisa só e fazê-la bem”. Mas, se por qualquer motivo um contêiner for fazer
fork, então na minha visão deveria haver um init de verdade no PID 1Pela minha experiência em robótica, muitos contêineres são sistemas complexos que originalmente rodavam em bare metal e depois foram migrados para contêineres. Há muito RPC não estruturado entre processos, então não há grande vantagem em quebrar isso em vários contêineres separados. Para subir vários processos dentro de um contêiner de app monolítico, circulam opções de todo tipo, como supervisor, runit, systemd e até tmux
Já usei hospedagens que cobram por contêiner, como Fly.io, Render e Google Cloud Run. Por causa do preço, às vezes é preciso rodar vários processos dentro de um único contêiner
O novo recurso do NixOS, modular-services, foi incluído no Nixpkgs. Isso deve facilitar bastante portar o NixOS para um novo sistema init ou até um novo kernel, então acho que este é um bom momento para testar experimentos como o nitro
Gostaria de comparar o dinit, usado no Chimera Linux, com o nitro. Dando uma olhada rápida no readme, parece que ele ainda não gerencia dependências de serviço
dinit: https://github.com/davmac314/dinit
O Nitro não lida com dependências de serviço de forma declarativa. Não dá para ver de forma bonita o grafo de dependências entre serviços com um único comando. Mas, se você indicar os serviços necessários no script de setup, ele verifica se esses serviços estão ativos e espera/re tenta automaticamente. Se quiser ver o grafo de dependências, não tem jeito além de escrever algo você mesmo com
grepou algo parecido. Por outro lado, é fácil esquecer de derrubar corretamente os serviços dependentes em cascata quando um serviço morre, e o próprio nitro não oferece uma maneira prática de detectar issoUsei o dinit no Artix Linux e achei realmente leve e impressionante
Artix FAQ: https://artixlinux.org/faq.php
Acho esses projetos de baixo nível realmente fascinantes. Gostei de como o systemd foi além do modelo tradicional de SysV·POSIX e aproveitou bem recursos específicos do kernel Linux. Mas espero que isso não seja o fim, e que continuem surgindo novas ideias e inovações. Recentemente implementei, eu mesmo, uma configuração de automação industrial em que um kernel Linux dá boot via rede diretamente a partir do firmware UEFI e embute apenas um único binário init escrito por mim em Go. Foi realmente libertador controlar todo o ambiente do SO só com código próprio e uma linguagem de alto nível, sem precisar administrar vários subprocessos e uma infinidade de arquivos de configuração em texto
Cerca de 13 anos atrás, tive a experiência de construir meu próprio sistema init em C. Exigiu muito mais esforço do que eu esperava, e eu o usei para inicializar rapidamente uma GUI e um backend em hardware de baixo desempenho. Foi um exercício de programação divertido, mas depois percebi que talvez já existissem soluções parecidas. Um colega acabou criando outro init na mesma empresa, então minha primeira versão era bem leve, quase sem dependências além da libc, enquanto a versão dele era baseada em
libevente tinha recursos mais avançadosMe incomoda a sobreposição de nome e função com o AWS Nitro
https://docs.aws.amazon.com/whitepapers/latest/security-design-of-aws-nitro-system/the-nitro-system-journey.html
É só coincidência no nome; um sistema init e um hipervisor são coisas fundamentalmente totalmente diferentes
Acho muito improvável que isso cause algum problema. Um é um sistema init que qualquer pessoa pode usar, e o AWS Nitro é um fork de KVM usado apenas internamente pela empresa
Fico curioso sobre como o nitro se compara ao s6. Recentemente tentei montar um sistema init com s6 em um contêiner Docker, mas com o s6-overlay tive que criar muitos arquivos manualmente e achei menos intuitivo do que eu esperava
Vale a pena dar uma olhada no tini também: https://github.com/krallin/tini
Na Distrust, escrevemos nós mesmos um sistema init ultrassimples em Rust, com menos de 500 linhas, e alguns clientes já o usam em produção em ambientes de enclave com exigências fortes de segurança. Usamos apenas a biblioteca padrão do Rust, o que tornou a auditoria muito fácil
https://git.distrust.co/public/nit
Sem possibilidade de declarar dependências, sem configuração de usuário/grupo, com ordem manual obrigatória, sem execução paralela de serviços, sem gerenciamento de recursos. Eu preferiria que um sistema sem essas coisas não fosse chamado de sistema init. É só um supervisor de processos bare-bones