Quand un pod est supprimé, Kubernetes déclenche deux actions en parallèle : envoyer SIGTERM au conteneur, et retirer le pod de la liste des endpoints du Service qui le référence. Ces deux actions ne sont jamais synchronisées entre elles : rien ne garantit que le retrait des endpoints se propage avant que le conteneur ne commence son arrêt, ni l’inverse. C’est cette absence de garantie d’ordre qui explique la majorité des erreurs 502/503 observées pendant un déploiement pourtant jugé « sans coupure ».
Deux horloges qui ne se parlent pas
Le kubelet envoie SIGTERM au conteneur dès que la suppression du pod est traitée localement. En parallèle, le contrôleur d’endpoints met à jour l’objet EndpointSlice du Service, une mise à jour qui doit ensuite se propager à kube-proxy sur chaque nœud, puis, si un service mesh est en jeu, à ses propres tables de routage. Ce second chemin prend un temps non nul, potentiellement plusieurs secondes sur un cluster de taille conséquente, pendant que le premier (SIGTERM) peut avoir déjà commencé à arrêter le processus.
t=0s : suppression du pod déclenchée
t=0s : SIGTERM envoyé au conteneur (immédiat)
t=0-3s : propagation du retrait des endpoints (variable)
Ce que ça produit concrètement
Pendant la fenêtre où le retrait des endpoints n’est pas encore propagé partout, une requête entrante peut encore être routée vers le pod en cours d’arrêt : si l’application a déjà réagi au SIGTERM en fermant son serveur HTTP, cette requête échoue avec une erreur de connexion refusée, précisément le genre d’erreur ponctuelle qu’un déploiement « zero-downtime » n’était pas censé produire.
preStop : acheter le temps que la propagation réclame
Un hook preStop retarde l’envoi effectif du SIGTERM en exécutant une action au préalable, le plus souvent une simple pause : le temps qu’elle dure laisse aux endpoints la chance de se propager avant que le conteneur ne commence réellement à s’arrêter.
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
Cette pause ne corrige rien côté endpoints : elle retarde simplement le moment où l’application cesse de répondre, pour qu’il arrive après, et non avant, la propagation du retrait. La durée à choisir dépend directement du temps de propagation observé dans le cluster, pas d’une valeur arbitraire copiée d’un exemple en ligne.
terminationGracePeriodSeconds : la limite au-delà de laquelle plus rien n’attend
terminationGracePeriodSeconds fixe la durée totale (hook preStop inclus) avant que Kubernetes n’envoie SIGKILL, un arrêt immédiat sans négociation possible avec le processus.
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
Un preStop de 5 secondes avec une période de grâce totale de 30 secondes laisse 25 secondes à l’application elle-même pour terminer proprement les requêtes en cours après réception du SIGTERM : un délai de grâce trop court par rapport à la durée réelle des requêtes en vol force un SIGKILL qui interrompt des transactions à mi-chemin, un problème différent de la course endpoints/SIGTERM mais qui se corrige par le même champ.
À retenir
Kubernetes envoie SIGTERM et retire un pod des endpoints du Service en parallèle, jamais dans un ordre garanti, une course qui explique les erreurs ponctuelles observées pendant un déploiement par ailleurs correctement configuré. Un hook preStop (une simple pause suffit souvent) achète le temps que la propagation des endpoints réclame, avant que l’application ne cesse réellement de répondre. terminationGracePeriodSeconds fixe la limite absolue au-delà de laquelle SIGKILL intervient, à dimensionner en fonction de la durée réelle des requêtes en vol, pas d’une valeur par défaut, un détail qui distingue un déploiement réellement sans coupure d’un déploiement qui se contente de le prétendre, central pour une migration Kubernetes qui vise une vraie continuité de service.