Vous reconnaissez l’odeur : tableaux de bord rouges, alerte Pager qui hurle, et un service qui « démarre » des centaines de fois par minute sans effectuer la moindre tâche utile. Sur Ubuntu 24.04, systemd n’est pas théâtral : il fait ce que vous lui avez demandé — redémarrer un processus défaillant — jusqu’à atteindre une limite de fréquence ou transformer vos logs en confettis.
Voici comment arrêter la boucle sans perdre les preuves, extraire la première erreur réelle (pas la 500e), puis corriger la cause sous-jacente. On va faire comme les opérateurs : peu d’héroïsme, beaucoup de signal, et des changements que vous pouvez justifier dans un postmortem.
Ce que signifie réellement une boucle de redémarrage dans systemd
Une « boucle de redémarrage » n’est rien d’autre qu’une boucle de rétroaction entre la politique de redémarrage d’une unité et un processus qui revient vers systemd trop rapidement. Le processus se termine (crash, mauvaise configuration, dépendance manquante, problème de permissions), systemd applique les règles Restart= du fichier d’unité, et réessaie. Répéter. Si c’est assez rapide, systemd finit par dire « assez » via la limitation de fréquence de démarrage (StartLimitIntervalSec + StartLimitBurst) et marque l’unité comme échouée.
Deux points clés pour les opérateurs :
- L’erreur racine se trouve presque toujours dans les premières tentatives. Après ça, vous obtenez surtout du bruit répété : « Main process exited » et « Scheduled restart job ».
- systemd ne sait pas ce qu’est « sain » : il sait seulement que « le processus s’est terminé ». Un service peut être « en cours d’exécution » et pourtant inutile, ou « failed » alors qu’il a réussi mais s’est daemonisé incorrectement.
Il existe un anti-pattern courant : « régler » la boucle en mettant Restart=no et en considérant le travail terminé. Ce n’est pas réparer ; c’est désactiver l’alarme incendie parce qu’elle fait du bruit.
Plan de diagnostic rapide (faire ceci en premier)
Voici l’ordre qui fait gagner du temps sous pression. Il est optimisé pour « trouver vite le goulot », pas pour une exhaustivité théorique.
1) Confirmer que vous avez une boucle et mesurer son tempo
- Vérifiez si systemd est en train de le redémarrer activement, et à quelle vitesse.
- Cherchez la limitation de fréquence (« Start request repeated too quickly ») — cela signifie que vous avez manqué les premières erreurs et devez extraire les logs par fenêtre temporelle.
2) Récupérer les premiers journaux d’échec, pas les plus récents
- Utilisez
journalctlavec--sinceet--reversepour trouver la première ligne problématique après un démarrage. - Obtenez le statut de sortie et les informations de signal depuis les messages systemd.
3) Geler le patient (arrêter la boucle) sans supprimer les preuves
- Masquez ou arrêtez l’unité, ou désactivez temporairement
Restart=via un override. - Ne nettoyez pas le journal. Ne redémarrez pas « juste pour faire propre ». C’est comme ça qu’on perd la seule preuve disponible.
4) Classifier le mode d’échec
La plupart des boucles entrent dans l’une de ces catégories :
- Sortie immédiate : mauvaise config, variable d’environnement manquante, mauvais répertoire de travail, option invalide, binaire absent.
- Crash : segfault, abort, instruction illégale (souvent incompatibilité de bibliothèque ou d’extension CPU), OOM.
- Inadéquation de readiness : le service indique qu’il a démarré mais systemd attend
Type=notify, ou il fork alors qu’il ne devrait pas. - Échec de dépendance : réseau non prêt, DNS cassé, montage absent, permissions/SELinux/AppArmor.
- Limites de ressources : descripteurs de fichiers, memlock, tâches, différences de ulimit sous systemd.
5) Reproduire dans le même environnement que celui utilisé par systemd
- Exécutez le même ExecStart dans un environnement propre, ou utilisez
systemd-runpour émuler. - Vérifiez les options de sandboxing de l’unité qui peuvent bloquer l’accès aux fichiers (ex.
ProtectSystem=,PrivateTmp=).
Arrêter la boucle en sécurité (et conserver les preuves)
Si un service clignote, il endommage activement votre système : il consomme du CPU, spamme les logs, rouvre des sockets, perturbe les caches, et peut déclencher des limites externes. Votre premier travail est d’arrêter l’hémorragie sans effacer la scène du crime.
Option A : Arrêter l’unité (pause courte)
C’est le bouton « pause » rapide. systemd peut quand même tenter un redémarrage si d’autres unités en dépendent et le provoquent, donc vérifiez après l’arrêt.
Option B : Masquer l’unité (arrêt total)
Masquer empêche tout démarrage (manuel ou déclenché par une dépendance). C’est la bonne action quand la boucle cause des dommages collatéraux et que vous avez besoin d’un hôte stable pour enquêter.
Option C : Override temporaire : désactiver la politique de redémarrage
C’est plus propre qu’éditer les fichiers unit vendeurs. Créez un drop-in override pour changer Restart= et éventuellement ajouter un RestartSec= plus long pour lire les logs entre les tentatives.
Blague #1 : Une boucle de redémarrage, c’est comme un stagiaire avec de l’enthousiasme infini et zéro contexte — rapide, persistant, et d’une façon ou d’une autre, rendant tout pire.
Attraper l’erreur racine : journaux, codes de sortie et coredumps
systemd est bon pour vous dire que quelque chose est mort. Il faut qu’il vous dise pourquoi. Le « pourquoi » se trouve généralement dans un des endroits suivants :
- journaux du journal pour l’unité et ses dépendances
- métadonnées de sortie systemd : code de statut, signal, notes de core dump
- logs applicatifs : fichiers de log, capture stderr/stdout, logs structurés
- coredumps : pour les vrais crashs
- logs kernel : OOM killer, segfault, erreurs de montage
Une citation autour de laquelle vous pouvez construire une réponse d’incident :
« L’espoir n’est pas une stratégie. » — idée paraphrasée souvent attribuée dans les cercles de management ingénierie
Traduction : arrêtez de deviner. Capturez les preuves. Ne faites qu’un changement à la fois.
Tâches pratiques : commandes, sorties, décisions (12+)
Ce sont les tâches que j’exécute réellement quand un service flashe sur Ubuntu 24.04. Chacune indique ce que signifie la sortie et quelle décision en découle.
Task 1: Confirm current state, recent failures, and restart counters
cr0x@server:~$ systemctl status myapp.service --no-pager -l
● myapp.service - MyApp API
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
Active: activating (auto-restart) (Result: exit-code) since Mon 2025-12-29 10:14:07 UTC; 2s ago
Process: 18422 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
Main PID: 18422 (code=exited, status=1/FAILURE)
CPU: 38ms
Dec 29 10:14:07 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:07 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 10:14:07 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 17.
Ce que cela signifie : Il sort avec le statut 1 rapidement et a redémarré 17 fois. Ce n’est pas un « lancement lent », c’est une défaillance déterministe.
Décision : Arrêter la boucle et extraire les premiers logs autour du premier redémarrage. Ne perdez pas de temps à ajuster les timeouts pour l’instant.
Task 2: Stop the loop immediately (pause)
cr0x@server:~$ sudo systemctl stop myapp.service
cr0x@server:~$ systemctl is-active myapp.service
inactive
Ce que cela signifie : systemd l’a arrêté. S’il revient en « activating », quelque chose d’autre le remettra en route.
Décision : S’il redémarre à cause de dépendances, masquez-le (tâche suivante).
Task 3: Mask it to prevent restarts triggered by dependencies
cr0x@server:~$ sudo systemctl mask myapp.service
Created symlink /etc/systemd/system/myapp.service → /dev/null.
cr0x@server:~$ systemctl status myapp.service --no-pager -l
● myapp.service
Loaded: masked (Reason: Unit myapp.service is masked.)
Active: inactive (dead)
Ce que cela signifie : Il ne pourra pas être démarré tant qu’il est masqué. C’est réversible et les logs restent intacts.
Décision : Enquêter calmement. Ne démasquez que lorsque vous êtes prêt à tester à nouveau.
Task 4: Pull unit-specific logs for the last boot, newest first
cr0x@server:~$ journalctl -u myapp.service -b --no-pager -n 200
Dec 29 10:13:59 server myapp[18391]: FATAL: cannot read config file: open /etc/myapp/config.yml: permission denied
Dec 29 10:13:59 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:00 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.
Ce que cela signifie : C’est de l’or : permission refusée pour lire le fichier de config.
Décision : Corriger les permissions/ownership, ou vérifier les options de durcissement qui ont modifié la vue du système de fichiers.
Task 5: Identify the exact ExecStart and unit settings
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp API
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
Group=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=always
RestartSec=1
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
Ce que cela signifie : ProtectSystem=strict peut rendre de larges parties du système de fichiers en lecture seule ; ProtectHome=true bloque l’accès à /home. Si la config est sous un chemin protégé ou nécessite des fichiers auxiliaires, vous pouvez casser le service « en améliorant la sécurité ».
Décision : Si les permissions semblent correctes, suspectez le durcissement de l’unité. Ajustez les chemins ou relâchez des options spécifiques via des overrides chirurgicaux.
Task 6: Check permissions and SELinux/AppArmor constraints (Ubuntu: usually AppArmor)
cr0x@server:~$ namei -l /etc/myapp/config.yml
f: /etc/myapp/config.yml
drwxr-xr-x root root /
drwxr-xr-x root root etc
drwx------ root root myapp
-rw------- root root config.yml
Ce que cela signifie : Le répertoire /etc/myapp est en 700, et le fichier en 600 appartenant à root. Si l’unité s’exécute en tant qu’utilisateur myapp, il ne peut pas le lire.
Décision : Changez la propriété ou les ACL. Ne lancez pas le service en root « parce que ça marche ». C’est comme ça que l’on a un autre incident la semaine suivante.
Task 7: Fix ownership safely (example) and validate
cr0x@server:~$ sudo chown -R root:myapp /etc/myapp
cr0x@server:~$ sudo chmod 750 /etc/myapp
cr0x@server:~$ sudo chmod 640 /etc/myapp/config.yml
cr0x@server:~$ sudo -u myapp test -r /etc/myapp/config.yml && echo OK
OK
Ce que cela signifie : L’utilisateur du service peut désormais lire le fichier.
Décision : Démasquez et démarrez une fois ; suivez les logs. S’il échoue encore, vous avez éliminé une cause racine et gardé le changement minimal.
Task 8: Unmask, start once, and follow logs live
cr0x@server:~$ sudo systemctl unmask myapp.service
Removed "/etc/systemd/system/myapp.service".
cr0x@server:~$ sudo systemctl start myapp.service
cr0x@server:~$ journalctl -u myapp.service -f --no-pager
Dec 29 10:22:41 server myapp[19012]: INFO: loaded config /etc/myapp/config.yml
Dec 29 10:22:41 server myapp[19012]: INFO: listening on 0.0.0.0:8080
Ce que cela signifie : Il démarre et reste en place (aucun redémarrage ultérieur).
Décision : Si stable, réactivez les alertes de monitoring et vérifiez les dépendances (BD, stockage, upstream).
Task 9: If logs are noisy, find the first failure window by time
cr0x@server:~$ journalctl -u myapp.service --since "2025-12-29 10:10:00" --until "2025-12-29 10:15:00" --no-pager
Dec 29 10:13:59 server myapp[18391]: FATAL: cannot read config file: open /etc/myapp/config.yml: permission denied
Dec 29 10:13:59 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:00 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.
Ce que cela signifie : Vous pouvez isoler la première erreur même si les redémarrages inondent le journal.
Décision : Toujours limiter les requêtes journal pendant les tempêtes de redémarrage. Ça vous garde sain d’esprit et rend vos notes d’incident crédibles.
Task 10: Inspect dependency chain and ordering
cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
myapp.service
● multi-user.target
Ce que cela signifie : Peu dépend de lui (bien). Si la liste inverse est énorme, l’arrêter peut casser d’autres unités.
Décision : Pour de grands arbres de dépendance, masquez avec soin et communiquez l’impact. « J’ai arrêté la chose » n’est pas un plan complet.
Task 11: Catch crashes: check coredumps
cr0x@server:~$ coredumpctl list myapp
TIME PID UID GID SIG COREFILE EXE
Mon 2025-12-29 09:58:12 UTC 17021 1001 1001 11 present /usr/local/bin/myapp
cr0x@server:~$ coredumpctl info 17021
PID: 17021 (myapp)
UID: 1001 (myapp)
GID: 1001 (myapp)
Signal: 11 (SEGV)
Timestamp: Mon 2025-12-29 09:58:12 UTC (17min ago)
Command Line: /usr/local/bin/myapp --config /etc/myapp/config.yml
Executable: /usr/local/bin/myapp
Control Group: /system.slice/myapp.service
Unit: myapp.service
Message: Process 17021 (myapp) of user 1001 dumped core.
Ce que cela signifie : Si vous voyez SIGSEGV/SIGABRT, vous êtes en territoire de crash réel : parsing de config corrompu, bibliothèque incompatible, ou bug mémoire.
Décision : Arrêtez d’essayer des tweaks unit aléatoires. Récupérez le core, le build ID et les versions de paquets ; puis reproduisez en staging ou avec le fournisseur.
Task 12: Check kernel logs for OOM kills and segfaults
cr0x@server:~$ journalctl -k -b --no-pager -n 200
Dec 29 10:01:44 server kernel: Out of memory: Killed process 17555 (myapp) total-vm:812340kB, anon-rss:612220kB, file-rss:1100kB, shmem-rss:0kB, UID:1001 pgtables:1872kB oom_score_adj:0
Dec 29 10:01:44 server kernel: oom_reaper: reaped process 17555 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Ce que cela signifie : Le kernel a tué le processus. systemd le redémarre. Boucle accomplie.
Décision : Corrigez la pression mémoire (limites, fuites, concurrence) ou ajoutez des contrôles mémoire systemd (MemoryMax=) pour échouer rapidement et de façon prévisible. Vérifiez aussi le swap et les charges co-localisées.
Task 13: Inspect start rate limiting (why it “stops trying”)
cr0x@server:~$ systemctl show myapp.service -p StartLimitIntervalUSec -p StartLimitBurst -p NRestarts -p Restart -p RestartUSec
StartLimitIntervalUSec=10s
StartLimitBurst=5
NRestarts=17
Restart=always
RestartUSec=1s
Ce que cela signifie : Il autorise 5 démarrages par 10 secondes. Avec RestartSec=1, vous allez atteindre vite le limiteur.
Décision : Ne « réparez » pas en augmentant StartLimitBurst à 10 000. Augmentez RestartSec pour ralentir la tempête et préserver les ressources pendant le debug.
Task 14: Reproduce under systemd-run to match the service environment
cr0x@server:~$ sudo systemd-run --unit=myapp-debug --property=User=myapp --property=Group=myapp \
/usr/local/bin/myapp --config /etc/myapp/config.yml
Running as unit: myapp-debug.service
cr0x@server:~$ journalctl -u myapp-debug.service --no-pager -n 50
Dec 29 10:30:10 server myapp[19411]: FATAL: cannot open /var/lib/myapp/state.db: permission denied
Ce que cela signifie : Votre test « ça marche quand je le lance » a probablement été exécuté en root, ou dans un répertoire de travail différent, ou avec des variables d’environnement différentes.
Décision : Reproduisez toujours avec l’utilisateur du service et le contexte systemd. Ça vous fait gagner des heures et réduit le folklore.
Task 15: Verify and fix RuntimeDirectory/StateDirectory ownership (modern systemd pattern)
cr0x@server:~$ systemctl show myapp.service -p StateDirectory -p RuntimeDirectory
StateDirectory=myapp
RuntimeDirectory=myapp
cr0x@server:~$ ls -ld /var/lib/myapp /run/myapp
ls: cannot access '/var/lib/myapp': No such file or directory
drwxr-xr-x 2 root root 40 Dec 29 10:12 /run/myapp
Ce que cela signifie : Si StateDirectory= est configuré, systemd doit le créer sous /var/lib avec la bonne propriété — sauf si l’unité est ancienne, mal spécifiée ou override. Ici il manque et le runtime dir est possédé par root.
Décision : Corrigez l’unité pour laisser systemd gérer les répertoires, ou créez-les avec la bonne propriété. Les bugs de propriété de répertoire sont une cause classique de boucle de redémarrage.
Quand ça ne plante que sous systemd
Ça embête les gens parce que ça semble surnaturel : vous lancez le binaire manuellement et ça marche. Sous systemd, il plante immédiatement. Ce n’est pas surnaturel. C’est l’environnement.
Voici ce qui change sous systemd et qui importe souvent :
- Utilisateur/groupe et groupes supplémentaires : les tests manuels tournent souvent en root ou en utilisateur connecté.
- Répertoire de travail : systemd utilise
/par défaut sauf siWorkingDirectory=est défini. - Variables d’environnement : votre shell charge des scripts de profil ; systemd non. Si l’app a besoin d’un
PATHmodifié ou deJAVA_HOME, déclarez-le explicitement. - Limites de descripteurs de fichiers : systemd peut définir des valeurs par défaut différentes, et votre service peut atteindre
EMFILE(trop de fichiers ouverts). - Sandboxing/durcissement :
ProtectSystem,PrivateTmp,RestrictAddressFamilies, etc. peuvent casser les apps subtilement. - Type de démarrage : si l’app se daemonise ou fork, mais que l’unité est
Type=simple(par défaut), systemd peut la considérer comme terminée et la redémarrer.
Blague #2 : systemd ne « déteste » pas votre app. Il refuse juste de participer à sa danse interprétative autour du PID 1.
Modes de défaillance du stockage et du système de fichiers qui ressemblent à des « app bugs »
En tant que personne spécialisée stockage, je le dis clairement : un nombre surprenant de boucles de redémarrage sont des problèmes de stockage déguisés en application.
Remontées en lecture seule
ext4 et similaires peuvent remonter en lecture seule après certaines erreurs I/O pour éviter d’aggraver les dégâts. Votre service échoue alors en créant des fichiers PID, en écrivant de l’état, en faisant la rotation des logs, ou en mettant à jour SQLite. systemd le redémarre sans cesse parce que le processus continue de sortir « pour une raison ».
Disque plein (ou table d’inodes pleine)
Un service qui ne peut pas écrire dans /var ou /tmp meurt souvent tôt. Pire : le spam de logs remplit l’espace restant et met d’autres services hors service. Vérifiez à la fois blocs et inodes.
Ordonnancement des montages et systèmes de fichiers réseau
Les services qui s’attendent à trouver /mnt/data vont planter si ce n’est pas monté. Ça devient épicé avec NFS, iSCSI et volumes chiffrés : le montage peut être « présent » mais pas prêt, ou le réseau est UP mais le DNS non, ou l’inverse.
Permissions sur les chemins d’état
Après une restauration, un rsync ou une « amélioration », la propriété dérive. Beaucoup de daemons quittent si ils détectent des permissions incorrectes sur des fichiers sensibles (SSH, Postgres, etc.). C’est une fonctionnalité, mais cela crée des boucles si vous ne voyez pas la première ligne de log.
Pièges réseau et DNS
Ubuntu 24.04 utilise typiquement systemd-resolved et Netplan. Une boucle de redémarrage survient souvent quand un service tente de résoudre un nom d’hôte ou de se connecter à une dépendance en amont au démarrage, échoue rapidement, puis sort. systemd le redémarre. Félicitations : votre instabilité DNS est devenue un benchmark CPU.
Déclencheurs réseau courants :
- Le service démarre avant que le réseau soit « en ligne » (link up n’est pas égal à routable).
- Le DNS pointe vers un résolveur mort ;
resolv.confest géré et vos anciennes hypothèses cassent. - Des règles de firewall bloquent l’egress ; l’app considère cela comme fatal au lieu de réessayer.
- Mauvais comportement IPv6 : l’app se lie seulement sur v6, ou tente v6 en premier et subit des timeouts.
Ordonnancement des dépendances et disponibilité
Les boucles de redémarrage ne concernent pas toujours l’échec applicatif. Parfois l’app démarre correctement, mais systemd pense qu’elle ne l’a pas fait, ou la démarre au mauvais moment.
Mauvais Type de service
Si votre daemon fork en arrière-plan mais que l’unité est Type=simple, systemd peut interpréter la sortie du parent comme un échec (ou un succès, selon le cas), puis redémarrer. La correction est souvent Type=forking avec un PIDFile=, ou mieux : configurez l’app pour rester au premier plan et gardez Type=simple.
Inadéquation de notification de readiness
Type=notify attend que le processus appelle sd_notify. S’il ne le fait pas, systemd attend, expire, le tue, puis le redémarre. Cette boucle ressemble à un crash applicatif, mais c’est une violation de contrat.
Les targets de montage et réseau ne sont pas magiques
After=network-online.target aide, mais cela ne fonctionne que si le service de « wait online » approprié est activé pour votre pile réseau. Pour les montages, utilisez RequiresMountsFor= pour lier une unité à un chemin qui doit être monté.
Limites de fréquence, politique de redémarrage et comment ajuster sans tricher
La politique de redémarrage n’est pas seulement « rendre hautement disponible ». C’est « déclarer comment le système doit se comporter en cas d’échec ». En production, ce comportement doit être délibéré.
Restart=always est rarement le bon choix par défaut
Restart=always redémarre même après une sortie propre. C’est parfait pour les workers conçus pour tourner en continu ; c’est terrible pour les jobs oneshot et pour les daemons qui sortent volontairement après une migration réussie.
Meilleures pratiques :
- Restart=on-failure pour la plupart des daemons.
- Restart=on-abnormal quand une sortie propre est légitime.
- Pas de redémarrage pour les jobs batch ; laissez l’ordonnanceur gérer les retries.
Utilisez RestartSec pour éviter l’auto-DOS
Si un service va échouer à répétition, faites-le échouer assez lentement pour que les humains lisent les logs et que la machine respire. Augmenter RestartSec de 100ms à 5s peut faire la différence entre « incident mineur » et « hôte inutilisable ».
StartLimit… est un disjoncteur, pas une solution
Les limites de démarrage arrêtent la tempête immédiate, mais ne résolvent pas la cause racine. Traitez « start request repeated too quickly » comme systemd qui vous dit poliment : vous déboguez trop tard dans la timeline.
Erreurs courantes : symptôme → cause racine → fix
Cette partie vous parlera en dix secondes, parce que vous l’avez déjà vécue.
1) Symptom: “Active: activating (auto-restart)” with status=1/FAILURE
Cause racine : échec déterministe de démarrage (parse de config, fichier manquant, permission refusée).
Fix : stoppez/masquez, puis journalctl -u par fenêtre temporelle. Corrigez les permissions ou la config. Si l’unité tourne en non-root, validez l’accès via sudo -u.
2) Symptom: “Start request repeated too quickly” and then it stays failed
Cause racine : limitation de fréquence déclenchée ; les premières erreurs ont défilé.
Fix : interrogez les logs avec --since/--until, ou -b et recherchez la première défaillance. Envisagez d’augmenter RestartSec pendant le débogage, pas StartLimitBurst.
3) Symptom: service works when run manually, fails under systemd
Cause racine : décalage d’environnement (utilisateur, répertoire de travail, PATH, ulimits, sandboxing).
Fix : reproduisez via systemd-run avec l’utilisateur du service. Vérifiez systemctl cat pour durcissement et options de répertoire. Définissez WorkingDirectory= et Environment= ou EnvironmentFile= explicites.
4) Symptom: exit status shows “status=203/EXEC”
Cause racine : ExecStart pointe vers un binaire manquant ou non exécutable, ou mauvaise architecture/format.
Fix : vérifiez le chemin et les permissions ; vérifiez les shebang pour les scripts. Assurez-vous que le binaire existe sur l’hôte, et pas seulement dans votre tête.
5) Symptom: status shows “code=killed, signal=KILL” with TimeoutStartSec messages
Cause racine : systemd l’a tué après un timeout de démarrage ; souvent inadéquation de readiness ou dépendance lente.
Fix : corrigez Type=, les notifications de readiness, ou faites en sorte que le démarrage ne bloque pas sur des dépendances. Si le démarrage prend légitimement plus de temps, augmentez TimeoutStartSec avec justification.
6) Symptom: kernel logs show OOM killer entries for the service
Cause racine : pression mémoire ou fuite ; peut être due à la cotention ou une concurrence excessive.
Fix : réduisez l’utilisation mémoire, ajoutez du swap si approprié, ou appliquez MemoryMax=. Puis corrigez la fuite. N’ajoutez pas juste de la RAM et appelez ça « capacity planning ».
7) Symptom: “permission denied” writing to /run or /var/lib
Cause racine : répertoires runtime/state manquants ou appartenant à root après un déploiement ou une restauration.
Fix : utilisez RuntimeDirectory= et StateDirectory=, ou corrigez la propriété dans tmpfiles.d. Évitez les mkdir ad-hoc dans ExecStartPre sauf si vous aimez les conditions de course.
8) Symptom: failures correlate with reboots; mount path missing at boot
Cause racine : ordonnancement des dépendances et disponibilité des montages ; système de fichiers réseau pas encore prêt.
Fix : ajoutez RequiresMountsFor=/path et corrigez After=. Pour network-online, assurez-vous que le service de wait-online approprié est activé.
Trois mini-récits d’entreprise (douleur, apprentissage, recettes)
Mini-story 1: The incident caused by a wrong assumption
L’équipe avait une petite API interne exécutée comme service systemd. Rien de spécial. Elle lisait un fichier YAML depuis /etc/company/app.yml et écrivait un petit état dans /var/lib/app. Ça fonctionnait depuis des mois.
Puis un sprint de durcissement de la sécurité est arrivé. Le fichier d’unité a été « amélioré » avec ProtectSystem=strict et NoNewPrivileges=true. Tout le monde a hoché la tête. « Secure by default » c’est bien. Le service redémarrait chaque seconde et a déclenché des alertes dans tout l’environnement.
L’hypothèse fausse était subtile : la config était « évidemment lisible » parce que ça avait toujours marché. Mais les permissions du fichier étaient restées root-only depuis le début, et le service tournait en root. Le sprint de durcissement l’a aussi changé pour tourner en utilisateur non privilégié. La posture sécurité s’est améliorée, et tout a cassé en même temps.
La réparation a été ennuyeuse : corriger la propriété et les permissions, puis conserver le durcissement. La leçon n’était pas « ne durcissez pas ». La leçon était « ne mélangez pas des changements de durcissement avec des changements de modèle de privilège sans plan de test ». Heureusement, le service est maintenant plus sûr et l’équipe a arrêté de considérer root comme un flag de fonctionnalité.
Mini-story 2: The optimization that backfired
Un groupe plateforme voulait des temps de récupération plus rapides. Quelqu’un a réduit RestartSec sur une flotte de 5s à 100ms. L’idée : si les processus plantent, les ramener immédiatement. En labo, c’était net. En production, ça a transformé une petite défaillance en un problème systémique.
Un service a commencé à échouer à cause d’un fichier secret tourné avec de mauvaises permissions. Au lieu d’un échec toutes les quelques secondes avec des logs lisibles, il a tenté dix redémarrages par seconde sur des dizaines de nœuds. Les journaux ont gonflé. L’amplification d’écriture disque a augmenté. Les nœuds ont commencé à rapporter de la latence. D’autres services sur les mêmes hôtes ont ralenti. La tempête de redémarrages est devenue l’incident.
Le postmortem a été inconfortable car la défaillance initiale était mineure et locale. L’optimisation a ajouté un mégaphone géant et une taxe sur les ressources. Les politiques de redémarrage font partie de la conception du système ; « plus rapide » n’est pas automatiquement « meilleur ».
Ils ont annulé le timing agressif, introduit des délais de redémarrage par service, et exigé une justification pour des valeurs RestartSec très basses. Surtout, ils ont arrêté de traiter le redémarrage comme une remédiation. C’est un outil de résilience seulement si le système peut aussi montrer la cause racine efficacement.
Mini-story 3: The boring but correct practice that saved the day
Une autre organisation exécutait un service proche des paiements avec un processus de changement strict. Pas glamour. Chaque unité avait : une override drop-in stockée dans la gestion de configuration, utilisateurs/groupes explicites, propriété de répertoire explicite via StateDirectory=, et un « debug override » standard qui pouvait définir Restart=no et augmenter temporairement la verbosité des logs.
Pendant une fenêtre de mise à niveau Ubuntu, un nœud a commencé à faire flasher un daemon à cause d’une incompatibilité de bibliothèque après une mise à jour partielle de paquets. Le on-call n’a pas bricolé sur le nœud. Il a appliqué l’override debug connu : arrêter les redémarrages, capturer les logs, récupérer les versions de paquets, et restaurer le nœud en complétant la transaction de mise à jour.
Pourquoi c’était utile ? Parce que l’incident n’a pas empiré. Le nœud est resté assez stable pour collecter des preuves. La flotte n’a pas reçu une vague de hotfixes incohérents. L’équipe a eu une seule remédiation propre : corriger l’automatisation de mise à jour pour éviter les mises à jour partielles et s’assurer que le service ne démarre qu’après que les bons paquets soient présents.
La pratique ennuyeuse marche parce qu’elle réduit le nombre de variables nouvelles que vous introduisez pendant que le système est en feu. Ce n’est pas excitant. C’est fiable.
Listes de contrôle / plan étape par étape
Étape par étape : arrêter la boucle, puis diagnostiquer
- Capturer l’état actuel :
systemctl status -let copiez les lignes de statut de sortie dans vos notes d’incident. - Arrêter ou masquer : si le service cause de la consommation de ressources, masquez-le.
- Extraire les premiers journaux d’échec : utilisez
journalctl -uavec--since/--untilautour du premier temps d’échec connu. - Classer le mode d’échec : permissions/config vs crash vs dépendance vs timeout vs OOM.
- Vérifier le fichier d’unité pour les pièges : user/group, working directory, durcissement, Type, timeouts, politique de redémarrage.
- Reproduire dans le contexte systemd :
systemd-runou exécuter en tant qu’utilisateur du service. - Vérifier la couche plateforme : espace disque/inodes, montages, DNS, logs kernel OOM.
- Faire un seul changement : le plus petit changement possible qui adresse la cause racine.
- Tester une fois : démasquez et démarrez ; taillez les logs.
- Restaurer la politique de redémarrage intentionnellement : ne la laissez pas en mode debug.
- Noter ce qui a changé : le vous du futur ne s’en souviendra pas, et les auditeurs encore moins.
Règles strictes quand vous êtes privé de sommeil
- N’éditez pas le fichier d’unité fournisseur dans
/lib/systemd/system. Utilisez des drop-ins. - Ne « réparez » pas en lançant le service en root à moins que le service soit intrinsèquement privilégié (et même là, minimisez la portée).
- Ne baissez pas StartLimitBurst comme première action. Ralentissez plutôt les redémarrages.
- Ne redémarrez pas juste pour faire disparaître les logs. Ce n’est pas de l’hygiène ; c’est de la destruction de preuves.
Faits intéressants et contexte historique
- systemd a introduit des sémantiques de redémarrage agressives et explicites comparé aux scripts init traditionnels, rendant le « flapping » plus visible — et plus fréquent en cas de mauvaise configuration.
- La limitation de fréquence de démarrage existe en partie parce que des daemons pouvaient fork-bomber PID 1 avec des cycles de plantage/redémarrage rapides ; le limiteur est une soupape de sécurité.
- La migration d’Ubuntu vers systemd-resolved a changé la gestion du DNS ; les anciennes hypothèses sur
/etc/resolv.confstatique cassent souvent les services pendant les upgrades. - La gestion des coredumps est passée de « core files partout » à une collecte centralisée avec
systemd-coredump, améliorant le debug fleet mais déconcertant ceux qui s’attendaient àcoredans le répertoire de travail. - Systemd moderne a ajouté la gestion déclarative des répertoires (
StateDirectory,RuntimeDirectory,LogsDirectory) pour réduire les scripts fragiles ExecStartPre. - Les options de durcissement comme ProtectSystem et PrivateTmp sont devenues courantes car ce sont des gains faibles contre des classes entières de compromission — mais elles exposent aussi des hypothèses fragiles sur le système de fichiers.
- « Ça marche dans un shell » est un mensonge depuis les débuts d’Unix ; les environnements de daemon ont toujours différé (PATH différent, pas de TTY, ulimits différentes). systemd rend juste le contrat plus explicite.
- Les limites de fréquence et les politiques de redémarrage sont des outils de fiabilité, pas des cosmétiques ; les premiers patterns HA essayaient souvent de « redémarrer tout instantanément », puis ont appris la dure leçon des défaillances en cascade.
FAQ
1) How do I stop a service restart loop without uninstalling anything?
Masquez-le : sudo systemctl mask myapp.service. Cela empêche les démarrages manuels et ceux déclenchés par des dépendances. Démasquez quand vous êtes prêt.
2) Why does systemd keep restarting a service that obviously can’t start?
Parce que le fichier d’unité l’exige. Cherchez Restart=always ou Restart=on-failure. systemd suppose que le redémarrage est souhaitable sauf indication contraire.
3) Where is the “real error” if systemctl status only shows restart messages?
Généralement dans journalctl -u myapp.service, souvent dans les premières lignes après la première tentative de démarrage. Utilisez --since/--until pour cibler le début de la boucle.
4) What does “status=203/EXEC” mean?
systemd n’a pas pu exécuter la commande de ExecStart=. Causes courantes : chemin erroné, binaire manquant, pas exécutable, ou script avec un mauvais shebang.
5) What does “Start request repeated too quickly” mean?
Le limiteur de démarrage a déclenché. L’unité a démarré et échoué trop de fois dans StartLimitIntervalSec, dépassant StartLimitBurst. C’est un disjoncteur, pas la cause racine.
6) How do I tell if it’s crashing (segfault) versus exiting cleanly?
Dans systemctl status et le journal, cherchez des sorties basées sur un signal (SIGSEGV, SIGABRT). Puis vérifiez coredumpctl list et coredumpctl info.
7) The service works when I run it manually. Why not under systemd?
Utilisateur différent, environnement différent, limites différentes, répertoire de travail différent, et peut-être restrictions de sandboxing. Reproduisez avec systemd-run et l’utilisateur du service.
8) Should I increase TimeoutStartSec to fix a loop?
Seulement si vous avez prouvé que le service est sain mais lent à devenir prêt. S’il échoue rapidement, un timeout plus long ne fait que retarder l’inévitable et gaspiller du temps.
9) Is it okay to set Restart=always for everything so it “self-heals”?
Non. Cela peut créer des tempêtes de redémarrage et masquer des échecs déterministes. Utilisez Restart=on-failure pour la plupart des daemons et concevez les apps pour réessayer leurs dépendances en interne.
10) How do I keep logs from flooding disk during a restart storm?
Masquez le service, puis envisagez de ralentir les redémarrages (RestartSec) lorsque vous le réactivez. Si journald occupe déjà beaucoup d’espace, faites une rotation/vacuum seulement après avoir capturé la fenêtre critique.
Conclusion : prochaines étapes pour rester hors de problèmes
Les boucles de redémarrage semblent chaotiques, mais elles sont généralement déterministes : permission manquante, dépendance cassée, inadéquation de readiness, ou crash réel. Votre travail est d’arrêter la churn, récupérer la première erreur significative, et changer la plus petite chose qui rend le service correct.
Faites ceci ensuite, dans l’ordre :
- Masquez le service s’il flashe et cause des dommages collatéraux.
- Extraire la première défaillance du journal par fenêtre temporelle.
- Classer le mode d’échec (permission/config, crash, timeout, dépendance, ressource).
- Reproduire dans le contexte systemd avec le bon utilisateur.
- Appliquer un correctif minimal, démasquer, démarrer une fois, et suivre les logs.
- Restaurer une politique de redémarrage saine (
on-failure, avec unRestartSecraisonnable) pour que de futurs échecs soient survivables et débogables.
Si vous ne retenez rien d’autre : ne combattez pas la boucle en rendant systemd plus silencieux. Rendez l’échec plus audible, plus précoce et plus facile à prouver. C’est ainsi que les incidents cessent de se répéter.