Não reinvente a roda você também

By | June 30, 2009

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):

Funcionalidade inittab ttys init.d rc.local /service
Fácil instalação e remoção de serviços Não Não Sim Não Sim
Simples startup inicial Não Não Não Não Sim
Reinicializações confiáveis Sim Sim Não Não Sim
Sinalização simples e confiável Não Não Não Não Sim
Estado de processo limpo Sim Sim Não Não Sim
Portabilidade Não Não Não Não Sim

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”.

# apt-get install daemontools daemontools-run

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 é:

#!/bin/bash
progname=$(basename $0)
LOG=/tmp/$progname.log
AVISO="Hummm... rosquinha..."
SONECA=2
echo `date`: "starting $progname..." >> $LOG
while true
do
    echo `date`: "$progname: $AVISO" >> $LOG
    echo `date`: "$progname: tirando uma soneca de $SONECA segundos..." >> $LOG
    sleep $SONECA
done

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á:

# mkdir -p /opt/services/homer
# mv homer.sh /opt/services/homer/

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

#!/bin/bash
exec ./homer.sh

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

# chmod 755 /opt/services/homer/*

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

# ln -s /opt/services/homer /etc/service/

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

# ps aux|grep homer
root      3250  0.0  0.1   1620   324 ?        S    19:28   0:00 supervise homer
root      3450  1.2  0.4   2792  1220 ?        S    19:31   0:00 /bin/bash /opt/services/homer/homer.sh

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?

# kill -9 3450
# ps aux|grep homer
root      3250  0.0  0.1   1620   324 ?        S    19:28   0:00 supervise homer
root      3660  8.0  0.4   2788  1212 ?        S    19:33   0:00 /bin/bash /opt/services/homer/homer.sh

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:

# useradd  -s /bin/false -d /dev/null simpson
# chown simpson:simpson /tmp/homer.sh.log

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

#!/bin/bash
exec setuidgid simpson ./homer.sh

Feito. Vamos reiniciar o processo:

# svc -t /etc/service/homer
# ps aux|grep homer
root      3250  0.0  0.1   1620   324 ?        S    19:28   0:00 supervise homer
simpson   4004  1.0  0.5   3304  1484 ?        S    19:39   0:02 /bin/bash ./homer.sh

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:

# svc -d /etc/service/serviço

Reiniciar um serviço parado manualmente:

# svc -u /etc/service/serviço

Enviando um sinal TERM (comportamento equivalente a restart):

# svc -t /etc/service/serviço

Colocando um serviço em pausa:

# svc -p /etc/service/serviço

Continuando um serviço pausado:

# svc -c /etc/service/serviço

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!

8 thoughts on “Não reinvente a roda você também

  1. Sidnei

    Cara muito boa solução… Assim até já pensei em algumas ferramentas que nem tinha pensado em criar ainda.

    Boa dica!

  2. Jorge Sousa

    A mim parece que você está a reinventar a roda ao usar o “svc”, ao menos que só use janelas. Os scripts na rc.d e init.d (depende da distro) não são para decoração.

  3. Eri Post author

    @Jorge Sousa

    Parabéns. Entrou pra minha lista de “comentários mais imbecis de alguém que não se deu ao trabalho de ler o texto”

  4. _JF

    Esse artigo ficou demais Eri, parabéns! E ainda tivemos a pérola acima, uauahuahuahuahuahuahua

  5. Erick

    :O
    Muito bom!

    Já pensei em uma coisa que posso usar isso 😀

    tnks Eri!
    ps: só achei que você poderia ser mais educado com o colega ali :/
    ahuhauhauahahu
    abraços!

  6. Chico

    Boa… eu usei isso uma vez com um msndump.
    Ótima resposta hehehehe

    abs

    Chico

Comments are closed.