Daemontools e derivados: uma filosofia minimalista para gerenciar os serviços do sistema

O daemontools, originalmente lançado em 1997 pelo criptanalista Daniel J. Bernstein, é um programa de gerenciamento de serviços para Linux/Unix pouco conhecido, mas que surpreendentemente gerou muitos imitadores, incluindo runit, usado Void, e o s6, uma das opções no Artix, e versões mais de nicho como o nosh e o daemontools-encore.

É baseado em dois programas pequenos designados para invocar e monitorar scripts produzidos pelo usuário ou pela distribuição, que em conjunto são um esquema quase que acidentalmente poderoso e flexível para gerenciar serviços. Essa flexibilidade não vem de programas grandes e cheios de recursos, mas do uso inteligente de conceitos do próprio sistema operacional.

Esse artigo não é bem uma introdução a como usar o daemontools e família. Há recursos muito melhores para isso na internet, como o site arquivado do daemontools original, o site do runit, do s6 ou a wiki do Artix. Em vez disso, é um artigo que analisa o “como funciona” dessa família e, pessoalmente, porque eu a aprecio.

Um núcleo minimalista, mas já funcional

Em um sistema com o daemontools ou seus derivados, a base para gerenciar os programas que funcionam durante toda a operação do sistema (serviços como o de impressão, o agendador de tarefas, etc.) passa a ser dois programas:

  • svscan: iniciado dentro de uma pasta. Para cada subpasta (chamada pasta de serviço), ele inicia um programa chamado…
  • supervise: ele, por sua vez, inicia o arquivo chamado ./run, dentro da subpasta, e aguarda comandos enviados pelo usuário. Se o arquivo ./run terminar, ele é reiniciado.
    • Se houver um arquivo chamado down na pasta, o ./run não é inicado automaticamente – o supervise aguarda um comando para iniciá-lo pela primeira vez.
    • O supervise também procura um subpasta chamada log ao lado do ./run, e, se ela tiver um arquivo também chamado run, este é executado, e a saída do ./run “principal” é enviada para esse segundo run, que no geral vai salvá-la em um arquivo.

Isso é basicamente tudo que as versões originais desses programas faziam em 1997. Os derivados, quando fazem algo a mais, é bem pouco.

Com a capacidade de enviar sinais para e obter informações sobre os processos monitorados através de comandos para o supervise, ele se torna, por si só, uma base até que completa para gerenciar serviços, apesar do minimalismo.

A abordagem do daemontools também torna trivial testar serviços, é só rodar ./run ou sudo ./run direto do terminal e ver a sua saída.

Espaço amplo para interação com outros programas

No Unix/Linux, um programa pode ser substituído por outro e continuar sendo, tecnicamente o mesmo processo aos olhos do sistema operacional (exec). Desse modo, nos daemontools, os ./runs raramente são atalhos ou cópias dos programas finais, mas scripts que fazem preparações, e então usam o exec para literalmente se tornar o programa a ser monitorado.

Por exemplo, nos daemontools mais primitivos não há gerenciamento de serviços que dependem um do outro. Porém, os ./runs se viram sozinhos: enviam comando a outros supervises perguntando se o serviço está pronto, ou verificam se o outro serviço está operacional de outras maneiras. Se não estiverem, simplesmente falham antes de dar exec no programa final, e mais tarde serão reiniciados pelo supervise correspondente.

Não só isso, é possível verificar se a internet funciona (fazendo ping ou solicitando confirmação do gerenciador de redes), conferir se algum módulo de kernel necessário está carregado, e tudo mais que pode ser feito em qualquer script. Os conhecimentos envolvidos na criação do ./run são bastante aplicáveis a qualquer script ou programa, e vice-versa.

‘Bernstein-chaining’: o sistema operacional como um arquivo de configuração

Uma das partes mais fascinantes do daemontools e derivados é como eles exploram o fato de que, no Unix/Linux, mesmo após o exec, algumas propriedades são conservadas.

Junto com o daemontools, vem uma série de programas (ou um programa com várias opções, no caso do runit) que praticamente abusam disso. Programas que alteram o usuário sob o qual o programa vai rodar, programas que impõem limites à quantidade de arquivos abertos, programas para configurar variáveis, entre outros. Esses programas são inclusive completamente independentes do svscan e do supervise – se for de seu interesse, é possível utilizá-los fora dos serviços de sistema.

No daemontools original, por exemplo, se o ./run for um shell script, fazer um programa rodar como o usuário ‘exemplo’ é uma questão de escrever:

exec setuidgid exemplo programa

em vez de

exec programa

no final. É chamado de “chaining” porque o seu ./run faz exec no setuidgid, que faz uma mudança e então faz exec no programa final. É uma cadeia de exec, a cada passo fazendo uma mudança.

Evoluções posteriores dos daemontools estenderam esse conceito bastante. Os módulos de rede do nosh e s6, por exemplo, incluem programas que abrem um “socket” e fazem exec em outro programa, aproveitando que os arquivos abertos são conservados. Isso permite várias coisas, desde criar servidores HTTP em shell script puro até simular a “ativação por soquete” do systemd. Isso independentemente do programa estar rodando como um serviço do sistema ou não.

Talvez o extremo disso é a linguagem Execline, incluída com o s6, a qual é uma reimaginação do shell script que gira em torno de fazer exec’s.

Desse modo, em geral, derivados do daemontools praticamente não usam arquivos de configuração. Os ./run se “configuram sozinhos” usando essa propriedade do sistema, e também viabilizam tarefas agendadas, scripts, etc, igualmente “autoconfigurantes”.

A “fila de registros” - logs descentralizados

Mais acima, eu falei da pasta log, dentro da pasta de serviço. Esse é outro traço comum a praticamente todos os membros da família daemontools: não há um gerenciador único de logs, como o syslogd ou o journald. Cada programa recebe um log/run próprio que coleta a saída e a salva no disco.

Para auxiliar na padronização, quase sempre o run da pasta log faz exec em um programa específico para colocar data e hora, salvar e organizar os registros em uma pasta. Portanto, quase sempre, os logs já estão separados de modo que cada serviço tenha a sua pasta própria, cujos conteúdos podem ser analisados com qualquer editor de texto. Esse programa de registro também não possui nenhuma conexão com o svscan e o supervise: pode ser utilizado na ocasião que for necessária, permitindo também criar uma pasta de registro para qualquer programa do sistema rodado em qualquer contexto.

Modularidade

Os daemontools, com sua função restrita, praticamente convida a adição de funcionalidades por programas adicionais, quer sejam dos autores, quer sejam de outros autores (que muitas vezes nem sequer esperavam o uso do daemontools em conjunto com seus programas), e, de modo contrário, frequentemente fazem parte de projetos inesperados.

Um bom exemplo disso, quase extremo, é o s6-rc, que interage com a versão do daemontools inclusa no s6 para adicionar capacidades de gerenciamento de dependências e realização de tarefas que não exigem reinício, usando os arquivos down e a comunicação com os supervise para iniciar cada serviço no momento exato. Ou seja, o gerenciador de dependências pode ser facilmente removido do gerenciador de serviços (ou mesmo nunca ser incluso).

Conclusão

Pra quem já ficou maravilhado com as possibilidades de automação de shell script no Linux, daemontools e companhia te convidam a estender isso ao coração do sistema.

Eles abraçam a programabilidade de frente e exploram ao máximo o sistema operacional, desde os humildes arquivos e pastas, até a conservação de propriedades entre exec’s, de modo a construir “peças”/programas com funções muito específicas, que acabam aplicáveis dentro e fora do projeto de onde vieram e nos mais variados contextos. Há também muita liberdade para o usuário ou a distribuição escolher como melhor montar o sistema – aliás, para o usuário e para a distribuição, uma vez que raramente é necessário recompilar para adicionar e remover recursos.

Aceitar e facilitar a programabilidade, ironicamente, quase sempre resulta em um código menor – às vezes, até menor que os arquivos de configuração que foram criados e ficaram famosos porque ostensivamente reduziriam a quantidade de código.


Como o foco desse artigo é introduzir a “filosofia” dessa família, eu foquei nas características em comum e nas vantagens que elas trazem mais do que exemplos específicos.

Porém, claro que há peculiaridades em cada implementação (desde o nome dos programas, até as opções e comandos possíveis para cara versão do supervise).

Há também limitações nessa família – um dos principais pontos fracos é que não é possível monitorar um serviço que intencionalmente se encerra e inicia uma cópia no fundo (como o Hamachi) sem gambiarras, enquanto inits que empilham no primeiro programa do sistema as funções que o daemontools prezam por separar tiram isso de letra devido ao fato de que, por padrão, o primeiro processo do sistema se responsabiliza por programas com esse comportamento.

Porém, desde que fui introduzido a esses conceitos quando usei o runit no início do ano, e, mais recentemente o s6, tenho acreditado que é uma maneira muito mais inteligente e direta de aproveitar as capacidades do sistema, e que deveria ao menos ser estudada.

5 curtidas