Ubuntu 24.04 : « Échec de démarrage … » — le workflow de triage systemd le plus rapide (cas n°62)

Cet article vous a aidé ?

Il est 02:13. Le pager indique « service down », le tableau de bord est en berne et votre console vous salue par : Échec de démarrage …. Ce message est la version systemd de « quelque chose s’est passé ». Aussi utile qu’un biscuit de la fortune.

Ceci est le workflow que j’utilise sur Ubuntu 24.04 quand j’ai besoin d’une réponse rapide : ce qui a échoué, pourquoi ça a échoué, de quoi ça dépend et s’il faut corriger, revenir en arrière ou isoler. Pas de mystique. Juste le chemin le plus court du symptôme à la décision.

Le modèle mental : ce que signifie vraiment « Échec de démarrage »

Systemd est un orchestrateur, pas un voyant. Quand il affiche « Échec de démarrage », il rapporte une transition d’état : une unité est passée d’« activating » à « failed », ou elle n’a jamais atteint « active », ou elle a été soumise à un throttle de redémarrage, ou l’une de ses dépendances a échoué en premier et systemd n’est que le messager.

Sur Ubuntu 24.04, vous verrez se répéter le même petit nombre de raisons sous-jacentes :

  • Mauvaise définition d’unité : faute de frappe, chemin incorrect, guillemets manquants, directive invalide, Type= erroné.
  • Échec à l’exécution : exécutable qui se termine avec un code non nul, crash, timeout ou impossibilité de binder un port.
  • Échec de dépendance : point de montage absent, réseau pas en ligne, secrets non lisibles, base de données indisponible.
  • Mésentente d’environnement : fichier de configuration déplacé, utilisateur changé, permissions modifiées, conflit de profil SELinux/AppArmor.
  • Pression au niveau système : disque plein, pression mémoire (OOM), limites de descripteurs de fichiers, quotas CPU, contraintes de cgroup.
  • Throttling de démarrage : « Start request repeated too quickly », alias boucle de redémarrage et systemd s’est lassé.

La clé est de considérer systemd comme un moteur de graphe. Les unités ont un ordonnancement (After=, Before=) et des exigences (Requires=, Wants=). L’ordonnancement indique « quand », les exigences indiquent « doit exister ». Les confondre est un classique générateur d’incidents.

Faits intéressants et contexte historique (parce que le passé revient souvent dans vos incidents)

  1. Systemd est arrivé dans le Linux grand public autour de 2010–2012 et a remplacé une ménagerie de scripts d’init par un gestionnaire unique conscient des dépendances.
  2. Ubuntu est passé d’Upstart à systemd en 15.04 ; une décennie plus tard, de nombreuses « hypothèses de scripts d’init » se cachent encore dans des services personnalisés.
  3. journald est binaire par défaut (métadonnées structurées, filtrage rapide), ce qui est excellent sauf si vous oubliez de persister les logs et qu’un reboot efface la scène du crime.
  4. Les unités ne sont pas que des services : mounts, sockets, timers, paths, scopes, targets — la plupart des événements de « service en échec » commencent par un mount ou un socket en échec.
  5. Le throttling de démarrage existe pour protéger l’hôte ; sans lui, un service en crash-loop peut DOS le propre hôte avec des tempêtes fork/exec.
  6. Le « network-online » de systemd est volontairement glissant : cela veut dire « un gestionnaire réseau dit que c’est en ligne », pas « votre endpoint SaaS est joignable ».
  7. Les valeurs par défaut des timeouts ont évolué ; les anciens fichiers d’unité copiés-collés définissent parfois des timeouts trop courts pour les chemins de démarrage modernes ou trop longs pour des attentes production.
  8. cgroups v2 est désormais le monde par défaut ; les contrôles de ressources et le comportement OOM peuvent différer des hôtes plus anciens, surprenant des services qui « fonctionnaient toujours ».

Et une citation opérationnelle à garder collée sur votre terminal :

« L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan

Le triage systemd est l’antidote à l’espoir.

Playbook de diagnostic rapide (premières/deuxièmes/troisièmes vérifications)

Si vous ne retenez qu’une chose : ne commencez pas par éditer des fichiers. Commencez par collecter des preuves, puis choisissez l’intervention la plus petite qui restaure le service en sécurité.

Première étape : identifier exactement ce qui a échoué et comment

  • Vérifier l’état de l’unité et le dernier résultat (systemctl status).
  • Extraire les logs pertinents depuis le boot courant (journalctl -u ... -b).
  • Confirmer s’il s’agit d’un échec direct ou d’une cascade de dépendances (systemctl list-dependencies et systemctl show).

Deuxième étape : classifier l’échec en 60 secondes

  • Exec/exit : rechercher code de sortie, signal, core dump, fichier manquant.
  • Timeout : « start operation timed out », souvent en attente de mounts, network-online, ou disque lent.
  • Permission : « permission denied », « cannot open », ou refus AppArmor.
  • Ressource : OOM kill, ENOSPC, trop de fichiers ouverts.
  • Restart throttle : « start-limit-hit » ou « request repeated too quickly ».

Troisième étape : choisir l’action corrective la moins risquée

  • Rollback connu et sain : revenir sur le dernier changement de configuration/package si la timeline correspond.
  • Isolation temporaire : arrêter les unités dépendantes, masquer les unités qui fluctuent, ou désactiver le timer pour stabiliser l’hôte.
  • Correction chirurgicale : ajuster le fichier d’unité, permissions, ordre de montage, ou fichier d’environnement — puis redémarrer proprement.

Une petite blague, parce que vous en aurez besoin : les logs systemd sont comme des romans policiers — tout le monde est suspect, et le coupable est souvent « un fichier manquant ».

Douze+ tâches pratiques avec commandes, signification des sorties et décisions

Voici les actions que j’effectue sur Ubuntu 24.04 lorsqu’une unité échoue. Chaque tâche inclut la commande, un extrait réaliste de sortie, ce que cela signifie et la décision suivante.

Tâche 1 : Confirmer l’état actuel de l’unité, le dernier code de sortie et l’indice immédiat

cr0x@server:~$ systemctl status nginx.service
× nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:11:02 UTC; 1min 4s ago
   Duration: 83ms
       Docs: man:nginx(8)
    Process: 2191 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)
        CPU: 72ms

Dec 30 02:11:02 server nginx[2191]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (2: No such file or directory)
Dec 30 02:11:02 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 02:11:02 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 02:11:02 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Signification : L’échec se situe dans le test de configuration ExecStartPre ; nginx a refusé de démarrer à cause d’un fichier inclus manquant.

Décision : Ne pas relancer à l’aveugle. Corriger l’include manquant ou revenir à la configuration précédente. Vérifier avec nginx -t une fois corrigé.

Tâche 2 : Extraire la tranche complète du journal pour l’unité, depuis le boot courant

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 80
Dec 30 02:11:02 server nginx[2191]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (2: No such file or directory)
Dec 30 02:11:02 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 02:11:02 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 02:11:02 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Signification : Journald confirme la même cause racine ; inutile de chasser les dépendances pour l’instant.

Décision : Corriger le chemin du fichier de config ou restaurer le snippet ; puis retester et démarrer.

Tâche 3 : Si c’est une cascade de dépendances, demander à systemd ce qu’il pense avoir déclenché l’arrêt

cr0x@server:~$ systemctl show -p Id -p ActiveState -p SubState -p Result -p ExecMainStatus -p ExecMainCode -p NRestarts nginx.service
Id=nginx.service
ActiveState=failed
SubState=failed
Result=exit-code
ExecMainCode=1
ExecMainStatus=1
NRestarts=0

Signification : Il s’agit d’un échec propre « process exited 1 », pas d’un kill par signal, pas d’une boucle de redémarrage.

Décision : Corriger le problème d’exécutable/config sous-jacent ; ne touchez pas aux limites de démarrage ni à la politique de redémarrage.

Tâche 4 : Afficher les dépendances de l’unité et chercher des exigences « mortes »

cr0x@server:~$ systemctl list-dependencies --reverse nginx.service
nginx.service
● nginx.service
○ systemd-user-sessions.service
○ multi-user.target

Signification : Rien de spécial ne dépend de nginx sauf le target ; les dépendances inverses ne bloqueront pas un redémarrage.

Décision : Sûr de redémarrer après correction de la configuration ; faible rayon d’impact.

Tâche 5 : Vérifier la validité du fichier d’unité et des overrides (éviter « ce n’est pas ce que vous pensez »)

cr0x@server:~$ systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

Signification : Aucun drop-in affiché ; cette unité utilise la définition packagée. Si vous attendiez des overrides personnalisés, vous déboguez le mauvais fichier.

Décision : Si une personnalisation est requise, créez un drop-in avec systemctl edit plutôt que de modifier les fichiers sous /lib.

Tâche 6 : Valider un override drop-in et vérifier les subtilités de mauvaise configuration

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/myapp.service.d
             └─override.conf
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:08:19 UTC; 2min 53s ago
Dec 30 02:08:19 server systemd[1]: myapp.service: Failed to run 'start' task: No such file or directory
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
User=myapp
Group=myapp
EnvironmentFile=/etc/myapp/myapp.env

# /etc/systemd/system/myapp.service.d/override.conf
[Service]
ExecStart=/opt/myapp/bin/myappd --config /etc/myapp/config.yaml

Signification : Les drop-in ont remplacé ExecStart. Si /opt/myapp/bin/myappd n’existe pas, systemd ne peut pas l’exécuter.

Décision : Corriger le chemin dans l’override ou supprimer le drop-in ; ensuite systemctl daemon-reload et redémarrer.

Tâche 7 : Détecter le « start-limit-hit » (throttle de redémarrage) et le réinitialiser correctement

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: start-limit-hit) since Mon 2025-12-30 02:09:02 UTC; 2min 10s ago
Dec 30 02:09:02 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 5.
Dec 30 02:09:02 server systemd[1]: myapp.service: Start request repeated too quickly.
Dec 30 02:09:02 server systemd[1]: myapp.service: Failed with result 'start-limit-hit'.

Signification : Le service a flotté et systemd l’a throttlé. C’est généralement un symptôme, pas la maladie.

Décision : Corriger d’abord le crash/sortie. Une fois corrigé, effacer le throttle :

cr0x@server:~$ sudo systemctl reset-failed myapp.service

Signification : Efface l’état d’échec pour que les tentatives de démarrage soient autorisées à nouveau.

Décision : Ne redémarrez le service qu’après résolution de la cause sous-jacente, sinon vous re-déclencherez le throttle.

Tâche 8 : Identifier un timeout vs un crash réel

cr0x@server:~$ systemctl status postgresql.service
× postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; preset: enabled)
     Active: failed (Result: timeout) since Mon 2025-12-30 02:05:41 UTC; 6min ago
Dec 30 02:04:11 server systemd[1]: Starting postgresql.service - PostgreSQL RDBMS...
Dec 30 02:05:41 server systemd[1]: postgresql.service: start operation timed out. Terminating.
Dec 30 02:05:41 server systemd[1]: postgresql.service: Failed with result 'timeout'.

Signification : Il ne s’est pas terminé rapidement ; il est resté bloqué au démarrage. Coupables fréquents : stockage lent, récupération WAL, répertoire de données verrouillé, délai DNS, ou dépendance de montage.

Décision : Vérifier les logs, la santé du stockage et si le répertoire de données est sur un montage qui n’était pas prêt.

Tâche 9 : Corréler le temps de démarrage de l’unité avec la disponibilité du stockage/du montage

cr0x@server:~$ systemd-analyze critical-chain postgresql.service
postgresql.service +1min 28.122s
└─local-fs.target @12.405s
  └─mnt-data.mount @11.902s +1min 15.701s
    └─systemd-fsck@dev-disk-by\x2duuid-3a1c...service @3.211s +8.614s
      └─dev-disk-by\x2duuid-3a1c....device @2.983s

Signification : Le vrai délai est le mnt-data.mount qui met 75 secondes. Postgres attend simplement le système de fichiers.

Décision : Corriger la performance du montage (stockage réseau ? fsck ? erreurs de périphérique?). Ou découpler l’ordre de démarrage si c’est sûr, mais ne maquillez pas un disque défaillant.

Tâche 10 : Inspecter les échecs d’unité de montage et les erreurs fstab

cr0x@server:~$ systemctl status mnt-data.mount
× mnt-data.mount - /mnt/data
     Loaded: loaded (/etc/fstab; generated)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:03:22 UTC; 8min ago
Dec 30 02:03:22 server mount[612]: mount: /mnt/data: wrong fs type, bad option, bad superblock on /dev/sdb1, missing codepage or helper program.
Dec 30 02:03:22 server systemd[1]: mnt-data.mount: Mount process exited, code=exited, status=32/n/a
Dec 30 02:03:22 server systemd[1]: mnt-data.mount: Failed with result 'exit-code'.

Signification : Le montage est cassé ; tout service qui en dépend échouera ou se bloquera. « wrong fs type » peut indiquer un type fstab erroné, un paquet manquant pour le système de fichiers, ou une corruption réelle.

Décision : Confirmer le périphérique et le type de système de fichiers. Si c’est des données de production, arrêter de thrasher et valider le block device avant que des montages répétés n’empirent la situation.

Tâche 11 : Confirmer quel système de fichiers le noyau pense que c’est (et si le périphérique est présent)

cr0x@server:~$ lsblk -f
NAME   FSTYPE FSVER LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda
├─sda1 vfat   FAT32       7C1A-3F2B                             510M     2% /boot/efi
├─sda2 ext4   1.0         2d4b2b3c-5a62-4b2f-8f87-1b9e8f8a0c19  14G    61% /
└─sda3 swap   1           7d3e0c7c-5d48-4d1b-9b8b-2a5d0f3b9e21                [SWAP]
sdb
└─sdb1 xfs                9a1e3f1f-6a71-4bb5-8a45-2e7a5bb1c5b2

Signification : /dev/sdb1 est en XFS. Si fstab indique ext4, c’est votre bug. Si fstab indique xfs et qu’il échoue encore, suspectez une réparation XFS nécessaire ou l’absence de xfsprogs (rare sur Ubuntu, mais possible sur des builds minimalistes).

Décision : Corriger le type/options dans fstab, ou lancer des checks du système de fichiers en mode maintenance. Ne forcez pas des options de montage sur un disque malade en production sauf si vous aimez les pertes de données surprises.

Tâche 12 : Vérifier si un service a été tué par l’OOM (l’assassin silencieux)

cr0x@server:~$ journalctl -b -k --no-pager | tail -n 12
Dec 30 02:07:12 server kernel: Out of memory: Killed process 3310 (myapp) total-vm:3128456kB, anon-rss:1452032kB, file-rss:132kB, shmem-rss:0kB
Dec 30 02:07:12 server kernel: oom_reaper: reaped process 3310 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Dec 30 02:07:12 server systemd[1]: myapp.service: Main process exited, code=killed, status=9/KILL
Dec 30 02:07:12 server systemd[1]: myapp.service: Failed with result 'signal'.

Signification : Le noyau l’a tué, pas systemd. Systemd rapporte les conséquences : SIGKILL.

Décision : C’est du travail sur la capacité/les limites : ajuster la mémoire, réduire l’usage, ajouter du swap (avec précaution), corriger les fuites, ou définir des limites cgroup raisonnables. Redémarrer seul est un mensonge temporaire.

Tâche 13 : Vérifier un « address already in use » (conflit de port) et choisir le bon coupable

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:12:48 UTC; 10s ago
Dec 30 02:12:48 server myapp[4021]: listen tcp 0.0.0.0:8080: bind: address already in use
cr0x@server:~$ sudo ss -ltnp | grep ':8080'
LISTEN 0      4096         0.0.0.0:8080      0.0.0.0:*    users:(("nginx",pid=2101,fd=12))

Signification : Nginx (ou autre) possède le port. Votre application ne peut pas binder.

Décision : Décider qui doit posséder le port. Soit déplacer l’appli sur un autre port, mettre à jour le reverse proxy, soit arrêter le service conflictuel. Ne tuez pas des PID au hasard sans savoir pourquoi ils sont là.

Tâche 14 : Confirmer si AppArmor a bloqué l’accès (commun sur Ubuntu)

cr0x@server:~$ journalctl -b --no-pager | grep -i apparmor | tail -n 6
Dec 30 02:10:02 server kernel: audit: type=1400 audit(1735524602.112:91): apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/etc/ssl/private/my.key" pid=2191 comm="nginx" requested_mask="r" denied_mask="r" fsuid=0 ouid=0

Signification : Le service est empêché de lire un fichier dont il a besoin. Cela ressemble à un problème de permission mais ne se résout pas par chmod 777 (ne le faites pas).

Décision : Ajuster proprement le profil AppArmor ou déplacer les secrets vers des chemins approuvés ; puis recharger le profil et redémarrer.

Tâche 15 : Vérifier que l’unité voit réellement l’environnement que vous pensez qu’elle a

cr0x@server:~$ systemctl show myapp.service -p Environment -p EnvironmentFiles
Environment=
EnvironmentFiles=/etc/myapp/myapp.env (ignore_errors=no)
cr0x@server:~$ sudo test -r /etc/myapp/myapp.env && echo readable || echo not_readable
not_readable

Signification : Le fichier d’environnement n’est pas lisible par root (ou par systemd au moment du chargement), donc l’unité peut échouer à démarrer ou manquer des variables.

Décision : Corriger la propriété/les permissions. Pour les secrets : lisible par root, lisible par l’utilisateur du service seulement si nécessaire, et éviter les configs lisibles par tout le monde.

Modes de défaillance qui importent sur Ubuntu 24.04

1) Confusion de dépendance : After= n’est pas Requires=

After=network-online.target signifie « démarrer après ce target », pas « échouer si network-online échoue ». Si votre service a réellement besoin de quelque chose, déclarez-le. À l’inverse, si vous déclarez Requires= sur un composant instable, vous ferez tomber votre service à chaque pépin de cette dépendance.

2) Unités oneshot qui prétendent être des services long-running

Beaucoup de services internes exécutent en réalité « un script de configuration » puis quittent. Si l’unité est définie en Type=simple avec un processus de courte durée, systemd pensera qu’il a planté. Utilisez Type=oneshot avec RemainAfterExit=yes quand c’est approprié.

3) Network-online est un piège (et pas toujours votre ami)

Sur les instances cloud, « network online » peut devenir vrai avant que le DNS fonctionne, avant que les routes convergent ou avant que votre réseau d’overlay soit opérationnel. Si votre service doit atteindre une BDD distante au démarrage, préférez une logique de retry explicite dans l’appli. L’ordonnancement systemd ne vous sauvera pas des upstreams instables.

4) Délais de stockage : votre service est innocent, votre montage est coupable

Quand le stockage est lent, les services timeout. Quand le stockage est cassé, les services échouent. Dans les deux cas, l’unité qui se fait blâmer est rarement celle qui a causé le retard.

Sur Ubuntu 24.04, faites attention à :

  • entrées fstab générant des unités de montage
  • comportement remote-fs (NFS, CIFS)
  • délais de fsck
  • renumérotation des périphériques après changements matériels

5) Le mythe du « ça marchait hier » : dérive de packaging et de configuration

Ubuntu 24.04 apporte un systemd plus récent, des valeurs par défaut OpenSSL plus strictes, un Python plus récent, et des noyaux plus récents. Des services qui tenaient de manière non définie sur de plus anciennes machines peuvent devenir correctement cassés. Ne vous battez pas contre ça. Corrigez les hypothèses.

Trois mini-récits du monde de l’entreprise (anonymisés, plausibles et douloureux)

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une équipe a migré une flotte vers Ubuntu 24.04 et a « standardisé » les fichiers d’unité. Quelqu’un a supposé que After=network-online.target signifiait que le service n’allait pas démarrer tant que la base de données n’était pas joignable. En staging, tout semblait OK. En production, un sous-ensemble d’hôtes est monté avec un DNS retardé après un changement réseau.

Le service a tenté de se connecter à la base au démarrage, a échoué une fois, et est sorti. La politique de redémarrage était Restart=on-failure avec une boucle serrée. Systemd a fait ce pour quoi il est conçu : redémarrages répétés, puis throttling avec start-limit-hit. Maintenant le service était down et refusait de démarrer, même après stabilisation du DNS.

La réaction initiale a été prévisible : des gens ont bidouillé StartLimitIntervalSec et StartLimitBurst pour « laisser essayer ». Cela a créé un nouveau problème : la boucle de crash a saturé le DNS et la base avec des rafales de connexions à chaque reboot d’hôte. Voici comment transformer un échec local en incident partagé.

La correction était ennuyeuse et correcte : l’appli a gagné un backoff exponentiel et des retries côté application, le démarrage a été rendu tolérant aux échecs initiaux des upstreams, et l’ordonnancement systemd a été simplifié. Ils ont gardé After=network-online.target pour la clarté, mais ont arrêté de prétendre que c’était une garantie de connectivité.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une initiative de réduction des coûts a poussé pour un boot plus rapide et moins de mémoire. Quelqu’un a défini des timeouts systemd agressifs globalement et resserré les limites mémoire pour une série d’unités de traitement. Le boot est devenu plus rapide dans des tests synthétiques, et une diapositive est née.

En production, un sous-ensemble de nœuds avait un stockage attaché plus lent (pas cassé, juste plus lent à certains moments). Postgres sur ces nœuds avait parfois besoin de plus de temps pour la récupération après un arrêt brutal. Les nouveaux timeouts l’ont tué en plein milieu de la récupération. Non seulement le service n’a pas démarré — des récupérations interrompues répétées ont rendu les démarrages encore plus lents ensuite, et la boucle de redémarrage a multiplié la douleur.

Les opérations ont blâmé la base. L’équipe base de données a blâmé le kernel. Le kernel a blâmé l’hyperviseur. Pendant ce temps, la cause racine était une « amélioration de performance » qui avait supprimé la marge nécessaire d’un système qui en avait besoin.

Le rollback a été immédiat : restaurer des timeouts raisonnables, puis définir des valeurs par service basées sur les temps de récupération observés. La solution à long terme a été une politique : les timeouts sont des contrats par service, pas des souhaits globaux. Et si vous voulez un démarrage plus rapide, corrigez les vrais goulots d’étranglement — généralement stockage et réseau, pas le chronomètre.

Mini-récit 3 : La pratique ennuyeuse qui a sauvé la mise

Un service proche des paiements tournait sur un cluster avec un contrôle de changement strict. Ce n’était pas glamour. Chaque unité systemd avait un fichier drop-in dans le contrôle de version, et chaque déploiement incluait un « reboot smoke » dans un canary pour attraper les problèmes d’ordre de démarrage et de dépendances.

Un jour, un patch de routine a introduit une entrée fstab pour un nouveau montage de reporting. Le serveur de montage était disponible, mais un nœud avait un résolveur DNS obsolète. Au reboot, l’unité de montage a échoué. Le service principal avait RequiresMountsFor= pointant vers ce chemin parce que quelqu’un pensait que c’était « bien d’avoir ». Le nœud n’est pas revenu correctement.

Parce qu’ils faisaient un reboot canary, le problème a été détecté avant que le patch n’atteigne la flotte. La correction a été chirurgicale : le montage de reporting a été changé en nofail et la dépendance du service a été retirée. Le service principal n’avait pas besoin du montage pour traiter les paiements ; il en avait besoin pour émettre un rapport. Cette distinction compte.

Pas d’héroïsme, pas de war room. Juste une checklist, un canary, et l’insistance sur la modélisation des dépendances comme critiques vs optionnelles. L’ennuyeux a gagné.

Erreurs courantes : symptôme → cause racine → correction

C’est la partie où vous arrêtez de répéter le même incident chaque trimestre.

1) « Start request repeated too quickly »

Symptôme : L’unité échoue avec Result: start-limit-hit, ne redémarre pas.

Cause racine : Boucle de crash ou sortie immédiate (mauvaise config, binaire manquant, conflit de port). Le throttle est systemd qui fait de l’auto-défense.

Correction : Identifier pourquoi elle sort, corriger cela, puis systemctl reset-failed UNIT. Éviter de « résoudre » en augmentant StartLimit sauf si vous aimez transformer un problème en plusieurs.

2) « Failed to run ‘start’ task: No such file or directory »

Symptôme : systemd ne peut pas exécuter la commande.

Cause racine : Chemin ExecStart= erroné, exécutable manquant, architecture différente, ou ExecStart surchargé dans un drop-in que vous avez oublié.

Correction : systemctl cat UNIT, confirmer le ExecStart final, vérifier que le fichier existe et est exécutable. Puis daemon-reload si vous avez modifié les fichiers d’unité.

3) Service « active (exited) » mais fonctionnalité manquante

Symptôme : systemctl montre un succès, mais le service n’est pas en cours d’exécution.

Cause racine : Type=oneshot script qui quitte ; ou unité mal déclarée où le process principal fork et systemd le perd de vue.

Correction : Utiliser le bon Type= (simple, forking, notify, oneshot) et définir PIDFile= si nécessaire. Valider avec systemctl show -p MainPID.

4) « Dependency failed for … » au démarrage

Symptôme : Un target ou service échoue parce qu’une autre unité a échoué.

Cause racine : Exigence stricte (Requires=) sur un mount/unit réseau qui est en réalité optionnel.

Correction : Reclasser les dépendances. Utiliser Wants= pour les composants optionnels. Pour les montages, envisager nofail dans fstab et/ou supprimer RequiresMountsFor= si ce n’est pas réellement obligatoire.

5) « Permission denied » même en root

Symptôme : Le service ne peut pas lire clés/certs/configs.

Cause racine : Refus AppArmor ou unité exécutée en tant qu’utilisateur non-root sans permissions suffisantes.

Correction : Confirmer l’utilisateur effectif (User=), la propriété des fichiers et les logs AppArmor. Ajuster le profil ou déplacer les fichiers vers des chemins attendus.

6) Timeouts sur services avec stockage après reboot

Symptôme : Base de données, queue ou application timeout au démarrage ; chaîne de montages montre de longs temps d’attente.

Cause racine : fsck lent, retards de montages distants, disque dégradé, ou fstab incorrect provoquant des retries.

Correction : Corriger d’abord les montages. Valider la santé du block device, corriger le type/options du système de fichiers et s’assurer que les services ne dépendent pas de montages optionnels.

7) Confusion « Unit file changed on disk »

Symptôme : Vous avez édité une unité et elle ne prend pas effet.

Cause racine : Oubli de systemctl daemon-reload, ou modification du mauvais fichier (unité packagée vs override).

Correction : Utiliser systemctl cat pour voir l’unité finale ; recharger le daemon ; redémarrer l’unité.

Une seconde petite blague, puis retour au travail : si vous déboguez une unité échouée en rebootant sans cesse, félicitations — vous avez inventé le chaos engineering, sans l’apprentissage.

Listes de contrôle / plan étape par étape

Checklist A : Quand un service échoue maintenant (incident en direct)

  1. Capturer status et logs (ne pas muter d’abord). Sauvegarder systemctl status et journalctl -u UNIT -b.
  2. Classifier l’échec : exit-code, signal, timeout, dépendance, start-limit-hit.
  3. Confirmer la définition finale de l’unité avec systemctl cat UNIT.
  4. Vérifier les bloqueurs de dépendance : montages, network-online, secrets, ports.
  5. Décider du chemin de restauration :
    • Si régression de config : rollback de la config.
    • Si régression de package : rollback du package ou pin de version.
    • Si dépendance infra : laisser en mode dégradé (fail open) si sûr ou dégrader proprement.
  6. Stabiliser : arrêter les unités qui flottent ; désactiver les timers ; utiliser reset-failed après suppression de la cause racine.
  7. Vérifier : endpoints de santé, sockets à l’écoute, requête synthétique, et systemctl is-active.

Checklist B : Le triage « je dois remettre l’hôte en ligne » au boot

  1. Identifier l’unité (ou les unités) qui échouent :
cr0x@server:~$ systemctl --failed
  UNIT                LOAD   ACTIVE SUB    DESCRIPTION
● mnt-data.mount      loaded failed failed /mnt/data
● postgresql.service  loaded failed failed PostgreSQL RDBMS

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state.
SUB    = The low-level unit activation state.

Signification : L’échec du montage cause probablement l’échec de la base de données.

Décision : Corriger le montage d’abord ; puis retenter la base.

  1. Obtenir le contexte d’ordonnancement :
cr0x@server:~$ systemd-analyze blame | head -n 10
1min 15.701s mnt-data.mount
12.233s cloud-init.service
8.614s systemd-fsck@dev-disk-by\x2duuid-3a1c...service
3.812s systemd-networkd-wait-online.service
2.115s snapd.service
1.998s apt-daily.service
1.233s systemd-resolved.service
1.101s ufw.service
981ms systemd-journald.service
742ms systemd-logind.service

Signification : Le goulot de démarrage est le montage et le wait-online. Cela oriente votre prochaine heure.

Décision : Si c’est un problème d’échelle flotte, ne perdez pas de temps à déboguer l’appli ; corrigez le stockage et la sémantique network-online.

Checklist C : Éditions sûres de fichiers d’unité (pour ne pas briquer l’hôte)

  1. Utiliser des drop-ins : systemctl edit UNIT (ne pas éditer /lib/systemd/system directement).
  2. Valider la syntaxe et la configuration fusionnée : systemctl cat UNIT.
  3. Recharger le manager : systemctl daemon-reload.
  4. Redémarrer : systemctl restart UNIT.
  5. Vérifier les logs et MainPID : systemctl status UNIT et systemctl show -p MainPID UNIT.
  6. Ce n’est qu’ensuite que vous activez/désactivez : systemctl enable --now UNIT si approprié.

Checklist D : Triage conscient du stockage (parce que « Failed to start » est souvent « le disque a dit non »)

  1. Vérifier les montages : systemctl status *.mount pour les montages en échec.
  2. Corréler la chaîne de boot : systemd-analyze critical-chain SERVICE.
  3. Valider les périphériques : lsblk -f et blkid.
  4. Vérifier l’espace disque et les inodes :
cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        20G   19G  200M  99% /

cr0x@server:~$ df -i /
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sda2      1310720 1310102     618  100% /

Signification : Vous pouvez être « à court d’inodes » tout en ayant de l’espace libre. Cela casse la journalisation, les fichiers PID, les sockets — les services échouent de manière étrange.

Décision : Purger les répertoires gourmands en inodes (souvent cache/tmp), faire la rotation des logs, ou agrandir le système de fichiers. Puis redémarrer les services affectés.

FAQ

1) Pourquoi systemctl dit « failed » mais le process tourne réellement ?

Parce que systemd suit ce qu’il croit être le « process principal ». Si le service fork de façon inattendue, écrit un mauvais PID file, ou utilise un Type= incorrect, systemd peut le perdre de vue. Vérifier systemctl show -p MainPID UNIT et le comparer à ps. Corriger le Type= et le suivi du PID.

2) Quelle est la différence entre « exit-code », « signal » et « timeout » dans Result ?

exit-code signifie que le process a retourné un code non nul. signal signifie qu’il a été tué par un signal (OOM montre souvent SIGKILL). timeout signifie que systemd a attendu plus longtemps que permis et l’a terminé. Chacun oriente vers un playbook différent : erreurs de config/exécution vs kills pour ressources vs délais/dépendances.

3) Pourquoi je vois « Unit file changed on disk » après avoir édité un service ?

Parce que systemd ne re-parsait pas automatiquement les fichiers d’unité à chaque start. Exécutez systemctl daemon-reload, puis redémarrez l’unité. Si vous éditez des unités packagées, évitez et utilisez un drop-in.

4) Quelle est la façon la plus rapide de trouver le vrai goulot d’étranglement au boot ?

Utilisez systemd-analyze blame pour trouver les consommateurs de temps, puis systemd-analyze critical-chain SERVICE pour voir la chaîne d’ordonnancement pertinente pour l’unité qui vous intéresse.

5) Dois-je augmenter StartLimitBurst et StartLimitIntervalSec pour éviter les outages ?

Rarement. Les limites de démarrage empêchent des crash-loops d’endommager les hôtes et les dépendances en aval. Si un service plante immédiatement, vous voulez qu’il s’arrête rapidement et bruyamment. Corrigez le crash, ajoutez du backoff côté appli, et utilisez des politiques de redémarrage sensées.

6) Comment voir les logs du boot précédent ?

Utilisez journalctl -b -1 pour le boot précédent, et journalctl -u UNIT -b -1 pour l’unité de ce boot. Si les logs ne sont pas là, journald peut ne pas être persistant sur cet hôte.

7) Quand dois-je utiliser « mask » vs « disable » ?

disable empêche le démarrage au boot ou via les wants. Il peut toujours être démarré manuellement ou comme dépendance. mask le rend in-démarrable (symlink vers /dev/null). Utilisez mask pour une unité qui flotte et que vous devez empêcher de démarrer pendant la stabilisation du système.

8) Comment confirmer si l’échec est dû à des permissions ou AppArmor ?

Les erreurs de permission apparaissent comme « permission denied » dans les logs applicatifs et se vérifient avec namei -l PATH et des vérifications de propriété. AppArmor affiche des audits kernel dans le journal. Cherchez « apparmor=DENIED » dans le journal et associez le profil au service.

9) Pourquoi « network-online.target » ralentit le boot ?

Parce que systemd-networkd-wait-online.service (ou l’équivalent NetworkManager) peut attendre que les interfaces soient configurées. C’est utile pour les services qui ont vraiment besoin d’un réseau configuré, mais nuisible si vous avez rendu tout dépendant de cela. Gardez les dépendances ciblées.

10) Que faire quand l’unité échoue à cause d’un fichier manquant sous /etc ?

D’abord, décider si c’est un bug de déploiement ou un fichier de configuration conffile supprimé par un package. Restaurer depuis la gestion de configuration ou une sauvegarde, ou rollback du changement. Ensuite ajouter une garde au niveau de l’unité si approprié (comme ConditionPathExists=) pour que l’échec soit explicite et rapide.

Conclusion : prochaines étapes à faire aujourd’hui

« Échec de démarrage … » n’est pas un diagnostic. C’est un départ. Votre travail est de le transformer en une des catégories concrètes : échec d’exécution, timeout, cascade de dépendances, permissions/AppArmor, pression de ressources, ou throttle de redémarrage.

Faites ces prochaines étapes tant que vous n’êtes pas en feu :

  1. Rendre journald persistant sur les serveurs où la forensique post-reboot compte, pour que les échecs ne s’évaporent pas au reboot.
  2. Auditer les fichiers d’unité personnalisés pour un Type= correct, des dépendances explicites, et des timeouts sensés.
  3. Séparer optionnel et critique pour les dépendances (montages, chemins de reporting, télémétrie). Utiliser Wants= ou dégrader proprement.
  4. Ajouter des reboots canary pour les changements touchant fstab, réseau, stockage ou unités systemd. Les bugs d’ordre de boot adorent la production.
  5. Documenter les « trois premières commandes » que votre équipe exécute (systemctl status, journalctl -u ... -b, systemctl cat) et les imposer pendant les incidents.

Systemd est déterministe. Si vous le traitez comme une boîte noire, il ressemble à une boîte à gremlins. Utilisez le workflow ci‑dessus et vous passerez moins de temps à deviner et plus de temps à restaurer.

← Précédent
Ubuntu 24.04 : Limitation de débit Nginx qui n’empêchera pas les vrais utilisateurs — comment l’ajuster
Suivant →
MCM Graphics : Ce qui peut mal tourner et comment les fournisseurs le corrigent

Laisser un commentaire