45 minutes (+ exercice optionnel)
Ce manifeste reconstruit l'état « historique » du fil rouge : deux Pods nus et un Service, tels que laissés à la fin du TP 1. C'est volontairement l'architecture fragile que vous allez moderniser dans ce TP.
Créez le fichier fil-rouge-tp03.yaml :
# fil-rouge-tp03.yaml
apiVersion: v1
kind: Pod
metadata:
name: vote
namespace: default
labels:
app: vote
spec:
containers:
- name: vote
image: dockersamples/examplevotingapp_vote:latest
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
---
apiVersion: v1
kind: Pod
metadata:
name: redis
namespace: default
labels:
app: redis
spec:
containers:
- name: redis
image: redis:8-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: default
labels:
app: redis
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
Puis appliquez-le et vérifiez :
kubectl apply -f fil-rouge-tp03.yaml
kubectl wait --for=condition=Ready pod/vote pod/redis --timeout=120s
kubectl get pods
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 35s
vote 1/1 Running 0 35s
Constater qu'un Pod nu ne survit à rien, et que notre application de vote est donc en danger.
# Notre application tourne...
kubectl get pods
# ...simulons un incident (nœud redémarré, OOM, fausse manipulation)
kubectl delete pod vote
# Le Pod revient-il ?
kubectl get pods
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 3m
❌ Le Pod vote a disparu définitivement : plus personne ne peut voter. Un Pod nu n'est surveillé par aucun contrôleur ; s'il meurt, rien ne le remplace.
✅ La solution : les Deployments. C'est l'objet de l'exercice suivant.
vote n'est-il pas recréé automatiquement ?Q: Pourquoi le Pod vote n'est-il pas recréé automatiquement ?
R: Parce qu'aucun contrôleur (ReplicaSet, Deployment) ne le surveille. Kubernetes ne recrée que ce qu'un objet de plus haut niveau déclare comme état désiré.
Q: Quels autres incidents feraient disparaître un Pod nu ?
R: La panne ou le drain du nœud qui l'héberge, une éviction pour manque de ressources, ou un crash définitif de son conteneur (selon la restartPolicy, le conteneur peut redémarrer, mais le Pod ne survit pas à la perte du nœud).
Reconstruire le fil rouge proprement : un namespace dédié, des Deployments résilients, des labels structurés.
# Créer le namespace
kubectl create namespace voting-app
# En faire le namespace par défaut de la session
kubectl config set-context --current --namespace=voting-app
# Vérification
kubectl config view --minify | grep namespace
Les manifestes servent aussi à supprimer ce qu'ils ont créé :
kubectl delete -f fil-rouge-tp03.yaml
Créez d'abord le fichier vote-deployment.yaml :
# vote-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vote
namespace: voting-app
labels:
app: vote
tier: frontend
spec:
replicas: 1
selector:
matchLabels:
app: vote
template:
metadata:
labels:
app: vote
tier: frontend
spec:
containers:
- name: vote
image: dockersamples/examplevotingapp_vote:latest
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
💡 La request cpu: "100m" n'est pas décorative : elle servira de référence à l'autoscaling du TP 7.
Créez ensuite le fichier redis-deployment.yaml :
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: voting-app
labels:
app: redis
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
tier: backend
spec:
containers:
- name: redis
image: redis:8-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
Puis déployez et recréez le Service redis dans le nouveau namespace :
kubectl apply -f vote-deployment.yaml -f redis-deployment.yaml
kubectl expose deployment redis --port=6379
kubectl get deployments,pods,services
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/redis 1/1 1 1 30s
deployment.apps/vote 1/1 1 1 30s
NAME READY STATUS RESTARTS AGE
pod/redis-6f4b8d7c9d-x2kqp 1/1 Running 0 30s
pod/vote-7d94f6c8b5-jw8tn 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/redis ClusterIP 10.108.22.190 <none> 6379/TCP 5s
# Passer l'interface de vote à 3 réplicas
kubectl scale deployment vote --replicas=3
kubectl get pods -l app=vote
# Supprimer un Pod (remplacez <nom-du-pod>)
kubectl delete pod <nom-du-pod>
# Un remplaçant apparaît immédiatement
kubectl get pods -l app=vote
✅ Cette fois, l'application se répare toute seule : le Deployment maintient l'état désiré.
# Tous les Pods avec leurs labels
kubectl get pods --show-labels
# Sélection par tier
kubectl get pods -l tier=frontend
kubectl get pods -l tier=backend
# Sélection par ensemble
kubectl get pods -l 'app in (vote,redis)'
# Comment le Deployment retrouve-t-il ses Pods ?
kubectl describe deployment vote | grep Selector
💡 Depuis un autre namespace, le Service redis serait joignable via son nom complet : redis.voting-app.svc.cluster.local. Dans voting-app, le nom court redis suffit : c'est ce qu'utilise l'application.
redis dans le namespace voting-app ?Q: Que se passe-t-il quand un Pod géré par un Deployment est supprimé ?
R: Le ReplicaSet du Deployment détecte l'écart avec l'état désiré et recrée immédiatement un Pod de remplacement (nouveau nom, nouvelle IP).
Q: Pourquoi a-t-il fallu recréer le Service redis dans le namespace voting-app ?
R: Un Service n'existe que dans son namespace, et son sélecteur ne cible que les Pods du même namespace. Le nom DNS court redis ne fonctionne pour vote que parce que le Service et les Pods sont dans le même namespace.
Q: Quelle est la différence entre les sélecteurs par égalité et par ensemble ?
R:
app=vote (correspond exactement à la valeur)app in (vote,redis) (correspond à une des valeurs de la liste)Réaliser une mise à jour progressive sans coupure de service, et la voir de ses propres yeux.
Le marketing veut tester de nouvelles options de vote : Thé contre Café. Les intitulés des boutons sont pilotés par les variables d'environnement OPTION_A et OPTION_B du Deployment vote, et toute modification du template des Pods déclenche un rolling update.
OPTION_A à Thé et OPTION_B à Café (via variable d'environnement), et suivez la bascule en direct avec kubectl rollout status.vote avec l'annotation kubernetes.io/change-cause (valeur : options v2 : Thé vs Café).vote, et que contient l'historique des révisions ?💡 Indices : kubectl set env, kubectl rollout history, kubectl rollout undo.
1. et 2. Déclencher et suivre la mise à jour :
# Documenter la raison du changement (visible dans l'historique)
kubectl annotate deployment/vote kubernetes.io/change-cause="options v2 : Thé vs Café"
# Modifier l'environnement => nouveau template => rolling update
kubectl set env deployment/vote OPTION_A="Thé" OPTION_B="Café"
# Suivre la bascule en direct
kubectl rollout status deployment/vote
Waiting for deployment "vote" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "vote" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "vote" successfully rolled out
3. Constater dans le navigateur :
kubectl port-forward deployment/vote 8080:80 --address 0.0.0.0
Ouvrez le port 8080 (menu ☰ → Traffic / Ports) : les boutons affichent désormais Thé et Café. Puis Ctrl+C pour rendre la main.
4. La mécanique :
# Deux ReplicaSets : l'ancien (à 0) et le nouveau (à 3)
kubectl get replicasets -l app=vote
# L'historique des révisions
kubectl rollout history deployment/vote
REVISION CHANGE-CAUSE
1 <none>
2 options v2 : Thé vs Café
💡 Le Deployment n'a jamais tout coupé : il a créé les nouveaux Pods par vagues (maxSurge) tout en retirant les anciens (maxUnavailable). Les votes restaient possibles pendant toute la bascule.
5. Rollback :
kubectl rollout undo deployment/vote
kubectl rollout status deployment/vote
# Vérifier l'environnement des Pods
kubectl describe deployment vote | grep -A2 Environment
Re-testez dans le navigateur si vous le souhaitez : Cats et Dogs sont de retour.
⚠️ Note sur les tags d'images : le rolling update « classique » se fait en changeant le tag de l'image (kubectl set image ...). L'éditeur de notre application ne publiant qu'un tag latest, nous déclenchons ici le rollout par les variables d'environnement ; la mécanique (ReplicaSets, vagues, historique, rollback) est rigoureusement identique.
Q: Qu'est-ce qui déclenche un rolling update sur un Deployment ?
R: Toute modification du template des Pods (spec.template) : image, variables d'environnement, ressources, labels du template… Un simple changement de replicas ne déclenche pas de rollout.
Q: Comment Kubernetes garantit-il l'absence de coupure pendant la mise à jour ?
R: Le Deployment crée un nouveau ReplicaSet et bascule progressivement : au plus maxSurge Pods en plus, au plus maxUnavailable indisponibles (25 % chacun par défaut). Les anciens Pods ne sont retirés que quand les nouveaux sont prêts.
Q: Comment revenir à la version précédente ?
R: kubectl rollout undo deployment/<nom> (ou --to-revision=N pour une révision précise, listée par kubectl rollout history).
Exécuter une tâche ponctuelle (injecter 50 votes de test dans la file) avec l'objet prévu pour ça : le Job.
L'équipe de test a besoin de données. Le script à exécuter (une seule fois !) est fourni ; à vous de l'emballer dans l'objet Kubernetes adapté :
for i in $(seq 1 50); do
redis-cli -h redis RPUSH votes "{\"voter_id\": \"seed-$i\", \"vote\": \"a\"}" > /dev/null
done
echo "✅ 50 votes injectés dans la file"
💡 Le format injecté ({"voter_id": ..., "vote": "a"}) est exactement celui que produit l'interface de vote (voir la présentation du fil rouge).
seed-votes dans voting-app qui exécute ce script avec l'image redis:8-alpine (elle contient redis-cli), au maximum 3 tentatives en cas d'échec, sans redémarrage du conteneur dans le Pod.Complete et consultez son compte-rendu dans les logs.votes doit contenir 50 entrées.💡 Indices : command: ["sh", "-c"] + args pour le script ; backoffLimit ; restartPolicy: Never.
1. Le Job :
Fichier seed-votes-job.yaml :
# seed-votes-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: seed-votes
namespace: voting-app
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: seed
image: redis:8-alpine
command: ["sh", "-c"]
args:
- |
for i in $(seq 1 50); do
redis-cli -h redis RPUSH votes "{\"voter_id\": \"seed-$i\", \"vote\": \"a\"}" > /dev/null
done
echo "✅ 50 votes injectés dans la file"
2. Exécution et compte-rendu :
kubectl apply -f seed-votes-job.yaml
# Suivre l'exécution
kubectl get jobs
kubectl get pods -l job-name=seed-votes
NAME STATUS COMPLETIONS DURATION AGE
seed-votes Complete 1/1 4s 20s
# Le compte-rendu de la tâche
kubectl logs job/seed-votes
✅ 50 votes injectés dans la file
3. Vérification côté Redis :
kubectl exec deployment/redis -- redis-cli LLEN votes
(integer) 50
⚠️ À suivre : 50 votes (plus les vôtres) attendent maintenant dans la file… et personne ne les consomme. Le consommateur arrive au TP 4.
restartPolicy: Never et que ferait backoffLimit en cas d'échec ?Q: Pourquoi utiliser un Job plutôt qu'un Deployment pour cette tâche ?
R: Un Deployment maintient des Pods en vie indéfiniment : il relancerait sans fin notre script terminé. Un Job vise la complétion : il exécute la tâche jusqu'au succès puis s'arrête (le Pod passe en Completed). Pour une exécution périodique, on utiliserait un CronJob.
Q: Que fait restartPolicy: Never et que ferait backoffLimit en cas d'échec ?
R: restartPolicy: Never évite de redémarrer le conteneur dans le même Pod ; en cas d'échec, le Job crée un nouveau Pod, au maximum backoffLimit fois (3 ici) avant de marquer le Job en échec.
Apprendre à diagnostiquer et résoudre les problèmes courants avec les Pods, Deployments et Labels.
Cet exercice se déroule hors du fil rouge : revenez d'abord dans le namespace default :
kubectl config set-context --current --namespace=default
Votre équipe vous signale plusieurs problèmes avec l'application. Identifiez et corrigez les erreurs dans cette configuration :
# Configuration problématique
apiVersion: apps/v1
kind: Deployment
metadata:
name: broken-webapp
labels:
app: webapp
spec:
replicas: 3
selector:
matchLabels:
app: web-application
template:
metadata:
labels:
app: webapp
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.99.nonexistent
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "32Mi"
cpu: "200m"
Mission :
kubectl describe et kubectl get events pour diagnostiquerselector.matchLabels correspondent à ceux du templatekubectl describe deployment et kubectl describe podsDiagnostic des problèmes :
Problème 1 - Selector incorrect :
# Le selector cherche app=web-application mais les Pods ont app=webapp
kubectl apply -f broken-webapp.yaml
# Montre : "selector does not match template labels" (rejeté à la création)
Problème 2 - Image incorrecte :
# L'image nginx:1.99.nonexistent n'existe pas
kubectl describe pods
# Montre : "Failed to pull image" puis ImagePullBackOff
Problème 3 - Limites de ressources invalides :
# memory limit (32Mi) < memory request (64Mi)
kubectl describe pods
# Montre : "Invalid value: must be less than or equal to memory limit"
Configuration corrigée :
apiVersion: apps/v1
kind: Deployment
metadata:
name: fixed-webapp
labels:
app: webapp
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.29.5
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
Commandes de vérification :
# Appliquer la configuration corrigée
kubectl apply -f fixed-webapp.yaml
# Vérifier que tout fonctionne
kubectl get deployment fixed-webapp
kubectl get pods -l app=webapp
kubectl describe deployment fixed-webapp
Q: Comment diagnostiquer un problème de selector dans un Deployment ?
R:
kubectl describe deployment <nom> → Vérifier "Selector" et "Labels"kubectl get pods -l <selector> → Tester si des Pods correspondentQ: Quelles commandes utiliser pour identifier les erreurs de Pods ?
R:
kubectl get pods → Status général des Podskubectl describe pod <nom> → Détails et événementskubectl logs <nom> → Logs de l'applicationkubectl get events --sort-by=.metadata.creationTimestamp → Événements récentsQ: Comment vérifier que les ressources sont correctement configurées ?
R:
kubectl apply --dry-run=clientDéployer un agent de supervision présent automatiquement sur chaque nœud du cluster.
L'équipe supervision veut l'agent node-exporter (métriques matérielles) sur tous les nœuds, y compris le controlplane, sans avoir à y penser quand le cluster grandit.
node-exporter en DaemonSet (namespace default) : image quay.io/prometheus/node-exporter:v1.12.1, port 9100, hostNetwork: true, et une toleration qui l'autorise sur le nœud controlplane malgré sa restriction.replicas n'existe.localhost:9100/metrics.💡 Indices : la toleration à reprendre utilise key: node-role.kubernetes.io/control-plane, operator: Exists, effect: NoSchedule (mécanisme détaillé au TP 7) ; hostNetwork: true fait écouter l'agent directement sur le port du nœud.
1. Le DaemonSet :
Fichier node-exporter-daemonset.yaml :
# node-exporter-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: default
labels:
app: node-exporter
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: node-exporter
image: quay.io/prometheus/node-exporter:v1.12.1
ports:
- containerPort: 9100
hostPort: 9100
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "64Mi"
2. Un Pod par nœud, sans réplicas à gérer :
kubectl apply -f node-exporter-daemonset.yaml
kubectl get daemonset
kubectl get pods -l app=node-exporter -o wide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
node-exporter 2 2 2 2 2 <none> 30s
NAME READY STATUS RESTARTS AGE IP NODE
node-exporter-7hkzt 1/1 Running 0 30s 172.30.1.2 controlplane
node-exporter-x59vm 1/1 Running 0 30s 172.30.2.2 node01
Un Pod par nœud, automatiquement ; et si un nœud rejoignait le cluster, il recevrait son agent sans aucune action de votre part. Notez qu'un DaemonSet n'a pas de champ replicas : c'est le nombre de nœuds qui décide.
3. Interroger l'agent local :
Grâce à hostNetwork: true, l'agent écoute directement sur le port 9100 du nœud :
curl -s localhost:9100/metrics | head -5
# HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 2.1152e-05
go_gc_duration_seconds{quantile="0.25"} 4.2647e-05
go_gc_duration_seconds{quantile="0.5"} 5.4692e-05
Q: Dans quels cas utilise-t-on un DaemonSet plutôt qu'un Deployment ?
R: Quand chaque nœud doit héberger exactement une instance : agents de supervision (node-exporter), collecteurs de logs (fluentd), agents réseau (kube-proxy, CNI) ou de stockage. Le nombre d'instances suit automatiquement le nombre de nœuds, sans champ replicas.
kubectl scale deployment <nom> --replicas=Nkubectl rollout undo, historique avec kubectl rollout historyapp=vote) ou ensemble (app in (vote,redis))voting-appservice.namespace.svc.cluster.local (nom court suffisant dans le même namespace):latest), avec l'exception pédagogique assumée des les images dockersamples/* du fil rouge, dont l'éditeur ne publie qu'un tag latestkubernetes.io/change-cause