Un service systemd qui plante en boucle ressemble, à s’y méprendre, à un CrashLoopBackOff Kubernetes : le même symptôme de fond (un processus qui redémarre indéfiniment) existe depuis toujours sur une VM classique, avec sa propre mécanique de backoff et son propre coupe-circuit, souvent mal connu parce que jamais réglé explicitement.
Ce que Restart= déclenche réellement
Restart=on-failure relance automatiquement un service qui se termine avec un code de sortie non nul, une configuration qui semble suffisante mais ne dit rien sur la fréquence des tentatives ni sur la limite au-delà de laquelle systemd doit abandonner.
[Service]
ExecStart=/usr/bin/my-app
Restart=on-failure
RestartSec=5
RestartSec fixe le délai fixe entre chaque tentative (5 secondes ici), une différence notable avec le backoff exponentiel de Kubernetes : systemd, par défaut, n’augmente pas ce délai automatiquement à chaque échec successif, sauf configuration explicite supplémentaire.
Le vrai coupe-circuit : StartLimitBurst
Sans limite explicite, un service en échec permanent redémarre indéfiniment, toutes les RestartSec secondes, pour toujours. StartLimitIntervalSec et StartLimitBurst définissent ensemble un coupe-circuit : au-delà d’un nombre de démarrages donné (StartLimitBurst) dans une fenêtre de temps donnée (StartLimitIntervalSec), systemd cesse purement et simplement de redémarrer le service.
[Unit]
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=5
Cette configuration tolère 5 démarrages en 60 secondes ; le sixième échec dans cette fenêtre bloque tout redémarrage ultérieur, jusqu’à une intervention manuelle (systemctl reset-failed). Sans ce réglage, un service dans une boucle d’échec rapide (une erreur de configuration qui plante en une seconde) peut consommer une quantité de ressources CPU disproportionnée en tentatives de démarrage répétées, sans jamais s’arrêter de lui-même.
Le piège : un service qui semble mort sans message clair
Une fois StartLimitBurst atteint, systemctl status affiche l’état failed, mais sans toujours expliciter clairement que la cause est le coupe-circuit plutôt qu’un problème applicatif encore actif. Confondre les deux mène à chercher un bug qui a peut-être déjà été corrigé, alors que le service reste simplement bloqué par le coupe-circuit, en attente d’un reset-failed explicite.
# Diagnostic : distinguer un vrai échec applicatif
# persistant d'un coupe-circuit déjà déclenché
systemctl status my-app
journalctl -u my-app --since "10 min ago"
# Réinitialise le compteur de coupe-circuit,
# nécessaire après correction, sinon le service
# reste bloqué même une fois le vrai bug corrigé
systemctl reset-failed my-app
systemctl start my-app
Ce que journalctl révèle que systemctl status ne montre pas
systemctl status affiche un résumé, souvent tronqué aux dernières lignes ; journalctl -u <service> donne l’historique complet des tentatives de démarrage et des messages d’erreur associés, la seule source fiable pour distinguer une erreur de configuration ponctuelle d’un problème qui se répète identiquement à chaque tentative.
# L'historique complet, pas seulement le dernier échec,
# nécessaire pour voir si l'erreur change entre tentatives
journalctl -u my-app -n 200 --no-pager
À retenir
Restart=on-failure relance un service en échec, mais ne l’empêche jamais de boucler indéfiniment sans un coupe-circuit explicite : StartLimitIntervalSec et StartLimitBurst, ensemble, sont ce coupe-circuit, l’équivalent systemd du mécanisme qui protège un cluster Kubernetes contre un CrashLoopBackOff qui ne s’arrête jamais. Un service bloqué après avoir atteint cette limite affiche failed sans toujours distinguer clairement le coupe-circuit d’un bug applicatif encore actif, une confusion qui se résout en lisant l’historique complet via journalctl, jamais en se fiant au seul résumé de systemctl status. Ce réglage, souvent oublié parce qu’il n’a de conséquence visible que le jour où un service part en boucle rapide, fait partie des bases d’exploitation Linux qui restent pertinentes même sur une infrastructure largement conteneurisée.