Entendendo o padrão LSB Init do Linux

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/