Introduction
Comprendre le cycle de vie des Pods Kubernetes est essentiel pour quiconque exécute des charges de travail conteneurisées en production. Les Pods sont les plus petites unités déployables dans Kubernetes, et leur comportement de la création à la terminaison a un impact direct sur la disponibilité, la résilience et l'efficacité opérationnelle des applications. Ce guide va au-delà des bases pour explorer des concepts avancés tels que les hooks de cycle de vie, la configuration des sondes, les conteneurs d'initialisation et les modèles d'arrêt gracieux.
Que vous soyez développeur, SRE ou ingénieur DevOps, vous repartirez avec des exemples concrets, des commandes précises et des stratégies de dépannage pour maintenir vos applications en bon état de fonctionnement. Nous nous concentrerons sur des scénarios réels, en montrant comment inspecter les états des Pods, diagnostiquer les échecs et mettre en œuvre des stratégies de déploiement robustes.
Toutes les commandes et tous les exemples supposent un cluster Kubernetes fonctionnel (version 1.21 ou ultérieure) et kubectl configuré avec les accès appropriés. Si vous débutez avec Kubernetes, envisagez de consulter la documentation officielle sur les Pods avant de poursuivre.
Phases du cycle de vie d'un Pod
Le champ « status » d'un Pod est un résumé de haut niveau de sa position dans son cycle de vie. La phase n'est pas destinée à être une machine à états exhaustive, mais plutôt un simple agrégat des états des conteneurs. Les cinq phases possibles sont :
- Pending : le Pod a été accepté par le système Kubernetes, mais un ou plusieurs conteneurs n'ont pas encore été créés. Cela inclut le temps passé à attendre la planification ainsi que le téléchargement des images de conteneurs.
- Running : le Pod a été lié à un nœud et tous les conteneurs ont été créés. Au moins un conteneur est encore en cours d'exécution, ou est en train de démarrer ou de redémarrer.
- Succeeded : tous les conteneurs du Pod se sont terminés avec succès et ne seront pas redémarrés.
- Failed : tous les conteneurs du Pod se sont terminés, et au moins un conteneur s'est terminé en échec.
- Unknown : l'état du Pod n'a pas pu être obtenu, généralement en raison d'une erreur de communication avec l'hôte où le Pod devrait s'exécuter.
Pour inspecter la phase d'un Pod, utilisez :
kubectl get pod my-pod -o jsonpath='{.status.phase}'
Par exemple, si la commande renvoie Running, le Pod est actif. Cependant, un Pod en état Running ne garantit pas que votre application est saine ; cela signifie simplement que les conteneurs sont opérationnels. Nous avons besoin de sondes (abordées plus loin) pour vérifier la vivacité et la préparation.
Analyse approfondie : Pods en attente (Pending)
Un Pod bloqué en Pending indique souvent des problèmes de planification ou des contraintes de ressources. Pour diagnostiquer, exécutez :
kubectl describe pod my-pod
Recherchez les événements en bas de la sortie. Les raisons courantes incluent :
- CPU ou mémoire insuffisants sur les nœuds.
- Aucun nœud ne correspond au sélecteur de nœud ou aux règles d'affinité du Pod.
- La revendication de volume persistant (PersistentVolumeClaim) ne peut pas être liée.
- Erreurs de tirage d'image (bien que celles-ci puissent également apparaître comme
PendingouContainerCreating).
Par exemple, si vous voyez un événement comme 0/3 nodes are available: 3 Insufficient memory, vous devez soit réduire les demandes de ressources du Pod, soit ajouter de la capacité au cluster.
États des conteneurs et politiques de redémarrage
Chaque conteneur dans un Pod a un état : Waiting, Running ou Terminated. L'état Waiting inclut une raison telle que ContainerCreating ou CrashLoopBackOff. Cette dernière indique que le conteneur plante à plusieurs reprises et est redémarré par le kubelet conformément à la restartPolicy du Pod (Always, OnFailure ou Never).
Pour voir les états détaillés des conteneurs :
kubectl get pod my-pod -o json | jq '.status.containerStatuses'
Exemple de sortie :
[
{
"name": "app",
"state": {
"waiting": {
"reason": "CrashLoopBackOff",
"message": "Back-off 5m0s restarting failed container=app pod=my-pod_default(...)"
}
},
"lastState": {
"terminated": {
"exitCode": 1,
"reason": "Error",
"startedAt": "2024-01-15T10:00:00Z",
"finishedAt": "2024-01-15T10:00:05Z"
}
},
"ready": false,
"restartCount": 3,
"image": "nginx:1.14.2",
"imageID": "docker-pullable://nginx@sha256:...",
"started": false
}
]
Lors du dépannage des plantages, récupérez les journaux de l'instance précédente avec :
kubectl logs my-pod --previous
Cela est précieux pour identifier les erreurs d'application qui ont provoqué le plantage.
Sondes : garantir la santé de l'application
Kubernetes fournit trois types de sondes pour gérer la santé et le cycle de vie des conteneurs :
- Sonde de vivacité (liveness probe) : détermine si le conteneur est en cours d'exécution. En cas d'échec, le kubelet tue le conteneur et le redémarre selon la politique de redémarrage.
- Sonde de préparation (readiness probe) : détermine si le conteneur est prêt à accepter du trafic. En cas d'échec, l'IP du Pod est retirée des points de terminaison du service jusqu'à ce qu'elle réussisse.
- Sonde de démarrage (startup probe) : détermine si l'application à l'intérieur du conteneur a démarré. Si elle est fournie, toutes les autres sondes sont désactivées jusqu'à ce qu'elle réussisse. Cela est utile pour les applications à démarrage lent afin d'éviter des échecs précoces de vivacité.
Les sondes peuvent être configurées pour utiliser des requêtes HTTP GET, des vérifications de socket TCP ou exécuter des commandes à l'intérieur du conteneur. Voici un exemple de manifeste de Pod avec les trois sondes :
apiVersion: v1
kind: Pod
metadata:
name: probe-example
spec:
containers:
- name: app
image: my-app:v1
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
Paramètres clés :
initialDelaySeconds: délai avant la première sonde.periodSeconds: fréquence d'exécution de la sonde.timeoutSeconds: délai d'expiration de la sonde.successThreshold: nombre minimal de succès consécutifs après un échec pour être considéré comme sain.failureThreshold: échecs consécutifs avant d'être marqué comme non sain.
Bonnes pratiques pour les sondes
- Utilisez des sondes de préparation pour vous assurer que votre application a établi des connexions à la base de données ou chargé des caches avant de servir les requêtes.
- Évitez les sondes de vivacité trop agressives ; une sonde de vivacité qui échoue en raison d'une lenteur temporaire peut redémarrer un conteneur inutilement.
- Pour les applications avec des temps de démarrage longs, utilisez une sonde de démarrage avec un seuil d'échec généreux pour éviter des redémarrages prématurés.
Hooks de cycle de vie : PostStart et PreStop
Les hooks de cycle de vie vous permettent d'exécuter du code à des moments spécifiques du cycle de vie d'un conteneur. Les deux hooks sont :
- PostStart : appelé immédiatement après la création d'un conteneur. Il s'exécute de manière asynchrone avec le processus principal du conteneur, il n'y a donc aucune garantie qu'il se termine avant que le conteneur ne commence à accepter du trafic.
- PreStop : appelé immédiatement avant qu'un conteneur ne soit terminé en raison d'une requête API, d'un échec de sonde de vivacité ou de contention de ressources. C'est un appel bloquant et il doit se terminer avant que le conteneur ne soit terminé, sauf si la période de grâce expire.
Les hooks peuvent soit exécuter une commande à l'intérieur du conteneur, soit effectuer une requête HTTP. Un cas d'utilisation courant de PreStop est d'arrêter gracieusement un serveur web, permettant aux requêtes en cours de se terminer.
Exemple avec des hooks de cycle de vie :
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-example
spec:
containers:
- name: nginx
image: nginx:1.19
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from postStart > /usr/share/nginx/html/index.html"]
preStop:
exec:
command: ["/usr/sbin/nginx", "-s", "quit"]
Dans cet exemple, le hook PostStart écrit un message personnalisé sur la page web par défaut, et le hook PreStop arrête gracieusement Nginx.
Arrêt gracieux
Lorsqu'un Pod est supprimé, Kubernetes envoie un signal SIGTERM à chaque conteneur et attend une période de grâce (30 secondes par défaut) avant de le tuer de force avec SIGKILL. Le champ terminationGracePeriodSeconds peut être défini dans la spécification du Pod pour remplacer cette valeur. Le hook PreStop est exécuté avant le SIGTERM, permettant aux applications de nettoyer.
Pour observer cela, créez un Pod avec une longue période de grâce de terminaison et un hook PreStop qui dort :
apiVersion: v1
kind: Pod
metadata:
name: graceful-shutdown
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
image: busybox
command: ["/bin/sh", "-c", "trap 'echo received SIGTERM; sleep 20; exit 0' TERM; while true; do sleep 1; done"]
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo preStop executed; sleep 10"]
Lorsque vous supprimez ce Pod (kubectl delete pod graceful-shutdown), le hook PreStop s'exécute en premier, puis le gestionnaire SIGTERM dans le conteneur s'exécute, et seulement après 60 secondes (s'il n'est toujours pas terminé) SIGKILL est envoyé.
Conteneurs d'initialisation (Init Containers)
Les conteneurs d'initialisation sont des conteneurs spécialisés qui s'exécutent avant les conteneurs d'application principaux. Ils sont utiles pour préparer l'environnement, comme attendre qu'une base de données soit prête, définir les autorisations de fichiers ou récupérer la configuration. Chaque conteneur d'initialisation doit se terminer avec succès avant que le suivant ne démarre.
Exemple : attendre qu'un service soit disponible avant de démarrer l'application principale.
apiVersion: v1
kind: Pod
metadata:
name: init-example
spec:
initContainers:
- name: wait-for-db
image: busybox:1.28
command: ['sh', '-c', 'until nc -z db 5432; do echo waiting for db; sleep 2; done;']
containers:
- name: app
image: my-app:v1
Si vous inspectez le Pod avec kubectl describe pod init-example, vous verrez le statut du conteneur d'initialisation et les événements montrant son achèvement.
Scénarios de dépannage du cycle de vie des Pods
Scénario 1 : Pod bloqué en CrashLoopBackOff
Étapes :
- Vérifiez les journaux du conteneur planté :
kubectl logs my-pod --previous
- Si les journaux ne sont pas utiles, vérifiez les événements :
kubectl describe pod my-pod
- Examinez les codes de sortie. Par exemple, le code de sortie 1 indique généralement une erreur d'application, tandis que 137 suggère OOMKilled.
- Si c'est OOMKilled, examinez les limites de mémoire dans la spécification du conteneur et ajustez si nécessaire.
Scénario 2 : La sonde de préparation échoue en permanence
- Vérifiez que le point de terminaison de la sonde est correct et renvoie un statut 200.
- Vérifiez si l'application écoute sur le bon port.
- Augmentez
initialDelaySecondssi l'application a besoin de plus de temps pour démarrer. - Utilisez
kubectl execpour tester la connectivité depuis le cluster :
kubectl exec -it my-pod -- curl localhost:8080/ready
Scénario 3 : Le Pod se termine pendant longtemps
- Vérifiez
terminationGracePeriodSeconds; il est peut-être trop élevé. - Assurez-vous que le hook PreStop ne bloque pas (par exemple, en attendant une ressource qui n'arrive jamais).
- Si l'application ignore SIGTERM, vous devrez peut-être gérer le signal dans votre code.
Liste de contrôle opérationnelle pour la gestion du cycle de vie des Pods
Utilisez cette liste quotidiennement pour maintenir des Pods sains :
- Surveillez les phases des Pods et les redémarrages avec :
kubectl get pods --all-namespaces -o wide
- Définissez des demandes et des limites de ressources pour tous les conteneurs afin d'éviter OOMKilled et les échecs de planification.
- Configurez correctement les sondes de vivacité, de préparation et de démarrage.
- Définissez des hooks PreStop pour un arrêt gracieux, en particulier pour les applications avec état.
- Utilisez des conteneurs d'initialisation pour les tâches d'initialisation afin de garder les conteneurs principaux légers.
- Gardez les images de conteneurs minimales et à jour pour réduire le temps de démarrage et les risques de sécurité.
- Testez le comportement du cycle de vie des Pods sous charge : simulez des pannes de nœuds, des partitions réseau et des mises à jour progressives.
- Pour les charges de travail critiques, envisagez d'utiliser des PodDisruptionBudgets pour limiter les perturbations volontaires.
- Examinez régulièrement les événements du cluster pour détecter les problèmes récurrents de cycle de vie :
kubectl get events --sort-by=.metadata.creationTimestamp
Conclusion
Maîtriser le cycle de vie des Pods Kubernetes est la pierre angulaire de l'exploitation d'applications conteneurisées fiables. En comprenant les phases, les sondes, les hooks de cycle de vie et l'arrêt gracieux, vous pouvez concevoir des systèmes résilients qui gèrent les pannes avec élégance et maintiennent une haute disponibilité. Les exemples pratiques et les commandes fournis ici vous donnent une base solide pour dépanner et optimiser vos propres déploiements. Continuez à explorer des sujets avancés tels que les StatefulSets, les Jobs et les contrôleurs personnalisés pour approfondir votre expertise Kubernetes. N'oubliez pas que l'observabilité est essentielle : surveillez toujours les journaux, les événements et les métriques pour détecter les problèmes de cycle de vie avant qu'ils n'affectent les utilisateurs.
Avec ces connaissances, vous êtes mieux équipé pour construire et exploiter des charges de travail Kubernetes de niveau production. Expérimentez avec les exemples dans un cluster de test et adaptez-les aux besoins spécifiques de votre application.