Preâmbulo:

Recentemente coloquei no ar o FollowUpTime, que como já disse em outros posts é um sistema de monitoração para redes e servidores.

Como podem imaginar, um bocado de linhas de código está envolvida num sistema assim e a conta só do engine principal passa das 90 mil linhas. Isso sem incluir templates, javascripts, etc.

O sistema é composto de duas partes: Uma que roda num servidor principal gerenciando a maior parte das tarefas e as “probes”. As probes são pequenos scripts que recebem instruções sobre hosts a serem testados e respondem para o servidor principal com o tempo de resposta, um OK ou um FAIL. Simples assim.

São essas probes que temos espalhados por vários cantos do planeta.

Porém quando recebi a probe dos desenvolvedores me vi com um pequeno, mas incoveniente problema. Os desenvolvedores realmente optaram pelo princípio KISS. Eles me entregaram uma excelente probe que fazia o que era necessário. E nada mais. Eu tinha nas mãos um daemon que rodava em foreground e jogava mensagens para stdout e stderr e nada mais.

Eu não me demorei e ataquei o problema da forma errada, pensando como um programador e não como um administrador Unix. E acabei reinventado a roda.

Introdução – O problema

Quando percebi o que eu tinha nas mãos logo pensei nos problemas que eu precisava resolver:

  • Preciso iniciar o daemon sempre que der boot na máquina
  • Preciso ser capaz de rodar em daemon em background, ou vai me zoar a seqüencia de boot
  • Posso precisar parar/reiniciar/iniciar manualmente este daemon
  • Se o processo morrer eu tenho que ser notificado e que, de preferência, ele seja reiniciado automaticamente
  • E sim, esse processo não deve rodar como root

Já velhaco de Linux logo pensei num script para ser colocado em /etc/init.d/ que ia resolver boa parte dos meus problemas. Com uma combinação bonita de bash scripting, várias ferramentas e comandos Linux que só se conhece depois de muito tempo de casa (como nohup, pidof, &) e gambiarras na crontab eu consegui. Consegui reinventar a roda. Não vou entrar em detalhes da solução que fiz, pois apesar de 100% funcional não era nem de perto tão bonita quanto:

A solução – Daemontools

Nosso caro amigo D.J.B, autor do famoso Qmail também já passou por um problema parecido com o meu, mas ao invés de criar um solução simples para resolver um problema pontual ele foi mais longe. Ele criou uma solução global que atende o problema dele, o meu e provavelmente também o seu.

Na página oficial do daemontools tem uma tabela, que traduzo abaixo, comparando a ferramenta dele com outras abordagens (incluindo a minha, que utilizava init.d):

<td width="86" align="center" bgcolor="#2300dc">
  <strong><em><span style="font-family: Verdana; color: #ffffff;">inittab</span></em></strong>
</td>

<td width="86" align="center" bgcolor="#2300dc">
  <strong><em><span style="font-family: Verdana; color: #ffffff;">ttys</span></em></strong>
</td>

<td width="86" align="center" bgcolor="#2300dc">
  <strong><em><span style="font-family: Verdana; color: #ffffff;">init.d</span></em></strong>
</td>

<td width="86" align="center" bgcolor="#2300dc">
  <strong><em><span style="font-family: Verdana; color: #ffffff;">rc.local</span></em></strong>
</td>

<td style="border-right: 1px solid #000000;" width="86" align="center" bgcolor="#2300dc">
  <strong><em><span style="font-family: Verdana; color: #ffffff;">/service</span></em></strong>
</td>
<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>
<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>
<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>
<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>
<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>
<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Não</span>
</td>

<td style="border: 1px solid #000000;" align="center">
  <span style="font-family: Verdana;">Sim</span>
</td>

Dá para perceber que o cara pensou em tudo e não tem porquê reinventar a roda. Vamos utilizar o daemontools!

Instalando o Daemontools no Debian 5.0 (Lenny):

Quem já instalou o daemontools no Debian no passado lembra que pentelhação que era, pois devido a licenciamento era apenas um pseudo-pacote que roda um script para realmente baixar/instalar o daemontools. Isso foi resolvido quando o D.J.B mudou a licença e agora o pacote é um .deb “de primeira linha”.

Pronto. Só isso. Próximo passo…

Criando um serviço

Para fins didáticos vamos criar um serviço simples, chamado homer.sh cujo conteúdo é:

Não faz muito, mas funciona. E tem exatamente as mesmas características do meu serviço da probe, inclusive rodando em foregroud. E agora? Como “daemonizar” isso atendendo todos aqueles meus requisitos iniciais?

Juntando no daemontools

Crie um diretório qualquer onde vai guardar seus serviços de forma organizada. Recomendo algo como /opt/services por exemplo. Lá vamos criar um subdiretório homer e moveremos nosso script para lá:

Para controlar a inicialização dele precisamos de um script dentro do mesmo diretório chamado run. Simples assim:

E estamos 50% prontos. Não esqueça que tanto o homer.sh como o run precisam ser executáveis!

Agora para ativar o serviço vem a parte realmente fácil:

Conte até 5 e dê um ps auxw no prompt. Sim… o seu serviço está no ar.

Observem que além do serviço ainda existe um supervisor que manterá os olhos nele e, caso o mesmo morra, será automaticamente reiniciado. Observem que o PID do processo é 3450, certo?

Menos de um segundo depois de matar o processo um novo (3660) já assume o lugar. Pra que mesmo que perdi todo aquele tempo desenvolvendo um script de inicialização e depois com outro que ficava vendo se o processo estava vivo?

Indo mais adiante

OK, mas um dos requisitos também era rodar o processo como usuário não privilegiado, certo? E, como podem ver ali em cima, tá rodando como root. Vamos modificar um pouco nosso serviço então, criando um usuário, mudando as permissões do arquivo de log e editando o run:

E agora o conteúdo do /opt/services/homer/run passa a ser:

Feito. Vamos reiniciar o processo:

Olha que coisa linda. Tudo que precisamos fazer foi falar pro daemontools: Executa esse cara usando esse id aqui. E pronto.

Comandos que você precisa saber:

Parar um serviço:

Reiniciar um serviço parado manualmente:

Enviando um sinal TERM (comportamento equivalente a restart):

Colocando um serviço em pausa:

Continuando um serviço pausado:

Ainda mais

Tem muito mais que pode ser feito em cima do daemontools, combinando com ferramentas como multilog e ucspi-tcp. Dê uma olhada na página the djb way, que foi ostensivamente copiada extensivamente utilizada como referência para este artigo.

E não vamos esquecer do jabá: Conheça o FollowUpTime! Em 5 minutos ou menos você pode se cadastrar e monitorar um site ou serviço gratuitamente!