5 pontos por GN⁺ 19 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • Timers do systemd são um substituto prático para o cron, executando unidades como .service de acordo com uma agenda e oferecendo gerenciamento mais claro de histórico, saída e ambiente
  • O cron tradicional tem fraquezas como $PATH ambíguo, stdout/stderr que se perdem facilmente, histórico de execução difícil de rastrear e uma sintaxe de agendamento difícil de ler
  • Timers conectam um .timer e um .service com o mesmo stem e expressam execuções baseadas em horário ou evento com OnCalendar, OnBootSec e OnUnitActiveSec
  • Com systemd-analyze calendar e systemctl list-timers, dá para verificar expressões de tempo e o próximo horário de execução, e WakeSystem= pode acordar a máquina para executar mesmo em estado de suspensão
  • RandomizedOffsetSec e FixedRandomDelay= reduzem picos de execuções simultâneas, e Persistent= compensa execuções perdidas enquanto a máquina estava inativa logo após ela voltar a ficar online

Por que usar timers do systemd como substituto do cron

  • cron job é um termo amplamente usado para se referir ao elemento básico da computação que executa tarefas conforme uma agenda, como “execute isto todos os dias” ou “execute aquilo todo mês”, mesmo quando não se trata literalmente do daemon cron
  • Um timer do systemd é uma unidade do systemd que executa outra unidade, normalmente um .service, de acordo com uma agenda específica, e pode servir como substituto funcional do daemon cron tradicional
  • O cron tradicional tem algumas limitações práticas
    • Por causa de configurações ambíguas de $PATH, é difícil prever o resultado da execução de scripts
    • Saídas em stdout e stderr muitas vezes vão para um buraco negro ou são enviadas para o sistema de e-mail do host
    • É difícil rastrear e consultar o histórico de execução
    • Sintaxes de agendamento como 01,31 04,05 1-15 1,6 * não são fáceis de ler nem intuitivas para humanos
  • Timers do systemd reduzem esses problemas e ainda oferecem configurações de calendário parecidas com expressões no estilo cron

Estrutura básica: serviço e timer

  • Um timer do systemd precisa de algo para executar, e uma unidade .service pode ser vista, em termos lógicos, como um script
  • Por exemplo, se você colocar a seguinte unidade em /etc/systemd/system/roulette.service, instalará um serviço que desliga o computador com probabilidade de 1 em 10
[Unit]
Description=1 in 10 chance to break your chains

[Service]
ExecStart=/usr/bin/env bash -c '[[ $(($RANDOM % 10)) == 0 ]] && systemctl poweroff || echo LIVE ANOTHER DAY'
  • ExecCondition= é uma forma mais integrada de expressar execução condicional com opções do serviço systemd, deixando mais claro no nível da unidade a pergunta “deve continuar executando?”
[Unit]
Description=1 in 10 chance to break your chains

[Service]
ExecCondition=/run/current-system/sw/bin/bash -c '[[ $(($RANDOM % 10)) == 0 ]]'
ExecStart=/run/current-system/sw/bin/systemctl poweroff
  • Se a condição não for satisfeita, o journal registra uma mensagem mais clara
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
  • Em geral, aproveitar as opções fornecidas pelo systemd oferece uma experiência melhor do que fazer tudo via script manualmente
    • OnFailure= pode ser usado para reagir a falhas no script de serviço
    • Restart= pode ser usado para tentar recuperar falhas transitórias

Conectando e executando a unidade timer

  • Se você criar /etc/systemd/system/roulette.timer com o mesmo stem do arquivo, poderá conectar o timer a roulette.service
[Unit]
Description=impending destruction

[Timer]
OnCalendar=10:00

[Install]
WantedBy=timers.target
  • Por padrão, a configuração Unit= do timer escolhe a unidade de serviço com o mesmo stem e o sufixo .service
    • Neste exemplo, roulette.service é escolhida
    • Se quiser executar uma unidade de serviço com outro nome, você pode alterar Unit=
  • O alvo de ExecStart= não é executado como comando de shell por padrão
    • Alvos com caminho absoluto devem ser tratados como scripts ou como interpretadores que esperam um script em forma de string como argumento
    • ExecStart=/usr/bin/echo Hello | /usr/bin/awk não funciona nesse contexto, porque o pipe não tem significado aqui
  • Argumentos de ExecStart= não herdam variáveis de ambiente além de alguns padrões do gerenciador do sistema
    • O $PATH padrão é quase vazio
    • Executar /usr/bin/env funciona como uma proteção simples para disponibilizar itens como systemctl
    • Se você tivesse usado apenas ExecStart=/usr/bin/bash, entradas padrão seriam adicionadas ao $PATH, mas usar env é uma camada extra de segurança
  • É possível executar o serviço diretamente, sem timer
systemctl start roulette
  • Um serviço sem seção [Install] não pode ser enabled, e nessa estrutura o timer é a forma padrão de executar o serviço de maneira consistente
  • O systemctl funciona por padrão sobre roulette.service mesmo sem o sufixo explícito
  • Aplicar systemctl start a uma unidade .timer coloca o timer em funcionamento, mas não executa imediatamente o serviço definido em Unit=
systemctl start roulette.timer
  • status mostra quando o timer será executado na próxima vez
systemctl status roulette.timer
Trigger: Sat 2026-04-18 10:00:00 MDT; 35min left
  • O fluxo mais simples é criar o serviço que será executado, colocar um timer com agenda no mesmo local e iniciar o timer, não o alvo
  • Se a seção [Install] da unidade timer tiver WantedBy=, o timer também pode subir no boot
systemctl enable roulette.timer

Representação do tempo: eventos de calendário e durações

  • Em timers, a forma de expressar a agenda é importante, e é preciso distinguir entre intervalos recorrentes de tempo e eventos de calendário ou timestamps
  • A página de manual systemd.time(7) tem exemplos suficientes e vale como a primeira referência ao escrever timers
  • systemd-analyze pode validar e explicar expressões de tempo
systemd-analyze calendar '*-*-* *:*:*'
Normalized form: *-*-* *:*:*
    Next elapse: Sat 2026-04-18 16:44:26 MDT
       (in UTC): Sat 2026-04-18 22:44:26 UTC
       From now: 431ms left
  • Timers do systemd podem definir não só horários recorrentes baseados no relógio de parede, mas também durações recorrentes baseadas em algum evento anterior, algo que o cron tradicional não faz
  • A forma completa de daily significa executar todos os anos, todos os meses, todos os dias às 00:00:00
*-*-* 00:00:00
│ │ │ │  │  ╰── at second 00
│ │ │ │  ╰───── at minute 00
│ │ │ ╰──────── at hour 00
│ │ ╰────────── every day
│ ╰──────────── every month
╰────────────── every year
  • Você pode usar abreviações como daily, a forma completa e outros valores suportados por systemd.time(7), e validar suas suposições com systemd-analyze

Quando execuções baseadas em eventos fazem mais sentido

  • Em trabalhos reais, muitas vezes faz mais sentido dizer “execute depois de outro evento” do que “execute todo dia no mesmo horário”
  • Num trabalho de limpar diretórios temporários, se a expressão do cron tiver passado logo após o boot, pode quase não haver nada para limpar em /tmp
  • Expressar isso como “execute uma hora depois que o computador iniciar e depois execute a cada hora” combina melhor com o comportamento real do serviço e a lógica do agendamento
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h
  • OnBootSec=1h significa executar uma vez uma hora após a máquina iniciar
  • OnUnitActiveSec=1h significa executar novamente uma hora após Unit= ter sido executada, fazendo com que o timer repita continuamente de forma implícita
  • Esse tipo de expressão periódica por duração costuma se encaixar melhor em casos de “execute de vez em quando” do que expressões como “execute nesse minuto de cada hora”
  • No exemplo de um bot do Slack que faz polling da API do Advent of Code, a expressão */15 do cron respeita a política de “a cada 15 minutos” da API, mas se todos fizerem polling do mesmo jeito, o tráfego pode se concentrar
  • Se você iniciar o timer depois de alterar o código e fizer com que ele execute a cada 15 minutos passados desde então, pode obter o comportamento necessário e ainda reduzir a chance do problema de thundering herd

Visualizando o estado dos timers de uma vez

  • systemctl list-timers é um comando de alto nível que resume a situação dos timers em uma máquina
systemctl list-timers
NEXT                                 LEFT LAST                                  PASSED UNIT                         ACTIVATES
Mon 2026-04-20 15:15:00 MDT      1min 40s Mon 2026-04-20 15:00:05 MDT        13min ago zfs-snapshot-frequent.timer  zfs-snapshot-frequent.service
Mon 2026-04-20 15:32:16 MDT         18min Mon 2026-04-20 14:22:15 MDT        51min ago fwupd-refresh.timer          fwupd-refresh.service
Mon 2026-04-20 16:00:00 MDT         46min Mon 2026-04-20 15:00:05 MDT        13min ago logrotate.timer              logrotate.service
Mon 2026-04-20 16:00:00 MDT         46min Mon 2026-04-20 15:00:05 MDT        13min ago zfs-snapshot-hourly.timer    zfs-snapshot-hourly.service
Tue 2026-04-21 00:00:00 MDT            8h Mon 2026-04-20 09:43:22 MDT     5h 29min ago zfs-snapshot-daily.timer     zfs-snapshot-daily.service
Tue 2026-04-21 07:31:28 MDT           16h Sun 2026-04-19 20:15:47 MDT           7h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2026-04-27 00:00:00 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago zfs-snapshot-weekly.timer    zfs-snapshot-weekly.service
Mon 2026-04-27 01:09:27 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago fstrim.timer                 fstrim.service
Mon 2026-04-27 04:28:38 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago zpool-trim.timer             zpool-trim.service
Fri 2026-05-01 00:00:00 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-snapshot-monthly.timer   zfs-snapshot-monthly.service
Fri 2026-05-01 03:17:17 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-scrub.timer              zfs-scrub.service

11 timers listed.
Pass --all to see loaded but inactive timers, too.
  • Com um único comando, dá para ter a visão geral de tudo o que é executado por agenda via timers
  • list-timers faz parte da família de subcomandos do systemd usada com frequência
    • list-units também é útil
    • list-paths é um subcomando adicionado mais recentemente ao systemctl

Acordar da suspensão para executar

  • WakeSystem= permite que um timer vencido acorde o sistema do estado de suspensão
WakeSystem=
    Takes a boolean argument. If true, an elapsing timer will
    cause the system to resume from suspend, should it be
    suspended and if the system supports this.
...
  • Esse recurso é útil quando scripts importantes precisam rodar sem depender da ação física de alguém abrir a tampa do notebook
  • Em distribuições como Arch ou NixOS, que suportam baixar atualizações de pacotes antes do uso, dá para buscar os pacotes tarde da noite e deixar a atualização para a manhã, já diante do teclado
  • O manual informa que, para voltar a suspender depois que o .service terminar, é preciso colocar o sistema em suspensão novamente de forma manual

Espalhando horários de execução e mitigando thundering herd

  • O problema de thundering herd é um problema de sistema que ocorre quando muitos processos acordam ao mesmo tempo
  • Se todos os sistemas Debian do mundo estivessem configurados de forma fixa para rodar apt update às 00:00:00, a meia-noite seria um péssimo horário de pico para todo mundo
  • FixedRandomDelay= e RandomizedOffsetSec= ajudam a espalhar os horários de execução
FixedRandomDelay=
    Takes a boolean argument. When enabled, the randomized delay
    specified by RandomizedDelaySec= is chosen deterministically,
    and remains stable between all firings of the same timer,
    even if the manager is restarted. ...

RandomizedOffsetSec=
    Offsets the timer by a stable, randomly-selected, and evenly
    distributed amount of time between 0 and the specified time
    value. ...
  • Essas configurações podem ser usadas em sistemas reais que verificam atualizações de software
  • Espalhar as execuções com distribuição uniforme ajuda a reduzir o problema de thundering herd, torna o comportamento mais consistente e ajuda a evitar atividades perturbadoras, como reinicializações de daemons durante a coordenação de serviços distribuídos
  • As opções de temporização, no geral, são altamente configuráveis e oferecem controle bem granular

Compensando execuções perdidas imediatamente

  • Persistent= é especialmente adequado para scripts agendados que não devem ser pulados por causa de um notebook suspenso, mas que também não exigem WakeSystem=
Persistent=
    Takes a boolean argument. If true, the time when the service
    unit was last triggered is stored on disk. When the timer is
    activated, the service unit is triggered immediately if it
    would have been triggered at least once during the time when
    the timer was inactive. ...
  • Se um sistema com check-in agendado de gerenciamento de configuração tiver passado por indisponibilidade, basta colocar Persistent= no .timer para que ele converja ao estado correto logo após voltar a ficar online
  • Sem Persistent=, talvez seja preciso esperar até o próximo horário normal de execução do timer, e essa espera pode ser longa
  • Outros trabalhos em que não se deve esperar ao detectar uma ativação perdida incluem atualizações de sistema e verificação de jobs em lote

Cuidados ao escrever timers

  • Timers no contexto do gerenciador de usuário, administrados com systemctl --user, também são válidos, mas é preciso prestar atenção ao alvo usado em [Install]
  • Dependendo da distribuição, o alvo adequado para timers de usuário pode ser default.target
  • Assim como no cron, o aviso geral sobre manter o relógio do sistema correto continua valendo
  • Usuários do systemd podem verificar o estado da sincronização com timedatectl timesync-status
  • Muitos editores oferecem suporte nativo ao formato de arquivos de unidade do systemd, o que ajuda quando os arquivos ficam maiores
  • No Emacs, você pode usar o pacote emacs systemd

1 comentários

 
Opiniões no Lobste.rs
  • O systemd não é perfeito, mas dá a sensação de que muitas decisões de design se baseiam em aprendizados obtidos com abordagens mais tradicionais do passado
    Recentemente ouvi de novo o episódio de 2015 do CRE em que Lennart Poettering explica esse contexto, e ainda hoje vale a recomendação

  • Sou do time que não gosta de systemd até os ossos, mas acho que systemd.timers é um dos conceitos “menos ruins” desse produto
    Por isso, achei meio surpreendente o autor defender isso de um jeito que parece diminuir pessoas com reclamações legítimas
    Ainda assim, gosto de usar junto com o comando at. Para comandos que devem rodar uma única vez em um horário específico, at; para o resto, timers do systemd e arquivos de unidade simples
    A melhoria que eu mais queria ver é poder saber qual usuário está executando um timer. Sou uma das poucas pessoas que ainda operam um shellbox em 2026, mas seria útil descobrir qual usuário criou um timer que fica martelando o disco a cada segundo

    • Para esse objetivo, talvez dê para usar unidades de usuário em vez de fazer todo mundo instalar timers do sistema
      Pelo que sei, com loginctl enable-linger eles podem rodar mesmo sem uma sessão de usuário ativa. Claro, talvez isso não cubra todos os casos de uso, e não conheço a situação específica
  • Eu gostaria especialmente que os timers do systemd tivessem uma barreira inicial menor no lado de gerenciamento por usuário
    Quando se olha a quantidade de configuração necessária, é realmente difícil superar crontab -e

    • A abordagem do systemd, que exige vários arquivos de configuração e serviços para configurar um único timer... como escolha de API, isso não faz sentido nenhum
  • Depois de passar muito tempo pensando em como reunir de forma organizada os logs de scripts do cron, percebi que bastava usar timers do systemd
    O problema de logging foi resolvido. Agora não tenho mais motivo para voltar a usar cron, e queria ter descoberto isso antes

    • Não bastaria mandar por pipe para logger, anexar ao arquivo de log com >> ou deixar no padrão e receber por e-mail?
  • Pode chamar de antiquado, mas ainda deixo e-mail configurado para me alcançar no servidor
    Se você automatiza isso, vem de graça em cada novo host, e no dia a dia ainda é bem conveniente
    Por exemplo, abro um multiplexador, executo long_running_process | mail root@localhost -s "done $?" e depois simplesmente esqueço

  • Bom texto, e eu também tinha um rascunho de algo parecido que precisei consultar de novo recentemente
    Se você, como eu, cair na toca do coelho do systemd, recomendo deixar os arquivos de unidade e os timers na pasta do projeto correspondente e criar links simbólicos para eles em /etc/systemd/system/
    Uma reclamação que tenho sobre o systemd é que ele não distingue as unidades instaladas pela distribuição daquelas que você mesmo escreveu, e com links simbólicos dá para manter essa separação por conta própria

    • Na verdade, essa distinção é feita pelo caminho
      Unidades de sistema/pacote/distribuição ficam em /usr/lib/systemd/system, e overrides locais ou unidades locais ficam em /etc/systemd/system