DEV Community

Dev du Sud
Dev du Sud

Posted on

Mon cron de 3 h ne tournait que les nuits où j'avais laissé le PC allumé

La ligne était propre. Trois heures du matin, tous les jours, un script de maintenance qui
compacte une base, archive des journaux et pousse une sauvegarde.

0 3 * * * /home/moi/bin/maintenance.sh >> /home/moi/logs/maintenance.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Elle a tourné quatre fois en trois semaines.

Pas parce qu'elle était mal écrite. Parce qu'à 3 h du matin, mon poste de travail est éteint.
Et cron, dans ce cas, ne fait rien du tout : ni exécution, ni erreur, ni ligne de log, ni trace
nulle part. L'exécution n'a simplement jamais existé.

Ce que cron ne promet pas

cron est un déclencheur, pas un ordonnanceur. La différence est minuscule dans la
documentation et énorme en pratique.

  • Un déclencheur dit : « à cette minute précise, si la machine est allumée et que le démon tourne, lance cette commande ».
  • Un ordonnanceur dit : « cette tâche doit s'être exécutée une fois par jour ; si elle a été manquée, rattrape-la ».

cron appartient à la première catégorie. Il n'a aucune mémoire des exécutions passées, aucun
état « en retard », aucun mécanisme de rattrapage. C'est un choix de conception assumé depuis
les années 1970, pas un bug.

Le corollaire est ce qui m'a eu : l'absence d'exécution ne produit aucun signal. Un job qui
échoue écrit au moins une trace. Un job qui n'a pas été déclenché n'écrit rien, et rien, dans
un fichier de log, ressemble exactement à « tout va bien ».

Pourquoi je ne l'ai vu que trois semaines plus tard

Mon garde-fou habituel est un fichier d'horodatage écrit après chaque succès. Je regarde son
âge, et s'il dépasse le seuil, je crie.

Sauf que ce garde-fou tournait dans le même cron, sur la même machine, à une heure où elle est
allumée. Il mesurait donc l'âge d'un heartbeat écrit par un job qui, lui, ne tournait jamais.
Le compteur montait, mais rien ne l'interrogeait : le contrôle était dans le même fichier que
le problème, et le cron ne l'a jamais exécuté non plus.

C'est le piège classique du chien de garde hébergé sur la machine gardée. Quand le poste est
éteint, les deux dorment ensemble.

La réponse historique : anacron

anacron existe précisément pour ça. Il ne raisonne pas en minutes mais en jours : chaque
tâche déclare un intervalle (« tous les jours »), et anacron vérifie au démarrage et une fois
par heure si l'intervalle est écoulé. Si oui, il lance, quelle que soit l'heure.

# /etc/anacrontab
1  10  maintenance  /home/moi/bin/maintenance.sh
Enter fullscreen mode Exit fullscreen mode

Le 1 est la périodicité en jours, le 10 le délai en minutes après le démarrage, et le
troisième champ sert au nom du fichier d'état qui empêche de relancer deux fois.

C'est solide, ça existe partout, et ça règle le cas « PC éteint ». Trois limites m'ont fait
choisir autre chose :

  • la granularité est la journée ; une tâche qui doit tourner toutes les six heures n'est pas son fort ;
  • anacron n'est pas toujours un service supervisé par systemd, et son journal ne se lit pas avec journalctl comme le reste du système ;
  • sur plusieurs distributions, il n'est même pas installé par défaut. Sur un poste de bureau avec une session utilisateur systemd, le timer est déjà là.

Ce que j'utilise maintenant : un timer systemd persistant

Un .service et un .timer, tous les deux dans ~/.config/systemd/user/.

# maintenance.service
[Unit]
Description=Maintenance nocturne

[Service]
Type=oneshot
ExecStart=/home/moi/bin/maintenance.sh
Nice=10
IOSchedulingClass=idle
Enter fullscreen mode Exit fullscreen mode
# maintenance.timer
[Unit]
Description=Maintenance quotidienne à 3 h, rattrapage inclus

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=600

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

La ligne qui compte tient en un mot : Persistent=true.

Avec elle, systemd enregistre la dernière exécution dans
~/.local/share/systemd/timers/. Au démarrage, il compare l'horodatage à la planification : si
une échéance a été manquée, il lance le service immédiatement, une fois. Pas cinq fois pour
cinq jours manqués — une fois. C'est le comportement qu'on veut : un état à rattraper, pas une
file de travail à vider.

RandomizedDelaySec=600 étale le déclenchement sur dix minutes. Sur un parc de machines, ça
évite que tout le monde frappe la même API à 3 h 00 pile. Sur un seul poste, ça évite aussi de
tomber pile sur une fenêtre de mise à jour.

systemctl --user daemon-reload
systemctl --user enable --now maintenance.timer
Enter fullscreen mode Exit fullscreen mode

Vérifier que ça a réellement rattrapé

Activer un timer ne prouve rien. Trois commandes, et deux d'entre elles doivent être lues
régulièrement.

systemctl --user list-timers maintenance.timer
systemctl --user status maintenance.service
journalctl --user -u maintenance.service -n 20 --no-pager
Enter fullscreen mode Exit fullscreen mode

list-timers affiche la prochaine échéance et la dernière exécution. C'est la colonne
« LAST » qui m'intéresse : si elle date de trois jours, le rattrapage n'a pas eu lieu.

Pour tester le rattrapage sans attendre 3 h du matin : un timer de test avec un OnCalendar
dans deux minutes, on éteint la machine avant l'échéance, on la rallume après, et on regarde si
le service a tourné. C'est fastidieux une fois, et ça évite de découvrir le problème en
production.

Ce que le rattrapage ne répare pas

Il faut être honnête sur les limites, parce que « persistant » fait croire à une garantie plus
large qu'elle ne l'est.

  • Un seul rattrapage, pas un historique. Cinq jours d'absence produisent une exécution, pas cinq. Si le script traite « les données du jour », celles des jours manqués ne seront jamais traitées : il lui faut une plage de dates, pas « hier ».
  • Le timer ne vérifie pas le résultat. Un service qui sort en 0 sans rien faire reste un succès. Le timer garantit le déclenchement, pas le travail.

Et aucun outil local ne peut rattraper quoi que ce soit si la machine ne redémarre jamais : la
surveillance des tâches critiques doit vivre ailleurs, dans un service qui attend un ping et qui
crie quand il manque.

La différence tient en une phrase. cron répond à « est-ce qu'il est 3 h ? ». Un timer persistant
répond à « est-ce que le travail d'aujourd'hui a été fait ? ». La première question est facile à
poser et n'apporte aucune garantie ; la seconde est la seule qui m'intéresse.

Ma maintenance tourne maintenant tous les jours, y compris les nuits où le PC dort. Et comme
elle est enfin visible dans journalctl, elle a arrêté d'être un rituel silencieux pour
devenir une tâche comme les autres.

Top comments (0)