5 points par GN⁺ 19 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Les timers systemd sont un remplaçant pragmatique de cron : ils exécutent des unités comme des .service selon un planning, tout en rendant l’historique, la sortie et la gestion de l’environnement plus explicites
  • Le cron traditionnel souffre de plusieurs faiblesses : un $PATH ambigu, des stdout/stderr faciles à perdre, un historique d’exécution difficile à suivre et une syntaxe de planification peu lisible
  • Les timers relient un .timer et un .service de même racine, et expriment des exécutions basées sur l’heure ou sur des événements avec OnCalendar, OnBootSec et OnUnitActiveSec
  • systemd-analyze calendar et systemctl list-timers permettent d’inspecter les expressions temporelles et la prochaine heure d’exécution, tandis que WakeSystem= peut réveiller la machine même depuis un état de veille
  • RandomizedOffsetSec et FixedRandomDelay= réduisent les pics d’exécution simultanée, et Persistent= rattrape juste après la reconnexion les exécutions manquées pendant l’inactivité

Pourquoi utiliser les timers systemd à la place de cron

  • L’expression cron job est largement utilisée pour désigner l’élément de base qui exécute des tâches selon un planning, comme « exécuter ceci tous les jours » ou « exécuter cela tous les mois », même lorsqu’il ne s’agit pas du démon cron lui-même
  • Un timer systemd est une unité systemd qui exécute une autre unité, généralement un .service, selon un planning donné, et peut servir de remplaçant fonctionnel au démon cron traditionnel
  • Le cron traditionnel présente plusieurs faiblesses en pratique
    • une configuration de $PATH ambiguë rend le résultat de l’exécution des scripts difficile à prévoir
    • les sorties stdout et stderr finissent souvent dans un trou noir ou sont envoyées au système de messagerie de l’hôte
    • il est difficile de suivre et d’interroger l’historique d’exécution
    • une syntaxe comme 01,31 04,05 1-15 1,6 * n’est ni facile à lire ni intuitive
  • Les timers systemd réduisent ces problèmes tout en proposant une configuration de calendrier proche des expressions de style cron

Structure de base : service et timer

  • Un timer systemd a besoin d’une cible à exécuter, et une unité .service peut logiquement être vue comme l’équivalent d’un script
  • Par exemple, placer l’unité suivante dans /etc/systemd/system/roulette.service installe un service qui éteint l’ordinateur avec une probabilité de 1 sur 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= est une manière plus intégrée d’exprimer une exécution conditionnelle via les options de service systemd, et rend plus clairement au niveau de l’unité la question « faut-il continuer l’exécution ? »
[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
  • Si la condition n’est pas remplie, le journal laisse un message plus explicite
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
  • En général, tirer parti des options fournies par systemd offre une meilleure expérience que d’écrire soi-même des scripts
    • OnFailure= peut être utilisé pour réagir à l’échec d’un script de service
    • Restart= peut être utilisé pour tenter une récupération après un échec temporaire

Associer une unité timer et l’exécuter

  • En plaçant un /etc/systemd/system/roulette.timer avec la même racine, on peut relier le timer à roulette.service
[Unit]
Description=impending destruction

[Timer]
OnCalendar=10:00

[Install]
WantedBy=timers.target
  • Par défaut, le paramètre Unit= d’un timer sélectionne l’unité de service portant la même racine, avec le suffixe .service
    • dans cet exemple, c’est roulette.service qui est sélectionné
    • pour exécuter une unité de service d’un autre nom, on peut modifier Unit=
  • La cible de ExecStart= n’est pas exécutée comme une commande shell par défaut
    • une cible avec chemin absolu doit être traitée comme un script, ou comme un interpréteur attendant un script sous forme de chaîne d’arguments
    • ExecStart=/usr/bin/echo Hello | /usr/bin/awk ne fonctionnera pas dans ce contexte, car le pipe n’y a pas de sens
  • Les arguments de ExecStart= n’héritent pas, par défaut, des variables d’environnement au-delà de quelques valeurs du gestionnaire système
    • le $PATH par défaut est presque vide
    • exécuter /usr/bin/env constitue une protection simple qui permet d’utiliser des éléments comme systemctl
    • ExecStart=/usr/bin/bash seul fournirait des entrées par défaut dans le $PATH, mais l’usage de env ajoute une sécurité supplémentaire
  • Il est possible d’exécuter directement le service sans timer
systemctl start roulette
  • Un service sans section [Install] ne peut pas être enable ; dans cette structure, le timer est la manière standard de lancer le service de façon cohérente
  • systemctl agit par défaut sur roulette.service même sans suffixe explicite
  • Appliquer systemctl start à une unité .timer active le timer, mais n’exécute pas immédiatement le service ciblé par Unit=
systemctl start roulette.timer
  • status indique quand le timer s’exécutera ensuite
systemctl status roulette.timer
Trigger: Sat 2026-04-18 10:00:00 MDT; 35min left
  • Le flux le plus simple consiste à créer le service cible, placer à côté un timer avec son planning, puis démarrer le timer plutôt que la cible
  • Si la section [Install] du timer contient WantedBy=, le timer peut aussi être lancé au démarrage
systemctl enable roulette.timer

Représentation du temps : événements de calendrier et durées

  • Dans les timers, la manière de représenter le planning est importante, et il faut distinguer les intervalles récurrents des événements de calendrier ou des horodatages
  • La page de manuel systemd.time(7) contient suffisamment d’exemples et constitue une première référence utile pour écrire des timers
  • systemd-analyze peut valider et expliquer les expressions temporelles
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
  • Les timers systemd peuvent définir non seulement des heures récurrentes basées sur l’horloge murale, mais aussi, contrairement au cron traditionnel, des durées récurrentes calculées à partir d’un événement antérieur
  • La forme complète de daily signifie une exécution chaque année, chaque mois et chaque jour à 00:00:00
*-*-* 00:00:00
│ │ │ │  │  ╰── at second 00
│ │ │ │  ╰───── at minute 00
│ │ │ ╰──────── at hour 00
│ │ ╰────────── every day
│ ╰──────────── every month
╰────────────── every year
  • On peut utiliser des raccourcis comme daily, la forme complète, ainsi que d’autres valeurs prises en charge par systemd.time(7), puis vérifier ses hypothèses avec systemd-analyze

Quand une exécution déclenchée par événement est plus adaptée

  • Dans les tâches réelles, il est souvent plus pertinent d’exprimer « exécuter après un autre événement » que « exécuter tous les jours à la même heure »
  • Pour le nettoyage d’un répertoire temporaire, si l’expression cron prévue vient juste de passer après le démarrage, il se peut qu’il n’y ait presque rien à supprimer dans /tmp
  • Exprimer la logique comme « exécuter une heure après le démarrage de l’ordinateur, puis toutes les heures » correspond mieux au comportement réel du service et à sa logique de planification
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h
  • OnBootSec=1h signifie une exécution unique une heure après le démarrage de la machine
  • OnUnitActiveSec=1h signifie une nouvelle exécution une heure après que Unit= a été exécutée, ce qui fait implicitement tourner le timer en boucle
  • Ce type d’expression périodique basée sur une durée convient plus souvent à des usages du type « exécuter de temps en temps » qu’à des formulations comme « exécuter à telle minute de chaque heure »
  • Dans l’exemple d’un bot Slack qui interroge l’API d’Advent of Code, une expression cron */15 respecte bien la politique « toutes les 15 minutes » de l’API, mais si tout le monde interroge au même rythme, cela peut concentrer le trafic
  • Démarrer le timer après avoir modifié le code, puis le laisser s’exécuter toutes les 15 minutes, peut répondre au besoin tout en réduisant potentiellement le problème de thundering herd

Voir l’état des timers en un coup d’œil

  • systemctl list-timers est une commande de haut niveau qui résume l’état des timers sur une machine
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.
  • Une seule commande permet d’obtenir une vue d’ensemble de tout ce qui s’exécute selon des plannings de timers
  • list-timers fait partie de la famille des sous-commandes systemd souvent utiles
    • list-units est également pratique
    • list-paths est une sous-commande ajoutée plus récemment à systemctl

Réveiller le système depuis la veille pour exécuter une tâche

  • WakeSystem= permet à un timer arrivé à échéance de réveiller le système depuis la veille
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.
...
  • Cette fonction est utile lorsqu’un script important doit s’exécuter sans qu’une personne ait à ouvrir physiquement le capot d’un ordinateur portable
  • Sur des distributions comme Arch ou NixOS, qui permettent de télécharger les mises à jour avant leur installation, on peut récupérer les paquets tard dans la nuit, puis faire la mise à jour le matin devant le clavier
  • Le manuel précise que si l’on veut remettre la machine en veille après la fin du .service, il faut la remettre en veille manuellement

Répartir les heures d’exécution et atténuer le thundering herd

  • Le problème de thundering herd est un problème système qui survient lorsque plusieurs processus se réveillent en même temps
  • Si tous les systèmes Debian du monde étaient codés en dur pour lancer apt update à 00:00:00, minuit deviendrait un très mauvais moment pour les pics de trafic
  • FixedRandomDelay= et RandomizedOffsetSec= aident à répartir les heures d’exécution
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. ...
  • Ces paramètres peuvent être utilisés sur de vrais systèmes qui vérifient la disponibilité de mises à jour logicielles
  • Répartir les exécutions selon une distribution uniforme aide à réduire le problème de thundering herd, à rendre le comportement plus cohérent, et à éviter des perturbations comme des redémarrages de démons pendant la coordination de services distribués
  • Les options de timing sont globalement très configurables et offrent un contrôle fin

Rattraper immédiatement les exécutions manquées

  • Persistent= est particulièrement adapté aux scripts planifiés qui ne doivent pas être ignorés parce qu’un ordinateur portable était en veille, sans pour autant nécessiter 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. ...
  • Si un système censé faire un check-in de gestion de configuration a subi une indisponibilité, il peut revenir à l’état attendu juste après sa reconnexion en activant simplement Persistent= dans le .timer
  • Sans Persistent=, il faut parfois attendre la prochaine heure normale d’exécution du timer, ce qui peut être long
  • Parmi les autres tâches pour lesquelles il ne faut pas attendre lorsqu’une activation manquée est détectée, on trouve les mises à jour système ou les vérifications de traitements batch

Points d’attention lors de l’écriture de timers

  • Les timers utilisés dans le contexte du gestionnaire utilisateur, via systemctl --user, sont également valables, mais il faut faire attention à la cible mentionnée dans [Install]
  • Selon la distribution, la cible appropriée pour des timers utilisateur peut être default.target
  • Comme avec cron, la recommandation générale de maintenir une horloge système précise reste valable
  • Les utilisateurs de systemd peuvent vérifier l’état de la synchronisation avec timedatectl timesync-status
  • De nombreux éditeurs prennent en charge nativement le format des fichiers d’unité systemd, ce qui aide lorsque ces fichiers grossissent
  • Dans Emacs, on peut utiliser le paquet emacs systemd

1 commentaires

 
Avis sur Lobste.rs
  • systemd n’est pas parfait, mais j’ai l’impression que beaucoup de ses choix de conception s’appuient sur des leçons tirées des approches plus traditionnelles d’autrefois
    J’ai récemment réécouté l’épisode de 2015 de CRE où Lennart Poettering en explique le contexte, et ça reste recommandable aujourd’hui

  • Je fais plutôt partie de ceux qui n’aiment pas systemd jusqu’au bout des ongles, mais je considère systemd.timers comme l’un des concepts “les moins mauvais” de ce produit
    C’est pourquoi j’ai été un peu surpris de voir l’auteur le défendre en rabaissant les gens qui ont des critiques légitimes
    Cela dit, j’aime bien l’utiliser avec la commande at. Pour une commande à exécuter une seule fois à une heure précise, j’utilise at, et pour le reste, des timers systemd avec des fichiers d’unité simples
    L’amélioration que j’aimerais le plus voir, c’est de pouvoir savoir quel utilisateur exécute un timer. Je suis certes l’un des rares à encore administrer une shell box en 2026, mais il serait utile de savoir quel utilisateur a créé un timer qui martèle le disque à chaque seconde

    • Pour cet usage, ne pourrait-on pas utiliser des unités utilisateur au lieu de faire installer des timers système à tout le monde ?
      Si je comprends bien, avec loginctl enable-linger, cela peut fonctionner même sans session utilisateur active. Bien sûr, il y a sans doute des cas d’usage où ce n’est pas suffisant, et je ne connais pas le contexte précis
  • J’aimerais surtout que les timers systemd aient une barrière à l’entrée plus faible, en particulier du côté de la gestion utilisateur
    Vu la quantité de configuration nécessaire, il est vraiment difficile de battre crontab -e

    • L’approche systemd, qui exige plusieurs fichiers de configuration et services pour définir un seul timer… en termes de choix d’API, c’est difficilement défendable
  • Après m’être longtemps demandé comment centraliser proprement les logs des scripts cron, j’ai fini par comprendre qu’il suffisait d’utiliser des timers systemd
    Le problème de journalisation est résolu. Je n’ai plus aucune raison de revenir à cron, et j’aurais aimé le savoir plus tôt

    • On ne peut pas simplement faire un pipe vers logger, ajouter à un fichier de log avec >>, ou laisser le comportement par défaut et recevoir un e-mail ?
  • Vous pouvez trouver ça vieillot, mais je garde encore les e-mails du serveur configurés pour m’atteindre
    Une fois automatisé, ça vient gratuitement avec chaque nouvel hôte, et c’est assez pratique au quotidien
    Par exemple, j’ouvre un multiplexeur, je lance long_running_process | mail root@localhost -s "done $?", puis je l’oublie

  • Bon article, et j’avais moi aussi un brouillon sur un sujet similaire, que j’ai dû reconsulter récemment
    Si, comme moi, vous vous engagez dans le terrier systemd, je recommande de placer les fichiers d’unité et les timers dans le dossier du projet concerné, puis de les lier symboliquement vers /etc/systemd/system/
    Un de mes reproches envers systemd est qu’il ne distingue pas les unités installées par la distribution de celles écrites à la main, mais avec des liens symboliques on peut maintenir soi-même cette séparation

    • En réalité, cette distinction est gérée par le chemin
      Les unités système/paquet/distribution vont dans /usr/lib/systemd/system, tandis que les overrides locaux ou les unités locales vont dans /etc/systemd/system