Entendendo o padrão LSB Init do Linux
Overview
Bem-vindos, entusiastas do Linux! Hoje, vamos mergulhar no mundo da automação e eficiência com scripts de inicialização, seguindo o padrão LSB. Se você alguma vez se perguntou como iniciar, parar ou verificar o status de um serviço no Linux sem complicações, este post é para você. Preparamos um guia completo, desde a criação do cabeçalho até comando por comando, com um exemplo real de um script para o banco de dados Postgre. Prepare-se para tornar seu sistema mais ágil e suas operações mais suaves com nossas dicas e truques.
Quando você faz um script de inicialização para o Linux, você precisa (ou deve) criar um cabeçalho no arquivo do script. Este cabeçalho, apesar de possuir apenas “linhas comentadas” e vai impedir seu script de funcionar, caso não esteja implementado corretamente. Este padrão (LSB) foi concebido pela Linux Foundation como uma forma de diminuir a diferença entre as infinitas distribuições de Linux, reduzindo o custo de portabilidade entre elas. Este padrão trata uma gama grande de itens, mas o foco deste post é nos scripts de inicialização.
Os scripts init são utilizados para iniciar, parar, reiniciar, recarregar (forçadamente ou não) e verificar o status de serviços e/ou softwares e são armazenados na pasta /etc/init.d/.
Sendo assim, se você vai criar um script de inicialização para o seu servidor de Minecraft, o seu init script será: /etc/init.d/minecraft*.* Para que o seu script esteja aderente ao padrão LSB, ele deve possuir:
- No mínimo, diretivas para os comandos: start, stop, restart, force-reload e status;
- Código de saída (exit code) adequado;
- Ter documentado as dependencias dele (run-time dependencies)
- [Opcional] Mensagens para sucesso (log_success_msg) e erro (log_failure_msg)
O padrão LSB provê uma série de funções que podem ser utilizadas e estão disponíveis no /lib/lsb/init-functions. Obviamente, você não precisa utilizar todas, mas se estiver com dúvidas quanto a forma de alguma função, este é um bom material de consulta. Outro detalhe importante é que estes scripts vão salvar o PID do seu processo em um arquivo no diretório /var/run. Isso pode ser util, caso algum script precise verificar o status do seu processo.
Bom, vamos ao script em si. Abaixo está um exemplo simples de um script para inicializar o banco de dados Postgre.
### BEGIN INIT INFO
# Provides: postgre_de_teste
# Required-Start: postgresql networking
# Required-Stop: postgresql networking
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Banco de dados Postgre
# Description: Banco de dados Postgre
# Vai atender as aplicações XPTO, ABC e 123.
### END INIT INFO
De acordo com a dica passada pelo leitor Edivaldo Alcantra, a partir da versão ubuntu2016-4 -debian 8
Isso é só o cabeçalho? Sim. Vamos detalhar o que ele faz:
- Todo o seu cabeçalho deve estar contido entre ### BEGIN INIT INFO e ### END INIT INFO;
- Provides: especifica o nome do que este script está inicializando. Este nome deve ser único.
- Required-start: Define quais são as dependencias deste script. Neste caso, para que o seu serviço de postgre (postgre_de_teste) possa ser iniciado, o postgresql e o networking já precisarão ter sido executados. Este ponto é importante para as inicializações sejam posicionadas de forma correta pelo comando update-rc.d.
Lembrete: o postgresql que está ali possui um init script próprio com o nome postgresql no campo provides - Required-stop: Similar ao required-start, este item mostra o que precisa ser desligado assim que este serviço parar. Neste exemplo, assim que o nosso serviço (postgre_de_teste) parar, o networking e o postgresql serão desligados também;
- Default-Start e Default-Stop: definições dos níveis de execução (run-levels) que este serviço precisa para ser iniciado ou finalizado;
- Short-Description e Description: Bem auto explicativos, estes campos servem para descrever o que este serviço vai fazer. O short-description é limitado a 1 linha, mas o description pode ter várias. (lembre-se de incluir o # no inicio da linha);
Vamos caminhar um pouco com o script. A primeira coisa que devemos acrescentar é:
# Utilizando funções padrões do LSB...
. /lib/lsb/init-functions
Estas linhas vão permitir que você utilize as funções padrões. Agora, vamos acrescentar algumas variáveis ao nosso script:
# Nome do meu processo (para exibição)
NAME=postgre_de_teste
# Nome do Daemon
DAEMON=/home/usuario_do_servidor/postgre
# Arquivo PID para o serviço
PIDFILE=/var/run/postgre_de_teste.pid
No código acima, acrescentamos 3 variáveis: NOME, DAEMON e PIDFILE. Elas serão utilizadas em diversos pontos do scripts. É importante chamar atenção para a variável DAEMON: Ela indica onde está o executável que será chamado por este script.
Como o seguro morreu de velho, é bom colocar uma verificação de segurança:
# Verifica se o arquivo existe
test -x $DAEMON || exit 5
Este teste verifica se o seu executável existe. Se não existir, encerra o script, retornando exit code 5.
Antes de passarmos para a próxima parte deste script, é bom relembrar uma coisa: Quando você chama um script passando uma linha de argumento, toda a linha de comando pode ser acessada durante a execução.
Exemplo:
./get_food.sh bacon
O script acima vai ter a variável $0 com o nome do script (get_food.sh) e a $1 com o argumento passado (bacon).
Ok, continuando…
Comando de inicialização (start) do serviço. Todo o script de start do serviço ficará dentro deste pedaço:
case $1 in
start)
# A inicialização toda vem aqui dentro...
;;
stop)
# O encerramento todo vem aqui dentro...
;;
...
esac
O “comando” esac (case ao contrário) indica que o case acabou. Da mesma forma que o fi indica que o if acabou.
Primeira parte do Start:
# Verifica se o arquivo PID existe...
if [ -e $PIDFILE ]; then
status_of_proc -p $PIDFILE $DAEMON "$NAME process" && status="0" || status="$?"
# Se o status for SUCCESS, encerra o script, pois o serviço já está rodando.
if [ $status = "0" ]; then
exit # Exit
fi
fi
O código acima verifica se o arquivo PID existe. Se existir, checa se o processo já está sendo executado e aborta se estiver, pois não faz sentido tentar executar novamente o mesmo serviço.
O próximo passo é iniciar o serviço em si…
# Inicia o serviço (daemon)
log_daemon_msg "Iniciando o serviço" "$NAME"
# Utiliza o start-stop-daemon para iniciar o serviço
# salva log de retorno.
if start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON ; then
log_end_msg 0
else
log_end_msg 1
fi
;;
A primeira coisa que o código acima faz é salvar uma mensagem de log “Iniciando o serviço postgre_de_teste”. Depois ele utiliza a função start-stop-daemon para inciar o serviço em si, passando os seguintes argumentos:
- –start (ou -s): Ação desejada;
- –quiet (ou -q): Omite mensagens informativas, exibindo apenas mensagens de erro;
- –oknodo (ou -o): Retorna exit code 0 no lugar de 1, se não for possível iniciar o serviço (daemon);
- –pidfile (ou -p): Utilizado para informar o nome do arquivo PID;
- –exec (ou -x): Utilizado para informar o que deve ser executado;
Se este comando der certo, retorna 0, caso contrário, retorna 1 (indicando erro).
O próximo case é o stop, utilizado para encerrar o serviço.
stop)
# Encerra o serviço
if [ -e $PIDFILE ]; then
status_of_proc -p $PIDFILE $DAEMON "Encerrando o serviço $NAME" && status="0" || status="$?"
if [ "$status" = 0 ]; then
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
/bin/rm -rf $PIDFILE
fi
else
log_daemon_msg "O serviço $NAME não está sendo executado"
log_end_msg 0
fi
;;
No fonte acima, primeiro verificamos se o arquivo PID existe. Se existir, tentamos encerrar o processo e removemos o PID (afinal, o conteúdo dele não será mais válido). Se o arquivo PID não existir, consideramos que o processo não está sendo executado.
O próximo case é utilizado para reiniciarmos (restart) o serviço:
restart)
# Reinicia o serviço
$0 stop && sleep 2 && $0 start
;;
O script acima faz uma espécie de recursividade. Perceba que ele está utilizando a variável $0 (que contém o nome do script em questão) e utiliza ele para disparar outra instancia deste mesmo script 2x: uma para parar o serviço e outra para iniciar novamente.
Seria o equivalente a fazer isso:
/etc/init.d/postgre_de_teste stop
sleep 2
/etc/init.d/postgre_de_teste start
O comando sleep faz com que a sequência de execução pause por 2 segundos. Temos agora mais dois cases para ver: Status e Reload.
Abaixo está o fonte para o case de Status:
status)
# Verifica o status do processo.
if [ -e $PIDFILE ]; then
status_of_proc -p $PIDFILE $DAEMON "Processo $NAME" && exit 0 || exit $?
else
log_daemon_msg "O processo $NAME não está sendo executado"
log_end_msg 0
fi
;;
Neste script, verificamos se o arquivo PID existe. Se existir, verificamos o status do processo (status_of_proc). Se o arquivo não existir, partimos do pressuposto de que o processo não está em execução.
Por útlimo, temos o case reload
reload)
# Recarrega um processo, enviando um sinal que de que o serviço deve reiniciar suas configurações.
if [ -e $PIDFILE ]; then
start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $NAME
log_success_msg "Processo $NAME recarregado com sucesso"
else
log_failure_msg "Arquivo PID $PIDFILE não existe."
fi
;;
Para este comando, também utilizamos o start-stop-daemon. Vou listar apenas os que não apareceram anteriormente:
- –stop (ou -K): Encerra o processo. Indica a ação desejada;
- –signal (ou -s): Envia um sinal de que algo deve ser feito. O padrão é TERM, que indica finalização rápida. O sinal USR1 indica que os arquivos de log devem ser reabertos.
Por último, mas não menos importante: um case final, que vai indicar como utilizar este script, caso seja passado algum parametro inválido:
# Mensagem de ajuda.
echo "Forma de utilizar: $0 {start|stop|restart|reload|status}"
exit 2
;;
Passamos por muitos cases e pedaços de script, então acho que vale lembrar: Eles devem estar entre o comando case $1 in e o esac.
Referências:
1. https://wiki.debian.org/LSBInitScripts
2. https://wiki.linuxfoundation.org/lsb/start
3. https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/iniscrptfunc.html
4. https://manpages.debian.org/wheezy/dpkg/start-stop-daemon.8.en.html
5. http://www.thegeekstuff.com/2012/03/lsbinit-script/
6. https://www.nginx.com/resources/wiki/start/topics/tutorials/commandline/