Entendendo o systemd: O Coração do Linux Moderno
O systemd é o sistema de inicialização e gerenciador de serviços que substituiu o antigo init (SysVinit) na maioria das distribuições Linux modernas. Ele vai muito além de simplesmente iniciar processos: gerencia dependências entre serviços, controla recursos de sistema, coordena montagens de filesystem e fornece logging centralizado através do journald.
A razão pela qual o systemd se tornou dominante é sua abordagem paralela na inicialização. Enquanto o init antigo executava scripts sequencialmente, o systemd inicia múltiplos serviços em paralelo, respeitando as dependências declaradas. Isso resulta em tempo de boot significativamente menor e maior flexibilidade no controle de processos. O systemd usa um arquivo de configuração declarativo (unit files) escrito em um formato INI simples, o que o torna muito mais legível e manutenível que scripts shell.
Unit Files: A Estrutura de Configuração
Um unit file é um arquivo de texto que descreve como o systemd deve gerenciar um serviço, socket, timer ou outro recurso do sistema. A localização padrão para unit files do sistema é /etc/systemd/system/ para customizações locais e /usr/lib/systemd/system/ para arquivos padrão da distribuição. Cada unit file segue uma estrutura de seções entre colchetes, com pares chave-valor simples.
Vamos criar um exemplo prático. Imagine que você tem uma aplicação Python que deve rodar como serviço:
# /etc/systemd/system/meu-app.service
[Unit]
Description=Minha Aplicação Python
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/meu-app
ExecStart=/usr/bin/python3 /opt/meu-app/main.py
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Agora vamos dissecar cada seção. A seção [Unit] define metadados sobre o serviço. A diretiva Description é apenas para legibilidade humana. After=network.target especifica que este serviço deve iniciar apenas depois que o target network.target for alcançado. Wants=network-online.target declara uma dependência fraca (se falhar, não afeta este serviço).
A seção [Service] é onde a mágica acontece. Type=simple significa que o systemd considera o serviço ativo quando o processo principal é iniciado (sem forking). Outros tipos incluem forking, oneshot e notify. User=appuser executa o serviço com esse usuário específico. ExecStart é o comando que inicia o serviço. Restart=on-failure faz o systemd reiniciar automaticamente se o processo terminar com código de erro não-zero. StandardOutput=journal e StandardError=journal redirecionam a saída para o journal (que veremos em detalhes na próxima seção).
A seção [Install] define como este unit é ativado. WantedBy=multi-user.target significa que quando você ativa este serviço, um symlink será criado em /etc/systemd/system/multi-user.target.wants/, fazendo com que ele inicie no boot do sistema.
Operações Básicas com Serviços
Depois de criar o arquivo acima, você deve recarregar a configuração do systemd:
sudo systemctl daemon-reload
Para iniciar o serviço imediatamente:
sudo systemctl start meu-app
Para parar:
sudo systemctl stop meu-app
Para habilitá-lo no boot (criar o symlink em targets.wants):
sudo systemctl enable meu-app
Para desabilitá-lo:
sudo systemctl disable meu-app
Para ver o status atual:
sudo systemctl status meu-app
Para ver logs em tempo real:
sudo journalctl -u meu-app -f
journald: Logging Centralizado e Poderoso
O journald é o subsistema de logging do systemd. Diferentemente do syslog tradicional que escreve em arquivos de texto plano em /var/log/, o journald armazena logs em um formato binário estruturado em /var/log/journal/. Esta abordagem oferece inúmeras vantagens: busca eficiente, menor consumo de disco (com compressão), retenção automática baseada em tempo ou tamanho, e campos estruturados para consultas complexas.
Cada entrada de log no journal é um conjunto de pares chave-valor. Além do texto da mensagem, há campos padrão como PRIORITY, SYSLOG_IDENTIFIER, _PID, _UID, _GID, e muitos outros. Campos que começam com underscore são adicionados pelo sistema, enquanto campos customizados podem ser adicionados por aplicações.
Consultando Logs com journalctl
O comando journalctl é sua ferramenta principal para interagir com o journal. Vamos aos exemplos práticos:
# Ver todos os logs (a partir do mais antigo)
journalctl
# Ver apenas logs de hoje
journalctl --since today
# Ver logs dos últimos 30 minutos
journalctl --since "30 minutes ago"
# Ver logs de um serviço específico
journalctl -u nginx
# Ver logs de um PID específico
journalctl _PID=1234
# Ver logs de um usuário específico
journalctl _UID=1000
# Ver logs de um boot específico
journalctl -b -1 # boot anterior
# Ver logs seguindo em tempo real (como tail -f)
journalctl -f
# Ver logs em formato JSON (ideal para parsing)
journalctl -u meu-app -o json
# Ver logs de prioridade error ou superior
journalctl -p err
# Combinar múltiplos critérios com AND
journalctl -u nginx _HOSTNAME=servidor1 --since "2024-01-15"
Configurando a Retenção de Logs
A retenção padrão do journal é geralmente 10% do tamanho do disco ou 4GB, o que for menor. Você pode customizar isso em /etc/systemd/journald.conf:
# /etc/systemd/journald.conf
[Journal]
Storage=persistent
Compress=yes
MaxRetentionSec=30day
MaxDiskUse=2G
ForwardToSyslog=no
Storage=persistent garante que os logs persistem entre boots. Compress=yes ativa compressão zstd. MaxRetentionSec=30day limita a retenção a 30 dias. MaxDiskUse=2G restringe o uso de disco a 2GB. Após modificar, reinicie o journal:
sudo systemctl restart systemd-journald
Enviando Logs Customizados para o Journal
Aplicações podem enviar logs diretamente para o journal usando a biblioteca libsystemd. Em Python, use o módulo systemd.journal:
from systemd import journal
import logging
# Usando logging padrão do Python
logger = logging.getLogger('meu-app')
handler = journal.JournalHandler()
logger.addHandler(handler)
# Configurar nível de log
logger.setLevel(logging.DEBUG)
# Agora todos os logs vão para o journal
logger.info("Aplicação iniciada")
logger.error("Erro crítico detectado")
logger.debug("Informação de debug")
Após isso, você pode consultar os logs com:
journalctl -u python -f
Gerenciamento Avançado de Processos
O systemd oferece recursos sofisticados de gerenciamento de recursos e comportamento de processos que vão além do controle básico de inicialização e parada.
Controle de Recursos com cgroups
O systemd integra control groups (cgroups) do kernel para limitar recursos consumidos por serviços. Você pode limitar CPU, memória, I/O de disco e muito mais:
# /etc/systemd/system/recurso-intensivo.service
[Unit]
Description=Serviço com Limite de Recursos
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/meu-processador-pesado
Restart=on-failure
# Limite de memória: máximo 512MB
MemoryLimit=512M
# Limite de CPU: máximo 50% de um core
CPUQuota=50%
# Limite de I/O de disco
IOWeight=200
# Timeout de parada: se não parar em 30s, mata o processo
TimeoutStopSec=30s
[Install]
WantedBy=multi-user.target
Você pode verificar os limites aplicados:
# Ver informações de cgroups do serviço
systemctl status recurso-intensivo
# Ver uso atual de memória
systemctl show recurso-intensivo -p MemoryCurrent
# Ver limite de memória
systemctl show recurso-intensivo -p MemoryLimit
Gerenciamento de Dependências e Ordering
O systemd permite declarar relacionamentos complexos entre units através de Before, After, Requires, Wants e BindsTo. Vamos a um exemplo prático com múltiplos serviços:
# /etc/systemd/system/banco-dados.service
[Unit]
Description=PostgreSQL Database
After=network.target
[Service]
Type=simple
User=postgres
ExecStart=/usr/lib/postgresql/13/bin/postgres -D /var/lib/postgresql/13/main
Restart=on-failure
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/api-backend.service
[Unit]
Description=API Backend
After=banco-dados.service
Requires=banco-dados.service
[Service]
Type=simple
User=appuser
ExecStart=/opt/api/start.sh
Restart=on-failure
TimeoutStartSec=60s
[Install]
WantedBy=multi-user.target
Neste cenário, api-backend.service especifica que banco-dados.service é obrigatório (Requires). Se o banco de dados falhar, a API será parada automaticamente. After=banco-dados.service garante que o banco será iniciado primeiro.
Sockets e Ativação por Demanda
Um recurso poderoso do systemd é iniciar serviços apenas quando necessário. Você pode definir um socket unit que ativa o serviço quando uma conexão chega:
# /etc/systemd/system/meu-daemon.socket
[Unit]
Description=Meu Daemon Socket
Before=meu-daemon.service
[Socket]
ListenStream=9000
Accept=no
[Install]
WantedBy=sockets.target
# /etc/systemd/system/meu-daemon.service
[Unit]
Description=Meu Daemon
Requires=meu-daemon.socket
After=meu-daemon.socket
[Service]
Type=simple
ExecStart=/opt/daemon/server
StandardInput=socket
Agora o daemon só inicia quando alguém conecta na porta 9000. Isso economiza recursos em servidores com múltiplos serviços.
Debugging e Solução de Problemas
Quando algo dá errado, o systemd fornece várias ferramentas para diagnóstico. Primeiro, sempre verifique o status detalhado:
# Ver status com output recente
systemctl status meu-app
# Ver logs específicos do serviço
journalctl -u meu-app -n 50 # últimas 50 linhas
# Ver logs em tempo real
journalctl -u meu-app -f
# Ver todo o output do último start/stop
journalctl -u meu-app --since "1 hour ago"
# Ver se há erros de sintaxe no unit file
systemd-analyze verify /etc/systemd/system/meu-app.service
Se um serviço está marcado como "failed", investigue com:
# Obter mais detalhes sobre o status
systemctl show meu-app
# Reiniciar e observar logs
systemctl restart meu-app && journalctl -u meu-app -f
Um comando muito útil para verificar o tempo de boot e a ordem de inicialização é:
# Ver análise de boot
systemd-analyze
# Ver serviços mais lentos no boot
systemd-analyze blame
# Ver gráfico visual das dependências
systemd-analyze plot > boot.svg
Se um serviço entra em loop de restart, configure StartLimitBurst e StartLimitIntervalSec:
[Service]
Type=simple
ExecStart=/opt/app/run
Restart=on-failure
StartLimitBurst=3
StartLimitIntervalSec=60s
Isso faz o systemd dar up se o serviço falhar 3 vezes em 60 segundos.
Conclusão
O domínio efetivo do systemd repousa em três pilares fundamentais: entender unit files como configurações declarativas que descrevem o comportamento desejado (não imperativos como scripts), saber consultar e interpretar logs no journal para debugging eficiente, e conhecer os recursos de controle de recursos e dependências para arquiteturas complexas de múltiplos serviços. Estes conhecimentos transformam você de alguém que apenas "inicia e para" serviços para um profissional capaz de projetar arquiteturas robustas e resilientes no Linux moderno.