Guia para Timers do Systemd: Substituindo Tarefas Cron por Agendamento Confiável

Use timers do systemd em vez do cron quando você quiser logs do journal, tratamento de execuções perdidas, dependências e controles de recursos.

Guia para Timers do Systemd: Substituindo Tarefas Cron por Agendamento Confiável

cron ainda é adequado para muitas tarefas. Se você precisa executar um script shell todas as noites e já possui redirecionamento de logs funcionando, não há prêmio por substituí-lo. O motivo pelo qual muitas equipes migram o trabalho agendado do Linux para timers do systemd não é moda. É porque os timers do systemd fornecem ao trabalho uma unidade de serviço real, logs previsíveis, tratamento de dependências, comportamento de execuções perdidas e limites de recursos.

Isso importa quando a tarefa não é apenas "executar um comando". Um trabalho de backup pode precisar de um disco montado. Um warmer de cache pode precisar que a rede esteja utilizável. Um exportador de relatórios pode precisar ser executado como uma conta de serviço restrita e deixar logs legíveis para o próximo plantonista. Um timer do systemd permite que você descreva essas necessidades no mesmo lugar onde gerencia o resto do ciclo de vida do serviço.

Entendendo os Timers do Systemd

Timers systemd são arquivos de unidade do systemd que controlam quando outras unidades do systemd, tipicamente unidades de serviço, são ativadas. Diferente do cron, que é um daemon independente, os timers do systemd são uma parte integrante do sistema init systemd. Essa integração profunda traz vários benefícios significativos, especialmente em relação à confiabilidade, registro de logs e gerenciamento de recursos.

Um timer systemd sempre funciona em conjunto com outra unidade, mais comumente uma unidade de serviço. O arquivo .timer define quando um evento deve ocorrer, e o arquivo .service correspondente define qual ação deve ser executada quando esse evento é acionado. Essa clara separação de responsabilidades torna os timers do systemd altamente modulares e flexíveis.

Principais Vantagens dos Timers do Systemd sobre o Cron

Embora o cron seja funcional, os timers do systemd abordam muitas de suas limitações, oferecendo uma solução de agendamento mais robusta e rica em recursos:

  • Confiabilidade e Persistência: Se um timer de calendário usa Persistent=true e o sistema é desligado durante uma execução agendada, o systemd registra que a execução foi perdida e inicia o serviço associado após a próxima inicialização. O cron comum geralmente não recupera sem uma ferramenta separada, como o anacron.
  • Integração com o systemd: Os timers se beneficiam do poderoso registro de logs do systemd (via journalctl), gerenciamento de dependências e controle de recursos (cgroups). Isso significa melhor monitoramento, relatórios de erro mais claros e a capacidade de definir sequências de inicialização complexas ou limites de recursos para tarefas agendadas.
  • Reprodutibilidade e Controle de Versão: Os arquivos de unidade do systemd são arquivos de texto simples que podem ser facilmente armazenados em sistemas de controle de versão. Isso permite implantações reproduzíveis e rastreamento mais fácil de alterações em tarefas agendadas em vários sistemas.
  • Agendamento Baseado em Eventos: Além do agendamento simples baseado em tempo, os timers do systemd podem ser acionados em relação à inicialização do sistema (OnBootSec) ou após a última ativação de uma unidade (OnUnitActiveSec), fornecendo opções de agendamento mais dinâmicas.
  • Expressões de Tempo Flexíveis: O systemd oferece um rico conjunto de expressões de eventos de calendário, muitas vezes mais legíveis e versáteis que a sintaxe do cron, incluindo horários, diários, semanais e datas/horas específicas.
  • Gerenciamento de Recursos e Dependências: Os serviços do systemd iniciados por timers herdam o ambiente do systemd, incluindo as configurações de cgroup, e podem declarar dependências de outras unidades do systemd (por exemplo, aguardar a rede ou um banco de dados estar disponível antes de executar).
  • Tratamento de Saída Padrão/Erro: O systemd captura automaticamente stdout e stderr dos serviços iniciados por timers e os direciona para o journal do sistema, tornando a depuração e auditoria muito mais simples do que com a saída baseada em e-mail do cron ou redirecionamento manual.

Configurando Timers do Systemd

Configurar um timer systemd envolve criar dois arquivos de unidade: uma unidade de serviço (.service) e uma unidade de timer (.timer). Esses arquivos são normalmente colocados em /etc/systemd/system/ para timers de todo o sistema ou ~/.config/systemd/user/ para timers específicos do usuário.

1. A Unidade de Serviço (arquivo .service)

A unidade de serviço define o comando ou script real a ser executado. É um arquivo de serviço systemd padrão, mas geralmente projetado para ser executado de forma não interativa e para realizar uma tarefa específica.

Exemplo: /etc/systemd/system/mytask.service

[Unit]
Description=Meu Serviço de Tarefa Agendada

[Service]
Type=oneshot
ExecStart=/usr/local/bin/mytask.sh
User=meuusuario
Group=meugrupo
# Opcional: Limitar recursos em versões mais recentes do systemd
# CPUWeight=50
# MemoryMax=1G

[Install]
WantedBy=multi-user.target

Explicação:

  • [Unit]: Contém informações genéricas sobre a unidade.
    • Description: Uma descrição legível por humanos.
  • [Service]: Define a configuração específica do serviço.
    • Type=oneshot: Indica que o serviço executa um único comando e depois termina. Isso é comum para tarefas agendadas.
    • ExecStart: O comando ou script a ser executado. Forneça o caminho completo.
    • User, Group: Define o usuário e o grupo sob os quais o comando será executado. Sempre execute tarefas com os privilégios mínimos necessários.
    • CPUWeight, MemoryMax: Controles de cgroup opcionais. Eles são úteis quando um trabalho agendado não deve prejudicar o resto do host.
  • [Install]: Define como a unidade deve ser habilitada.
    • WantedBy=multi-user.target: Embora presente, esta seção é frequentemente menos crítica para serviços acionados por timer, pois a própria unidade de timer geralmente determina a ativação. No entanto, pode ser útil se você também quiser que o serviço seja ativável manualmente ou para integrar em outros targets do systemd.

2. A Unidade de Timer (arquivo .timer)

A unidade de timer define quando a unidade de serviço correspondente deve ser ativada. Ela deve ter o mesmo nome que sua contraparte de serviço (por exemplo, mytask.timer para mytask.service).

Exemplo: /etc/systemd/system/mytask.timer

[Unit]
Description=Executa mytask.service diariamente

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=600
AccuracySec=1min

[Install]
WantedBy=timers.target

Explicação:

  • [Unit]: Informações genéricas.
    • Description: Uma descrição para o timer.
  • [Timer]: Define a configuração específica do timer.
    • OnCalendar: A configuração mais comum, definindo um evento de calendário. Usa expressões como:
      • daily: Todos os dias à meia-noite.
      • weekly: Toda segunda-feira à meia-noite.
      • monthly: No primeiro dia de cada mês à meia-noite.
      • hourly: A cada hora no minuto.
      • *-*-* 03:00:00: Todos os dias às 3:00 AM.
      • Mon..Fri 08:00..17:00: Dias úteis entre 8 AM e 5 PM.
      • Mon *-*-* 03:00:00: Toda segunda-feira às 3 AM.
    • OnBootSec: Ativa o serviço após um tempo especificado a partir da inicialização do sistema. Exemplo: OnBootSec=10min.
    • OnUnitActiveSec: Ativa o serviço após um tempo especificado a partir da última ativação do serviço. Exemplo: OnUnitActiveSec=1h para executar a cada hora após a execução anterior ser concluída.
    • Persistent=true: Crucial para confiabilidade. Se o sistema estiver desligado durante uma execução agendada, o serviço será acionado logo após a próxima inicialização.
    • RandomizedDelaySec=600: Adiciona um atraso aleatório de até 600 segundos. Isso é útil quando muitas máquinas compartilham o mesmo timer e você não quer que todos os hosts atinjam um banco de dados, API ou servidor de backup exatamente no mesmo segundo.

Uma Migração Real de Cron para Timer

Suponha que você atualmente tenha esta entrada de cron do root:

15 2 * * * /usr/local/sbin/backup-app.sh >> /var/log/backup-app.log 2>&1

Funciona em uma máquina silenciosa, mas tem os pontos fracos usuais. Se o disco de backup não estiver montado, o script pode falhar no meio do caminho. Se o servidor estiver desligado às 2:15 AM, a execução é pulada. Se o script escrever um erro útil, alguém tem que lembrar qual arquivo de log personalizado verificar. Se o script começar a usar muita memória, o cron não ajudará a contê-lo.

A versão do systemd separa o comando do agendamento:

# /etc/systemd/system/backup-app.service
[Unit]
Description=Fazer backup dos dados da aplicação
RequiresMountsFor=/mnt/backups
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=backup
Group=backup
WorkingDirectory=/srv/app
ExecStart=/usr/local/sbin/backup-app.sh
MemoryMax=1G
CPUWeight=40
Nice=10
# /etc/systemd/system/backup-app.timer
[Unit]
Description=Executar backup da aplicação todas as noites

[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
RandomizedDelaySec=15min
AccuracySec=1min
Unit=backup-app.service

[Install]
WantedBy=timers.target

Há alguns detalhes que valem a pena notar. RequiresMountsFor=/mnt/backups diz ao systemd que o caminho deve ser montado antes do serviço iniciar. After=network-online.target e Wants=network-online.target são úteis apenas se seu gerenciador de rede realmente fornecer um serviço de aguardar-online; em muitas distribuições, esse serviço está desabilitado por padrão. Se o backup apenas escreve em um disco local, deixe a dependência de rede de fora.

Type=oneshot é adequado para scripts que fazem seu trabalho e saem. Não use para um daemon que permanece em execução. WorkingDirectory= evita scripts que acidentalmente dependem de serem lançados de um shell em um diretório específico. User=backup é geralmente melhor do que executar o trabalho como root e esperar que todos os comandos dentro do script sejam cuidadosos.

Após salvar os arquivos:

sudo systemctl daemon-reload
sudo systemctl enable --now backup-app.timer
systemctl list-timers backup-app.timer

Para testar o trabalho imediatamente, inicie o serviço, não o timer:

sudo systemctl start backup-app.service
journalctl -u backup-app.service -n 100 --no-pager

Essa distinção evita muita confusão. Iniciar backup-app.timer arma o agendamento. Iniciar backup-app.service executa o backup real.

Escolhendo a Expressão de Timer Correta

OnCalendar= é a substituição mais próxima da sintaxe do cron, mas é lida de forma diferente. Você pode verificar o que o systemd acha que uma expressão significa antes de enviá-la:

systemd-analyze calendar 'Mon..Fri 03:30'
systemd-analyze calendar '*-*-01 04:00:00'
systemd-analyze calendar 'Sun *-*-* 23:00:00'

Use timers de calendário para trabalhos de relógio de parede: backups noturnos, relatórios semanais, limpeza mensal, verificações de certificados e outras tarefas onde o calendário humano importa. Use timers monotônicos para comportamento "executar depois que algo aconteceu":

[Timer]
OnBootSec=10min
OnUnitActiveSec=1h

Esse padrão inicia o serviço dez minutos após a inicialização e, em seguida, novamente uma hora após a última ativação. É uma boa opção para polling, limpeza local e loops de manutenção leves. Não é o mesmo que "no minuto zero de cada hora". Se o trabalho leva doze minutos, a próxima execução é contada a partir do momento da ativação, não da sua expectativa de relógio de parede.

Pense também na sobreposição. Para uma unidade de serviço normal, o systemd não iniciará uma segunda cópia da mesma unidade ativa apenas porque o próximo evento de timer chegou. Se seu trabalho pode ser executado por mais tempo que seu intervalo, decida se isso é aceitável. Às vezes, a resposta certa é um bloqueio no script, como flock, porque pode produzir uma mensagem clara de "execução anterior ainda ativa". Às vezes, a resposta certa é aumentar o intervalo.

Hábitos Operacionais que Economizam Tempo

A visualização do timer é seu primeiro painel:

systemctl list-timers --all

Mostra a última execução, a próxima execução e a unidade que cada timer ativa. Se o timer estiver listado, mas o serviço nunca for executado, verifique a expressão do calendário e se o timer está habilitado. Se o serviço for executado e falhar, ignore o timer por um momento e inspecione o serviço:

systemctl status backup-app.service
journalctl -u backup-app.service --since today

Quando você editar qualquer arquivo de unidade, execute:

sudo systemctl daemon-reload
sudo systemctl restart backup-app.timer

Reiniciar o timer após alterações no agendamento é um bom hábito porque faz com que o próximo horário de ativação seja atualizado imediatamente. Se você alterou apenas o script em si, geralmente não precisa de daemon-reload.

Para timers de usuário, use systemctl --user e coloque as unidades em ~/.config/systemd/user/. Eles são úteis para estações de trabalho de desenvolvedores e automação por usuário, mas têm uma pegadinha importante: por padrão, os serviços de usuário estão vinculados à sessão de login do usuário. Se você precisar que um timer de usuário continue sendo executado após o logout, habilite o lingering com loginctl enable-linger username. Essa é uma escolha administrativa deliberada, não algo para esconder dentro do artigo como uma correção mágica.

Quando o Cron Ainda é a Melhor Ferramenta

Não mova tudo cegamente. O Cron é mais fácil de ler para tarefas pequenas e locais do usuário, especialmente em servidores mais antigos ou contêineres mínimos onde o systemd não é o PID 1. Se seu único requisito é "executar este comando inofensivo a cada cinco minutos", o cron pode ser a resposta mais clara.

Os timers do Systemd compensam quando o trabalho tem necessidades semelhantes a serviços: identidade controlada, logs no journal, limites de recursos, dependências, comportamento de recuperação ou implantação padrão através de arquivos de unidade. Na prática, recorro a timers quando a tarefa agendada acordaria alguém se falhasse. O arquivo de unidade extra vale a pena quando dá ao próximo operador um caminho direto de "o que foi executado?" para "o que falhou?" para "o que mudou?".

Um último hábito que vale a pena adotar durante as migrações: mantenha a entrada antiga do cron comentada por perto apenas até o timer ter sido executado com sucesso algumas vezes, depois remova-a. Agendamentos duplicados são uma fonte silenciosa de danos. Dois trabalhos de backup podem competir pelo mesmo bloqueio, dois trabalhos de limpeza podem excluir arquivos antes do esperado e dois trabalhos de relatório podem enviar e-mails duplicados. Após habilitar o timer, verifique systemctl list-timers --all, confirme o journal do serviço e certifique-se de que o caminho antigo do cron não está mais ativo.