Services et journaux
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 :
| Type | Extension | Ce qu'elle décrit | Exemple sur sig-app-1 |
|---|---|---|---|
| service | .service | un programme à lancer et à surveiller | signalements.service, ssh.service, postgresql.service |
| minuteur | .timer | une planification qui déclenche un service | apt-daily.timer, logrotate.timer |
| socket | .socket | une socket ouverte par systemd, qui démarre le service à la première connexion | ssh.socket sur Ubuntu 24.04 |
| cible | .target | un regroupement, un point de rendez-vous du démarrage | multi-user.target (système démarré, réseau, sans interface graphique) |
| montage | .mount | un 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épertoirewantsde la cible indiquée dans la section[Install]de l'unité (souventmulti-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.targetChaque ligne a une raison :
After=ordonne : démarrer après le réseau et PostgreSQL.Wants=network-online.targetdemande 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é parExecStartest le service, et il reste au premier plan. C'est le cas de Gunicorn par défaut.User=etGroup=: le compte système dédié de la leçon 8. Jamaisrootpour une application web.WorkingDirectory=: le répertoire courant du processus, où Gunicorn trouve le moduleapp.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 avecsystemctl 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$VARou${VAR}sont remplacées.ExecReload=: ce que faitsystemctl reload; ici, envoyerHUPau maître, que Gunicorn interprète comme « recharger » (leçon 10).$MAINPIDest fourni par systemd.Restart=on-failureetRestartSec=5: relancer si le processus se termine en erreur ou est tué par un signal, cinq secondes après. Un arrêt demandé parsystemctl stopn'est jamais suivi d'une relance.WantedBy=multi-user.target:enablerattache 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
| Commande | Effet |
|---|---|
sudo systemctl start signalements | démarre maintenant |
sudo systemctl stop signalements | SIGTERM 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 signalements | arrê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 signalements | exécute ExecReload= : recharge sans arrêter le service |
sudo systemctl enable signalements / disable | inscrit au démarrage / retire |
sudo systemctl enable --now / disable --now | inscrit et démarre / retire et arrête |
systemctl list-units --type=service --state=failed | les 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:appSans 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 :
systemctl status signalements: l'état (failed), la ligneProcess:ouMain PID:avec le code de sortie et la raison (status=203/EXEC), et les dernières lignes du journal.journalctl -u signalements -n 50 --no-pager: le message complet de l'application ou de systemd.- 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 :
| Code | Nom | Cause habituelle |
|---|---|---|
| 200 | EXIT_CHDIR | WorkingDirectory= n'existe pas ou n'est pas accessible |
| 203 | EXIT_EXEC | l'appel execve() a échoué : chemin de ExecStart faux, fichier non exécutable, environnement virtuel déplacé |
| 216 | EXIT_GROUP | le groupe de Group= n'existe pas |
| 217 | EXIT_USER | l'utilisateur de User= n'existe pas |
| 226 | EXIT_NAMESPACE | une 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_URLavec son mot de passe, un en-têteAuthorizationjournalisé : 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 groupessystemd-journal,admetwheellisent 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 danssudo. systemctl catest lisible par tous. N'écrivez jamais de secret dansEnvironment=d'une unité : utilisezEnvironmentFile=vers un fichier en0640ou0600, 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=failedetNRestartssont 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ù
kubeletetcontainerdsont 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:appLa 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 avecsystemctl edit(drop-in). - Démarrer (
start) n'est pas activer (enable) ;enable --nowfait les deux. Après une modification manuelle :daemon-reload. systemctl statusdonne l'état, le processus principal, la consommation et les dernières lignes du journal ;systemctl showdonne toutes les propriétés, dontNRestarts.journalctl -u <unité>avec-f,-p,--since/--until,-brépond à presque toutes les questions ; le journal est persistant sous/var/log/journal/sur Ubuntu et Debian.- Ubuntu 24.04 garde
rsysloget/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)etjournalctl(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.
Sources
- systemd, page de manuel systemd.unit(5)
- systemd, page de manuel systemd.service(5)
- systemd, page de manuel systemd.exec(5), section Process Exit Codes
- systemd, page de manuel systemctl(1)
- systemd, pages de manuel journalctl(1) et journald.conf(5)
- Lennart Poettering, Rethinking PID 1 (2010)
- Debian 12, notes de publication : rsyslog n'est plus installé par défaut
- Gunicorn, documentation : Signal Handling