Dominando o Systemd: Criando Seu Primeiro Arquivo de Unidade de Serviço Personalizado

Aprenda os fundamentos do gerenciamento de serviços do Systemd criando um Arquivo de Unidade personalizado. Este tutorial detalha as seções essenciais `[Unit]`, `[Service]` e `[Install]`, fornecendo instruções passo a passo para definir, habilitar, iniciar e verificar um serviço básico em segundo plano no Linux usando `systemctl`.

Dominando o Systemd: Criando Seu Primeiro Arquivo de Unidade de Serviço Personalizado

Uma unidade de serviço systemd personalizada é o que você usa quando um script ou aplicação pequena superou uma sessão de terminal, uma janela screen ou uma solução alternativa frágil com cron. Talvez você tenha um worker que deve reiniciar após uma falha. Talvez uma API interna pequena precise iniciar depois que a rede estiver pronta. Talvez um script de backup deva ser executado como um serviço controlado para que seus logs fiquem no journal e os operadores possam usar os mesmos comandos systemctl que usam para todo o resto.

A parte útil do systemd não é que o arquivo de unidade seja complicado. É que o arquivo de unidade torna o processo explícito: o que é executado, quem executa, quando inicia, como para, para onde os logs vão e o que o systemd deve fazer quando falha. Depois que essas decisões são registradas, o serviço se torna muito mais fácil de operar.

Este tutorial constrói um pequeno serviço do zero. O exemplo é intencionalmente simples, mas os padrões são os mesmos que você usaria para um worker em segundo plano, consumidor de fila, exportador de métricas ou daemon interno.

Comece com um comando real, não um arquivo de unidade

Uma boa unidade de serviço começa com um comando que já funciona manualmente. Antes de escrever a configuração do systemd, certifique-se de que pode executar o programa diretamente e entenda o que ele faz em primeiro plano.

Para este exemplo, crie um pequeno script de relatório:

sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh

Adicione este conteúdo:

#!/usr/bin/env bash
set -euo pipefail

while true; do
  echo "$(date --iso-8601=seconds) reporter heartbeat"
  sleep 10
done

Torne-o executável e teste:

sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh

Pare com Ctrl-C depois de ver algumas linhas. Observe que o script escreve na saída padrão em vez de anexar diretamente a /var/log/reporter.log. Isso é deliberado. Para a maioria dos serviços personalizados, deixar o systemd capturar stdout e stderr no journal é mais limpo do que fazer cada script gerenciar suas próprias permissões de arquivo de log, rotação e comportamento de falha.

Crie um usuário de serviço dedicado

Evite executar serviços de aplicação como root, a menos que eles realmente precisem de privilégios de root. Um script de heartbeat não precisa. Um aplicativo web geralmente não precisa. Um worker que lê de uma fila e escreve em um banco de dados geralmente não precisa.

Crie um usuário de sistema bloqueado:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter

Se sua distribuição usar um caminho nologin diferente, verifique com:

command -v nologin || command -v false

O usuário do serviço deve possuir apenas os arquivos que precisa escrever. Neste exemplo, o script escreve no journal através do systemd, então não precisa de propriedade de /opt/my-custom-service.

Escreva a unidade de serviço

Unidades de sistema personalizadas gerenciadas por administradores normalmente ficam em /etc/systemd/system/. Unidades de pacotes de fornecedores geralmente ficam em /usr/lib/systemd/system/ ou /lib/systemd/system/, dependendo da distribuição. Não edite arquivos de unidade de fornecedores diretamente quando puder evitar; use /etc/systemd/system/ para suas próprias unidades e drop-ins para substituições.

Crie a unidade:

sudo nano /etc/systemd/system/my-reporter.service

Use esta como uma primeira versão prática:

[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

A seção [Unit] descreve relacionamentos. After=network-online.target controla a ordenação; não puxa o alvo network-online por si só. Wants=network-online.target pede ao systemd para iniciar esse alvo também. Se seu serviço não precisa de rede, remova ambas as linhas e mantenha a unidade mais simples.

A seção [Service] descreve o processo. Type=simple é adequado para um processo em primeiro plano que não se bifurca em segundo plano. Esse é o caso comum para serviços modernos. Se um daemon legado bifurca, escreve um arquivo PID e retorna o controle ao shell, então você pode precisar de Type=forking, mas não o use só porque a palavra soa mais como daemon.

ExecStart deve ser um caminho absoluto. Recursos do shell como pipes, redirecionamentos e && não são interpretados a menos que você execute explicitamente um shell, por exemplo ExecStart=/bin/bash -lc 'command one && command two'. Prefira um script quando o comando precisar de lógica de shell; é mais fácil de testar e mais fácil de ler.

Restart=on-failure diz ao systemd para reiniciar o serviço após saídas anormais. Não reiniciará após um systemctl stop limpo. RestartSec=5s impede que um loop de reinicialização apertado sobrecarregue a máquina.

As opções de hardening aqui são modestas, mas úteis. NoNewPrivileges=true impede que o processo e seus filhos ganhem novos privilégios através de binários setuid ou capacidades de arquivo. PrivateTmp=true dá ao serviço uma visão privada de /tmp. Geralmente são seguras para serviços simples, mas teste com aplicações reais porque alguns softwares esperam caminhos temporários compartilhados.

Carregue e inicie a unidade

Depois de adicionar ou alterar um arquivo de unidade, recarregue a configuração do gerenciador do systemd:

sudo systemctl daemon-reload

Inicie o serviço agora:

sudo systemctl start my-reporter.service

Verifique seu estado:

systemctl status my-reporter.service

Você quer ver Active: active (running). Se falhou, não adivinhe. Leia os logs:

journalctl -u my-reporter.service -n 50 --no-pager

Acompanhe logs ao vivo enquanto testa:

journalctl -u my-reporter.service -f

Se o caminho do script estiver errado, as permissões estiverem faltando, o usuário não existir ou o comando sair imediatamente, o systemd geralmente dirá claramente no journal.

Habilite a inicialização na inicialização

Iniciar um serviço e habilitar um serviço são ações diferentes. start executa agora. enable conecta-o ao alvo de inicialização para que inicie em futuras inicializações.

sudo systemctl enable my-reporter.service

Você pode fazer ambos em um comando depois que a unidade for testada:

sudo systemctl enable --now my-reporter.service

Para ver se está habilitado:

systemctl is-enabled my-reporter.service

Torne as falhas mais fáceis de diagnosticar

As falhas mais comuns de primeiro serviço são problemas Linux comuns vestidos com roupas de systemd.

Se você vir status=203/EXEC, o systemd não conseguiu executar o comando. Verifique o caminho, bit executável, linha shebang e finais de linha. Um script copiado do Windows com finais de linha CRLF pode falhar mesmo que pareça correto em um editor.

Se você vir erros de permissão, lembre-se de que o serviço é executado como reporter, não como seu usuário de shell. Teste com:

sudo -u reporter /opt/my-custom-service/reporter.sh

Se o serviço iniciar e parar imediatamente, o processo provavelmente sai. Type=simple espera que o comando continue em execução. Um comando de configuração única deve usar Type=oneshot, não simple.

Se os logs estiverem faltando, verifique se a aplicação escreve em arquivos em vez de stdout/stderr, ou se muda de usuário internamente. Para a maioria dos serviços pequenos, escrever em stdout é a opção menos surpreendente.

Comandos de gerenciamento úteis

Uma vez que a unidade está no lugar, a operação diária é direta:

sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart

systemctl cat é especialmente útil em máquinas com substituições drop-in porque mostra os fragmentos de unidade efetivos que o systemd está lendo.

Um arquivo de unidade personalizado não precisa ser inteligente. Precisa ser chato, explícito e testável. Faça o comando funcionar manualmente, execute-o como um usuário dedicado, escreva a menor unidade que descreva o serviço com precisão, recarregue o systemd e use o journal quando algo falhar. Esse fluxo de trabalho escala de um script de brinquedo para daemons de produção reais.

Adicione ambiente e configuração de forma limpa

Mais cedo ou mais tarde, o serviço precisa de configuração: uma porta, uma URL de banco de dados, uma flag de funcionalidade ou um caminho. Evite enterrar esses valores dentro do arquivo de unidade quando eles variam por ambiente. Um padrão comum é um arquivo de ambiente:

sudo nano /etc/my-reporter.env

Exemplo:

REPORT_INTERVAL=10
REPORT_LABEL=production

Proteja o arquivo se contiver algo sensível:

sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env

Em seguida, referencie-o da unidade:

[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh

No script, leia a variável com um valor padrão:

interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"

Para segredos, tenha cuidado. Uma variável de ambiente pode ser exposta através de inspeção de processo ou metadados de serviço dependendo da configuração do sistema e permissões. Para valores altamente sensíveis, prefira um gerenciador de segredos adequado, um arquivo de credenciais com permissões restritas ou os recursos mais recentes de credenciais do systemd se sua distribuição os suportar. O hábito importante é decidir deliberadamente em vez de espalhar senhas em arquivos de unidade porque é conveniente.

Use substituições drop-in para mudanças locais

Se um pacote instalar uma unidade e você precisar alterar uma configuração, não edite o arquivo do fornecedor. Use um drop-in:

sudo systemctl edit my-reporter.service

Isso abre um arquivo de substituição em /etc/systemd/system/my-reporter.service.d/. Por exemplo:

[Service]
RestartSec=15s

Recarregue e reinicie após salvar:

sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service

Verifique o resultado mesclado:

systemctl cat my-reporter.service

Drop-ins são importantes porque atualizações de pacotes podem substituir unidades de fornecedores. Suas substituições em /etc permanecem visíveis e intencionais.

Pense sobre o comportamento de desligamento

Iniciar é apenas metade do ciclo de vida. Um serviço também deve parar de forma limpa. Por padrão, o systemd envia SIGTERM, espera e depois pode enviar SIGKILL se o processo não sair. Para muitos serviços simples, isso é suficiente. Para workers de fila, processadores de upload e escritores de banco de dados, você pode precisar lidar com a terminação para que o processo termine ou abandone o trabalho atual com segurança.

Você pode ajustar o timeout:

[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM

Não defina timeouts de parada extremamente longos a menos que tenha um motivo. Desligamentos longos atrasam implantações, reinicializações e recuperação de incidentes. Um worker geralmente deve parar de aceitar novo trabalho, terminar o item que está processando e sair dentro de um tempo limitado.

Evite loops de reinicialização ruidosos

Restart=on-failure é útil, mas um serviço quebrado ainda pode reiniciar repetidamente. Adicione limites quando o modo de falha puder ser ruidoso:

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

Isso diz ao systemd para parar de tentar após muitas falhas dentro do intervalo. Quando corrigir o problema, redefina o estado de falha:

sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service

Esse comando também é útil durante testes. Um serviço pode permanecer em estado de falha mesmo depois de você ter corrigido o script ou as permissões.

Valide unidades antes de confiar nelas

O systemd tem um verificador útil:

systemd-analyze verify /etc/systemd/system/my-reporter.service

Ele não pegará todos os problemas de aplicação, mas pode pegar erros de sintaxe, configurações desconhecidas na sua versão do systemd e alguns problemas de ordenação. Execute-o após edições maiores ou ao copiar uma unidade entre distribuições. Os recursos do systemd variam por versão, então uma opção de hardening que funciona em um novo servidor Fedora pode não existir em uma distribuição empresarial mais antiga.

Verifique também a visão de dependências da unidade quando a ordenação de inicialização ficar confusa:

systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service

O primeiro comando mostra o que o serviço puxa ou depende. A visão reversa mostra o que depende dele. Isso é útil quando um serviço inicia inesperadamente ou quando desabilitar uma unidade afeta outra.

Decida se o serviço é de nível de sistema ou de nível de usuário

Este artigo usa um serviço de sistema em /etc/systemd/system/. Essa é a escolha certa para serviços de máquina que devem iniciar na inicialização e funcionar independentemente de uma sessão de login. O systemd também suporta serviços de usuário, geralmente gerenciados com systemctl --user, para processos em segundo plano por usuário.

Não use um serviço de usuário para daemons de infraestrutura apenas para evitar sudo. Serviços de usuário têm regras de ciclo de vida diferentes, manipulação de ambiente e comportamento de login. Para workers de aplicação, exportadores e agentes de nível de host, um serviço de sistema com um usuário dedicado de privilégios mínimos geralmente é mais fácil de raciocinar.

Mantenha a primeira unidade chata

É tentador adicionar todas as diretivas de hardening que você encontra online: dispositivos privados, caminhos somente leitura, filtros de syscall, delimitação de capacidade, restrições de namespace e mais. Essas são ferramentas valiosas, mas adicione-as uma de cada vez depois que o serviço funcionar. Quando um serviço fortemente restrito falha ao iniciar, iniciantes muitas vezes não conseguem dizer se a unidade está errada, a aplicação está errada ou uma configuração de sandbox bloqueou um arquivo que ela precisa.

Um bom caminho de produção é incremental: serviço funcionando, usuário dedicado, logging confiável, política de reinicialização, hardening básico, depois sandboxing mais forte depois que você tiver um teste que prove que a aplicação ainda se comporta corretamente.