Kubernetes dispose de trois types de probes (readiness, liveness et startup), et chacune répond à une question différente. La plupart des incidents liés aux probes viennent du fait qu’on les traite comme un seul mécanisme : le même endpoint /healthz collé dans readinessProbe et dans livenessProbe, aucun startupProbe sur une application qui met une minute à démarrer, et des timeouts par défaut que personne n’a lus. Voici une référence de ce que chaque probe contrôle vraiment, des modes de défaillance qui découlent de leur confusion, et de la manière d’en choisir les paramètres.
Trois probes, trois questions différentes
Les trois probes partagent la même structure YAML et les mêmes handlers. Ce qui change, c’est ce que le kubelet fait du résultat.
readinessProbe : ce pod peut-il servir du trafic maintenant ?
La readiness contrôle le load balancing. Tant que la probe passe, l’IP du pod figure dans les EndpointSlices de chaque Service qui le sélectionne. Quand elle échoue (failureThreshold fois de suite), le pod est marqué not-ready et retiré du load balancing. Le conteneur n’est pas touché : il continue de tourner, et dès que la probe repasse au vert le pod revient dans la rotation.
La readiness est la seule probe conçue pour échouer en fonctionnement normal. Un pod momentanément surchargé, en attente d’un préchauffage de cache, ou en train de se vider avant un arrêt correspond exactement à ce que « not ready » veut dire. Elle tourne pendant toute la vie du conteneur, pas seulement au démarrage.
Les échecs de readiness conditionnent aussi les rollouts : une mise à jour progressive ne dépasse un nouveau pod que lorsque celui-ci se déclare Ready. Un contrôle de readiness mal fichu a donc un second rayon d’action : il peut laisser un rolling update remplacer des pods sains par des pods cassés qui prétendent aller bien.
livenessProbe : ce processus est-il bloqué au point d’être irrécupérable ?
La liveness contrôle les redémarrages de conteneur. Quand elle échoue failureThreshold fois de suite, le kubelet tue le conteneur et le redémarre selon le restartPolicy du pod, avec un backoff exponentiel (CrashLoopBackOff quand ça se répète).
C’est un remède brutal, et c’est pourquoi la question à laquelle répond la liveness doit rester étroite : le processus est-il en deadlock, bloqué, ou incapable de progresser d’une façon que seul un redémarrage corrige ? Tout ce qui est moins grave relève de la readiness. Une probe de liveness ne devrait presque jamais échouer sur une flotte saine. Si la vôtre se déclenche régulièrement, soit l’application a un vrai défaut, soit la probe vérifie la mauvaise chose.
startupProbe : ce conteneur a-t-il fini de démarrer ?
La startup contrôle les deux autres probes. Tant qu’un startupProbe est défini et n’a pas encore réussi, le kubelet n’exécute ni la liveness ni la readiness. Dès qu’elle réussit une seule fois, elle ne tourne plus jamais et les autres probes prennent le relais.
Son but concerne les applications lentes à démarrer. Le temps de boot maximal accordé vaut failureThreshold × periodSeconds (plus initialDelaySeconds s’il est défini) ; si la probe n’a toujours pas réussi à ce moment-là, le conteneur est tué et redémarré. Ce budget vit à un seul endroit, sur une seule probe, au lieu d’être étalé sur l’initialDelaySeconds des deux autres.
Mécanique et paramètres des probes
Chaque probe utilise l’un des quatre handlers :
httpGet: le succès est un statut HTTP ≥ 200 et < 400. Le choix le plus courant pour les workloads web.tcpSocket: le succès est l’établissement d’une connexion TCP. Prouve qu’un port est ouvert, rien sur l’application derrière.exec: lance une commande dans le conteneur ; le code de sortie 0 est un succès. Souple, et le plus coûteux, puisque chaque exécution effectue un fork dans le conteneur.grpc: utilise le gRPC Health Checking Protocol standard (GA depuis Kubernetes 1.27). La bonne réponse pour les serveurs gRPC, en remplacement de l’ancien contournement par execgrpc_health_probe.
Les paramètres de timing, avec leurs valeurs par défaut :
| Paramètre | Défaut | Signification |
|---|---|---|
initialDelaySeconds | 0 | Attente avant le premier contrôle |
periodSeconds | 10 | Intervalle entre les contrôles |
timeoutSeconds | 1 | Temps alloué par contrôle |
failureThreshold | 3 | Échecs consécutifs avant d’agir |
successThreshold | 1 | Succès consécutifs pour récupérer (doit valoir 1 pour liveness et startup) |
Deux valeurs par défaut méritent l’attention. timeoutSeconds: 1 est agressif : une probe exec qui fork un shell, ou un handler HTTP qui touche quoi que ce soit de plus lent que la mémoire, peut faire sauter un budget d’une seconde sous charge, et un timeout de probe compte comme un échec. periodSeconds: 10 avec failureThreshold: 3 signifie que la détection prend jusqu’à 30 secondes, souvent plus lent que ce que les gens imaginent de la réactivité de leurs « health checks ».
Les modes de défaillance des probes copiées-collées
Voici les schémas qui transforment les probes d’un mécanisme de sécurité en amplificateur d’incident. Tous viennent de la copie d’un bloc qui a l’air de marcher sans se demander à quelle question il répond.
Liveness et readiness pointant sur le même endpoint
Le plus fréquent. Si /healthz échoue parce qu’une dépendance en aval est lente, la readiness sort correctement le pod de la rotation. La liveness, qui surveille le même endpoint, redémarre alors un processus qui n’a jamais été cassé. Multipliez par chaque réplica qui surveille la même dépendance lente, et vous obtenez une tempête de redémarrages coordonnés : la dépendance récupère, mais votre couche applicative est désormais en CrashLoopBackOff.
Les deux probes ont besoin d’endpoints différents parce qu’elles répondent à des questions différentes. /ready peut consulter tout ce dont l’application a besoin pour servir une requête ; /live ne devrait vérifier que le fait que le processus lui-même répond.
Contrôles de dépendances dans la probe de liveness
La version généralisée de l’erreur précédente. Un handler de liveness qui ping la base de données, interroge un broker de messages ou appelle un autre service convertit chaque hoquet de dépendance en un redémarrage massif des dépendants. Redémarrer vos pods d’API ne répare pas votre base de données ; cela ajoute du churn de connexions et des caches froids par-dessus le problème initial, et le fait sur chaque réplica à peu près au même moment.
La conscience des dépendances relève de la readiness, et même là avec précaution : si la readiness de chaque réplica échoue simultanément parce qu’une dépendance partagée est tombée, le Service se retrouve avec zéro endpoint et les clients reçoivent un connection refused au lieu d’une réponse dégradée. Pour une dépendance sans laquelle l’application pourrait fonctionner partiellement, renvoyer un 200 avec un comportement dégradé vaut souvent mieux que faire échouer la probe.
Aucun startupProbe sur une application lente à démarrer
Sans probe de startup, la liveness commence à vérifier après initialDelaySeconds. Pour une application au temps de boot variable (JVM qui chauffent, migrations qui tournent, gros caches à charger), cela impose un mauvais choix : soit régler initialDelaySeconds assez haut pour le pire cas, et encaisser ce délai à chaque redémarrage, même les rapides, soit le régler avec optimisme et regarder le kubelet tuer des conteneurs qui étaient à 5 secondes de la fin de leur boot. Le kill provoque un redémarrage, qui reboote lentement, qui se fait tuer à nouveau : une boucle de crash au démarrage entièrement causée par la configuration des probes.
Un startupProbe avec un failureThreshold généreux résout cela proprement :
startupProbe:
httpGet:
path: /live
port: 8080
periodSeconds: 5
failureThreshold: 24 # up to 120 s to boot
L’application dispose de jusqu’à deux minutes pour se lever, contrôlée toutes les cinq secondes, si bien qu’un boot rapide est détecté rapidement. Une fois levée, la liveness tourne avec des réglages serrés de régime établi, plutôt que des réglages gonflés pour couvrir le démarrage.
Aucun readinessProbe du tout
Sans probe de readiness, un pod compte comme Ready dès que ses conteneurs tournent. Le trafic arrive avant que l’application n’écoute, et les rolling updates progressent sur la base de ce mensonge : les anciens pods sont tués parce que les nouveaux se déclarent « Ready ». Résultat, une salve d’erreurs à chaque déploiement qu’aucun réglage de liveness ne peut corriger.
tcpSocket là où httpGet était nécessaire
Une probe de readiness tcpSocket sur une application web prouve que le socket est ouvert. Une application peut accepter des connexions TCP bien avant que ses routes ne soient câblées, et bien après que sa boucle d’événements s’est bloquée. Si l’application parle HTTP, sondez-la en HTTP.
Un exemple complet
Les trois probes réunies, sur une application hypothétique au démarrage parfois lent :
containers:
- name: app
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /live
port: 8080
periodSeconds: 5
failureThreshold: 24 # boot budget: 120 s
readinessProbe:
httpGet:
path: /ready # may check what serving requires
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2 # out of rotation within ~10 s
livenessProbe:
httpGet:
path: /live # process responsive, nothing else
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3 # restart only after ~30 s of silence
Notez l’asymétrie : la readiness réagit plus vite que la liveness (failureThreshold: 2 contre 3, période plus courte). Être retiré du load balancing est peu coûteux et réversible ; un redémarrage n’est ni l’un ni l’autre. Vous voulez que le remède le moins cher se déclenche en premier.
Table de décision
| Question | Probe | En cas d’échec | L’endpoint devrait vérifier |
|---|---|---|---|
| Ce pod peut-il prendre du trafic maintenant ? | readinessProbe | Retiré des endpoints du Service | Application pleinement initialisée ; dépendances critiques, avec précaution |
| Le processus est-il bloqué au point d’être irrécupérable ? | livenessProbe | Conteneur tué et redémarré | Réactivité du processus / de la boucle d’événements uniquement, jamais les dépendances |
| Le conteneur a-t-il fini de démarrer ? | startupProbe | Conteneur tué et redémarré | Même handler que la liveness, failureThreshold généreux |
Et pour les paramètres :
| Situation | Réglage |
|---|---|
| Temps de boot variable ou > ~10 s | Ajouter un startupProbe ; garder initialDelaySeconds à 0 ailleurs |
| Le handler fait un vrai travail (exec, disque, appel en aval) | Monter timeoutSeconds au-dessus du défaut d’1 s |
| Le trafic doit quitter vite un pod défaillant | Baisser le produit periodSeconds × failureThreshold de la readiness |
| Les redémarrages coûtent cher (caches, connexions) | Monter le failureThreshold de la liveness ; rendre le handler trivial |
| Serveur gRPC | Utiliser le handler grpc, pas un contournement par exec |
Ce que les probes ne résolvent pas
Deux limites qu’il vaut la peine de connaître. D’abord, un readinessProbe correct ne vous donne pas des déploiements sans interruption : la terminaison d’un pod pendant un rollout est une course de propagation que la readiness ne peut pas influencer, et la corriger passe par les hooks preStop et la gestion de SIGTERM. Les rouages sont résumés dans l’entrée de glossaire sur les rolling deployments. Ensuite, les probes continuent de fonctionner sous une NetworkPolicy default-deny : le kubelet sonde les pods depuis le nœud lui-même et les CNI exemptent ce trafic host-local, un détail qui compte quand on déploie une NetworkPolicy default-deny.
Le modèle mental qui survit au contact de la production est simple : la readiness est une décision de routage, la liveness une décision de redémarrage, la startup un budget de temps de démarrage. Configurez chacune pour ne répondre qu’à sa propre question, donnez au remède le moins cher un déclenchement plus rapide qu’au remède brutal, et lisez les valeurs par défaut avant de leur faire confiance. Régler les probes pour qu’elles répondent honnêtement est une petite pièce d’une discipline plus large, amener une plateforme à signaler ses propres pannes avant que les utilisateurs ne les voient, ce qui est l’objet de mon offre fiabilité et observabilité. Plus de contenu Kubernetes dans la catégorie kubernetes.