CrashLoopBackOff est l’état Kubernetes le plus vu en production et le plus mal diagnostiqué, parce qu’il décrit un symptôme identique pour trois familles de causes complètement différentes. Perdre du temps à chercher la mauvaise famille est le piège le plus fréquent : la méthode qui suit distingue les trois en quelques commandes, avant toute hypothèse.

Ce que l’état décrit, et ce qu’il ne décrit pas

Le kubelet affiche CrashLoopBackOff quand un conteneur redémarre de façon répétée, en espaçant chaque tentative d’un délai croissant (backoff exponentiel : 10 secondes, 20, 40, jusqu’à un plafond de 5 minutes). Ce mécanisme protège le nœud d’un martèlement de redémarrages, il ne dit strictement rien sur la cause du crash. Trois familles se cachent derrière le même état, et la première étape consiste à identifier laquelle, pas à réparer directement.

# Le point de départ systématique, avant toute hypothèse :
# l'historique d'événements du pod
kubectl describe pod my-app-7d9f8c-x2k4p

Famille 1 : le processus plante réellement

Le conteneur démarre puis s’arrête de lui-même : configuration invalide, dépendance absente au démarrage, exception non gérée. Le code de sortie et les logs du conteneur précédent (celui qui vient de mourir, pas le nouveau qui redémarre) sont la source de vérité :

# --previous récupère les logs du conteneur mort,
# pas ceux du nouveau qui vient de redémarrer
kubectl logs my-app-7d9f8c-x2k4p --previous

# Le code de sortie oriente le diagnostic :
# 0 = arrêt volontaire, 1 = erreur applicative générique,
# 137 = SIGKILL (voir famille 2)
kubectl get pod my-app-7d9f8c-x2k4p -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

Un code 1 avec une stack trace explicite dans les logs --previous confirme cette famille : le correctif est applicatif ou de configuration, pas un problème Kubernetes.

Famille 2 : le kernel tue le conteneur (OOMKilled)

Un conteneur qui dépasse sa limite mémoire (resources.limits.memory) est tué par le kernel, pas par une erreur applicative. Le signal distinctif : code de sortie 137 (128 + SIGKILL) et raison OOMKilled explicitement affichée par describe pod, sans stack trace utile dans les logs, puisque le processus est arrêté brutalement, pas terminé proprement.

# La raison OOMKilled est directement visible dans describe,
# pas besoin de deviner depuis le seul code de sortie
kubectl describe pod my-app-7d9f8c-x2k4p | grep -A3 "Last State"

Le correctif ici n’est jamais dans le code applicatif seul : soit la limite mémoire est trop basse pour un usage réel (voir l’article sur les requests et limits), soit il y a une vraie fuite mémoire à corriger côté application. Augmenter la limite sans vérifier laquelle des deux masque parfois un vrai bug.

Famille 3 : une sonde tue un processus sain

Une sonde liveness mal réglée (timeout trop court, endpoint qui répond lentement sous charge) peut tuer un processus parfaitement sain, qui n’aurait jamais crashé seul. Le signal distinctif : les événements du pod montrent explicitement Liveness probe failed, jamais un OOMKilled ni une stack trace applicative.

# Les probe failures apparaissent dans les événements,
# distinctes des crashs applicatifs ou OOM
kubectl describe pod my-app-7d9f8c-x2k4p | grep -i "probe failed"

Cette famille est la plus sournoise : le conteneur fonctionnait, la sonde l’a jugé mort à tort. Le correctif porte sur les paramètres de la sonde (timeoutSeconds, failureThreshold) ou sur la dépendance lente qu’elle surveille, jamais sur le code applicatif lui-même.

Le piège du redémarrage en cascade

Une sonde liveness mal réglée sur une dépendance partagée peut mettre en CrashLoopBackOff toutes les replicas d’un service simultanément : chacune surveille le même endpoint lent, chacune redémarre au même instant, et le service entier disparaît pendant que la dépendance sous-jacente n’a, elle, jamais été réellement en panne. Le backoff exponentiel ralentit la fréquence des redémarrages sans jamais résoudre la cause, ce qui rend le diagnostic de famille d’autant plus urgent avant que l’incident ne s’aggrave.

À retenir

CrashLoopBackOff décrit un symptôme identique pour trois causes radicalement différentes : crash applicatif réel (code de sortie et logs --previous), dépassement mémoire (code 137, raison OOMKilled), ou sonde liveness mal réglée qui tue un processus sain (Liveness probe failed dans les événements). Distinguer la famille avant d’agir, via describe pod et logs --previous, évite de corriger la mauvaise chose, un réflexe qui fait gagner un temps disproportionné lors d’un incident en production, un des points qui se travaille dès une migration Kubernetes.