Aller au contenu

Systemd

Quand le bootloader (Grub, systemd-boot, efistub…) a chargé le noyau en mémoire, le noyau doit lancer le premier processus du système - le PID 1. Ce processus, c’est le gestionnaire d’initialisation (ou init), et il a un rôle central : démarrer et superviser tout le système depuis le boot jusqu’à l’extinction.

La grande majorité des distributions Linux utilisent systemd comme init. Il en existe d’autres - SysVinit, OpenRC, Runit - mais systemd est devenu le standard de facto, pour le meilleur et pour le débat.

Tout dans systemd est une unit. Une unit est un fichier de configuration qui décrit une ressource que systemd doit gérer. Chaque type d’unit a son extension :

ExtensionRôle
.serviceUn service (daemon, programme)
.socketUn socket réseau ou UNIX
.mountUn point de montage
.timerUn planificateur (alternative à cron)
.targetUn groupe d’units (remplace les runlevels)
.pathSurveillance d’un fichier/dossier

Au quotidien, on touche surtout des .service et parfois des .timer.

/lib/systemd/system/ # units du système (installés avec les paquets)
/etc/systemd/system/ # tes units custom ou tes overrides (prioritaire)
~/.config/systemd/user/ # units utilisateur (systemd --user)

On ne touche jamais aux fichiers dans /lib/systemd/system/. Pour personnaliser un service système, il suffit de créer un fichier dans /etc/systemd/system/ - il sera prioritaire.

Les targets regroupent des units ensemble. Elles remplacent le concept de runlevels de SysVinit.

TargetÉquivalent SysVDescription
poweroff.targetrunlevel 0Extinction
rescue.targetrunlevel 1Mode maintenance (root seul)
multi-user.targetrunlevel 3Multi-utilisateur sans GUI
graphical.targetrunlevel 5Multi-utilisateur avec GUI
reboot.targetrunlevel 6Reboot

Pour voir et changer la target par défaut (le “runlevel” au boot) :

Fenêtre de terminal
systemctl get-default
systemctl set-default multi-user.target

Un fichier .service est un fichier INI avec trois sections principales :

[Unit]
Description=Mon app
Documentation=https://example.com
After=network.target # démarre après network.target
Requires=postgresql.service # a besoin de postgres (bloquant)
Wants=redis.service # veut redis, mais pas bloquant si absent
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/app
ExecStart=/opt/app/bin/start --config /etc/app/config.toml
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
graph TD
app["Mon app"]
app -->|Requires| pg["postgresql.service"]
app -->|Wants| redis["redis.service"]
app -->|After| net["network.target"]
target["multi-user.target"] -->|WantedBy| app

Il définit comment systemd sait que le service est prêt :

  • simple (défaut) - le processus lancé par ExecStart est le service
  • forking - le processus se fork en arrière-plan (vieux daemons style Apache)
  • oneshot - s’exécute une fois puis se termine (scripts d’init)
  • notify - le service envoie une notification à systemd quand il est prêt
Restart=on-failure # redémarrage si exit code != 0 ou signal
Restart=always # redémarrage dans tous les cas
Restart=no # jamais (défaut)
Fenêtre de terminal
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx # recharge la config sans coupure (si le service le supporte)
systemctl status nginx

Pour activer/désactiver au boot :

Fenêtre de terminal
systemctl enable nginx # activer au démarrage
systemctl disable nginx # désactiver
systemctl enable --now nginx # activer ET démarrer maintenant
systemctl disable --now nginx # désactiver ET arrêter maintenant

Pour interroger l’état :

Fenêtre de terminal
systemctl is-active nginx # active / inactive
systemctl is-enabled nginx # enabled / disabled
systemctl is-failed nginx # failed / active

Pour lister les units :

Fenêtre de terminal
systemctl list-units # units actives
systemctl list-units --type=service # services uniquement
systemctl list-units --state=failed # ce qui a crashé
systemctl list-unit-files # toutes les units + leur état

Après avoir modifié un fichier unit, toujours faire :

Fenêtre de terminal
systemctl daemon-reload

systemd met en cache les fichiers unit. Sans daemon-reload, il ignore les modifications.

systemd centralise tous les logs dans le journal (journald), accessibles via journalctl.

Fenêtre de terminal
journalctl -u nginx # logs du service nginx
journalctl -u nginx -f # suivi en temps réel
journalctl -u nginx --since "1h ago" # dernière heure
journalctl -u nginx -b # depuis le dernier boot
journalctl -u nginx -b -1 # boot d'avant
journalctl -p err # erreurs uniquement

Faire tourner une app Node.js en permanence, par exemple. On crée le fichier unit :

Fenêtre de terminal
sudo nano /etc/systemd/system/monapp.service
[Unit]
Description=Mon app Node.js
After=network.target
[Service]
Type=simple
User=tim
WorkingDirectory=/home/tim/monapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=3s
Environment=NODE_ENV=production PORT=3000
[Install]
WantedBy=multi-user.target

Puis on recharge, active et démarre en une commande :

Fenêtre de terminal
sudo systemctl daemon-reload
sudo systemctl enable --now monapp

Pour vérifier que tout tourne :

Fenêtre de terminal
systemctl status monapp
journalctl -u monapp -f

L’app redémarre si elle crash et se relance au boot. Plus besoin de screen ou de nohup.

Les .timer permettent de planifier des tâches. Les logs passent dans journald et le contrôle sur les dépendances est bien plus fin qu’avec cron.

Un script de backup toutes les heures :

/etc/systemd/system/backup.timer
[Unit]
Description=Backup toutes les heures
[Timer]
OnCalendar=hourly
Persistent=true # rattrape les exécutions manquées si la machine était éteinte
[Install]
WantedBy=timers.target
/etc/systemd/system/backup.service
[Unit]
Description=Script de backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Fenêtre de terminal
sudo systemctl enable --now backup.timer
systemctl list-timers # voir tous les timers actifs et leur prochaine exécution

systemd est bavard au premier abord, mais la logique est cohérente une fois qu’on a saisi le concept d’unit. Dans mon setup Arch + i3, je l’utilise surtout pour gérer mes services perso (syncthing, pipewire, des scripts de démarrage) et les timers pour quelques tâches récurrentes. journalctl -u <service> -f est probablement la commande que j’utilise le plus quand quelque chose ne démarre pas comme prévu.