Module 3 · k8s par la pratique (via k9s)¶
Objectifs
- Consolider les objets k8s en les manipulant, pas en les lisant.
- Debugger les pannes classiques :
CrashLoopBackOff,ImagePullBackOff,Pending. - Maîtriser scale, rollout, édition à chaud, ConfigMap/Secret depuis k9s.
- Savoir lancer une tâche qui se termine (Job, CronJob).
- Durée : ~2 h 30 · Pré-requis : Module 1 et Module 2.
Le cycle de vie d'un pod¶
graph LR
P[Pending] --> CC[ContainerCreating] --> R[Running]
P -.image KO.-> W[ImagePullBackOff]
R --> S[Succeeded]
R --> F[Failed]
R -.le process meurt.-> C[CrashLoopBackOff]
C -.redémarre.-> R
R --> T[Terminating] --> X[supprimé]
| État affiché | Ce que ça veut dire | Où regarder |
|---|---|---|
Pending |
Accepté par l'API, pas encore sur un nœud (ressources ? volume ? scheduling ?) | d describe → events |
ContainerCreating |
Nœud choisi, k8s prépare le conteneur : tirage de l'image, montage des volumes | events |
ImagePullBackOff |
La préparation coince : image introuvable ou accès refusé | describe → events |
Running |
Le(s) conteneur(s) tourne(nt) | logs |
CrashLoopBackOff |
Le conteneur démarre puis meurt en boucle ; k8s le relance (délai croissant) | logs (--previous) |
Succeeded / Failed |
Le pod s'est terminé (fini avec succès / en erreur) - normal pour un Job | logs |
Terminating |
En cours de suppression (SIGTERM, puis SIGKILL après le délai de grâce) | events |
Phase réelle vs statut affiché
Kubernetes ne connaît que 4 phases : Pending · Running · Succeeded · Failed.
Mais la colonne STATUS de kubectl get pods (et k9s) est plus riche : elle affiche
souvent la raison du moment - ContainerCreating, ImagePullBackOff,
CrashLoopBackOff, Terminating.
Ce ne sont pas des phases, mais des états de conteneur ou des raisons d'attente. D'où le réflexe : lire les events, qui racontent ce que la phase seule cache.
La règle d'or du debug k8s
describe → events → logs. Dans cet ordre. 90 % des pannes se lisent dans les events.
ImagePullBackOff a deux visages
L'event du describe fait la différence : not found = l'image n'existe pas (tag mal
orthographié, le cas du TP) · unauthorized = l'image existe mais le registre est
privé - il faut donner au namespace un secret d'accès et le référencer dans le pod
(imagePullSecrets). Le secret est par namespace : ça marche dans dev et pas dans
prod ? C'est probablement ça.
TP de debug : chercher fait partie de l'exercice
Les TP de panne de ce module ne donnent volontairement pas la commande de correction
à l'avance : tu pars du symptôme et tu remontes à la cause. Les gestes nouveaux se
voient en séance, et la cheat-sheet kubectl garde la
famille kubectl set sous la main.
ConfigMaps & Secrets¶
Un ConfigMap stocke de la config non sensible ; un Secret des données sensibles (base64).
kubectl create configmap app-config --from-literal COLOR=blue
kubectl create secret generic app-secret --from-literal API_KEY=s3cr3t
Dans k9s : :cm et :secret. Sur un secret, x peut décoder les valeurs (selon version) ;
sinon y montre le base64.
Warning
Un Secret k8s n'est pas chiffré par défaut, juste encodé en base64. La « sécurité » vient du RBAC (qui peut le lire) - on en parle au module 4.
graph LR
CM[ConfigMap<br/>config non sensible] -->|env / envFrom| C[Conteneur du Pod]
SE[Secret<br/>données sensibles] -->|env| C
SE -.fichier monté.-> C
L'app ne lit jamais un ConfigMap ou un Secret directement : Kubernetes en injecte le contenu dans le conteneur, en variables d'environnement ou en fichier monté. Même image, config différente selon l'environnement - on découple le code de sa configuration.
Pods multi-conteneurs : init containers & sidecars¶
Un pod contient le plus souvent un conteneur - mais pas toujours. Deux motifs à connaître, et surtout à ne pas confondre :
| Motif | Quand tourne-t-il ? | Cas d'usage |
|---|---|---|
| Init container | Avant l'app, jusqu'à terminaison (en séquence) | attendre la BDD, migrer un schéma, préparer une config |
| Sidecar | En même temps que l'app, tout du long | agent de logs, proxy, rechargement de config |
spec:
initContainers: # chacun doit réussir avant que l'app démarre
- name: attend-bdd
image: busybox
command: ["sh", "-c", "until nc -z db 5432; do sleep 1; done"]
containers: # puis l'app (et ses éventuels sidecars)
- name: app
image: monapp:1.0
Le test pour les distinguer : « ce conteneur doit-il se terminer avant que l'app
démarre (init), ou vivre avec elle (sidecar) ? ». Dans k9s, les conteneurs d'un pod
se voient en drill-down (Entrée sur le pod) - et un statut Init:0/1 signifie
« j'attends mes init containers » : encore un état à savoir lire.
graph LR
I1[init-1] -->|finit| I2[init-2] -->|finit| A[app démarre]
A -.tournent ensemble.- SC[sidecar]
Les init containers s'exécutent en séquence et doivent tous finir avant que l'app démarre ; le sidecar démarre avec l'app et vit tout du long. Lire le temps de gauche à droite : c'est la frise des deux motifs.
Scaling & rollout¶
kubectl scale deploy web --replicas 5
kubectl set image deploy/web nginx=nginx:1.27 # déclenche un rolling update
kubectl rollout status deploy/web
kubectl rollout undo deploy/web # rollback
Dans k9s, sur un deployment : s pour scaler, r pour restart, l pour suivre les logs pendant le déploiement.
La stratégie derrière le ballet
Par défaut (strategy: RollingUpdate), le remplacement est borné par maxSurge
(combien de pods en plus pendant la bascule) et maxUnavailable (combien en
moins) - 25 % chacun. L'alternative Recreate tue tout puis recrée : coupure
assumée, mais nécessaire quand deux versions ne peuvent pas cohabiter (ex. un volume
qu'un seul pod peut monter - module 4b).
Éditer à chaud - e dans k9s, kubectl edit au terminal¶
Troisième façon de modifier un objet, la plus directe : ouvrir l'objet vivant dans un éditeur, changer un champ, sauvegarder - l'API applique aussitôt.
Dans k9s : e sur n'importe quelle ressource. C'est l'outil du dépannage (corriger une image, un label, une variable, tout de suite) et de l'exploration (voir l'objet complet, avec les champs que tu n'as jamais écrits).
Puissant, mais sans trace
Ce que tu changes par edit n'est écrit nulle part ailleurs que dans le cluster :
pas de fichier, pas d'historique, pas de revue. Et certains champs sont immutables
(l'éditeur rouvre avec l'erreur en commentaire - lis-la). Dépanner oui, livrer non :
c'est tout l'objet du module 3y, et le
module 4e te montrera même un robot qui annule tes edits.
Jobs & CronJobs - les pods qui se terminent¶
Tout ce qu'on a vu jusqu'ici tourne en continu (un serveur web ne « finit » jamais).
Mais une migration de schéma, une sauvegarde, un batch de nuit : ces tâches doivent se
terminer. C'est le rôle du Job - et un pod en état Completed est alors un
succès, pas une panne (c'est l'état Succeeded du schéma du cycle de vie).
| Objet | Rôle | En une phrase |
|---|---|---|
| Job | la tâche à finir | lance un pod et le relance jusqu'au succès (backoffLimit essais) |
| CronJob | la tâche planifiée | crée un Job selon un planning cron (0 3 * * * = 3 h du matin) |
kubectl create job migration --image busybox:1.36 -- sh -c "echo migration ok"
kubectl create cronjob sauvegarde --image busybox:1.36 --schedule "0 3 * * *" -- date
kubectl create job test-svg --from cronjob/sauvegarde # déclencher SANS attendre 3 h du matin
Dans k9s : :job et :cronjob. Les logs d'un pod Completed restent lisibles
(l) - c'est même tout l'intérêt : le compte rendu de la tâche.
Tip
Un Job utilise restartPolicy: Never ou OnFailure - jamais Always (sinon le pod
redémarrerait après le succès… et ta « tâche finie » ne finirait jamais).
Exercices¶
Exercice - CrashLoopBackOff
kubectl run crash --image busybox:1.36 -- sh -c "echo 'démarrage du worker...'; sleep 2; echo 'FATAL: variable DB_HOST absente'; exit 1"
- Dans k9s, observe le pod boucler (
Running→Error→CrashLoopBackOff). - Lis les logs (l) : selon le moment, tu ne vois que « démarrage… » (le
conteneur vient de renaître). Puis
kubectl logs crash --previous: là, la vraie cause -FATAL: variable DB_HOST absente. - Explique pourquoi k8s le redémarre en boucle (
restartPolicy).
Exercice - ImagePullBackOff
- d describe → trouve l'event exact.
- Corrige : supprime et relance avec
nginx.
Exercice - Config injectée
kubectl create configmap demo --from-literal MESSAGE="Salut la sandbox"
kubectl create deployment reader --image nginx:1.27-alpine
kubectl set env deployment/reader --from configmap/demo # injecte toutes les clés du CM
kubectl exec deploy/reader -- env | grep MESSAGE: la variable injectée est là.- Dans k9s : e sur le deployment
reader→ repère le blocenvFromajouté.
Exercice - Rolling update + rollback
kubectl create deployment site --image nginx:1.25 --replicas 3
kubectl set image deploy/site nginx=nginx:1.27
- Suis le rollout dans k9s (les pods se remplacent un à un).
- Casse-le volontairement :
kubectl set image deploy/site nginx=nginx:nope. - Observe les pods bloqués, puis
kubectl rollout undo deploy/site.
Exercice - Édition à chaud
- Sur le deployment
site, appuie sur e dans k9s. - Trouve
spec.replicaset passe-le à 4. Sauvegarde, regarde la liste des pods. - Même geste pour revenir à 3. Tu viens de faire un
kubectl editsans le savoir.
Comment ça s'emboîte¶
La même app, vue d'ensemble - pas une liste d'objets, un système où chacun se branche :
graph TD
D[Deployment] -->|maintient| RS[ReplicaSet]
RS -->|crée / remplace| P[Pods]
SVC[Service] -->|selector = labels| P
SVC -->|pods prêts| EP[Endpoints]
CM[ConfigMap] -.injecté.-> P
SEC[Secret] -.injecté.-> P
Trois choses à voir : on pilote le Deployment (jamais un Pod nu) ; le Service trouve ses pods par labels (pas par IP) et n'envoie le trafic qu'aux pods prêts listés dans les Endpoints ; la config (ConfigMap/Secret) arrive dans le Pod par injection. Garde cette carte en tête : au module 4r on y ajoutera l'Ingress devant le Service, au 4b le stockage sous le Pod.
Et après ? Du clavier au fichier¶
Tout ce module est impératif (commandes à la main) : parfait pour explorer et debugger. L'étape suivante - décrire ces mêmes objets dans des manifestes YAML qu'on applique - a son module dédié : Module 3y - Le déclaratif (YAML).
Pratique¶
Dans le terminal de ta sandbox (à droite), les TP auto-corrigés de ce module - à dérouler une fois la lecture faite :
tp start 02-deploiement # déploiement, scale, service
tp start 02-rollout # mise à jour (rolling update), rollback, édition à chaud
tp start 02-batch # jobs & cronjobs : les pods qui se terminent
tp start 02-debug-image # debug : un déploiement qui ne démarre pas (ImagePullBackOff)
tp start 02-debug-service # debug : un service qui ne route nulle part
tp start 02-debug-crashloop # debug : un pod en CrashLoopBackOff (réflexe logs)
fais la manip puis tp check (ou tp watch pour l'auto-validation) · tp hint / tp solution si tu bloques · tp status pour ta progression.
Ce qu'il faut retenir¶
- describe → events → logs : le triptyque du debug.
- Init container = avant l'app (et doit réussir) · sidecar = à côté de l'app.
CrashLoopBackOff= problème applicatif (logs--previous).ImagePullBackOff/Pending= problème plateforme (events du describe).- Scale / rollout / rollback : Kubernetes gère le remplacement progressif pour toi.
- e /
kubectl editdépanne à chaud - mais sans trace : à réserver à l'urgence. - Un pod
Completedn'est pas une panne : c'est un Job qui a fini son travail. - L'impératif sert à explorer ; pour livrer, on passe au déclaratif (module 3y).
Quiz éclair¶
Teste-toi : réponds de tête, puis déplie.
1. CrashLoopBackOff vs ImagePullBackOff : où regardes-tu d'abord ?
CrashLoopBackOff = problème applicatif → les logs (kubectl logs --previous).
ImagePullBackOff = problème plateforme (image/registre) → describe → events.
2. Un pod reste en Pending depuis 5 minutes. Quelles pistes ?
describe → Events : pas de nœud avec assez de ressources, volume non disponible,
ou contrainte de scheduling impossible à satisfaire. Pending = problème plateforme.
3. Une mise à jour s'est mal passée. Comment revenir en arrière ?
kubectl rollout undo deploy/<nom> - Kubernetes rebascule sur la révision précédente.
4. Un pod affiche Completed depuis une heure. Tu t'inquiètes ?
Non - Completed signifie que le conteneur a fini avec succès. C'est l'état
normal d'un pod de Job (ou d'un init container). Ses logs restent lisibles :
c'est le compte rendu de la tâche.
5. Corriger un champ d'un objet immédiatement, sans fichier sous la main ?
e dans k9s ou kubectl edit : l'objet vivant s'ouvre dans l'éditeur,
sauvegarder = appliquer. Mais aucune trace hors du cluster - en prod, on répercute
ensuite dans le manifeste (module 3y) sous peine de voir le correctif disparaître.
➡️ Suite : Module 2b - k9s approfondi (optionnel, pour gagner en vitesse) · puis Module 3y - Le déclaratif (YAML)