1 line
14 KiB
JSON
1 line
14 KiB
JSON
{"uuid":"e1e8a0c1-6971-4357-9aaa-7e7a748922f3","slug":"quand-systemd-remplace-cron-pourquoi-et-comment-migrer-ses-taches-planifiees","title":"Quand systemd remplace cron : pourquoi (et comment) migrer ses tâches planifiées","author":"cedric@abonnel.fr","published":true,"published_at":"2026-06-01 07:56","created_at":"2026-05-12 13:57:29","updated_at":"2026-05-12 13:58:58","revisions":[],"cover":"","files_meta":{"b116a3e7b4b54c5c-13369.svg":{"author":"Générée par IA our Cédrix","source_url":""}},"external_links":[],"seo_title":"","seo_description":"","og_image":"","category":"informatique","content":"# Quand systemd remplace cron : pourquoi (et comment) migrer ses tâches planifiées\n\n*Cron tourne sur Linux depuis 1975. Il a fait son temps pour beaucoup d'usages : voici ce que les timers systemd apportent, et comment basculer sans tout casser.*\n\n## Pourquoi cron reste partout\n\n`cron` est l'un des plus anciens outils Unix encore en service. Son principe tient en deux idées : un démon qui se réveille toutes les minutes, et un fichier texte — la crontab — où chaque ligne décrit une commande et son moment d'exécution avec cinq champs (minute, heure, jour du mois, mois, jour de la semaine).\n\n```\n# m h dom mon dow command\n0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1\n```\n\nCinquante ans plus tard, ça marche. C'est installé partout, c'est documenté à mort, ça tient sur une ligne, et n'importe quel administrateur sait lire `0 2 * * *`. Pour beaucoup de besoins simples — « lancer ce script tous les jours à 2h du matin » — cron reste le bon choix.\n\nLe problème est que les besoins ont rarement été aussi simples depuis longtemps.\n\n## Les limites de cron qu'on finit toujours par rencontrer\n\nÀ chaque administration de serveur sérieuse, on retombe sur les mêmes frustrations.\n\n**La machine était éteinte au moment du job.** Cron saute purement et simplement l'occurrence ratée. Si le portable de l'utilisateur dormait à 2h, la sauvegarde quotidienne n'aura pas lieu — point. Le job s'exécutera de nouveau le lendemain à 2h, sans rattrapage, sans alerte.\n\n**Les logs sont dispersés ou perdus.** Par défaut, la sortie standard du job est envoyée par mail à l'utilisateur (si `MAILTO` est défini et qu'un MTA tourne) ou simplement perdue. Le démon lui-même logue dans syslog quand il *démarre* un job, mais pas son contenu. Diagnostiquer pourquoi un job a échoué la semaine dernière relève souvent de l'archéologie.\n\n**Pas de dépendances.** Un job qui doit attendre que le réseau soit monté, qu'un point de montage soit présent, qu'un autre service ait fini son démarrage : cron ne sait pas exprimer ça. La parade habituelle — un `sleep 60` ou un `@reboot` suivi d'une boucle d'attente — fonctionne mais reste un bricolage.\n\n**Pas de recouvrement entre exécutions.** Si un job de 5 minutes en prend 7 ce jour-là, cron lance la prochaine occurrence pile au moment où la précédente tourne encore. Deux instances simultanées d'un script de synchronisation, c'est rarement ce qu'on veut.\n\n**Pas de jitter, pas de randomisation.** Quand cinquante VMs lancent leur `cron.daily` toutes en même temps à 6h25 (l'heure d'anacron par défaut sur Debian), le pic de charge sur l'hyperviseur est garanti. Cron n'offre aucune primitive pour étaler les exécutions.\n\n**Pas de visibilité globale.** Pour répondre à « quels jobs vont tourner aujourd'hui sur cette machine ? », il faut lire la crontab système, les crontabs utilisateur (`/var/spool/cron/`), le contenu de `/etc/cron.d/`, `/etc/cron.daily/`, `/etc/cron.hourly/`, etc. Aucune commande ne donne la vue consolidée.\n\n**Pas d'isolation, pas de quota.** Le job s'exécute avec les privilèges et les ressources du shell qui l'a lancé. Aucune façon native de limiter à 50 % de CPU, à 1 Go de RAM, ou de couper si ça dépasse 10 minutes.\n\nAucun de ces points ne rend cron inutilisable. Mais accumulés sur une dizaine de jobs critiques, ils transforment l'administration en travail de surveillance permanente.\n\n## Ce qu'apporte un timer systemd\n\nSur toute distribution Linux moderne basée sur systemd (la quasi-totalité, hors BSD, Alpine, Gentoo et quelques cas particuliers), une alternative native existe : les **timers**. Le principe est différent dès le départ.\n\nUn timer systemd, c'est **deux fichiers** au lieu d'une ligne :\n\n- Un fichier `.service` qui décrit **ce qu'il faut faire** — exactement comme on décrit un service classique, en mode `Type=oneshot` pour un job ponctuel\n- Un fichier `.timer` qui décrit **quand le faire** — ce sont les règles de déclenchement\n\nCette séparation entre le « quoi » et le « quand » est plus verbeuse au départ, mais elle débloque tout le reste.\n\n### Une syntaxe d'horaire lisible\n\nLà où cron oblige à mentaliser `0 2 * * 1-5`, systemd écrit :\n\n```ini\nOnCalendar=Mon..Fri 02:00:00\n```\n\nEt la commande `systemd-analyze calendar \"Mon..Fri 02:00:00\"` valide l'expression en montrant la prochaine exécution prévue. Une erreur de jour-de-semaine ou un décalage horaire ne plante pas en silence : on le voit avant de déployer.\n\nD'autres formes utiles que cron ne sait pas exprimer :\n\n```ini\nOnCalendar=*-*-* 02..04:00:00 # toutes les heures entre 2h et 4h\nOnCalendar=*-*-01 03:00:00 # tous les 1er du mois à 3h\nOnCalendar=*-*-* 09:00:00 Europe/Paris # à 9h heure de Paris, été comme hiver\n```\n\nLe support natif des fuseaux horaires est une avancée significative pour qui gère des serveurs distribués géographiquement — cron ignore tout du concept et tourne sur le fuseau du système.\n\n### Du temps relatif, pas seulement du temps absolu\n\nCron raisonne uniquement en horloge murale (« tel jour, à telle heure »). systemd ajoute le **temps monotone**, relatif à un événement :\n\n```ini\nOnBootSec=10min # 10 minutes après le démarrage\nOnUnitActiveSec=6h # toutes les 6 heures après la dernière exécution\nOnStartupSec=5min # 5 minutes après le démarrage de systemd\n```\n\n`OnUnitActiveSec=6h` règle proprement le problème des exécutions qui se chevauchent : la prochaine instance se déclenche 6 heures **après la fin** de la précédente, pas 6 heures après son démarrage. Aucune équivalence simple en cron.\n\n### Le rattrapage des exécutions ratées\n\nUne seule ligne change tout :\n\n```ini\nPersistent=true\n```\n\nAvec cette option, systemd mémorise la dernière exécution réussie. Si la machine était éteinte au moment prévu, le job se déclenche dès le démarrage suivant (après le `RandomizedDelaySec` éventuel, voir plus bas). Pour un portable, un poste de développement, ou n'importe quelle machine qui n'est pas en service 24/7, c'est une différence majeure de fiabilité.\n\n### Du jitter intégré\n\n```ini\nRandomizedDelaySec=15min\n```\n\nLe déclenchement se fait à un instant aléatoire dans la fenêtre `[heure_prévue, heure_prévue + 15 min]`. Quand cinquante machines lancent leur mise à jour quotidienne, le pic de charge se lisse au lieu de tomber au même instant. C'est la fonctionnalité que tous les administrateurs de flottes finissent par re-bricoler en cron avec un `sleep $((RANDOM % 900))` peu élégant.\n\n### Le logging gratuit dans journald\n\nTout ce que le service écrit sur stdout et stderr est capturé automatiquement par journald. Une seule commande pour tout consulter :\n\n```bash\njournalctl -u backup.service # toutes les exécutions historisées\njournalctl -u backup.service -f # en suivi temps réel\njournalctl -u backup.service --since yesterday\n```\n\nPas de configuration, pas de redirection à la main, pas de `>> /var/log/backup.log 2>&1` à coller à chaque ligne de crontab. Et accessoirement, journald gère la rotation, la compression et la rétention.\n\n### Les dépendances déclaratives\n\nDans le fichier `.service`, on peut dire au planificateur qu'un job nécessite que le réseau soit prêt, qu'un point de montage soit présent, qu'un autre service ait démarré :\n\n```ini\n[Unit]\nWants=network-online.target\nAfter=network-online.target\nRequiresMountsFor=/mnt/backup\n```\n\nsystemd attend que ces conditions soient remplies avant de déclencher le service. Le job ne tente plus de s'exécuter sur un montage absent ou avant que la résolution DNS soit fonctionnelle.\n\n### Le contrôle des ressources via cgroups\n\nPuisque chaque exécution passe par un service, on bénéficie de tout l'arsenal cgroups de systemd :\n\n```ini\n[Service]\nCPUQuota=50%\nMemoryMax=1G\nIOWeight=10\n```\n\nUn job de sauvegarde qui pourrait saturer le disque ne sortira pas de son enveloppe. Cron n'offre rien d'équivalent — au mieux on enrobe la commande dans `nice` et `ionice`, ce qui reste primitif.\n\n### La vue consolidée\n\n```bash\nsystemctl list-timers --all\n```\n\nUne seule commande, toutes les exécutions planifiées du système, classées par prochaine échéance, avec date de dernière exécution. La question « qu'est-ce qui tourne automatiquement sur cette machine ? » trouve enfin une réponse en une ligne.\n\n## Un exemple complet, pas-à-pas\n\nReprenons le job de sauvegarde initial — `0 2 * * * /usr/local/bin/backup.sh` — et traduisons-le.\n\n`/etc/systemd/system/backup.service` :\n\n```ini\n[Unit]\nDescription=Sauvegarde quotidienne\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=oneshot\nUser=backup\nGroup=backup\nExecStart=/usr/local/bin/backup.sh\n# Capture stdout/stderr dans journald (comportement par défaut, ici explicité)\nStandardOutput=journal\nStandardError=journal\n# Garde-fous ressources\nCPUQuota=50%\nMemoryMax=1G\n```\n\n`/etc/systemd/system/backup.timer` :\n\n```ini\n[Unit]\nDescription=Déclenche la sauvegarde tous les jours à 2h\n\n[Timer]\nOnCalendar=*-*-* 02:00:00\nPersistent=true\nRandomizedDelaySec=15min\n\n[Install]\nWantedBy=timers.target\n```\n\nActivation :\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable --now backup.timer\n```\n\nVérifications :\n\n```bash\nsystemctl list-timers backup.timer\nsystemctl status backup.timer\nsystemd-analyze calendar \"*-*-* 02:00:00\" # voir la prochaine échéance\njournalctl -u backup.service # voir l'historique\nsudo systemctl start backup.service # déclencher manuellement pour tester\n```\n\nComparé à la ligne de crontab originale, c'est plus verbeux. Mais on a, sans rien ajouter : le rattrapage en cas d'arrêt machine, du jitter pour éviter les pics, l'attente du réseau, des limites de ressources, du logging structuré, et une commande pour tout inspecter.\n\n## Quelques recettes utiles\n\n**Tous les jours à 3h sauf le dimanche** :\n\n```ini\nOnCalendar=Mon..Sat 03:00:00\n```\n\n**Toutes les 15 minutes pendant les heures de bureau** :\n\n```ini\nOnCalendar=Mon..Fri 08..18:00/15:00\n```\n\n**Le premier lundi de chaque mois à 5h** : pas faisable en une seule expression, mais combinable avec une condition `ExecStartPre` qui vérifie la date et sort si ce n'est pas le bon jour. C'est l'une des rares zones où cron reste plus naturel (`0 5 * * 1` + `[ $(date +\\%d) -le 7 ]` dans le script).\n\n**Toutes les six heures à partir du dernier passage** (jamais de chevauchement) :\n\n```ini\n[Timer]\nOnBootSec=5min\nOnUnitActiveSec=6h\n```\n\n**Timer utilisateur, sans `sudo`** : dans `~/.config/systemd/user/`, puis :\n\n```bash\nsystemctl --user daemon-reload\nsystemctl --user enable --now monjob.timer\nloginctl enable-linger $USER # pour que ça tourne sans session ouverte\n```\n\n## Quand garder cron\n\nTout n'est pas à migrer. Cron reste le bon choix dans plusieurs cas :\n\n- **Scripts portables vers BSD, macOS, ou des conteneurs minimaux**. systemd n'existe pas dans Alpine Linux, sur les BSD, ni dans la plupart des images Docker légères.\n- **Tâches utilisateur très simples sur un serveur partagé**, où chaque utilisateur gère sa propre crontab sans privilèges admin.\n- **Notification par mail intégrée** : si `MAILTO=admin@domain.tld` suivi d'une sortie sur stderr couvre déjà le besoin de monitoring, repasser par journald + un exporter Prometheus est de la sur-ingénierie.\n- **Un job de trente secondes à ajouter sur un serveur existant** déjà couvert par cron. Mélanger les deux outils est sans risque — ils coexistent sans interférence — et créer deux fichiers pour un alias unique d'une ligne reste excessif.\n\nLa meilleure stratégie est rarement migratoire au pas de charge. Elle consiste à **utiliser systemd pour toute nouvelle tâche planifiée**, et à ne migrer les jobs cron existants que quand ils posent un problème concret : un job raté qu'il fallait rattraper, un log perdu qu'il fallait retrouver, un chevauchement qui a corrompu des données.\n\n## En résumé\n\nCron n'est pas obsolète, il est sous-dimensionné pour des besoins modernes. Les timers systemd ne remplacent pas la simplicité d'une ligne de crontab pour un job trivial, mais ils apportent à peu près tout ce qui manque dès qu'une tâche planifiée devient critique : rattrapage, logging, dépendances, isolation, observabilité.\n\nPour un DevOps qui construit aujourd'hui un nouveau service, le choix par défaut a basculé : commencer en systemd, et n'utiliser cron que par exception justifiée. La verbosité initiale des deux fichiers se rentabilise au premier incident de production qu'on diagnostique en `journalctl -u nom.service` au lieu de fouiller dans des logs disparates.\n\nEt même sans migrer quoi que ce soit, la commande `systemctl list-timers` mérite d'entrer dans le réflexe de tout audit de machine Linux. C'est là que se cache la moitié des tâches planifiées qu'on croit avoir comprises.","featured":false,"tags":[]} |