19 de setembro de 2021

Do bit Ao Byte

Embarcados, Linux e programação

Systemd timer – Alternativa ao cron

Systemd timer

O systemd timer é uma alternativa ao tradicional cron, o agendador de tarefas dos *NIX e Linux. Se não conhece o cron, acompanhe os próximos parágrafos introdutórios ou então pule diretamente para o próximo tópico.

Podemos agendar tarefas facilmente em nosso Raspberry para, por exemplo, publicar seu status via MQTT.

Cron e crontab

O Cron é o agendador de tarefas. Isso é, se pretende que uma rotina seja executada, esse agendador cumprirá a tarefa. Para agendar tarefas usamos o programa crontab. Com a flag -l são listadas as tarefas do usuário e com a flag -e a lista de tarefas é editada. Estou citando algumas características sem detalhamento, apenas para figurar o cron antes de iniciar o tema principal, que é o systemd timer.

Usando o comando info crontab encontramos as referências sobre cada um dos 5 campos:

crontab

Podemos usar asterisco para “todos”, faixas separadas por vírgula (1,2,5) ou hífen (0-4,9,12) e mais um monte de coisas descritas no info crontab. Uma coisa que uso bastante é execuções a cada minuto. Supondo que um comando deva ser executado a cada minuto durante todos os dias do ano:

*/1 * * * * /bin/ls / >>arquivos.txt

A execução nesse exemplo é estúpida mas é apenas exemplo.

Para executar uma tarefa a cada duas horas, poderia ser:

0 0-23/2 * * * /bin/ls /  >>arquivos.txt

Se fosse colocado o asterisco no campo dos minutos, esse comando seria executado a cada minuto dentro da hora par.

O shell usado pelo cron é sempre o que estiver no link /bin/sh, exceto a variável SHELL esteja definida no crontab. Para garantir que o shell padrão é o shell utilizado, use o comando:

ls -l $(which sh)

Tem uma imensidão de coisas para saber sobre o cron; quebra de linha, LF, gatilhos especiais (@reboot, invés dos primeiros 5 campos fará com que o script seja executado apenas 1 vez ao iniciar o sistema) etc.

Se no início do crontab for definida a variável shell, como citando anteriormente, garante-se que a execução acontecerá usando o interpretador escolhido:

SHELL=/bin/bash
@reboot echo $(date) >/home/usuario/started.log

Últimas curiosidades: Dia 0 e dia 7 da semana são ambos considerados domingo. Listas e faixas podem co-existir na mesma posição (ex minuto):

0-15,45-59 * * * * /bin/ls >>/home/usuario/ranges_and_lists.txt

Faixas podem conter passos. Por exemplo, um salto a cada 2:

0-15/2 * * * * /bin/ls >>/home/usuario/ranges_and_steps.txt

Meses e dias da semana podem ser especificados por nome, mas começa virar zona, em minha fraca opinião.

Systemd timer

Tem bastante coisa relacionada ao conceito, mas o que mais me agrada é explícito; cara tarefa que outrora seria inserida no cron agora pode ser criada como um serviço de sistema. Pode ser positivo, pode ser negativo; por um lado, podemos ter montes de tarefas que virarão montes de serviços. Já por outro lado, evita-se o risco de fazer uma besteira em todos os agendamentos centralizados no arquivo do crontab. Por fim, o formato do agendamento é extremamente amigável. Podemos criar e executar um teste antes de torná-lo serviço de sistema e para exemplificar, vamos criar o arquivo /etc/systemd/system/tarefa.service com o seguinte conteúdo:

#Servico de teste relacionado ao artigo https://www.systemd-timer-alternativa-ao-cron

[Unit]
Description=Consumo de memoria
Wants=tarefa.timer

[Service]
Type=oneshot
ExecStart=/usr/bin/free -m

[Install]
WantedBy=multi-user.target

Depois podemos testar sem instalar o serviço, executando:

systemctl start tarefa.service

E podemos ver o resultado trocando start por status:

Systemd timer test

Se digitar algum parâmetro errado, use stop invés de status e então após corrigir o erro, recarregue o daemon do systemd:

systemctl daemon-reload

Se não fizer isso e reiniciar o serviço, o erro persistirá.

Em ExecStart podemos adicionar qualquer coisa; um programa binário, um comando shell ou um script.

Tendo comprovado o funcionamento do serviço sem erros, hora de criar o arquivo tarefa.timer, também em /etc/systemd/system. Vamos supor:

[Unit]
Description=monitor de uso de memoria
Requires=tarefa.service

[Timer]
Unit=tarefa.service
OnCalendar=*-*-* *:*:00

[Install]
WantedBy=timers.target

O mais legal aqui é o formato do agendador, YYYY-MM-DD HH:MM:SS. Do jeito que está a tarefa é executada a cada 1 minuto. Pare e execute seu serviço novamente, depois mantenha o status à vista com o comando journalctl -S today -f -u tarefa.service

A primeira coisa a notar é que a saída está indo para o journal do sistema, característico do systemd. Olhando a saída vemos também que a execução não acontece em um tempo preciso de 60 segundos – totalmente remediável, mas característico do processo, como forma de prevenção de gatilhos simultâneos. Porém, sabemos que há muitos casos que a precisão é desejada ou requerida e nesses casos podemos determinar a precisão, usando o parâmetro AccuracySec=1us para 1 microsegundo, ou ms para milisegundos, segundos, etc. As palavras chaves (em ordem dimensional de tempo) podem ser:

usec,us

msec,ms

seconds,second,sec,s

minutes,minute,min,m

hours,hour,hr,h

days,day,d

weeks,week,w

mounts,month,M

years,year,y

Meses são definidos como 30.44 dias e anos são definidos como 365.25 dias.

Esse parâmetro deve ser adicionado ao arquivo *.timer da sua tarefa, na seção Timer. Ficaria assim:

Systemd timer task

E com isso resolvemos a questão de precisão do timer!

systemd journal

Concluídos os testes, já podemos habilitar nossa tarefa durante a inicialização do sistema, usando o comando:

systemctl enable tarefa.timer

Uma das vantagens sobre o cron são os timers monotônicos, invariáveis ou condicionados. Podemos determinar que um timer deve reagir em N segundos após sua última execução, com o parâmetro OnActiveSec ou relativo ao tempo de boot, em OnBootSec após a inicialização do sistema. Outros recursos estão disponíveis, consulte a documentação oficial do systemd para mais informações.

Usando OnCalendar, temos como opcional o dia da semana e os demais campos podem ser referenciados com asterisco.

Uma tarefa executada uma vez por semana pode ser representada assim:

Mon *-*-* 00:00:00

Ou assim:

Weekly

E dá pra elaborar muito mais. Por exemplo, para o quinto dia do mês de abril e depois a cada 5 dias até que o mês acabe:

*-04-05/5

E invés de calcular de cabeça uma agenda, para que não haja erro temos uma ferramenta que ajuda com isso: O systemd-analyze:

Systemd timer analyzer

A contrapartida é que devemos ser organizados para gerenciar nosso Systemd timer, mas eis aí uma ótima alternativa ao cron!