Aller au contenu
Services et journaux

Services et journaux

200 Pratiquer ⏱ 1 h 15 linuxsystemdubuntudebian

À la fin, vous saurez

  • Expliquer le rôle de systemd comme PID 1 et gestionnaire de services, et reconnaître les principaux types d'unités
  • Lire la sortie de systemctl status ligne par ligne
  • Démarrer, arrêter, recharger, activer et désactiver un service, et savoir quand daemon-reload est nécessaire
  • Écrire l'unité minimale d'une application et la surcharger par un fichier drop-in
  • Interroger le journal avec journalctl : par unité, priorité, période, démarrage, et en continu
  • Diagnostiquer un service qui ne démarre pas à partir de son état, de son code de sortie et de son journal

Prérequis

Testé avec logrotate 3.21.0 rsyslog 8.2312.0 systemd 255 ubuntu 24.04 , vérifié le 5 octobre 2026

Pourquoi

À la leçon précédente, vous avez appris à observer et arrêter des processus. Mais sur sig-app-1, qui a lancé Gunicorn ? Qui le relancera s'il plante à 3 heures du matin ? Qui le démarrera après le redémarrage mensuel de la machine ? Et où sont passés ses messages d'erreur ?

Sans gestionnaire de services, chacune de ces questions a une réponse bricolée : un nohup dans un terminal, une ligne dans /etc/rc.local, un script de surveillance maison qui relance le processus, des journaux dans un fichier qui grossit jusqu'à remplir le disque. Ces bricolages ont une chose en commun : personne d'autre que leur auteur ne sait qu'ils existent.

Sur Ubuntu, Debian, Red Hat et presque toutes les distributions actuelles, la réponse standard s'appelle systemd. Il démarre les services dans le bon ordre, les relance quand ils tombent, les arrête proprement, et recueille tout ce qu'ils écrivent dans un journal unique que l'on peut interroger. Cette leçon donne à Signalements son fichier de service, puis vous apprend à lire ce que systemd et son journal racontent quand quelque chose ne va pas. C'est la compétence qui vous fera gagner le plus de temps en astreinte.

Les concepts

systemd, le PID 1

Au démarrage, une fois le noyau chargé, celui-ci lance un seul programme, qui reçoit le PID 1 : c'est l'ancêtre de tous les autres processus (leçon 10). Historiquement, c'était init de System V, qui exécutait des scripts shell dans l'ordre alphabétique. En 2010, Lennart Poettering publie Rethinking PID 1, qui part d'un constat : démarrer les services en série est lent, et les scripts shell ne savent pas surveiller ce qu'ils lancent. systemd en est issu. Debian l'adopte par défaut en 2015 (Debian 8), Ubuntu la même année (15.04).

systemd remplit plusieurs rôles, qu'il vaut mieux distinguer :

  • gestionnaire de services : il démarre, arrête, surveille et relance les programmes, en respectant leurs dépendances ;
  • superviseur : chaque service est placé dans son propre groupe de contrôle (cgroup, voir cgroup), ce qui permet à systemd de connaître tous ses processus, même ceux qui se sont détachés, de les arrêter tous ensemble, et de limiter leurs ressources ;
  • collecteur de journaux, par son composant systemd-journald ;
  • et une série d'outils annexes (minuteurs, résolution DNS, gestion des connexions) que l'on rencontrera plus tard.

Les unités

systemd manipule des unités (units) : des objets décrits par un petit fichier texte au format « INI », dont l'extension donne le type. Les plus courantes :

TypeExtensionCe qu'elle décritExemple sur sig-app-1
service.serviceun programme à lancer et à surveillersignalements.service, ssh.service, postgresql.service
minuteur.timerune planification qui déclenche un serviceapt-daily.timer, logrotate.timer
socket.socketune socket ouverte par systemd, qui démarre le service à la première connexionssh.socket sur Ubuntu 24.04
cible.targetun regroupement, un point de rendez-vous du démarragemulti-user.target (système démarré, réseau, sans interface graphique)
montage.mountun système de fichiers montéhome.mount

Les fichiers d'unités se trouvent à trois endroits, du moins prioritaire au plus prioritaire :

  • /usr/lib/systemd/system/ (ou /lib/systemd/system/, le même répertoire sur Ubuntu 24.04) : les unités livrées par les paquets. On n'y touche jamais : la prochaine mise à jour du paquet écraserait vos modifications.
  • /run/systemd/system/ : unités temporaires, perdues au redémarrage.
  • /etc/systemd/system/ : les unités et surcharges de l'administrateur. C'est là que vous écrirez celle de Signalements.

Une unité peut être complétée sans être recopiée, par des fichiers drop-in : tout fichier .conf placé dans un répertoire <unité>.d/ est lu après l'unité et en modifie les réglages. La page systemd.unit(5) précise que les drop-ins de /etc/ l'emportent sur ceux de /run/, qui l'emportent sur ceux de /usr/lib/, et qu'au sein de ces répertoires ils s'appliquent dans l'ordre alphabétique de leur nom.

Activer n'est pas démarrer

Deux notions indépendantes, que l'on confond souvent :

  • Démarrer (start) lance le service maintenant. Au prochain redémarrage de la machine, il ne sera pas relancé pour autant.
  • Activer (enable) inscrit le service pour qu'il démarre au démarrage de la machine : systemd crée un lien symbolique dans le répertoire wants de la cible indiquée dans la section [Install] de l'unité (souvent multi-user.target). Activer ne démarre rien.

systemctl enable --now fait les deux. Un service peut donc être actif mais désactivé (il tourne, mais ne reviendra pas après un redémarrage, piège classique), ou activé mais inactif (il démarrera au prochain démarrage, mais il est arrêté).

Le journal

systemd-journald recueille tout ce que les services écrivent sur leur sortie standard et leur sortie d'erreur, les messages du noyau, et ceux envoyés par l'interface syslog classique. Il les range dans un journal binaire, indexé, où chaque entrée porte des métadonnées sûres : l'unité, le PID, l'utilisateur, la priorité, le démarrage de la machine. C'est ce qui permet de demander « les erreurs de Signalements depuis 8 heures ce matin » en une commande, au lieu de chercher dans des fichiers texte.

Le réglage Storage= de journald.conf décide où vit le journal. Dans systemd 255 (Ubuntu 24.04), la valeur par défaut est auto : persistant sur disque, sous /var/log/journal/, si ce répertoire existe, et sinon en mémoire seulement, sous /run/log/journal/, perdu à chaque redémarrage. Ubuntu et Debian créent le répertoire, donc le journal est persistant. Par défaut, il occupe au plus 10 % du système de fichiers, plafonné à 4 Gio (réglage SystemMaxUse=).

Et /var/log ?

Avant journald, les journaux étaient des fichiers texte sous /var/log, écrits par un démon syslog. Sur Ubuntu 24.04, ce monde coexiste encore : rsyslog est installé (le métapaquet ubuntu-minimal le recommande), reçoit une copie des messages du journal, et continue d'écrire /var/log/syslog et /var/log/auth.log. Debian a fait un autre choix : depuis Debian 12, ses notes de publication indiquent que rsyslog n'est plus installé par défaut, et seul le journal de systemd subsiste. Sur Debian 13, cherchez donc dans journalctl, pas dans /var/log/syslog.

Les fichiers texte grossissent sans fin ; logrotate les fait tourner : il renomme le fichier courant (syslog.1), compresse les anciens, et supprime les plus vieux, selon des règles posées dans /etc/logrotate.d/. Sur Ubuntu 24.04, il est déclenché chaque jour par logrotate.timer.

En pratique

Les sorties de cette section ont été produites sur une machine Ubuntu 24.04 avec LC_ALL=C.UTF-8 ; seul le nom de la machine a été remplacé par sig-app-1. Les commandes qui modifient le système (sudo) concernent le serveur de Signalements et sont décrites sans sortie.

Lire systemctl status

Commençons par un service présent sur toute machine Ubuntu, le planificateur cron :

$ systemctl status cron.service
● cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-09-28 16:56:41 CEST; 6 days ago
       Docs: man:cron(8)
   Main PID: 1408 (cron)
      Tasks: 1 (limit: 34264)
     Memory: 736.0K (peak: 3.3M)
        CPU: 3.657s
     CGroup: /system.slice/cron.service
             └─1408 /usr/sbin/cron -f -P

Oct 05 10:35:01 sig-app-1 CRON[498068]: pam_unix(cron:session): session opened for user root(uid=0) by root(uid=0)

Ligne par ligne :

  • Le point : vert et plein pour un service actif, blanc pour inactif, rouge pour un échec.
  • Loaded : systemd a trouvé et lu l'unité, dans le fichier indiqué (ici, celui du paquet). enabled : le service est activé au démarrage. preset: enabled : la valeur par défaut que la distribution prévoit pour ce service.
  • Active : l'état actuel, active (running), depuis quand. Les autres valeurs courantes : inactive (dead) (arrêté), failed (en échec, avec la raison), activating (auto-restart) (en attente de relance après un plantage).
  • Main PID : le processus principal, celui à qui systemd enverra les signaux.
  • Tasks, Memory, CPU : la consommation de tout le groupe de contrôle du service, enfants compris, et la limite de tâches.
  • CGroup : l'arbre des processus du service. Pour Signalements, on y verrait le maître Gunicorn et ses deux workers.
  • Les dernières lignes : les messages les plus récents du journal pour ce service. Ils suffisent souvent à comprendre une panne.

systemctl status sans argument résume la machine. Pour une réponse exploitable dans un script, il y a les commandes courtes, qui renvoient un code de sortie :

$ systemctl is-enabled cron
enabled
$ systemctl is-active cron
active
$ systemctl show cron -p Restart -p MainPID -p NRestarts -p ExecMainStatus
Restart=on-failure
MainPID=1408
NRestarts=0
ExecMainStatus=0

systemctl show affiche n'importe quelle propriété de l'unité telle que systemd l'a comprise, valeurs par défaut comprises. NRestarts compte les relances automatiques depuis le dernier démarrage manuel : une valeur qui grimpe trahit un service qui plante en boucle.

Lire une unité

systemctl cat affiche l'unité et ses drop-ins, avec le chemin de chaque fichier :

$ systemctl cat cron.service
# /usr/lib/systemd/system/cron.service
[Unit]
Description=Regular background program processing daemon
Documentation=man:cron(8)
After=remote-fs.target nss-user-lookup.target

[Service]
EnvironmentFile=-/etc/default/cron
ExecStart=/usr/sbin/cron -f -P $EXTRA_OPTS
IgnoreSIGPIPE=false
KillMode=process
Restart=on-failure
SyslogFacility=cron

[Install]
WantedBy=multi-user.target

Trois sections : [Unit] décrit l'unité et son ordre par rapport aux autres, [Service] dit comment lancer et surveiller le programme, [Install] dit à quelle cible le rattacher quand on l'active. Le tiret devant /etc/default/cron rend ce fichier facultatif. -f demande à cron de rester au premier plan : sous systemd, un service ne se « démonise » plus lui-même, c'est systemd qui le garde en arrière-plan.

Écrire l'unité de Signalements

Sur sig-app-1, créez /etc/systemd/system/signalements.service (avec sudoedit, leçon 8) :

[Unit]
Description=API Signalements (Gunicorn)
Documentation=https://apprendre.lyneko.com/fondations/linux-premiers-pas/11-services-et-journaux/
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=signalements
Group=signalements
WorkingDirectory=/opt/signalements
EnvironmentFile=/etc/signalements/env
ExecStart=/opt/signalements/venv/bin/gunicorn --bind 127.0.0.1:8000 --workers 2 app:app
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Chaque ligne a une raison :

  • After= ordonne : démarrer après le réseau et PostgreSQL. Wants=network-online.target demande en plus que cette cible soit atteinte. After= seul n'entraîne pas le démarrage de l'autre unité ; il dit seulement « si les deux démarrent, moi après ».
  • Type=simple : le processus lancé par ExecStart est le service, et il reste au premier plan. C'est le cas de Gunicorn par défaut.
  • User= et Group= : le compte système dédié de la leçon 8. Jamais root pour une application web.
  • WorkingDirectory= : le répertoire courant du processus, où Gunicorn trouve le module app.
  • EnvironmentFile= : les variables (DATABASE_URL, APP_VERSION) lues dans un fichier aux droits restreints, plutôt qu'écrites dans l'unité, que tout le monde peut lire avec systemctl cat.
  • ExecStart= : un chemin absolu. systemd ne passe pas par un shell : pas de ~, pas de |, pas de &&, et seules les variables de la forme $VAR ou ${VAR} sont remplacées.
  • ExecReload= : ce que fait systemctl reload ; ici, envoyer HUP au maître, que Gunicorn interprète comme « recharger » (leçon 10). $MAINPID est fourni par systemd.
  • Restart=on-failure et RestartSec=5 : relancer si le processus se termine en erreur ou est tué par un signal, cinq secondes après. Un arrêt demandé par systemctl stop n'est jamais suivi d'une relance.
  • WantedBy=multi-user.target : enable rattache le service au démarrage normal du serveur.

Avant de le charger, faites vérifier la syntaxe par systemd. Sur la machine de test, où l'application n'est pas installée, la vérification relève déjà le problème qui ferait échouer le démarrage :

$ systemd-analyze verify ./signalements.service
signalements.service: Command /opt/signalements/venv/bin/gunicorn is not executable: No such file or directory

Sur sig-app-1, la commande ne doit rien afficher. Puis :

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now signalements
$ systemctl status signalements
$ curl -s http://127.0.0.1:8000/sante

daemon-reload demande à systemd de relire tous les fichiers d'unités ; c'est obligatoire après chaque création ou modification d'un fichier d'unité à la main, sans quoi systemd continue d'utiliser l'ancienne version (et systemctl status vous le signale par un avertissement). La page systemd.unit(5) précise que l'état d'exécution des services est conservé pendant ce rechargement : il n'arrête rien.

Piloter le service

CommandeEffet
sudo systemctl start signalementsdémarre maintenant
sudo systemctl stop signalementsSIGTERM au processus principal, puis SIGKILL à tout le groupe s'il n'est pas arrêté au bout de TimeoutStopSec (90 s par défaut)
sudo systemctl restart signalementsarrête puis démarre : les requêtes en cours sont coupées au-delà du délai de grâce de Gunicorn
sudo systemctl reload signalementsexécute ExecReload= : recharge sans arrêter le service
sudo systemctl enable signalements / disableinscrit au démarrage / retire
sudo systemctl enable --now / disable --nowinscrit et démarre / retire et arrête
systemctl list-units --type=service --state=failedles services en échec sur la machine

Surcharger sans modifier : systemctl edit

Vous voulez donner à Gunicorn une minute pour s'arrêter, et relancer plus vite. Ne modifiez pas l'unité : créez une surcharge.

$ sudo systemctl edit signalements

La commande ouvre un éditeur sur un fichier vide (avec, en commentaire, le contenu actuel de l'unité). Écrivez seulement ce qui change :

[Service]
TimeoutStopSec=60
RestartSec=2

À l'enregistrement, systemd écrit /etc/systemd/system/signalements.service.d/override.conf et se recharge de lui-même (la page systemctl(1) précise que l'effet équivaut à daemon-reload). systemctl cat signalements affiche désormais l'unité puis le drop-in, chacun avec son chemin. Redémarrez le service pour que les nouveaux réglages s'appliquent au processus.

Cette méthode vaut surtout pour les unités livrées par un paquet (postgresql, nginx, ssh) : votre surcharge survit à leurs mises à jour, alors qu'une modification du fichier dans /usr/lib serait écrasée.

Note

Certains réglages sont des listes : plusieurs lignes ExecStart=, Environment= ou After= s'additionnent. Pour remplacer la commande de lancement dans un drop-in, il faut d'abord la vider par une ligne ExecStart= sans valeur, puis donner la nouvelle :

[Service]
ExecStart=
ExecStart=/opt/signalements/venv/bin/gunicorn --bind 127.0.0.1:8000 --workers 4 app:app

Sans la ligne vide, systemd refuse l'unité : un service Type=simple n'accepte qu'une commande de démarrage.

Interroger le journal

journalctl lit le journal. Sans option, il affiche tout, du plus ancien au plus récent, dans un afficheur (less). Les filtres utiles :

$ journalctl -u signalements                     # une unité
$ journalctl -u signalements -n 50               # les 50 dernières lignes
$ journalctl -u signalements -f                  # en continu, comme tail -f
$ journalctl -u signalements -p err              # priorité « err » et plus grave
$ journalctl -u signalements --since "08:00" --until "09:30"
$ journalctl -u signalements --since "2026-10-05 08:00" --until "1 hour ago"
$ journalctl -b                                  # depuis le démarrage en cours
$ journalctl -b -1 -p warning                    # avertissements du démarrage précédent
$ journalctl -k                                  # messages du noyau (comme dmesg)
$ journalctl -u signalements -o cat              # le message seul, sans date ni machine
$ journalctl -u signalements -o json-pretty -n 1 # une entrée avec toutes ses métadonnées

Les filtres se combinent. Les priorités suivent celles de syslog, de la plus grave à la moins grave : emerg (0), alert, crit, err (3), warning, notice, info, debug (7) ; -p err affiche les niveaux 0 à 3. -b -1 désigne le démarrage précédent, ce qui sert après un redémarrage inattendu : que s'est-il passé juste avant ?

Voici les trois dernières entrées de cron sur la machine de test :

$ journalctl -u cron -n 3 --no-pager
Oct 05 11:17:01 sig-app-1 CRON[609957]: pam_unix(cron:session): session opened for user root(uid=0) by root(uid=0)
Oct 05 11:17:01 sig-app-1 CRON[609958]: (root) CMD (cd / && run-parts --report /etc/cron.hourly)
Oct 05 11:17:01 sig-app-1 CRON[609957]: pam_unix(cron:session): session closed for user root

Chaque ligne : date, machine, identifiant et PID de l'émetteur, message. La sortie -o json-pretty montre ce qui se cache derrière : des champs comme _SYSTEMD_UNIT, _PID, _UID, PRIORITY, _BOOT_ID, que le journal ajoute lui-même et qu'un processus ne peut pas falsifier (ceux qui commencent par un souligné).

Tip

Gunicorn écrit ses journaux d'erreurs sur la sortie d'erreur par défaut, et rien pour les accès. Sous systemd, cette sortie part directement dans le journal : inutile de configurer des fichiers de journaux. Ajoutez --access-logfile - à ExecStart si vous voulez aussi les requêtes, en sachant qu'elles feront grossir le journal.

La place occupée

$ journalctl --disk-usage
$ sudo journalctl --vacuum-size=500M
$ sudo journalctl --vacuum-time=30d

La première commande affiche l'espace occupé par les fichiers du journal. Les deux suivantes suppriment les fichiers archivés les plus anciens jusqu'à repasser sous une taille ou une ancienneté. Pour une limite permanente, réglez SystemMaxUse= dans un drop-in de /etc/systemd/journald.conf.d/, puis redémarrez systemd-journald.

Les minuteurs

Les tâches planifiées du système sont de plus en plus des minuteurs systemd plutôt que des lignes cron :

$ systemctl list-timers apt-daily.timer apt-daily-upgrade.timer logrotate.timer
NEXT                         LEFT LAST                              PASSED UNIT                    ACTIVATES
Tue 2026-10-06 00:00:00 CEST  12h Mon 2026-10-05 09:07:23 CEST 2h 9min ago logrotate.timer         logrotate.service
Tue 2026-10-06 00:00:57 CEST  12h Mon 2026-10-05 09:07:23 CEST 2h 9min ago apt-daily.timer         apt-daily.service
Tue 2026-10-06 06:00:57 CEST  18h Mon 2026-10-05 09:07:23 CEST 2h 9min ago apt-daily-upgrade.timer apt-daily-upgrade.service

3 timers listed.
Pass --all to see loaded but inactive timers, too.

Chaque minuteur déclenche un service du même nom ; on lit leur résultat dans le journal de ce service (journalctl -u apt-daily-upgrade). La leçon 12 revient sur ces deux minuteurs d'APT.

Diagnostiquer un service qui ne démarre pas

Après une livraison, sudo systemctl start signalements répond Job for signalements.service failed because the control process exited with error code. La méthode, toujours la même :

  1. systemctl status signalements : l'état (failed), la ligne Process: ou Main PID: avec le code de sortie et la raison (status=203/EXEC), et les dernières lignes du journal.
  2. journalctl -u signalements -n 50 --no-pager : le message complet de l'application ou de systemd.
  3. Interpréter le code. Les codes 0 à 7 suivent la convention LSB ; à partir de 200, ce sont des codes de systemd lui-même, que la page systemd.exec(5) liste. Ils disent que l'échec s'est produit avant que l'application ne démarre :
CodeNomCause habituelle
200EXIT_CHDIRWorkingDirectory= n'existe pas ou n'est pas accessible
203EXIT_EXECl'appel execve() a échoué : chemin de ExecStart faux, fichier non exécutable, environnement virtuel déplacé
216EXIT_GROUPle groupe de Group= n'existe pas
217EXIT_USERl'utilisateur de User= n'existe pas
226EXIT_NAMESPACEune option d'isolation (répertoire protégé, par exemple) n'a pas pu être mise en place

Un autre code (1, 2, 3...) vient de l'application elle-même : c'est son message dans le journal qui compte. status=9/KILL ou signal=KILL : tué par SIGKILL (mémoire épuisée, délai d'arrêt dépassé). 4. Rejouer à la main, avec la même identité, pour voir l'erreur en direct :

$ sudo -u signalements bash -c 'cd /opt/signalements && set -a && . /etc/signalements/env && exec /opt/signalements/venv/bin/gunicorn --bind 127.0.0.1:8000 app:app'

Si la commande fonctionne ainsi mais pas sous systemd, la différence vient de l'environnement (variables, répertoire courant, options d'isolation de l'unité).

La boucle de redémarrage. Avec Restart=on-failure, un service qui plante dès son lancement est relancé, replante, est relancé... systemd limite ce cycle : au-delà de StartLimitBurst démarrages (5 par défaut) en StartLimitIntervalSec (10 secondes par défaut), il abandonne, et status affiche Start request repeated too quickly. Le service reste failed. Après avoir corrigé la cause, sudo systemctl reset-failed signalements remet le compteur à zéro, puis start. Notez qu'avec RestartSec=5, Signalements ne peut pas atteindre cinq démarrages en dix secondes : il redémarrera indéfiniment toutes les cinq secondes. NRestarts dans systemctl show, et une alerte sur cette valeur, sont alors le seul signe visible.

Sous le capot

Comment systemd sait que le service est mort. systemd est le parent (ou l'ancêtre) des processus de service. Quand le processus principal se termine, le noyau envoie SIGCHLD à systemd, qui fait wait() et récupère le code de sortie : c'est le mécanisme de la leçon 10, appliqué au PID 1. Pour les enfants qui se détachent (un ancien démon qui fait un double fork()), le groupe de contrôle prend le relais : tous les processus créés par le service restent dans /system.slice/signalements.service, quoi qu'ils fassent. C'est ce qui permet à systemctl stop de tout arrêter, sans PID à deviner ni fichier .pid périmé.

L'activation. systemctl enable ne fait que manipuler des liens symboliques : pour WantedBy=multi-user.target, il crée /etc/systemd/system/multi-user.target.wants/signalements.service, qui pointe vers l'unité. Au démarrage, systemd calcule l'ensemble des unités voulues par la cible par défaut, les ordonne d'après leurs After=/Before=, et démarre en parallèle tout ce qui peut l'être.

Le journal. Les services n'écrivent pas eux-mêmes dans le journal : systemd branche leur sortie standard et leur sortie d'erreur sur une socket de systemd-journald, qui sait de quel service vient chaque ligne parce qu'il connaît le processus à l'autre bout. Les fichiers sous /var/log/journal/<identifiant de machine>/ sont binaires, organisés par démarrage et par utilisateur, et indexés par champ, d'où la rapidité des filtres. Quand rsyslog est installé, journald lui fait suivre une copie de chaque message, que rsyslog répartit dans /var/log/syslog, /var/log/auth.log, etc.

Pièges courants

Oublier daemon-reload. Vous modifiez l'unité, vous redémarrez le service, rien ne change. systemd utilise encore l'ancienne version ; systemctl status affiche un avertissement qui demande de lancer systemctl daemon-reload. Toujours daemon-reload après une modification à la main (systemctl edit le fait pour vous).

Démarrer sans activer. Le service tourne, la machine redémarre pour une mise à jour du noyau, le service ne revient pas. systemctl is-enabled dans vos vérifications, ou enable --now systématique.

Modifier un fichier sous /usr/lib/systemd/system/. Écrasé à la prochaine mise à jour du paquet, sans prévenir. Toujours systemctl edit.

Un ExecStart écrit comme une ligne de shell. ExecStart=cd /opt/signalements && gunicorn app:app échoue : systemd ne lance pas de shell, il essaie d'exécuter un programme appelé cd. Utilisez WorkingDirectory=, ou, en dernier recours, ExecStart=/bin/sh -c '...'.

Des guillemets dans EnvironmentFile. Le format ressemble à du shell sans en être : pas d'export, pas de substitution de commande. Une valeur avec des espaces se met entre guillemets, mais une variable n'en référence pas une autre comme dans un script.

Chercher /var/log/syslog sur Debian. Depuis Debian 12, il n'existe plus sans rsyslog. Le journal, lui, est là : journalctl.

Un journal vide après redémarrage. Le journal est en mémoire seule (Storage=volatile, ou auto sans répertoire /var/log/journal). Créez le répertoire ou réglez Storage=persistent, sinon chaque incident qui se termine par un redémarrage efface ses propres traces.

Confondre restart et reload. restart coupe les requêtes en cours au-delà du délai de grâce ; reload ne fait que ce qu'ExecReload= prévoit, et échoue si l'unité n'en déclare pas.

Sécurité

  • Les journaux contiennent des secrets plus souvent qu'on ne le croit. Une application qui écrit sa configuration au démarrage, une trace d'erreur qui inclut la DATABASE_URL avec son mot de passe, un en-tête Authorization journalisé : tout cela est conservé des semaines, et copié vers la centralisation des journaux. Relisez ce que votre application écrit, en particulier en mode débogage.
  • Qui peut lire le journal. Un utilisateur ordinaire ne lit que ses propres entrées. D'après journalctl(1), les membres des groupes systemd-journal, adm et wheel lisent tout le journal de la machine. Sur Ubuntu, le premier compte créé à l'installation est membre d'adm : vérifiez qui l'est (getent group adm), comme vous vérifiez qui est dans sudo.
  • systemctl cat est lisible par tous. N'écrivez jamais de secret dans Environment= d'une unité : utilisez EnvironmentFile= vers un fichier en 0640 ou 0600, ou mieux, les credentials de systemd (LoadCredential=), traités dans le cours systemd en profondeur.
  • L'isolation des services. systemd peut restreindre fortement un service : NoNewPrivileges=yes (le service ne peut plus gagner de privilèges, voir no-new-privileges), ProtectSystem=strict (système de fichiers en lecture seule), PrivateTmp=yes, ProtectHome=yes. systemd-analyze security <unité> note l'exposition d'un service de 0 à 10. Sur la machine de test, cron.service, qui ne déclare aucune de ces options, obtient la note de 9.6, « UNSAFE ». Le cours systemd en profondeur durcit l'unité de Signalements option par option.
  • Les journaux sont une preuve. En cas d'intrusion, ils disent ce qui s'est passé, à condition d'avoir été conservés et de ne pas avoir été effacés par l'attaquant, ce qui plaide pour leur envoi vers une machine centrale (cours Journaux centralisés avec Loki).

En production

  • Chaque application a son unité, versionnée avec son code ou sa configuration, et déployée par l'outil d'automatisation (cours Ansible : les fondamentaux). Personne ne lance d'application à la main.
  • Les journaux quittent la machine. Un journal local disparaît avec la machine, et ne se consulte pas sur vingt serveurs à la fois. On l'expédie vers un système central, et on garde localement une rétention courte, bornée par SystemMaxUse=.
  • Surveillez les échecs et les relances : systemctl list-units --state=failed et NRestarts sont des métriques à collecter. Un service qui redémarre toutes les cinq secondes peut avoir l'air disponible pour une sonde lente, et ne servir aucune requête.
  • Les mêmes notions se retrouvent ailleurs. Kubernetes reprend les idées de systemd à l'échelle d'un cluster : politique de redémarrage, signal d'arrêt et délai de grâce, journaux collectés depuis la sortie standard. Chez Lyneko, les applications tournent en conteneurs sur Kapsule, mais les nœuds du cluster, eux, sont des machines Linux où kubelet et containerd sont des services systemd.

Exercices

1. Lire un état (niveau 100). systemctl status signalements affiche : Loaded: loaded (/etc/systemd/system/signalements.service; disabled; preset: enabled) et Active: active (running) since .... Que se passera-t-il au prochain redémarrage de la machine, et que faites-vous ?

Solution

Le service tourne maintenant, mais il est désactivé (disabled) : il ne démarrera pas au prochain démarrage de la machine. Quelqu'un l'a lancé avec start sans enable. sudo systemctl enable signalements crée le lien dans multi-user.target.wants/ sans toucher au processus en cours ; vérifiez ensuite avec systemctl is-enabled signalements.

2. Le code 203 (niveau 200). Après une mise à jour qui a recréé l'environnement virtuel dans /opt/signalements/.venv au lieu de /opt/signalements/venv, le service est en échec avec status=203/EXEC. Expliquez, et corrigez sans modifier le fichier d'unité.

Solution

203 (EXIT_EXEC) signifie que systemd n'a pas pu exécuter le programme de ExecStart= : le chemin /opt/signalements/venv/bin/gunicorn n'existe plus. Correction par un drop-in : sudo systemctl edit signalements, puis :

[Service]
ExecStart=
ExecStart=/opt/signalements/.venv/bin/gunicorn --bind 127.0.0.1:8000 --workers 2 app:app

La ligne vide est indispensable pour remplacer la commande au lieu d'en ajouter une seconde. Puis sudo systemctl restart signalements. À plus long terme, il vaut mieux corriger le chemin dans l'unité versionnée (elle est sous /etc, elle appartient à l'équipe) et fixer le chemin de l'environnement dans le processus de livraison.

3. Le journal d'une nuit (niveau 200). La mairie signale des erreurs entre 2 h et 2 h 30 cette nuit. Écrivez la commande qui affiche les messages de priorité warning et plus graves de Signalements sur cette période, puis celle qui vérifie si la machine a redémarré pendant la nuit.

Solution

journalctl -u signalements -p warning --since "02:00" --until "02:30" (les heures seules désignent aujourd'hui ; ajoutez la date si l'on est déjà le lendemain, par exemple --since "2026-10-05 02:00"). Pour les redémarrages : journalctl --list-boots affiche la liste des démarrages avec leurs dates de début et de fin ; s'il y en a un pendant la nuit, journalctl -b -1 -n 100 montre les dernières lignes avant l'arrêt, et journalctl -b -1 -k -p err les erreurs du noyau.

4. Une relance qui s'emballe (niveau 200). systemctl show signalements -p NRestarts renvoie NRestarts=4312, et le service semble actif. Que se passe-t-il, pourquoi systemd n'a-t-il pas abandonné, et comment le voir plus tôt la prochaine fois ?

Solution

Le service plante et redémarre en boucle depuis longtemps : 4 312 relances automatiques. systemd n'a pas abandonné parce que la limite (5 démarrages en 10 secondes par défaut) n'est jamais atteinte avec RestartSec=5 : il ne démarre qu'une fois toutes les cinq secondes. La cause est dans le journal : journalctl -u signalements -p err -n 50. Pour le voir plus tôt : collecter NRestarts (ou alerter sur les messages Scheduled restart job), et, si l'on préfère un échec franc, régler StartLimitIntervalSec= et StartLimitBurst= dans [Unit] pour qu'une rafale de plantages fasse passer le service en failed, ce qui déclenche les alertes sur les services en échec.

Récapitulatif

  • systemd est le PID 1 : il démarre, surveille, relance et arrête les services, et place chacun dans son cgroup.
  • Une unité est un fichier texte ; on n'édite jamais celles de /usr/lib/systemd/system/, on écrit les siennes dans /etc/systemd/system/ et on surcharge les autres avec systemctl edit (drop-in).
  • Démarrer (start) n'est pas activer (enable) ; enable --now fait les deux. Après une modification manuelle : daemon-reload.
  • systemctl status donne l'état, le processus principal, la consommation et les dernières lignes du journal ; systemctl show donne toutes les propriétés, dont NRestarts.
  • journalctl -u <unité> avec -f, -p, --since/--until, -b répond à presque toutes les questions ; le journal est persistant sous /var/log/journal/ sur Ubuntu et Debian.
  • Ubuntu 24.04 garde rsyslog et /var/log/syslog ; Debian, depuis la version 12, non.
  • Un service qui ne démarre pas : status, journal, code de sortie (200 et plus : systemd ; en dessous : l'application), puis rejouer à la main avec la même identité.

Pour aller plus loin

  • Les pages systemd.service(5), systemd.exec(5) et journalctl(1), sur freedesktop.org : elles décrivent chaque option avec précision.
  • Rethinking PID 1, de Lennart Poettering, pour comprendre les choix de conception de systemd.
  • Le cours systemd en profondeur, pour les minuteurs, l'activation par socket, les credentials et le durcissement des unités.
  • La leçon suivante, Les paquets et les mises à jour.
Voir ma constellation →

Sources