Retour au cours

infra / linux-bash

systemd avancé (unit files custom, timers)

Leçon 181 exercice

Explication

Un script bash, une fois lancé, tourne jusqu'à ce qu'il se termine ou plante, sans supervision. systemd fonctionne autrement : il surveille en continu chaque service qu'il gère, et peut le relancer automatiquement en cas d'arrêt inattendu.

Ce que vous allez apprendre

  • Écrire un fichier .service custom avec les bonnes directives (ExecStart, User, Restart)
  • Comprendre pourquoi daemon-reload est obligatoire après toute modification d'un fichier .service
  • Créer un timer systemd pour remplacer une entrée cron, avec des logs intégrés
  • Distinguer une dépendance dure (Requires=) d'une dépendance souple (Wants=)
  • Surveiller un service en temps réel avec journalctl -u

Dans quel contexte ?

Une équipe backend vient de packager son API FastAPI pour la déployer en production, sans passer par un simple nohup uvicorn & fragile. Il faut que le service démarre automatiquement au boot du serveur, qu'il redémarre tout seul si uvicorn plante à 3h du matin, et que ses logs soient consultables proprement sans fouiller dans des fichiers texte épars. C'est exactement ce qu'un fichier .service bien écrit apporte.

C'est exactement le rôle de Restart=on-failure dans un fichier .service : si le processus uvicorn plante, systemd le redémarre tout seul après 5 secondes (RestartSec=5).

Ensuite, comprends qu'un fichier .service décrit un état, pas une suite d'étapes

Contrairement à un script qui dit "fais ceci, puis cela", un fichier .service déclare ce qui doit être vrai : quel programme lancer (ExecStart), sous quel utilisateur (User=deploy), avec quelle politique de redémarrage. systemd se charge lui-même d'atteindre et de maintenir cet état.

Prérequis

Il faut déjà savoir utiliser systemctl status/start/stop/restart sur un service existant (voir la leçon sur les processus et services) avant d'écrire son propre fichier .service.

Il reste une étape qu'on oublie presque tous une fois

Modifier /etc/systemd/system/mon-app.service sur le disque ne suffit pas : systemd garde en mémoire une version déjà chargée de ce fichier. Tant que tu n'as pas lancé sudo systemctl daemon-reload, il continue de travailler avec l'ancienne définition, même après un systemctl restart.

Piège fréquent

Oublier daemon-reload après une modification est l'erreur la plus fréquente : tu modifies le fichier .service, tu relances le service avec systemctl restart, et rien ne change, car l'ancienne définition est toujours active en mémoire. Le réflexe : toujours sudo systemctl daemon-reload immédiatement après avoir édité un fichier .service.

Maintenant que le service tourne, voyons comment le planifier automatiquement

Un couple .service (avec Type=oneshot, pour une tâche qui se termine) et .timer (avec OnCalendar=*-*-* 03:00:00) reproduit ce que fait cron, mais avec des logs consultables via journalctl -u backup.service et une syntaxe plus lisible.

Une nuance à connaître entre Requires et Wants

DirectiveComportementRecommandation
Requires=Dépendance dure : si elle échoue, le service échoue aussiCas spécifiques uniquement
Wants=Dépendance souple : une dépendance manquante ne bloque pas le démarrageChoix par défaut recommandé
After=Ordre de démarrage uniquement, ne force rienÀ combiner avec les deux précédentes

Requires=postgresql.service crée une dépendance dure : si PostgreSQL échoue à démarrer, le service dépendant échoue aussi. Wants= est plus souple et recommandé dans la majorité des cas.

Maintenant que tu sais créer et planifier des services, la prochaine leçon montre comment consulter leurs logs efficacement avec journalctl.

Commandes & code

systemd avancé

bash
# Créer un service custom : /etc/systemd/system/mon-app.service
sudo tee /etc/systemd/system/mon-app.service <<'EOF'
[Unit]
Description=API backend Technologik
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/opt/mon-app
ExecStart=/opt/mon-app/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
Environment=ENV=production
EnvironmentFile=/opt/mon-app/.env
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload      # OBLIGATOIRE après toute modification d'un fichier .service
sudo systemctl enable --now mon-app     # active au boot + démarre immédiatement
sudo systemctl status mon-app
journalctl -u mon-app -f                  # logs en temps réel du service

# Types de service courants
# simple    -> le process principal EST le process lancé par ExecStart (le plus courant)
# forking   -> le programme se démonise lui-même (vieux daemons)
# oneshot   -> tâche ponctuelle qui se termine (typique pour un timer)
# notify    -> le programme signale lui-même à systemd qu'il est prêt (sd_notify)

# Créer un timer (remplace cron pour un service géré par systemd)
sudo tee /etc/systemd/system/backup.service <<'EOF'
[Unit]
Description=Backup nocturne de la base

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
EOF

sudo tee /etc/systemd/system/backup.timer <<'EOF'
[Unit]
Description=Déclenche backup.service tous les jours à 3h

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

[Install]
WantedBy=timers.target
EOF

sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer          # prochaine exécution planifiée

# Dépendances et ordonnancement
# After=/Before=       -> ordre de démarrage uniquement (ne force pas le démarrage)
# Requires=              -> dépendance dure (échoue si la dépendance échoue)
# Wants=                   -> dépendance souple (recommandé pour la plupart des cas)

# Limiter les ressources d'un service (voir aussi la leçon cgroups Docker pour le parallèle)
# Dans [Service] :
# MemoryMax=512M
# CPUQuota=50%

# Override sans toucher au fichier original (utile pour un paquet fourni par la distro)
sudo systemctl edit nginx
# ouvre un fragment /etc/systemd/system/nginx.service.d/override.conf

Résumé

  • daemon-reload est obligatoire après chaque modification d'un .service, sinon systemd garde l'ancienne version en mémoire.
  • Un couple .service (oneshot) + .timer remplace avantageusement cron pour un service systemd, avec logs intégrés.
  • Requires= est une dépendance dure, Wants= une dépendance souple — Wants= est le choix par défaut recommandé.

Exercices pratiques

1 disponible
1

Mission : packager une API FastAPI en service systemd fiable

Objectif : Écrire et faire évoluer un fichier .service supervisé par systemd, en évitant le piège classique du daemon-reload oublié.

Contexte

Ton équipe vient de packager son API FastAPI et veut la déployer via un fichier /etc/systemd/system/mon-app.service, plutôt qu'un fragile nohup uvicorn ... &. Le service doit démarrer au boot et redémarrer seul si uvicorn plante.

Résoudre l’exercice →