Kit du cours : https://github.com/hrhouma2/aiopsatlas-kubernetes-docker-desktop-labo-fr
Tu clones le kit dans un dossier lab2, tu vérifies que Docker Desktop et Kubernetes sont prêts, tu lis l'état d'un cluster vide, puis tu déploies l'API whoami de la bibliothèque en cinq réplicas derrière un Service LoadBalancer sur http://localhost:8080. Ensuite tu casses deux choses exprès (une image qui n'existe pas, un -n oublié), tu répares, et tu exposes le frontend en NodePort. Garde un second terminal ouvert avec kubectl get pods -n premiers-pas -w pour regarder les Pods bouger. Le détail de chaque étape suit ; commence par exécuter ce bloc.
Windows (PowerShell)
git clone https://github.com/hrhouma2/aiopsatlas-kubernetes-docker-desktop-labo-fr.git lab2
cd lab2
ls # explorer le contenu : labo.ps1, labo.sh, un dossier par module (01-… à 08-…)
$env:Path = "C:\Program Files\Docker\Docker\resources\bin;" + $env:Path # kubectl de Docker Desktop en priorité
.\labo.ps1 prerequis
.\labo.ps1 etat # cluster vide : attendu docker-desktop Ready, aucun namespace du coursDans un second terminal (à garder visible tout le lab) :
kubectl get pods -n premiers-pas -wLes fichiers de ce lab sont dans 01-installer-kubernetes-avec-docker-desktop\pratique\. Vérifier ensuite dans le navigateur : http://localhost:8080 (API whoami).
.\labo.ps1 etat # livrable : premiers-pas 7/7 pods Running et deux Services
.\labo.ps1 nettoyer # à la fin, supprime les namespaces du coursSi PowerShell refuse .\labo.ps1 (« l'exécution de scripts est désactivée ») : Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, réponds O, relance.
Linux, macOS, WSL 2, Git Bash
git clone https://github.com/hrhouma2/aiopsatlas-kubernetes-docker-desktop-labo-fr.git lab2
cd lab2
ls # explorer le contenu : labo.sh, labo.ps1, un dossier par module (01-… à 08-…)
./labo.sh prerequis
./labo.sh etat # cluster vide : attendu docker-desktop Ready, aucun namespace du coursDans un second terminal (à garder visible tout le lab) :
kubectl get pods -n premiers-pas -wLes fichiers de ce lab sont dans 01-installer-kubernetes-avec-docker-desktop/pratique/. Vérifier ensuite dans le navigateur : http://localhost:8080 (API whoami).
./labo.sh etat # livrable : premiers-pas 7/7 pods Running et deux Services
./labo.sh nettoyer # à la fin, supprime les namespaces du coursTu rejoins l'équipe qui construit le catalogue de la bibliothèque de la plateforme de cours. Ta cheffe te tend un portable : « Demain matin, je veux que Kubernetes tourne sur ton poste, que tu me prouves en une commande que tout est en ordre, et que tu ne m'appelles pas la première fois qu'un Pod refuse de démarrer. » Tu vas donc installer le kit du cours, dérouler sa liste de contrôle, déployer l'API et la regarder se multiplier en direct, puis casser deux choses exprès : demander une image qui n'existe pas, et oublier -n. Dans les deux cas tu liras le message, tu nommeras la cause et tu répareras. Savoir distinguer « le cluster refuse ce que je lui demande » de « je regarde au mauvais endroit », c'est ce qui évite une heure perdue. Le parcours : kit et prerequis → etat à vide → api derrière un LoadBalancer, sous -w → cinq réplicas et dix requêtes → deux pannes provoquées → réparation → frontend en NodePort → livrable.
premiers-pas ne doit plus exister : kubectl get namespace premiers-pas répond NotFound (sinon kubectl delete namespace premiers-pas d'abord).git clone https://github.com/hrhouma2/aiopsatlas-kubernetes-docker-desktop-labo-fr.git, puis place-toi à sa racine (celle qui contient labo.ps1 et labo.sh). Toutes les commandes ci-dessous sont lancées depuis là.kubectl get pods -n premiers-pas -w). PowerShell ou bash (Git Bash, WSL 2, macOS, Linux) : les commandes kubectl sont identiques, et chaque commande du kit existe dans les deux saveurs (.\labo.ps1 … / ./labo.sh …).$env:Path = "C:\Program Files\Docker\Docker\resources\bin;" + $env:Path.prerequisLe script du kit fait en une commande les vérifications des leçons 02 et 03 : docker, démon, Kubernetes Docker Desktop, contexte, versions client/serveur, helm, mémoire, ports. Partout dans cette pratique, la variante bash d'une commande du kit s'obtient en remplaçant .\labo.ps1 par ./labo.sh.
.\labo.ps1 prerequisPoint de contrôle : une coche ✔ par ligne et la phrase finale Tout est prêt. Sortie réelle (la version bash affiche les mêmes lignes, avec le chemin /c/Program Files/Docker/Docker/resources/bin/kubectl) :
== Prérequis ==
✔ docker : Docker version 29.3.1, build c2be9cc
✔ le démon Docker répond (Docker Engine 29.3.1)
✔ Kubernetes Docker Desktop : running (mode kubeadm, 1 nœud, v1.34.1)
✔ contexte kubectl courant : docker-desktop
✔ kubectl client v1.34.1 (C:\Program Files\Docker\Docker\resources\bin\kubectl.exe)
✔ serveur Kubernetes v1.34.1 (même version mineure que le client)
✔ helm : v4.2.4+g3900f43
✔ mémoire disponible pour Docker : 31 Go
✔ port 8080 : libre
✔ port 8443 : libre
✔ port 30080 : libre
Tout est prêt. Étape suivante : .\labo.ps1 etatLis la ligne kubectl client : elle te dit quel binaire répond, avec son chemin. C'est la réponse en une ligne au problème de la leçon 03. Helm ne sert qu'au module 7 : s'il manque, la ligne passe en ! (avertissement), pas en ✘.
Si tu vois autre chose : ✘ kubectl client v1.30.0 trop ancien (minimum 1.33) — mettez C:\Program Files\Docker\Docker\resources\bin en tête du PATH ; where.exe kubectl montre l'ordre actuel (sortie réelle obtenue avec un vieux kubectl en tête du PATH) → leçon 03, étape 3 ; ✘ le démon Docker ne répond pas → Docker Desktop n'est pas lancé ; PowerShell refuse d'exécuter le script → Set-ExecutionPolicy -Scope CurrentUser RemoteSigned une fois pour toutes.
etat sur un cluster videAvant de créer quoi que ce soit, regarde ce que le kit affiche quand il n'y a rien. Tu compareras avec l'étape 9.
.\labo.ps1 etatPoint de contrôle : le nœud Ready, premiers-pas marqué absent, aucun Service exposé. Sortie réelle (les lignes des autres namespaces du cours, qui apparaîtront module après module, sont coupées) :
== Nœuds ==
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane 18d v1.34.1
== Namespaces du cours ==
— premiers-pas absent
== Services exposés (LoadBalancer / NodePort) dans les namespaces du cours ==
— aucun Service exposé pour l'instant (les leçons en créent avec kubectl expose)Trois symboles à connaître : ✔ tout tourne, ! quelque chose mérite un regard (des Pods pas Running, un avertissement), — information neutre (absent, rien à afficher). Le script ne regarde que les namespaces du cours : ce qui vit dans default ou kube-system ne l'intéresse pas.
Dans le second terminal, lance la surveillance et ne la quitte plus jusqu'au nettoyage :
kubectl get pods -n premiers-pas -wIl ne se passe rien tant que le namespace n'existe pas, c'est normal. Dans le premier terminal :
kubectl create namespace premiers-pas
kubectl create deployment api --image=traefik/whoami:v1.10 --port=80 -n premiers-pas
kubectl expose deployment api --type=LoadBalancer --port=8080 --target-port=80 -n premiers-pas
kubectl get svc -n premiers-pasPoint de contrôle : trois confirmations puis un Service avec EXTERNAL-IP localhost.
namespace/premiers-pas created
deployment.apps/api created
service/api exposed
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api LoadBalancer 10.96.92.182 localhost 8080:31427/TCP 5sPendant ce temps, le second terminal a affiché la naissance du premier Pod, ligne par ligne :
NAME READY STATUS RESTARTS AGE
api-9bfb55fc6-mh24c 0/1 Pending 0 0s
api-9bfb55fc6-mh24c 0/1 Pending 0 0s
api-9bfb55fc6-mh24c 0/1 ContainerCreating 0 0s
api-9bfb55fc6-mh24c 1/1 Running 0 2sPending (le scheduler cherche un nœud), ContainerCreating (le kubelet demande le conteneur au runtime), Running : deux secondes, parce que l'image est déjà sur la machine depuis la leçon 04. Vérifie que ça répond :
curl -s http://localhost:8080curl.exe -s http://localhost:8080Hostname: api-9bfb55fc6-mh24c
IP: 10.1.2.255
RemoteAddr: 192.168.65.3:49552
Host: localhost:8080Extrait : Hostname est le nom du Pod qui a répondu, IP son adresse dans le cluster ; RemoteAddr: 192.168.65.3 est l'adresse du nœud, par laquelle Docker Desktop fait entrer ta requête. Le même Deployment et le même Service, écrits en YAML complet, sont dans le kit : 01-installer-kubernetes-avec-docker-desktop/04-api-declaratif.yaml (module 2 pour les lire ligne à ligne).
Si tu vois autre chose : EXTERNAL-IP <pending> qui ne bouge pas → un autre Service LoadBalancer publie déjà le port 8080 (kubectl get svc -A), supprime-le ou change de port ; AlreadyExists → le namespace traînait de la leçon 04, kubectl delete namespace premiers-pas puis recommence.
Regarde bien le second terminal au moment où tu tapes ceci :
kubectl scale deployment api --replicas=5 -n premiers-pas
kubectl get pods -n premiers-pas -o widePoint de contrôle : cinq Pods Running, chacun avec sa propre IP 10.1.x.x, tous sur le nœud docker-desktop.
deployment.apps/api scaled
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
api-9bfb55fc6-7b7br 1/1 Running 0 15s 10.1.3.1 docker-desktop <none> <none>
api-9bfb55fc6-hp76n 1/1 Running 0 15s 10.1.3.3 docker-desktop <none> <none>
api-9bfb55fc6-mh24c 1/1 Running 0 27s 10.1.2.255 docker-desktop <none> <none>
api-9bfb55fc6-r4d5w 1/1 Running 0 15s 10.1.3.0 docker-desktop <none> <none>
api-9bfb55fc6-rd95r 1/1 Running 0 15s 10.1.3.2 docker-desktop <none> <none>Le premier Pod (mh24c, 27 s) a douze secondes de plus que les quatre autres : c'est celui de l'étape 3.
Le second terminal a montré les quatre nouveaux Pods passer ensemble par Pending, ContainerCreating puis Running en trois secondes (extrait réel) :
api-9bfb55fc6-r4d5w 0/1 Pending 0 0s
api-9bfb55fc6-r4d5w 0/1 ContainerCreating 0 0s
api-9bfb55fc6-hp76n 1/1 Running 0 2s
api-9bfb55fc6-r4d5w 1/1 Running 0 3sLe kit contient un petit script qui envoie dix requêtes et compte les Pods distincts qui ont répondu :
.\01-installer-kubernetes-avec-docker-desktop\pratique\repartition.ps1./01-installer-kubernetes-avec-docker-desktop/pratique/repartition.shSortie réelle (PowerShell ; le script affiche d'abord les dix Hostname reçus, un par ligne, puis ce décompte) :
10 requêtes, 5 Pods distincts ont répondu :
api-9bfb55fc6-7b7br x2
api-9bfb55fc6-hp76n x2
api-9bfb55fc6-mh24c x2
api-9bfb55fc6-r4d5w x2
api-9bfb55fc6-rd95r x2Le même script lancé en bash juste après a donné 10 requêtes, 4 Pods distincts avec un Pod servi quatre fois : la répartition est aléatoire, pas un tour de rôle. Sur dix requêtes, voir quatre ou cinq Pods est normal ; en voir un seul ne l'est pas (voir « Si ça coince »). Le script ne fait rien de magique : une boucle de curl qui garde la ligne Hostname:. Ouvre-le pour le lire.
Le kit contient un Pod dont le tag d'image est faux. Lis-le avant de l'appliquer : 01-installer-kubernetes-avec-docker-desktop/pratique/01-pod-image-inexistante.yaml.
apiVersion: v1
kind: Pod
metadata:
name: api-cassee
namespace: premiers-pas
labels:
app: api-cassee
partie: bibliotheque
spec:
containers:
- name: whoami
image: traefik/whoami:v9.99
ports:
- containerPort: 80kubectl apply -f 01-installer-kubernetes-avec-docker-desktop/pratique/01-pod-image-inexistante.yaml
kubectl get pods -n premiers-pasPoint de contrôle : le Pod existe mais n'est pas Running, et les cinq autres ne sont pas affectés.
pod/api-cassee created
NAME READY STATUS RESTARTS AGE
api-9bfb55fc6-7b7br 1/1 Running 0 27s
api-9bfb55fc6-mh24c 1/1 Running 0 39s
api-cassee 0/1 ErrImagePull 0 4s(Trois lignes api-9bfb55fc6-… identiques coupées.)
Dans le second terminal, l'état oscille : le kubelet réessaie, échoue, attend de plus en plus longtemps (extrait réel) :
api-cassee 0/1 ErrImagePull 0 2s
api-cassee 0/1 ImagePullBackOff 0 17s
api-cassee 0/1 ErrImagePull 0 29sget te dit que ça échoue ; seul describe te dit pourquoi. Va droit à la section Events, en bas :
kubectl describe pod api-cassee -n premiers-pasSortie réelle (seule la fin est montrée) :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 34s default-scheduler Successfully assigned premiers-pas/api-cassee to docker-desktop
Normal Pulling 18s (x2 over 34s) kubelet Pulling image "traefik/whoami:v9.99"
Warning Failed 18s (x2 over 34s) kubelet Failed to pull image "traefik/whoami:v9.99": Error response from daemon: failed to resolve reference "docker.io/traefik/whoami:v9.99": docker.io/traefik/whoami:v9.99: not found
Warning Failed 18s (x2 over 34s) kubelet Error: ErrImagePull
Normal BackOff 6s (x2 over 33s) kubelet Back-off pulling image "traefik/whoami:v9.99"
Warning Failed 6s (x2 over 33s) kubelet Error: ImagePullBackOffTout y est : qui (kubelet), quoi (Failed to pull image), pourquoi (docker.io/traefik/whoami:v9.99: not found), et le rythme (x2 over 34s). Un réflexe à perdre tout de suite : chercher la cause dans les logs. Il n'y en a pas, le conteneur n'a jamais démarré :
kubectl logs api-cassee -n premiers-pasError from server (BadRequest): container "whoami" in pod "api-cassee" is waiting to start: image can't be pulledRESTARTS reste à 0 pour la même raison. Écris en une phrase la cause de la panne : tu en auras besoin à l'étape 9.
-nTu veux vérifier le Pod cassé, et tu tapes la commande sans le namespace :
kubectl get pod api-cassee
kubectl logs api-casseePoint de contrôle : deux erreurs NotFound, alors que le Pod existe bel et bien.
Error from server (NotFound): pods "api-cassee" not found
error: error from server (NotFound): pods "api-cassee" not found in namespace "default"La seconde ligne dit tout : in namespace "default". Sans -n, kubectl cherche dans le namespace du contexte, default, où il n'y a pas d'api-cassee. Le Pod n'a pas disparu, tu regardes dans le mauvais tiroir. Preuve :
kubectl get pod api-cassee -n premiers-pasNAME READY STATUS RESTARTS AGE
api-cassee 0/1 ErrImagePull 0 36sRetiens la règle : un NotFound sur un objet que tu viens de créer, c'est presque toujours un -n oublié. Ce cours ne change jamais le namespace par défaut du contexte, précisément pour que tu prennes l'habitude de le dire à chaque commande.
kubectl set imageUn Pod dont l'image ne se télécharge pas ne se répare pas en attendant. Deux remèdes : le supprimer et réappliquer un YAML corrigé, ou changer l'image en place. Fais la seconde, la plus rapide, en regardant le second terminal :
kubectl set image pod/api-cassee whoami=traefik/whoami:v1.10 -n premiers-pas
kubectl get pod api-cassee -n premiers-paswhoami est le nom du conteneur dans le YAML (containers[0].name), pas celui de l'image.
Point de contrôle :
pod/api-cassee image updated
NAME READY STATUS RESTARTS AGE
api-cassee 1/1 Running 0 44sLe second terminal a montré la bascule ImagePullBackOff → Running entre la 36e et la 37e seconde de vie du Pod : une seconde, parce que traefik/whoami:v1.10 est déjà sur la machine. Note que RESTARTS vaut toujours 0 : Kubernetes n'a pas redémarré un conteneur, il en a démarré un pour la première fois avec la bonne image.
frontend en NodePortL'API tourne. Ajoute maintenant le second composant de la bibliothèque : un Deployment frontend à partir de l'image nginx:1.27-alpine (port 80), exposé par un Service de type NodePort (pas LoadBalancer). Puis trouve sur quel port de ta machine il répond et affiche le titre de sa page d'accueil avec curl.
Indices : trois commandes de la leçon 04 suffisent, en remplaçant le type du Service ; le port choisi par Kubernetes se lit dans la colonne PORT(S) de kubectl get svc, après les deux-points ; sur Docker Desktop, un NodePort répond sur localhost:<port>. Le kit contient le YAML équivalent si tu préfères : 01-installer-kubernetes-avec-docker-desktop/pratique/02-frontend-nginx-nodeport.yaml.
kubectl create deployment frontend --image=nginx:1.27-alpine --port=80 -n premiers-pas
kubectl expose deployment frontend --type=NodePort --port=80 -n premiers-pas
kubectl get svc frontend -n premiers-pasdeployment.apps/frontend created
service/frontend exposed
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend NodePort 10.111.158.77 <none> 80:32740/TCP 10s80:32740 : 80 est le port du Service dans le cluster, 32740 le port de nœud tiré au sort entre 30000 et 32767. Chez toi il sera différent : lis-le, ne recopie pas celui-ci.
curl -s http://localhost:32740 | grep "<title>"
curl -sI http://localhost:32740 | head -3(PowerShell : curl.exe -s … | Select-String "<title>" et curl.exe -sI … | Select-Object -First 3.)
<title>Welcome to nginx!</title>
HTTP/1.1 200 OK
Server: nginx/1.27.5
Date: Fri, 11 Sep 2026 03:47:01 GMTServer: nginx/1.27.5 confirme le tag 1.27-alpine. EXTERNAL-IP <none> est normal pour un NodePort : personne ne lui attribue d'adresse, il ouvre un port sur le nœud, et Docker Desktop relaie ce port sur localhost. Le module 4 compare les trois types de Service en détail. Avec le YAML du kit : kubectl apply -f 01-installer-kubernetes-avec-docker-desktop/pratique/02-frontend-nginx-nodeport.yaml crée les deux mêmes objets (vérifié : deployment.apps/frontend created, service/frontend created).
etat et trois questionsRelance le script du kit et compare avec l'étape 2.
.\labo.ps1 etatSortie réelle (mêmes coupures qu'à l'étape 2) :
== Nœuds ==
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane 18d v1.34.1
== Namespaces du cours ==
✔ premiers-pas 7/7 pods Running
== Services exposés (LoadBalancer / NodePort) dans les namespaces du cours ==
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
premiers-pas api LoadBalancer 10.96.92.182 localhost 8080:31427/TCP 91s
premiers-pas frontend NodePort 10.111.158.77 <none> 80:32740/TCP 17sSept Pods : cinq api, api-cassee réparé, un frontend. Copie cette sortie dans ton livrable, puis réponds en une ou deux phrases chacune :
kubectl logs api-cassee -n premiers-pas n'a-t-il rien pu afficher à l'étape 5, et où as-tu trouvé la cause ?not found in namespace "default" : le Pod avait-il disparu ? Qu'aurais-tu dû taper ?api affiche EXTERNAL-IP localhost et frontend <none> : quel type de Service a chacun, et par quel port de ta machine réponds-tu à chacun ?.\labo.ps1 prerequis (ou ./labo.sh prerequis) se termine par Tout est prêt.kubectl get all -n premiers-pas montre deployment.apps/api 5/5 5 5, deployment.apps/frontend 1/1 1 1, pod/api-cassee 1/1 Running, un replicaset.apps/api-9bfb55fc6 5 5 5 et les deux Services (sortie réelle : sept Pods, deux Services, deux Deployments, deux ReplicaSets).curl -s http://localhost:8080 | grep Hostname (PowerShell : curl.exe -s http://localhost:8080 | Select-String Hostname) renvoie un Hostname: api-… qui change d'un appel à l'autre.curl -sI http://localhost:<port de frontend> renvoie HTTP/1.1 200 OK et Server: nginx/1.27.5.ErrImagePull, ImagePullBackOff et not found in namespace "default".Le kit sait supprimer tous les namespaces du cours d'un coup, mais il refuse de le faire sans confirmation. Regarde d'abord ce qu'il ferait :
.\labo.ps1 nettoyer== Nettoyage des namespaces du cours ==
Namespaces qui seront supprimés (avec tout ce qu'ils contiennent) : premiers-pas
Jamais touchés : default, kube-system, kube-public, kube-node-lease.
! rien n'a été supprimé. Pour confirmer : .\labo.ps1 nettoyer -ouiPour ce module, supprime seulement premiers-pas, à la main, et garde le second terminal ouvert pour voir les sept Pods passer en Terminating :
kubectl delete namespace premiers-pas
kubectl get namespace premiers-pasnamespace "premiers-pas" deleted
Error from server (NotFound): namespaces "premiers-pas" not foundCtrl+C dans le second terminal. http://localhost:8080 ne répond plus (curl échoue avec le code 7, connexion refusée, ou 52, réponse vide). .\labo.ps1 nettoyer -oui / ./labo.sh nettoyer --oui servira quand tu voudras remettre tout le cours à zéro : il ne touche jamais default ni kube-system.
labo.ps1 refuse de se lancer : l'exécution de scripts est désactivée sur ce système → Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, ferme et rouvre PowerShell. Le script est en UTF-8 avec BOM et fonctionne sous PowerShell 5.1 comme sous PowerShell 7.
./labo.sh: Permission denied ou /usr/bin/env: 'bash\r': No such file or directory → chmod +x labo.sh pour le premier ; pour le second, Git a converti les fins de ligne en CRLF à l'extraction : sed -i 's/\r$//' labo.sh (ou git config core.autocrlf false puis re-clone).
prerequis affiche ✘ port 8080 déjà occupé par <programme> (PID …) — arrêtez-le ou choisissez un autre port dans les leçons → une application locale écoute déjà (un autre serveur de développement, un port-forward oublié) ; le script nomme le processus. Ferme-le, ou choisis --port=8081 à l'étape 3 et adapte les URL. Si le port est tenu par un Service Kubernetes du cours, la ligne reste ✔ : utilisé par un Service du cours (LoadBalancer ou NodePort).
EXTERNAL-IP <pending> à l'étape 3 → sur Docker Desktop, le port demandé est déjà publié par un autre Service LoadBalancer (vérifié : un second Service sur 8080 reste <pending> tant que le premier existe). kubectl get svc -A montre lequel. Sur kind ou minikube, <pending> est l'état normal : utilise kubectl port-forward.
repartition renvoie dix fois le même Hostname → soit scale n'a pas encore fini (kubectl get pods -n premiers-pas : attends cinq Running), soit le Service n'a qu'un seul point de terminaison : kubectl get endpointslice -n premiers-pas doit lister cinq IP (10.1.2.255,10.1.3.3,10.1.3.0 + 2 more... sur la machine du cours).
Après set image, api-cassee reste en ImagePullBackOff → tu as tapé un nom de conteneur ou un tag faux : kubectl describe pod api-cassee -n premiers-pas montre l'image réellement demandée dans Containers: whoami: Image:. Le nom à gauche du = est celui du conteneur (whoami), pas de l'image.
labo.ps1 prerequis / labo.sh prerequis vérifie en une commande ce que les leçons 02 et 03 faisaient à la main, et nomme le binaire kubectl qui répond.labo.ps1 etat résume nœud, Pods par namespace du cours et Services exposés : c'est la photo à joindre quand tu demandes de l'aide.kubectl get pods -w, un Pod passe par Pending, ContainerCreating, Running ; cinq réplicas naissent en trois secondes quand l'image est déjà là.ErrImagePull puis ImagePullBackOff = l'image ne se télécharge pas ; la cause est dans kubectl describe, section Events, jamais dans kubectl logs (le conteneur n'a pas démarré).not found in namespace "default" = -n oublié ; l'objet est toujours là, dans un autre namespace.kubectl set image pod/<nom> <conteneur>=<image> corrige une image en place ; un Service NodePort répond sur localhost:<30000-32767> avec Docker Desktop, un LoadBalancer sur le port que tu as choisi.Sans supprimer le namespace, provoque une troisième panne et répare-la : passe le Deployment api sur un tag inexistant avec kubectl set image deployment/api whoami=traefik/whoami:v9.99 -n premiers-pas, puis observe dans le second terminal que Kubernetes ne détruit pas les Pods qui marchent : sur la machine du cours, il a créé trois Pods api-78d7f59657-… en ErrImagePull, retiré un seul ancien, gardé quatre api-9bfb55fc6-… en Running, et s'est arrêté là (kubectl rollout status deployment/api -n premiers-pas reste sur Waiting for deployment "api" rollout to finish: 3 out of 5 new replicas have been updated...). http://localhost:8080 continue de répondre. Répare avec kubectl rollout undo deployment/api -n premiers-pas (deployment.apps/api rolled back) et vérifie que les Pods cassés disparaissent et que cinq api-9bfb55fc6-… tournent. Tu viens de voir le rolling update, ses garde-fous (maxSurge, maxUnavailable) et le rollback, sujets du module 3.