Solução de Problemas de Falhas no Systemd Service: Um Guia Passo a Passo

Diagnostique falhas no systemd service com verificações de status, logs do journal, revisão de arquivos de unidade, correções de dependências e depuração de ambiente.

Solução de Problemas de Falhas no Systemd Service: Um Guia Passo a Passo

Falhas no systemd service são mais fáceis de depurar quando você desacelera e segue as evidências. Uma unidade com falha geralmente deixa três pistas úteis: o estado registrado pelo systemd, o comando que ele tentou executar e os logs escritos pelo systemd ou pela aplicação. Se você ler essas pistas em ordem, evita a armadilha comum de editar um arquivo de unidade antes de saber se o problema é a unidade, a aplicação, uma dependência ou o host.

Os exemplos abaixo usam um mywebapp.service fictício, mas o mesmo fluxo de trabalho se aplica a auxiliares de banco de dados, consumidores de fila, tarefas de backup, exportadores e daemons internos.

A Primeira Linha de Defesa: systemctl status

Quando um serviço falha ao iniciar, o primeiro comando que você deve executar é systemctl status <nome_do_serviço>. Este comando fornece um instantâneo do estado atual do serviço, incluindo se está ativo, carregado e, crucialmente, um trecho de seus logs recentes. Isso geralmente fornece informações suficientes para identificar rapidamente o problema.

Digamos que seu serviço de aplicação web, mywebapp.service, não está iniciando:

systemctl status mywebapp.service

Interpretação do Exemplo de Saída:

● mywebapp.service - Minha Aplicação Web
     Carregado: carregado (/etc/systemd/system/mywebapp.service; habilitado; predefinição do fornecedor: desabilitado)
     Ativo: falhou (Resultado: código de saída) desde Seg 2023-10-26 10:30:05 UTC; há 10s
    Processo: 12345 ExecStart=/usr/local/bin/mywebapp-start.sh (código: saiu, status=1/FALHA)
   PID Principal: 12345 (código: saiu, status=1/FALHA)
        CPU: 10ms

Out 26 10:30:05 hostname systemd[1]: Iniciada Minha Aplicação Web.
Out 26 10:30:05 hostname mywebapp-start.sh[12345]: Erro: Porta 8080 já em uso
Out 26 10:30:05 hostname systemd[1]: mywebapp.service: Processo principal saiu, código=exit, status=1/FALHA
Out 26 10:30:05 hostname systemd[1]: mywebapp.service: Falhou com resultado 'exit-code'.

A partir desta saída, podemos ver imediatamente:

  • O serviço mywebapp.service está falhou.
  • Ele falhou com Resultado: código de saída, significando que o comando ExecStart saiu com um status diferente de zero.
  • A linha Processo mostra que o comando mywebapp-start.sh falhou com status=1/FALHA.
  • Crucialmente, as linhas de log indicam: Erro: Porta 8080 já em uso. Este é um indicador claro do problema.

Este comando é sua primeira ferramenta de diagnóstico, muitas vezes apontando diretamente para a causa ou estreitando onde procurar em seguida.

Mergulhando Fundo com journalctl

Enquanto systemctl status fornece um resumo rápido, journalctl é seu comando de referência para logs detalhados. Ele consulta o journal do systemd, que coleta logs de todas as partes do sistema, incluindo serviços.

Revisão Básica de Logs

Para ver todos os logs de um serviço específico, incluindo entradas históricas:

journalctl -u mywebapp.service

Isso mostrará todas as entradas de log associadas a mywebapp.service. Se o serviço falhar repetidamente, você verá entradas de cada tentativa falha.

Filtragem e Consultas Baseadas em Tempo

Para restringir os resultados, especialmente após uma falha recente, você pode usar flags como --since e --priority:

  • Mostrar logs desde um horário específico:
    journalctl -u mywebapp.service --since "10 minutos atrás"
    journalctl -u mywebapp.service --since "2023-10-26 10:00:00"
    
  • Mostrar apenas mensagens de nível de erro ou superiores:
    journalctl -u mywebapp.service -p err
    
  • Combinar com -xe para explicação estendida e saída verbosa:
    journalctl -u mywebapp.service -xe --since "5 minutos atrás"
    
    -x pode adicionar texto explicativo para algumas mensagens do systemd. Trate essas explicações como dicas, não como um substituto para os logs específicos da unidade.

Entendendo Mensagens de Log

Procure por palavras-chave como Erro, Falhou, Aviso ou mensagens específicas da aplicação que indiquem o que deu errado. Preste atenção aos timestamps para entender a sequência de eventos que levaram à falha.

Dica: Se o script ExecStart do seu serviço imprime na saída padrão ou erro padrão, essas mensagens geralmente são capturadas pelo journalctl. Certifique-se de que seus scripts registrem mensagens de erro descritivas.

Inspecionando o Arquivo de Unidade: O Projeto do Seu Serviço

Cada serviço systemd é definido por um arquivo de unidade (ex.: mywebapp.service). Configurações incorretas neste arquivo são uma fonte comum de falhas de inicialização. Você precisa entender o que o serviço está tentando fazer.

Recuperando o Arquivo de Unidade

Para visualizar o arquivo de unidade ativo para seu serviço:

systemctl cat mywebapp.service

Este comando mostra o arquivo de unidade exato que o systemd está usando, incluindo quaisquer substituições.

Diretivas Chave para Verificar

Concentre-se na seção [Service] para problemas relacionados à execução e [Unit] para dependências.

  • ExecStart: Este é o comando que o systemd executa para iniciar seu serviço. Verifique se o caminho está correto e se o comando em si é executável e é executado com sucesso quando invocado manualmente (ex.: como o User especificado).
    ExecStart=/usr/local/bin/mywebapp-start.sh
    
  • Type: Define o tipo de inicialização do processo. Tipos comuns incluem:
    • simple (padrão): ExecStart é o processo principal.
    • forking: ExecStart bifurca um processo filho e o pai sai. O systemd espera o pai sair.
    • oneshot: ExecStart executa e sai; o systemd considera o serviço ativo enquanto o comando estiver em execução.
    • notify: O serviço envia uma notificação ao systemd quando estiver pronto.
    • Type incorreto pode levar o systemd a pensar que um serviço falhou quando na verdade iniciou, ou vice-versa.
  • User / Group: O usuário e grupo sob os quais o serviço será executado. Problemas de permissão geralmente decorrem do serviço tentar acessar arquivos ou recursos para os quais não tem direitos sob este usuário.
    User=mywebappuser
    Group=mywebappgroup
    
  • WorkingDirectory: O diretório a partir do qual o serviço será executado. Caminhos relativos em ExecStart ou outros comandos dependem disso.
  • Restart: Define quando o serviço deve ser reiniciado. Se definido como on-failure ou always, um serviço com falha pode reiniciar constantemente, tornando mais difícil capturar a falha inicial.
  • TimeoutStartSec / TimeoutStopSec: Quanto tempo o systemd espera para o serviço iniciar ou parar. Se um serviço demorar mais para inicializar do que TimeoutStartSec, o systemd o matará e reportará uma falha.

Problemas Comuns em Arquivos de Unidade

  • Caminhos incorretos: Erro de digitação em ExecStart ou outros caminhos de arquivo.
  • Variáveis de Environment ausentes: Serviços frequentemente exigem variáveis de ambiente específicas (ex.: PATH) que podem não estar presentes no ambiente limpo do systemd (veja abaixo).
  • Permissões: O User especificado não tem permissões de execução para o script ou permissões de leitura/escrita para arquivos de dados necessários.
  • Erros de sintaxe: Simples erros de digitação no próprio arquivo de unidade.

Para testar ExecStart manualmente:

Mude para o usuário do serviço e tente executar o comando diretamente:

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

Isso geralmente reproduz o erro visto no journalctl diretamente no seu terminal, tornando a depuração mais fácil.

Gerenciamento de Dependências: Quando os Serviços Não Podem Iniciar Sozinhos

Os serviços frequentemente dependem de outros serviços ou componentes do sistema para estarem ativos antes de poderem iniciar. O systemd usa as diretivas Wants, Requires, After e Before para gerenciar essas dependências.

Identificando Dependências

Use systemctl list-dependencies <nome_do_serviço> para ver o que um serviço explicitamente requer ou deseja executar.

systemctl list-dependencies mywebapp.service

Diretivas comuns na seção [Unit]:

  • After=: Especifica que este serviço deve iniciar após as unidades listadas. Se a unidade listada falhar, este serviço ainda tentará iniciar (a menos que Requires= também seja usado).
  • Requires=: Especifica que este serviço requer as unidades listadas. Se alguma das unidades requeridas falhar ao iniciar, este serviço não iniciará.
  • Wants=: Uma forma mais fraca de Requires=. Se uma unidade desejada falhar, este serviço ainda tentará iniciar.

Exemplo:

[Unit]
Description=Minha Aplicação Web
After=network.target mysql.service
Requires=mysql.service

Aqui, mywebapp.service é ordenado após network.target e mysql.service, e requer que mysql.service seja iniciado com sucesso. Se mysql.service falhar, mywebapp.service não iniciará.

Resolvendo Conflitos de Dependência

Se um serviço falhar devido a um problema de dependência, o journalctl geralmente indicará qual dependência não pôde ser atendida. Por exemplo, pode afirmar Dependência falhou para Minha Aplicação Web seguido de detalhes sobre a falha do mysql.service.

Passos para resolver:

  1. Verifique o serviço dependente: Execute systemctl status <serviço_dependente> (ex.: systemctl status mysql.service) e journalctl -u <serviço_dependente> para solucionar a falha dele primeiro.
  2. Verifique as diretivas After= e Requires=: Certifique-se de que elas refletem corretamente a ordem de inicialização desejada e a rigidez. Às vezes, um serviço precisa esperar que uma porta específica esteja aberta, não apenas que o trabalho de inicialização de outra unidade termine. Para verificações estreitas, ExecStartPre= pode ajudar. Para daemons de rede, ativação de socket ou lógica de repetição em nível de aplicação é frequentemente mais confiável.

Variáveis de Ambiente e Caminhos: As Pegadinhas Ocultas

Os serviços systemd são executados em um ambiente muito limpo e mínimo. Isso frequentemente leva a problemas onde comandos que funcionam perfeitamente no shell de um usuário falham quando executados pelo systemd porque variáveis de ambiente cruciais (como PATH) estão ausentes.

Ambiente Limpo do Systemd

Quando o systemd inicia um serviço, ele não herda o ambiente completo do usuário que iniciou systemctl start. A variável PATH, por exemplo, é frequentemente reduzida, significando que comandos como python ou node podem não ser encontrados se não estiverem em locais padrão como /usr/bin ou /bin.

Sintoma: ExecStart=/usr/local/bin/myscript.sh falha com python: comando não encontrado, node: comando não encontrado, um erro de biblioteca ausente ou uma mensagem da aplicação dizendo que uma configuração necessária está vazia.

Correção: Torne o ambiente do serviço explícito.

[Service]
WorkingDirectory=/opt/mywebapp
Environment="APP_ENV=production"
Environment="PATH=/opt/mywebapp/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/opt/mywebapp/venv/bin/gunicorn app:app

Para muitas variáveis, use um arquivo de ambiente:

[Service]
EnvironmentFile=/etc/mywebapp/mywebapp.env
ExecStart=/opt/mywebapp/bin/server

Mantenha esse arquivo simples. EnvironmentFile= não é um script Bash. Use linhas KEY=value, não export KEY=value, substituição de comando ou condicionais de shell. Também defina permissões restritivas se o arquivo contiver segredos:

sudo chown root:mywebapp /etc/mywebapp/mywebapp.env
sudo chmod 0640 /etc/mywebapp/mywebapp.env

Permissões: Reproduza a Falha como o Usuário do Serviço

Problemas de permissão são comuns porque os testes manuais geralmente acontecem como root ou como seu usuário de login, enquanto a unidade é executada como uma conta de serviço dedicada.

Verifique o usuário configurado:

systemctl show mywebapp.service -p User -p Group

Em seguida, execute o mesmo comando como aquele usuário:

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

Se a aplicação precisar de um diretório de trabalho, inclua-o:

sudo -u mywebappuser bash -lc 'cd /opt/mywebapp && /usr/local/bin/mywebapp-start.sh'

Olhe além do executável. O usuário do serviço pode precisar de acesso de leitura a /etc/mywebapp/config.yml, acesso de escrita a /var/lib/mywebapp, acesso de execução em cada diretório pai ou permissão para criar um socket Unix em /run/mywebapp. Uma verificação rápida pode economizar muitos palpites:

sudo -u mywebappuser test -r /etc/mywebapp/config.yml
sudo -u mywebappuser test -w /var/lib/mywebapp
namei -l /var/lib/mywebapp/uploads

Se o serviço falhar apenas ao vincular a uma porta baixa, como 80 ou 443, não o execute imediatamente como root. Um proxy reverso, ativação de socket ou uma capacidade direcionada podem ser mais seguros, dependendo do serviço.

Limites de Inicialização e Loops de Reinicialização

Um serviço que falha repetidamente pode parar com uma mensagem como solicitação de início repetida muito rapidamente. Isso significa que o limite de taxa do systemd foi acionado. A falha original ocorreu antes, então não foque apenas na mensagem de limite de taxa.

Use:

journalctl -u mywebapp.service --since "30 minutos atrás"
systemctl show mywebapp.service -p NRestarts -p Restart -p StartLimitBurst -p StartLimitIntervalUSec

Após corrigir a causa raiz, limpe o estado de falha:

sudo systemctl reset-failed mywebapp.service
sudo systemctl start mywebapp.service

Tenha cuidado com Restart=always. É útil para daemons resilientes, mas durante a depuração pode inundar o journal e esconder o primeiro erro claro. Você pode parar temporariamente a unidade, revisar os logs e iniciá-la manualmente depois de ter alterado uma coisa.

Valide a Unidade Antes de Recarregar

Antes de reiniciar um serviço após editar um arquivo de unidade, valide o arquivo e recarregue o systemd:

sudo systemd-analyze verify /etc/systemd/system/mywebapp.service
sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service

Se o serviço tiver substituições drop-in, inspecione a versão mesclada:

systemctl cat mywebapp.service
systemctl show mywebapp.service -p FragmentPath -p DropInPaths -p ExecStart

Isso captura os casos estranhos: você editou um arquivo em /usr/lib/systemd/system, mas um drop-in em /etc/systemd/system/mywebapp.service.d/override.conf ainda altera ExecStart; ou você corrigiu um arquivo de unidade copiado que não é o que o systemd carregou.

Uma Ordem Prática de Operações

Quando um serviço de produção está inativo, use um loop curto e repetível:

  1. Execute systemctl status mywebapp.service --no-pager.
  2. Leia journalctl -u mywebapp.service --since "15 minutos atrás".
  3. Inspecione systemctl cat mywebapp.service.
  4. Verifique o comando, usuário, diretório de trabalho, ambiente e dependências.
  5. Reproduza o comando como o usuário do serviço.
  6. Faça uma alteração.
  7. Execute systemctl daemon-reload se a unidade mudou.
  8. Reinicie e verifique o journal novamente.

Essa ordem mantém a investigação fundamentada. Se o journal disser Permissão negada, corrija as permissões. Se disser Arquivo ou diretório não encontrado, verifique os caminhos do ponto de vista do systemd. Se disser Dependência falhou, depure a dependência primeiro. Se disser que o processo saiu com status 0/SUCESSO mas o serviço falhou, verifique Type= e se a aplicação se daemoniza ou sai imediatamente.

O objetivo não é memorizar todas as diretivas do systemd. É continuar combinando a mensagem de falha com a camada que a produziu.