Dominando Arquivos de Serviço Systemd: Um Guia Abrangente

Crie arquivos de serviço systemd confiáveis com seções de unidade corretas, comportamento de reinicialização, logs, segurança e temporizadores.

Dominando Arquivos de Serviço do Systemd: Um Guia Abrangente

Os arquivos de serviço do Systemd informam ao Linux como iniciar, parar, reiniciar e supervisionar sua aplicação. Se o seu serviço inicia manualmente, mas falha na inicialização, reinicia agressivamente demais ou escreve logs no lugar errado, o arquivo de unidade geralmente é onde você precisa olhar.

Este guia foca em criar e configurar arquivos de unidade de serviço do systemd do zero. Você verá as seções principais, um exemplo de serviço Python funcional, comandos comuns de solução de problemas e alguns controles de segurança e recursos que vale a pena usar em produção.

Entendendo os Arquivos de Unidade do Systemd

O Systemd usa arquivos de unidade para descrever vários recursos do sistema, como serviços, soquetes, dispositivos, pontos de montagem e muito mais. Um arquivo de unidade de serviço, normalmente terminando com a extensão .service, define como o systemd deve gerenciar um daemon ou aplicação específica.

Esses arquivos são organizados em seções, com cada seção contendo pares chave-valor representando diretivas de configuração. As seções principais nas quais vamos focar são [Unit], [Service] e [Install].

Anatomia de um Arquivo de Serviço do Systemd

Um arquivo de serviço systemd típico tem a seguinte estrutura:

[Unit]
Description=Uma breve descrição do serviço.
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/minha_aplicacao --config /etc/minha_app.conf
Restart=on-failure
User=meuusuario
Group=meugrupo

[Install]
WantedBy=multi-user.target

Vamos detalhar cada seção e suas diretivas comuns:

A Seção [Unit]

Esta seção fornece metadados sobre a unidade e define seu relacionamento com outras unidades. É usada para dependências e ordenação.

  • Description=: Um nome legível por humanos para o serviço. É o que você verá na saída de systemctl status.
  • Documentation=: URLs ou caminhos para documentação do serviço.
  • Requires=: Define dependências fortes. Se uma unidade listada aqui falhar ao iniciar, esta unidade também falhará ao iniciar.
  • Wants=: Define dependências fracas. Se uma unidade listada aqui falhar ao iniciar, esta unidade ainda tentará iniciar.
  • Before=: Garante que esta unidade inicie antes das unidades listadas.
  • After=: Controla apenas a ordenação. Por exemplo, After=network.target inicia esta unidade após o alvo de rede básico, mas não garante conectividade externa. Serviços dependentes de rede podem precisar de After=network-online.target mais o serviço wait-online da distribuição.
  • Conflicts=: Se uma unidade listada aqui for iniciada, esta unidade será parada, e vice-versa.

A Seção [Service]

Esta seção configura o comportamento do próprio serviço. É onde você define como iniciar, parar e gerenciar o processo.

  • Type=: Especifica o tipo de inicialização do processo. Valores comuns incluem:

    • simple (padrão): O processo principal é aquele especificado em ExecStart=. O Systemd assume que o serviço é iniciado imediatamente após o processo ExecStart= ser bifurcado (fork).
    • forking: O processo ExecStart= bifurca um filho, e o pai sai. O Systemd considera o serviço iniciado quando o pai sai. Muitas vezes você precisa especificar PIDFile= com este tipo.
    • oneshot: Similar a simple, mas espera-se que o processo saia após seu trabalho ser concluído. Útil para scripts de configuração.
    • notify: O daemon envia uma mensagem de notificação ao systemd quando foi iniciado com sucesso. Este é o tipo preferido para daemons modernos que o suportam.
    • dbus: O serviço adquire um nome D-Bus.
  • ExecStart=: O comando a ser executado para iniciar o serviço. Para a maioria dos tipos de serviço, use um comando ExecStart=. Múltiplas linhas ExecStart= são válidas para Type=oneshot, onde elas são executadas sequencialmente.

  • ExecStop=: O comando a ser executado para parar o serviço.

  • ExecReload=: O comando a ser executado para recarregar a configuração do serviço sem reiniciar.

  • Restart=: Define quando o serviço deve ser reiniciado automaticamente. Valores comuns:

    • no (padrão): Nunca reiniciar.
    • on-success: Reiniciar apenas se o serviço sair limpo (código de saída 0).
    • on-failure: Reiniciar se o serviço sair com um código de saída diferente de zero, for terminado por um sinal ou exceder o tempo limite.
    • on-abnormal: Reiniciar se for terminado por um sinal ou exceder o tempo limite.
    • on-abort: Reiniciar apenas se for terminado de forma suja por um sinal.
    • always: Sempre reiniciar, independentemente do status de saída.
  • RestartSec=: O tempo para esperar antes de reiniciar o serviço (padrão é 100ms).

  • User=: O usuário para executar o serviço.

  • Group=: O grupo para executar o serviço.

  • WorkingDirectory=: O diretório para mudar antes de executar os comandos.

  • Environment=: Define variáveis de ambiente para o serviço.

  • EnvironmentFile=: Lê variáveis de ambiente de um arquivo.

  • PIDFile=: Caminho para o arquivo PID (frequentemente usado com Type=forking).

  • StandardOutput= / StandardError=: Controla para onde stdout e stderr vão, como journal, null ou inherit. Em distribuições comuns baseadas em systemd, a saída do serviço normalmente vai para o diário (journal), a menos que os padrões do gerenciador tenham sido alterados.

A Seção [Install]

Esta seção define como a unidade deve ser habilitada ou desabilitada, tipicamente criando links simbólicos.

  • WantedBy=: Especifica o alvo que deve "querer" este serviço quando ele for habilitado. Valores comuns:
    • multi-user.target: Para serviços que devem iniciar quando o sistema atinge um estado de linha de comando multiusuário.
    • graphical.target: Para serviços que devem iniciar quando o sistema atinge um estado de login gráfico.

Criando Seu Primeiro Arquivo de Serviço do Systemd

Vamos criar um arquivo de serviço simples para um script Python hipotético chamado meu_app.py localizado em /opt/meu_app/meu_app.py.

1. Crie o arquivo de serviço:

Arquivos de serviço para aplicações personalizadas são tipicamente colocados em /etc/systemd/system/. Vamos nomear nosso arquivo meu_app.service.

# Crie o diretório se ele não existir
sudo mkdir -p /etc/systemd/system/

# Crie o arquivo de serviço usando um editor de texto
sudo nano /etc/systemd/system/meu_app.service

2. Adicione o seguinte conteúdo a meu_app.service:

[Unit]
Description=Minha Aplicação Python Personalizada
After=network.target

[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/meu_app/
ExecStart=/usr/bin/python3 /opt/meu_app/meu_app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

Explicação do exemplo:

  • Description: Identifica claramente nossa aplicação.
  • After=network.target: Garante que a rede esteja disponível antes de iniciar.
  • Type=simple: Assume que meu_app.py é o processo principal e não bifurca.
  • User=appuser, Group=appgroup: Especifica o usuário e o grupo sob os quais a aplicação deve ser executada. Certifique-se de que esses usuários e grupos existam em seu sistema e tenham as permissões apropriadas. Você pode precisar criá-los:
    sudo groupadd appgroup
    sudo useradd -r -g appgroup appuser
    sudo chown -R appuser:appgroup /opt/meu_app/
    
  • WorkingDirectory: Define o contexto para o script.
  • ExecStart: O comando para executar o script Python. Certifique-se de que /usr/bin/python3 é o caminho correto para seu interpretador Python e que o script é executável.
  • Restart=on-failure: Se o script falhar, o systemd tentará reiniciá-lo.
  • WantedBy=multi-user.target: Este serviço será iniciado automaticamente quando o sistema inicializar em um ambiente multiusuário.

3. Recarregue a configuração do gerenciador systemd:

Após criar ou modificar um arquivo de serviço, você deve informar ao systemd para recarregar sua configuração.

sudo systemctl daemon-reload

4. Habilite e Inicie o Serviço:

  • Habilitar: Isso faz com que o serviço inicie automaticamente na inicialização.
    sudo systemctl enable meu_app.service
    
  • Iniciar: Isso inicia o serviço imediatamente.
    sudo systemctl start meu_app.service
    

5. Verifique o Status do Serviço:

Para verificar se seu serviço está em execução e ver quaisquer erros potenciais:

sudo systemctl status meu_app.service

Se houver problemas, o comando status frequentemente mostrará mensagens de erro ou logs do journald.

6. Visualizando Logs:

O Systemd se integra com o journald para registro de logs. Você pode visualizar logs para seu serviço usando:

sudo journalctl -u meu_app.service

Você também pode seguir logs em tempo real:

sudo journalctl -f -u meu_app.service

Outros Comandos Úteis

  • Parar o serviço: sudo systemctl stop meu_app.service
  • Reiniciar o serviço: sudo systemctl restart meu_app.service
  • Recarregar configuração (se suportado pelo app): sudo systemctl reload meu_app.service
  • Desabilitar início automático na inicialização: sudo systemctl disable meu_app.service

Configuração Avançada e Melhores Práticas

Considerações de Segurança

  • Execute serviços como usuários não-root: Sempre especifique User= e Group= a menos que seja absolutamente necessário. Isso segue o princípio do menor privilégio.
  • Isole serviços: Considere recursos de sandboxing como PrivateTmp=true, ProtectSystem=strict, ProtectHome=true e NoNewPrivileges=true. Teste-os com sua aplicação porque eles podem bloquear gravações legítimas de arquivos.
    • PrivateTmp=true: Dá ao serviço seus próprios diretórios privados /tmp e /var/tmp.
    • ProtectSystem=strict: Torna a maior parte do sistema de arquivos somente leitura para o serviço. Use ReadWritePaths= para diretórios nos quais o serviço deve escrever.
    • NoNewPrivileges=true: Impede que o serviço obtenha novos privilégios.

Lidando com Inicializações Complexas

  • Type=forking com PIDFile=: Para aplicações mais antigas que bifurcam, certifique-se de que PIDFile= aponte para o arquivo correto.
  • Type=notify: Se sua aplicação suportar, esta é a maneira mais robusta para o systemd saber quando ela está realmente pronta.
  • ExecStartPre= e ExecStartPost=: Comandos para executar antes e depois de ExecStart=. Úteis para tarefas de configuração ou limpeza.

Controle de Recursos

O Systemd permite limitar o uso de recursos:

  • CPUWeight=: Peso relativo de CPU para o serviço.
  • MemoryMax=: Memória máxima que o serviço pode usar.
  • IOWeight=: Peso relativo de E/S onde suportado pelo kernel e configuração do cgroup.

Exemplo:

[Service]
# ... outras diretivas ...
MemoryMax=512M
CPUWeight=50

Temporizadores vs. Cron

Os temporizadores do Systemd oferecem uma alternativa moderna aos trabalhos cron tradicionais. Eles são mais flexíveis e se integram melhor ao gerenciamento de logs e dependências do systemd.

  • Cron: Tarefas agendadas definidas em arquivos crontab.
  • Temporizadores do Systemd (unidades .timer): Essas unidades agendam unidades .service. Você define um arquivo .timer que especifica quando um arquivo .service correspondente deve ser executado.

Exemplo:

Para executar um script diariamente às 3h:

  1. meu_script.service: O serviço a ser executado.

    [Unit]
    Description=Meu script diário
    
    [Service]
    Type=oneshot
    ExecStart=/opt/meus_scripts/executar_diario.sh
    User=scriptuser
    
  2. meu_script.timer: O temporizador que agenda o serviço.

    [Unit]
    Description=Executar meu script diário uma vez por dia
    
    [Timer]
    # Executar às 03:00 todos os dias
    OnCalendar=*-*-* 03:00:00
    # Executar logo após a inicialização se o horário agendado foi perdido enquanto a máquina estava desligada.
    Persistent=true
    
    [Install]
    WantedBy=timers.target
    

Para usar isso:

  • Coloque ambos os arquivos em /etc/systemd/system/.
  • Execute sudo systemctl daemon-reload.
  • Habilite e inicie o temporizador: sudo systemctl enable meu_script.timer e sudo systemctl start meu_script.timer.

Os temporizadores oferecem vantagens como Persistent=true (executa trabalhos perdidos na inicialização), eventos de calendário (como hourly, daily, weekly) e melhor integração com journalctl.

Solucionando Problemas Comuns

  • Serviço não está iniciando: Verifique systemctl status <nome_do_servico> e journalctl -u <nome_do_servico>. Procure por erros de digitação, caminhos incorretos, dependências ausentes ou erros de permissão.
  • Type= incorreto: Se um serviço falha imediatamente ou trava, o Type= pode estar errado. Tente simple ou forking e certifique-se de que PIDFile está correto se estiver usando forking.
  • Permissão negada: Certifique-se de que o User= e Group= especificados tenham acesso de leitura/gravação aos arquivos e diretórios necessários.
  • Variáveis de ambiente: Se sua aplicação depende de variáveis de ambiente específicas, certifique-se de que elas estejam definidas corretamente usando Environment= ou EnvironmentFile=.
  • Dependências: Verifique se After=, Wants= e Requires= correspondem ao que você quer. After= ordena a inicialização; não puxa outra unidade por si só.

Antes de habilitar uma nova unidade em um host de produção, execute:

sudo systemd-analyze verify /etc/systemd/system/meu_app.service

Isso captura muitos erros de sintaxe e diretiva antes que você dependa do serviço na inicialização.

Conclusão Principal

Escreva o menor arquivo de serviço que descreva precisamente seu aplicativo, então adicione política de reinicialização, logs, restrições de segurança e limites de recursos deliberadamente. Após cada alteração, execute systemctl daemon-reload, verifique a unidade e confira systemctl status mais journalctl -u antes de confiar nele em produção.