nuage de tags sur la liste, suppression dropdown navbar, rôles/droits sur le profil

This commit is contained in:
Cedric Abonnel
2026-05-12 20:07:33 +02:00
parent 1d2e3d9a24
commit 6e438835f8
3470 changed files with 97124 additions and 109 deletions
@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 680 460" width="680" height="460" role="img">
<title>cron vs timers systemd : comparaison des capacités</title>
<desc>Comparaison visuelle en deux colonnes entre cron (à gauche) et les timers systemd (à droite). Chaque ligne représente une capacité de planification : syntaxe d'horaire, rattrapage des jobs ratés, logging, dépendances, jitter, limites de ressources, vue consolidée. La colonne cron coche les bases, la colonne systemd coche toutes les capacités.</desc>
<!-- Background -->
<rect width="680" height="460" fill="#fafaf7"/>
<!-- Background grid -->
<g opacity="0.06">
<line x1="0" y1="100" x2="680" y2="100" stroke="#888" stroke-width="0.5"/>
<line x1="0" y1="200" x2="680" y2="200" stroke="#888" stroke-width="0.5"/>
<line x1="0" y1="300" x2="680" y2="300" stroke="#888" stroke-width="0.5"/>
<line x1="0" y1="400" x2="680" y2="400" stroke="#888" stroke-width="0.5"/>
</g>
<!-- ===== HEADERS ===== -->
<!-- cron column header -->
<g transform="translate(40, 30)">
<rect width="270" height="55" rx="6" fill="#f0eee8" stroke="#666" stroke-width="1"/>
<!-- Old clock icon -->
<circle cx="32" cy="27" r="16" fill="#fff" stroke="#666" stroke-width="1.5"/>
<line x1="32" y1="27" x2="32" y2="17" stroke="#666" stroke-width="1.5" stroke-linecap="round"/>
<line x1="32" y1="27" x2="40" y2="30" stroke="#666" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="32" cy="27" r="1.5" fill="#666"/>
<text x="60" y="22" font-family="system-ui, sans-serif" font-size="14" fill="#333" font-weight="500">cron</text>
<text x="60" y="40" font-family="ui-monospace, monospace" font-size="9" fill="#888">depuis 1975 — simple, partout</text>
</g>
<!-- systemd column header -->
<g transform="translate(370, 30)">
<rect width="270" height="55" rx="6" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="1"/>
<!-- Modern gear icon -->
<g transform="translate(32, 27)">
<circle r="14" fill="#fff" stroke="#3b8a4f" stroke-width="1.5"/>
<circle r="5" fill="none" stroke="#3b8a4f" stroke-width="1.5"/>
<g stroke="#3b8a4f" stroke-width="1.5" stroke-linecap="round">
<line x1="0" y1="-14" x2="0" y2="-10"/>
<line x1="0" y1="14" x2="0" y2="10"/>
<line x1="-14" y1="0" x2="-10" y2="0"/>
<line x1="14" y1="0" x2="10" y2="0"/>
<line x1="-10" y1="-10" x2="-7" y2="-7"/>
<line x1="10" y1="10" x2="7" y2="7"/>
<line x1="-10" y1="10" x2="-7" y2="7"/>
<line x1="10" y1="-10" x2="7" y2="-7"/>
</g>
</g>
<text x="60" y="22" font-family="system-ui, sans-serif" font-size="14" fill="#1f4a2a" font-weight="500">systemd timer</text>
<text x="60" y="40" font-family="ui-monospace, monospace" font-size="9" fill="#3b6a44">intégré, déclaratif, observable</text>
</g>
<!-- ===== CAPABILITY LABELS (center column) ===== -->
<text x="340" y="115" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">syntaxe d'horaire</text>
<text x="340" y="150" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">rattrapage si éteint</text>
<text x="340" y="185" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">logs intégrés</text>
<text x="340" y="220" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">dépendances</text>
<text x="340" y="255" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">jitter (anti pics)</text>
<text x="340" y="290" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">limites cgroups</text>
<text x="340" y="325" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">vue consolidée</text>
<text x="340" y="360" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">fuseaux horaires</text>
<text x="340" y="395" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#666" font-weight="500">portabilité (BSD, Alpine)</text>
<!-- ===== ROWS ===== -->
<!-- Helper: cron column row centers -->
<!-- All rows: cron block at x=40 (270 wide), systemd at x=370 (270 wide) -->
<!-- Each row: feature label centered at x=340, indicator on left/right -->
<!-- Row 1: syntax -->
<!-- cron: limited (yellow/warn) -->
<g transform="translate(40, 105)">
<rect width="270" height="20" rx="3" fill="#f5e4cd" stroke="#c98030" stroke-width="0.8"/>
<polygon points="12,15 18,4 24,15" fill="#c98030"/>
<text x="18" y="14" text-anchor="middle" font-family="system-ui, sans-serif" font-size="9" fill="#fff" font-weight="500">!</text>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#7a4a14">0 2 * * * (cryptique)</text>
</g>
<g transform="translate(370, 105)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">OnCalendar=Mon..Fri 02:00</text>
</g>
<!-- Row 2: catch-up -->
<g transform="translate(40, 140)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">job sauté en silence</text>
</g>
<g transform="translate(370, 140)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">Persistent=true</text>
</g>
<!-- Row 3: logging -->
<g transform="translate(40, 175)">
<rect width="270" height="20" rx="3" fill="#f5e4cd" stroke="#c98030" stroke-width="0.8"/>
<polygon points="12,15 18,4 24,15" fill="#c98030"/>
<text x="18" y="14" text-anchor="middle" font-family="system-ui, sans-serif" font-size="9" fill="#fff" font-weight="500">!</text>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#7a4a14">mail ou redirection manuelle</text>
</g>
<g transform="translate(370, 175)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">journalctl -u nom.service</text>
</g>
<!-- Row 4: dependencies -->
<g transform="translate(40, 210)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">aucune (sleep + boucle)</text>
</g>
<g transform="translate(370, 210)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">After=network-online.target</text>
</g>
<!-- Row 5: jitter -->
<g transform="translate(40, 245)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">sleep $((RANDOM % N))</text>
</g>
<g transform="translate(370, 245)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">RandomizedDelaySec=15min</text>
</g>
<!-- Row 6: cgroups -->
<g transform="translate(40, 280)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">nice + ionice au mieux</text>
</g>
<g transform="translate(370, 280)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">CPUQuota=50% MemoryMax=1G</text>
</g>
<!-- Row 7: consolidated view -->
<g transform="translate(40, 315)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">cron.d, cron.daily, users…</text>
</g>
<g transform="translate(370, 315)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">systemctl list-timers</text>
</g>
<!-- Row 8: timezones -->
<g transform="translate(40, 350)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">fuseau système uniquement</text>
</g>
<g transform="translate(370, 350)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">09:00:00 Europe/Paris</text>
</g>
<!-- Row 9: portability — cron WINS here -->
<g transform="translate(40, 385)">
<rect width="270" height="20" rx="3" fill="#d9ecdf" stroke="#3b8a4f" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#3b8a4f"/>
<path d="M 14 10 L 17 13 L 22 7" stroke="#fff" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#1f4a2a">BSD, macOS, conteneurs</text>
</g>
<g transform="translate(370, 385)">
<rect width="270" height="20" rx="3" fill="#efd3cf" stroke="#a8312a" stroke-width="0.8"/>
<circle cx="18" cy="10" r="6" fill="#a8312a"/>
<line x1="14" y1="6" x2="22" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<line x1="22" y1="6" x2="14" y2="14" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<text x="34" y="14" font-family="ui-monospace, monospace" font-size="9" fill="#6b1e18">Linux systemd uniquement</text>
</g>
<!-- ===== Bottom verdict ===== -->
<rect x="40" y="420" width="600" height="28" rx="6" fill="#f0eee8" stroke="#666" stroke-width="0.8"/>
<text x="340" y="438" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#444" font-style="italic">cron pour la simplicité et la portabilité — systemd dès que la tâche devient critique</text>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,251 @@
*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.*
## Pourquoi cron reste partout
`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).
```
# m h dom mon dow command
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
```
Cinquante 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.
Le problème est que les besoins ont rarement été aussi simples depuis longtemps.
## Les limites de cron qu'on finit toujours par rencontrer
À chaque administration de serveur sérieuse, on retombe sur les mêmes frustrations.
**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.
**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.
**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.
**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.
**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.
**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.
**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.
Aucun 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.
## Ce qu'apporte un timer systemd
Sur 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.
Un timer systemd, c'est **deux fichiers** au lieu d'une ligne :
- 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
- Un fichier `.timer` qui décrit **quand le faire** — ce sont les règles de déclenchement
Cette séparation entre le « quoi » et le « quand » est plus verbeuse au départ, mais elle débloque tout le reste.
### Une syntaxe d'horaire lisible
Là où cron oblige à mentaliser `0 2 * * 1-5`, systemd écrit :
```ini
OnCalendar=Mon..Fri 02:00:00
```
Et 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.
D'autres formes utiles que cron ne sait pas exprimer :
```ini
OnCalendar=*-*-* 02..04:00:00 # toutes les heures entre 2h et 4h
OnCalendar=*-*-01 03:00:00 # tous les 1er du mois à 3h
OnCalendar=*-*-* 09:00:00 Europe/Paris # à 9h heure de Paris, été comme hiver
```
Le 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.
### Du temps relatif, pas seulement du temps absolu
Cron raisonne uniquement en horloge murale (« tel jour, à telle heure »). systemd ajoute le **temps monotone**, relatif à un événement :
```ini
OnBootSec=10min # 10 minutes après le démarrage
OnUnitActiveSec=6h # toutes les 6 heures après la dernière exécution
OnStartupSec=5min # 5 minutes après le démarrage de systemd
```
`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.
### Le rattrapage des exécutions ratées
Une seule ligne change tout :
```ini
Persistent=true
```
Avec 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é.
### Du jitter intégré
```ini
RandomizedDelaySec=15min
```
Le 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.
### Le logging gratuit dans journald
Tout ce que le service écrit sur stdout et stderr est capturé automatiquement par journald. Une seule commande pour tout consulter :
```bash
journalctl -u backup.service # toutes les exécutions historisées
journalctl -u backup.service -f # en suivi temps réel
journalctl -u backup.service --since yesterday
```
Pas 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.
### Les dépendances déclaratives
Dans 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é :
```ini
[Unit]
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/mnt/backup
```
systemd 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.
### Le contrôle des ressources via cgroups
Puisque chaque exécution passe par un service, on bénéficie de tout l'arsenal cgroups de systemd :
```ini
[Service]
CPUQuota=50%
MemoryMax=1G
IOWeight=10
```
Un 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.
### La vue consolidée
```bash
systemctl list-timers --all
```
Une 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.
## Un exemple complet, pas-à-pas
Reprenons le job de sauvegarde initial — `0 2 * * * /usr/local/bin/backup.sh` — et traduisons-le.
`/etc/systemd/system/backup.service` :
```ini
[Unit]
Description=Sauvegarde quotidienne
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/backup.sh
# Capture stdout/stderr dans journald (comportement par défaut, ici explicité)
StandardOutput=journal
StandardError=journal
# Garde-fous ressources
CPUQuota=50%
MemoryMax=1G
```
`/etc/systemd/system/backup.timer` :
```ini
[Unit]
Description=Déclenche la sauvegarde tous les jours à 2h
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=15min
[Install]
WantedBy=timers.target
```
Activation :
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
```
Vérifications :
```bash
systemctl list-timers backup.timer
systemctl status backup.timer
systemd-analyze calendar "*-*-* 02:00:00" # voir la prochaine échéance
journalctl -u backup.service # voir l'historique
sudo systemctl start backup.service # déclencher manuellement pour tester
```
Comparé à 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.
## Quelques recettes utiles
**Tous les jours à 3h sauf le dimanche** :
```ini
OnCalendar=Mon..Sat 03:00:00
```
**Toutes les 15 minutes pendant les heures de bureau** :
```ini
OnCalendar=Mon..Fri 08..18:00/15:00
```
**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).
**Toutes les six heures à partir du dernier passage** (jamais de chevauchement) :
```ini
[Timer]
OnBootSec=5min
OnUnitActiveSec=6h
```
**Timer utilisateur, sans `sudo`** : dans `~/.config/systemd/user/`, puis :
```bash
systemctl --user daemon-reload
systemctl --user enable --now monjob.timer
loginctl enable-linger $USER # pour que ça tourne sans session ouverte
```
## Quand garder cron
Tout n'est pas à migrer. Cron reste le bon choix dans plusieurs cas :
- **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.
- **Tâches utilisateur très simples sur un serveur partagé**, où chaque utilisateur gère sa propre crontab sans privilèges admin.
- **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.
- **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.
La 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.
## En résumé
Cron 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é.
Pour 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.
Et 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.
@@ -0,0 +1,23 @@
{
"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"
}