TP : Déployer l'application Guestbook
Ce tutoriel pratique vous guide pas à pas dans le déploiement d'une application web complète sur Kubernetes. Vous allez découvrir comment les différents objets Kubernetes s'articulent ensemble.
Présentation de l'application
Guestbook est une application web classique composée de :
- Un frontend en Go qui sert une interface web
- Un backend Redis pour stocker les messages
┌─────────────────────────────────────────────────────────────┐
│ Navigateur Web │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Service frontend ││
│ │ (LoadBalancer:3000) ││
│ └─────────────────────────────────────────────────────────┘│
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ guestbook-1 │ │ guestbook-2 │ │ guestbook-3 │ │
│ │ (Go app) │ │ (Go app) │ │ (Go app) │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Service redis ││
│ │ (ClusterIP:6379) ││
│ └─────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ redis │ │
│ │ (backend) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘Fonctionnement
- L'utilisateur accède au frontend via son navigateur
- Le frontend affiche un formulaire pour laisser un message
- Les messages sont stockés dans Redis
- Le frontend poll Redis toutes les secondes pour afficher les nouveaux messages
Partie 1 : Les Pods simples
Commençons par comprendre les briques de base avant de passer aux Deployments.
1.1 Le Pod Redis
# k8s-resources/redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis
labels:
app: redis
tier: backend
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379Analyse ligne par ligne
| Ligne | Explication |
|---|---|
apiVersion: v1 | Version de l'API Kubernetes. v1 est le core API, utilisé pour les objets de base (Pod, Service, ConfigMap). |
kind: Pod | Type d'objet Kubernetes à créer. |
metadata.name: redis | Nom unique du Pod dans le namespace. C'est l'identifiant pour kubectl get pod redis. |
metadata.labels | Paires clé-valeur pour identifier et sélectionner le Pod. Essentiels pour les Services. |
labels.app: redis | Label applicatif. Convention commune pour identifier l'application. |
labels.tier: backend | Label de couche. Indique que c'est un composant backend (vs frontend). |
spec.containers | Liste des containers du Pod. Un Pod peut en avoir plusieurs (sidecar pattern). |
containers[0].name | Nom du container. Utile pour kubectl logs redis -c redis. |
containers[0].image | Image Docker à utiliser. redis = docker.io/library/redis:latest. |
containers[0].ports | Ports exposés par le container. Informatif, pas de filtrage. |
containerPort: 6379 | Port standard de Redis. Doit correspondre à ce qu'écoute l'application. |
Déployer et tester
# Créer le Pod
kubectl apply -f k8s-resources/redis-pod.yaml
# Vérifier l'état
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# redis 1/1 Running 0 30s
# Voir les détails
kubectl describe pod redis
# Tester Redis directement
kubectl exec -it redis -- redis-cli PING
# PONGNOTE
Pourquoi les labels sont importants ? Les labels app: redis et tier: backend seront utilisés par le Service pour trouver ce Pod. Sans labels, le Service ne pourrait pas router le trafic.
1.2 Le Pod Guestbook (frontend)
# k8s-resources/guestbook-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: guestbook
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: guestbook
image: omerxx/guestbook-example
imagePullPolicy: Always
ports:
- name: http-server
containerPort: 3000Nouveautés par rapport au Pod Redis
| Ligne | Explication |
|---|---|
imagePullPolicy: Always | Force Kubernetes à toujours télécharger l'image, même si elle existe localement. Utile en développement pour avoir la dernière version. Autres valeurs : IfNotPresent (défaut), Never. |
ports[0].name: http-server | Nom du port. Permet de le référencer par nom dans le Service au lieu du numéro. Plus lisible et maintenable. |
Comment l'application trouve Redis ?
Dans le code Go (main.go), on voit :
redisURL := os.Getenv("REDIS_URL")
if redisURL == "" {
redisURL = "localhost:6379"
}L'application cherche la variable d'environnement REDIS_URL. Si elle n'existe pas, elle tente localhost:6379.
Problème : dans Kubernetes, chaque Pod a sa propre IP. localhost ne fonctionnera pas !
Solution : on va créer un Service qui expose Redis avec un nom DNS prévisible.
Partie 2 : Les Services
Les Services résolvent deux problèmes :
- Découverte : donner un nom DNS stable aux Pods
- Load balancing : répartir le trafic entre plusieurs réplicas
2.1 Service Redis (ClusterIP)
# k8s-resources/redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
tier: backendAnalyse ligne par ligne
| Ligne | Explication |
|---|---|
kind: Service | Objet Service Kubernetes. |
metadata.name: redis | Crucial ! Ce nom devient le hostname DNS : redis.default.svc.cluster.local (ou simplement redis dans le même namespace). |
spec.ports[0].port | Port exposé par le Service. Les clients se connectent à redis:6379. |
spec.ports[0].targetPort | Port du container vers lequel le trafic est envoyé. Doit correspondre à containerPort du Pod. |
spec.selector | Le cœur du Service ! Sélectionne les Pods avec ces labels. Le trafic sera routé uniquement vers les Pods qui matchent. |
Type de Service
Aucun type n'est spécifié, donc c'est le type par défaut : ClusterIP.
- Crée une IP virtuelle interne au cluster
- Accessible uniquement depuis l'intérieur du cluster
- Parfait pour les backends (bases de données, caches, etc.)
Comment fonctionne le selector ?
Service redis Pods du cluster
┌─────────────────┐ ┌─────────────────┐
│ selector: │ │ Pod: redis │
│ app: redis │─────────────▶│ labels: │ ✓ Match !
│ tier: backend │ │ app: redis │
└─────────────────┘ │ tier: backend │
└─────────────────┘
┌─────────────────┐
│ Pod: guestbook │
│ labels: │ ✗ Pas de match
│ app: guestbook│
│ tier: frontend│
└─────────────────┘Déployer et vérifier
kubectl apply -f k8s-resources/redis-service.yaml
# Voir le Service
kubectl get svc redis
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# redis ClusterIP 10.96.xxx.xxx <none> 6379/TCP 10s
# Vérifier les endpoints (Pods sélectionnés)
kubectl get endpoints redis
# NAME ENDPOINTS AGE
# redis 10.244.0.5:6379 10s
# L'IP dans ENDPOINTS est celle du Pod redis
kubectl get pod redis -o wide
# NAME READY STATUS IP
# redis 1/1 Running 10.244.0.52.2 Service Frontend (LoadBalancer)
# k8s-resources/guestbook-service.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
ports:
- port: 3000
targetPort: http-server
selector:
app: guestbook
type: LoadBalancerDifférences avec le Service Redis
| Ligne | Explication |
|---|---|
targetPort: http-server | Au lieu d'un numéro, on utilise le nom du port défini dans le Pod (ports[0].name: http-server). Plus maintenable : si le port change, on ne modifie qu'un seul endroit. |
selector.app: guestbook | Sélectionne tous les Pods avec app: guestbook, peu importe leur tier. |
type: LoadBalancer | Expose le Service à l'extérieur du cluster via un load balancer. Sur le cloud (AWS, GCP), provisionne un vrai LB. Sur Minikube, utilisez minikube tunnel. |
Déployer et accéder
kubectl apply -f k8s-resources/guestbook-service.yaml
# Voir le Service
kubectl get svc frontend
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# frontend LoadBalancer 10.96.xxx.xxx <pending> 3000:32xxx/TCP 10s
# Sur Minikube, ouvrir un tunnel dans un autre terminal
minikube tunnel
# L'EXTERNAL-IP devrait maintenant être assignée
kubectl get svc frontend
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# frontend LoadBalancer 10.96.xxx.xxx 127.0.0.1 3000:32xxx/TCP 1mOuvrez http://127.0.0.1:3000 (ou l'IP externe) dans votre navigateur.
Partie 3 : Les Deployments
Les Pods seuls sont fragiles : si un Pod meurt, il n'est pas recréé. Les Deployments résolvent ce problème.
3.1 Deployment Redis
# k8s-resources/redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
selector:
matchLabels:
app: redis
tier: backend
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: redis
ports:
- containerPort: 6379Analyse ligne par ligne
| Ligne | Explication |
|---|---|
apiVersion: apps/v1 | Les Deployments sont dans le groupe API apps, pas v1 core. |
kind: Deployment | Objet qui gère le cycle de vie des Pods. |
spec.selector.matchLabels | Obligatoire ! Le Deployment doit savoir quels Pods il gère. Doit matcher les labels du template. |
spec.replicas: 1 | Nombre de Pods souhaités. Le Deployment maintiendra toujours ce nombre. |
spec.template | Template du Pod à créer. C'est une définition de Pod complète (sans apiVersion/kind). |
template.metadata.labels | Labels appliqués aux Pods créés. Doivent inclure ceux du selector.matchLabels. |
labels.role: master | Label supplémentaire. Utile pour distinguer master/replica dans un setup Redis avancé. |
Différence avec un Pod simple
Pod simple Deployment
┌─────────────────┐ ┌─────────────────┐
│ Si le Pod │ │ Controller │
│ crash... │ │ Manager │
│ │ │ │ │
│ ┌─────┐ │ │ ▼ │
│ │ Pod │ ──X │ │ ┌─────────┐ │
│ └─────┘ │ │ │Deployment│ │
│ │ │ └────┬────┘ │
│ ...il reste │ │ │ observe │
│ mort. │ │ ▼ │
│ │ │ replicas: 1 │
│ │ │ actual: 0 │
│ │ │ │ │
│ │ │ ▼ crée │
│ │ │ ┌─────┐ │
│ │ │ │ Pod │ │
│ │ │ └─────┘ │
└─────────────────┘ └─────────────────┘Déployer
# Supprimer le Pod simple d'abord (si créé précédemment)
kubectl delete pod redis --ignore-not-found
# Créer le Deployment
kubectl apply -f k8s-resources/redis-deployment.yaml
# Voir le Deployment
kubectl get deployment redis
# NAME READY UP-TO-DATE AVAILABLE AGE
# redis 1/1 1 1 30s
# Voir le Pod créé automatiquement
kubectl get pods -l app=redis
# NAME READY STATUS RESTARTS AGE
# redis-7d8b4c5f4c-xj2km 1/1 Running 0 30sNOTE
Remarquez le nom du Pod : redis-7d8b4c5f4c-xj2km
redis: nom du Deployment7d8b4c5f4c: hash du ReplicaSet (généré automatiquement)xj2km: identifiant unique du Pod
3.2 Deployment Guestbook
# k8s-resources/guestbook-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook
labels:
app: guestbook
spec:
selector:
matchLabels:
app: guestbook
tier: frontend
replicas: 3
template:
metadata:
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: guestbook
image: omerxx/guestbook-example
imagePullPolicy: Always
ports:
- name: http-server
containerPort: 3000Nouveautés
| Ligne | Explication |
|---|---|
replicas: 3 | 3 instances du frontend pour la haute disponibilité et le load balancing. |
metadata.labels (sur le Deployment) | Labels sur le Deployment lui-même (pas les Pods). Utile pour kubectl get deployment -l app=guestbook. |
Déployer et observer le scaling
# Supprimer le Pod simple d'abord
kubectl delete pod guestbook --ignore-not-found
# Créer le Deployment
kubectl apply -f k8s-resources/guestbook-deployment.yaml
# Voir les 3 réplicas
kubectl get pods -l app=guestbook
# NAME READY STATUS RESTARTS AGE
# guestbook-5f4d7b8c9-abc12 1/1 Running 0 30s
# guestbook-5f4d7b8c9-def34 1/1 Running 0 30s
# guestbook-5f4d7b8c9-ghi56 1/1 Running 0 30s
# Le Service frontend load-balance entre les 3
kubectl get endpoints frontend
# NAME ENDPOINTS AGE
# frontend 10.244.0.6:3000,10.244.0.7:3000,10.244.0.8:3000 5mTester le self-healing
# Supprimer un Pod manuellement
kubectl delete pod guestbook-5f4d7b8c9-abc12
# Observer : un nouveau Pod est immédiatement créé !
kubectl get pods -l app=guestbook -w
# NAME READY STATUS RESTARTS AGE
# guestbook-5f4d7b8c9-def34 1/1 Running 0 2m
# guestbook-5f4d7b8c9-ghi56 1/1 Running 0 2m
# guestbook-5f4d7b8c9-xyz99 0/1 ContainerCreating 0 2s
# guestbook-5f4d7b8c9-xyz99 1/1 Running 0 5sPartie 4 : ConfigMaps et Secrets
4.1 ConfigMap
# k8s-resources/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: example-configmap
data:
my-config-key: "This is the config value"À quoi ça sert ?
Les ConfigMaps permettent d'externaliser la configuration de vos applications :
- Variables d'environnement
- Fichiers de configuration
- Arguments de commande
Avantage : changer la config sans reconstruire l'image Docker.
Déployer et inspecter
kubectl apply -f k8s-resources/configmap.yaml
# Voir le contenu
kubectl get configmap example-configmap -o yaml
kubectl describe configmap example-configmap4.2 Pod utilisant un ConfigMap
# k8s-resources/pod-using-cm.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-using-configmap
spec:
containers:
- name: example-container
image: busybox:latest
command: ["sh", "-c", "echo $(MY_CONFIG_KEY) && sleep 3600"]
env:
- name: MY_CONFIG_KEY
valueFrom:
configMapKeyRef:
name: example-configmap
key: my-config-key
restartPolicy: NeverAnalyse
| Ligne | Explication |
|---|---|
command: [...] | Remplace le CMD du Dockerfile. Ici, affiche la variable puis dort. |
env[0].name | Nom de la variable d'environnement dans le container. |
valueFrom.configMapKeyRef | Injecte une valeur depuis un ConfigMap. |
configMapKeyRef.name | Nom du ConfigMap à utiliser. |
configMapKeyRef.key | Clé dans le ConfigMap dont on veut la valeur. |
restartPolicy: Never | Ne pas redémarrer le Pod s'il s'arrête. Utile pour les jobs. |
Tester
kubectl apply -f k8s-resources/pod-using-cm.yaml
# Voir les logs (la valeur de la config)
kubectl logs pod-using-configmap
# This is the config value
# Vérifier la variable dans le container
kubectl exec pod-using-configmap -- env | grep MY_CONFIG
# MY_CONFIG_KEY=This is the config value4.3 Secret
# k8s-resources/secret-configmap.yaml
apiVersion: v1
kind: Secret
metadata:
name: example-secret
type: Opaque
data:
my-secret-key: c2VjcmV0LXZhbHVl # base64 encoded valueAnalyse
| Ligne | Explication |
|---|---|
kind: Secret | Objet pour stocker des données sensibles. |
type: Opaque | Type générique (vs kubernetes.io/tls pour les certificats, etc.). |
data.my-secret-key | Valeur encodée en base64. |
Encoder/Décoder en base64
# Encoder
echo -n "secret-value" | base64
# c2VjcmV0LXZhbHVl
# Décoder
echo "c2VjcmV0LXZhbHVl" | base64 -d
# secret-valueWARNING
Base64 n'est PAS du chiffrement ! Les Secrets sont encodés, pas chiffrés. Toute personne ayant accès au cluster peut les lire. En production, utilisez des solutions comme Vault ou Sealed Secrets.
Partie 5 : Jobs et CronJobs
5.1 Job (tâche ponctuelle)
# k8s-resources/job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: example-job
spec:
template:
spec:
containers:
- name: example
image: busybox:latest
command: ["sh", "-c", "echo Hello, Kubernetes! && sleep 10"]
restartPolicy: Never
backoffLimit: 4Analyse
| Ligne | Explication |
|---|---|
apiVersion: batch/v1 | Les Jobs sont dans le groupe API batch. |
kind: Job | Tâche qui s'exécute jusqu'à complétion (vs Deployment qui tourne en continu). |
spec.template | Template du Pod à créer (comme un Deployment). |
restartPolicy: Never | Obligatoire pour les Jobs. Un Job qui échoue ne doit pas redémarrer en boucle. |
backoffLimit: 4 | Nombre de tentatives avant d'abandonner. Après 4 échecs, le Job est marqué Failed. |
Cas d'usage
- Migrations de base de données
- Traitements batch (export de données, génération de rapports)
- Tâches d'initialisation
Déployer et observer
kubectl apply -f k8s-resources/job.yaml
# Suivre l'exécution
kubectl get jobs -w
# NAME COMPLETIONS DURATION AGE
# example-job 0/1 5s 5s
# example-job 1/1 12s 12s
# Voir les logs
kubectl logs job/example-job
# Hello, Kubernetes!
# Le Pod reste visible après complétion (statut Completed)
kubectl get pods -l job-name=example-job
# NAME READY STATUS RESTARTS AGE
# example-job-abc12 0/1 Completed 0 1m5.2 CronJob (tâche planifiée)
# k8s-resources/cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: example-cronjob
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: example
image: busybox:latest
command: ["sh", "-c", "echo Hello, Kubernetes! && sleep 10"]
restartPolicy: Never
backoffLimit: 4Analyse
| Ligne | Explication |
|---|---|
kind: CronJob | Job planifié selon un schedule cron. |
spec.schedule | Expression cron : */5 * * * * = toutes les 5 minutes. |
spec.jobTemplate | Template du Job à créer à chaque exécution. |
Format cron
┌───────────── minute (0 - 59)
│ ┌───────────── heure (0 - 23)
│ │ ┌───────────── jour du mois (1 - 31)
│ │ │ ┌───────────── mois (1 - 12)
│ │ │ │ ┌───────────── jour de la semaine (0 - 6, dimanche = 0)
│ │ │ │ │
* * * * *Exemples :
0 * * * *: toutes les heures (à :00)0 0 * * *: tous les jours à minuit0 0 * * 0: tous les dimanches à minuit*/5 * * * *: toutes les 5 minutes
Déployer et observer
kubectl apply -f k8s-resources/cronjob.yaml
# Voir le CronJob
kubectl get cronjobs
# NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
# example-cronjob */5 * * * * False 0 <none> 10s
# Attendre 5 minutes et voir les Jobs créés
kubectl get jobs -l job-name=example-cronjobPartie 6 : DaemonSet
# k8s-resources/daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-daemonset
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80Différence avec un Deployment
| Deployment | DaemonSet |
|---|---|
replicas: N pods, répartis selon le scheduler | 1 pod par nœud (automatiquement) |
| Pour les applications | Pour les agents système (logs, monitoring, réseau) |
| Peut avoir 0 pod sur certains nœuds | Garantit 1 pod sur chaque nœud |
Cas d'usage
- Collecteurs de logs : Fluentd, Filebeat
- Agents de monitoring : Node Exporter, Datadog agent
- Plugins réseau : Calico, Cilium
- Stockage : Longhorn, Ceph agents
Déployer et observer
kubectl apply -f k8s-resources/daemonset.yaml
# Voir le DaemonSet
kubectl get daemonset
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
# nginx-daemonset 1 1 1 1 1 <none> 30s
# Un Pod par nœud
kubectl get pods -l app=nginx -o wide
# NAME READY STATUS NODE
# nginx-daemonset-abc12 1/1 Running minikube
# Si vous ajoutez un nœud, un Pod sera automatiquement créé dessus
minikube node add
kubectl get pods -l app=nginx -o wide
# NAME READY STATUS NODE
# nginx-daemonset-abc12 1/1 Running minikube
# nginx-daemonset-def34 1/1 Running minikube-m02Partie 7 : StatefulSet
# k8s-resources/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1GiDifférences avec un Deployment
| Deployment | StatefulSet |
|---|---|
Noms de pods aléatoires (app-7d8b4-xj2km) | Noms prévisibles (web-0, web-1, web-2) |
| Pods interchangeables | Identité stable |
| Stockage partagé ou éphémère | PVC dédié par pod |
| Ordre de déploiement quelconque | Ordre garanti (0, puis 1, puis 2) |
Analyse des nouveautés
| Ligne | Explication |
|---|---|
spec.serviceName | Nom du Service Headless associé. Requis pour le DNS des pods individuels. |
volumeClaimTemplates | Template de PVC. Un PVC unique sera créé pour chaque pod (www-web-0, www-web-1). |
accessModes: ["ReadWriteOnce"] | Le volume peut être monté en lecture-écriture par un seul nœud. |
resources.requests.storage | Taille du volume demandée. |
Cas d'usage
- Bases de données (PostgreSQL, MySQL, MongoDB)
- Systèmes distribués (Kafka, Elasticsearch, Zookeeper)
- Tout ce qui a besoin d'une identité stable et de données persistantes
Déployer et observer
kubectl apply -f k8s-resources/statefulset.yaml
# Voir le StatefulSet
kubectl get statefulset
# NAME READY AGE
# web 1/1 30s
# Nom de pod prévisible !
kubectl get pods -l app=nginx
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 30s
# PVC créé automatiquement
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# www-web-0 Bound pvc-abc123-def456-ghi789 1Gi RWO standard 30sPartie 8 : Affectation des Pods aux nœuds
8.1 nodeName (affectation directe)
# k8s-resources/pod-with-nodename.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: minikubeAnalyse
| Ligne | Explication |
|---|---|
nodeName: minikube | Force le pod sur ce nœud spécifique. Bypass le scheduler. |
WARNING
À éviter en production !nodeName lie le pod à un nœud spécifique. Si le nœud tombe, le pod ne sera jamais replanifié ailleurs. Utilisez plutôt nodeSelector ou les affinités.
8.2 nodeSelector
# k8s-resources/pod-with-node-selector.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeSelector:
disktype: ssd
containers:
- name: nginx
image: nginxAnalyse
| Ligne | Explication |
|---|---|
nodeSelector.disktype: ssd | Le pod sera schedulé uniquement sur les nœuds ayant le label disktype=ssd. |
Utilisation
# D'abord, labelliser un nœud
kubectl label nodes minikube disktype=ssd
# Puis déployer le pod
kubectl apply -f k8s-resources/pod-with-node-selector.yaml
# Vérifier
kubectl get pod nginx -o wide
# NAME READY STATUS NODE
# nginx 1/1 Running minikube8.3 Tolerations (pour les taints)
# k8s-resources/pod-with-tolerations.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "test-environment"
operator: "Exists"
effect: "NoSchedule"Rappel : Taints et Tolerations
- Taint (sur le nœud) : repousse les pods
- Toleration (sur le pod) : permet au pod d'ignorer une taint
Analyse
| Ligne | Explication |
|---|---|
tolerations[0].key | Clé de la taint à tolérer. |
operator: "Exists" | La taint existe, peu importe sa valeur. Alternative : Equal pour matcher une valeur précise. |
effect: "NoSchedule" | Type de taint tolérée. Doit correspondre à l'effet de la taint sur le nœud. |
Utilisation
# Ajouter une taint à un nœud
kubectl taint nodes minikube test-environment=true:NoSchedule
# Sans toleration, les nouveaux pods ne seront pas schedulés sur ce nœud
kubectl run test --image=nginx
kubectl get pod test
# NAME READY STATUS RESTARTS AGE
# test 0/1 Pending 0 30s # Reste Pending !
# Avec toleration, le pod peut y aller
kubectl apply -f k8s-resources/pod-with-tolerations.yaml
kubectl get pod nginx -o wide
# NAME READY STATUS NODE
# nginx 1/1 Running minikube
# Nettoyer
kubectl taint nodes minikube test-environment:NoSchedule-Partie 9 : Déploiement complet
Maintenant que vous comprenez chaque composant, déployons l'application complète.
Ordre de déploiement recommandé
# 1. ConfigMaps et Secrets (si nécessaire)
kubectl apply -f k8s-resources/configmap.yaml
# 2. Backend (Redis)
kubectl apply -f k8s-resources/redis-deployment.yaml
kubectl apply -f k8s-resources/redis-service.yaml
# 3. Attendre que Redis soit prêt
kubectl wait --for=condition=ready pod -l app=redis --timeout=60s
# 4. Frontend (Guestbook)
kubectl apply -f k8s-resources/guestbook-deployment.yaml
kubectl apply -f k8s-resources/guestbook-service.yaml
# 5. Vérifier
kubectl get all -l app=guestbook
kubectl get all -l app=redisAccéder à l'application
# Sur Minikube
minikube tunnel # Dans un autre terminal
# Ou utiliser le port-forward
kubectl port-forward svc/frontend 3000:3000
# Ouvrir http://localhost:3000Partie 10 : Cycle de vie et comportements Kubernetes
Cette partie est cruciale pour comprendre la puissance de Kubernetes. Nous allons observer en temps réel comment le cluster réagit à différentes situations.
10.1 Self-Healing : résurrection automatique des Pods
Le self-healing est la capacité de Kubernetes à maintenir l'état désiré automatiquement.
Concept : La boucle de réconciliation
┌─────────────────────────────────────────────────────────────┐
│ Controller Manager │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ État désiré │ │ État actuel │ │
│ │ replicas: 3 │ ≠ │ replicas: 2 │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Réconcilier │ │
│ │ Créer 1 pod │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘Le Controller Manager compare en permanence l'état désiré (défini dans le Deployment) avec l'état actuel du cluster. S'il y a une différence, il agit pour corriger.
TP : Tuer un Pod et observer la résurrection
# Vérifier l'état initial (3 pods)
kubectl get pods -l app=guestbook
# NAME READY STATUS RESTARTS AGE
# guestbook-5f4d7b8c9-abc12 1/1 Running 0 5m
# guestbook-5f4d7b8c9-def34 1/1 Running 0 5m
# guestbook-5f4d7b8c9-ghi56 1/1 Running 0 5m
# Ouvrir un second terminal pour observer en temps réel
kubectl get pods -l app=guestbook -w
# Dans le premier terminal, supprimer un pod
kubectl delete pod guestbook-5f4d7b8c9-abc12
# Observer dans le second terminal :
# guestbook-5f4d7b8c9-abc12 1/1 Terminating 0 5m
# guestbook-5f4d7b8c9-xyz99 0/1 Pending 0 0s
# guestbook-5f4d7b8c9-xyz99 0/1 ContainerCreating 0 0s
# guestbook-5f4d7b8c9-xyz99 1/1 Running 0 2sCe qui s'est passé :
- Vous avez supprimé un pod → état actuel = 2 pods
- Le Controller Manager détecte : 2 ≠ 3 (désiré)
- Il crée immédiatement un nouveau pod
- En ~2 secondes, le cluster est revenu à l'état désiré
TP : Tuer TOUS les Pods en même temps
# Supprimer tous les pods guestbook d'un coup
kubectl delete pods -l app=guestbook --wait=false
# Observer : TOUS sont recréés immédiatement
kubectl get pods -l app=guestbook -w
# Tous en Terminating, puis 3 nouveaux en Creating, puis RunningPoint clé : Même si vous supprimez tous les pods, Kubernetes les recrée. C'est la magie du Deployment.
TP : Simuler un crash d'application
# Entrer dans un pod et tuer le processus principal
kubectl exec -it $(kubectl get pod -l app=guestbook -o jsonpath='{.items[0].metadata.name}') -- /bin/sh
# Dans le container, trouver et tuer le processus (PID 1)
kill 1
# Le container s'arrête, vous êtes éjecté
# Observer : le pod redémarre (RESTARTS augmente)
kubectl get pods -l app=guestbook
# NAME READY STATUS RESTARTS AGE
# guestbook-5f4d7b8c9-xyz99 1/1 Running 1 10m # RESTARTS = 1Différence importante :
- Si le container crash → le Pod redémarre le container (RESTARTS augmente)
- Si le Pod est supprimé → le Deployment crée un nouveau Pod (nouveau nom)
10.2 Scaling : ajuster le nombre de réplicas
Scaling manuel
# État initial : 3 réplicas
kubectl get deployment guestbook
# NAME READY UP-TO-DATE AVAILABLE AGE
# guestbook 3/3 3 3 15m
# Scale UP à 5 réplicas
kubectl scale deployment guestbook --replicas=5
# Observer la création des nouveaux pods
kubectl get pods -l app=guestbook -w
# 2 nouveaux pods apparaissent en Creating puis Running
# Vérifier les endpoints (le Service les découvre automatiquement)
kubectl get endpoints frontend
# NAME ENDPOINTS
# frontend 10.244.0.6:3000,10.244.0.7:3000,10.244.0.8:3000,10.244.0.9:3000,10.244.0.10:3000
# Scale DOWN à 2 réplicas
kubectl scale deployment guestbook --replicas=2
# Observer : 3 pods sont terminés
kubectl get pods -l app=guestbook -w
# 3 pods passent en Terminating puis disparaissentObserver le load balancing
# Identifier quel pod répond (l'app affiche son hostname)
# Ouvrez http://localhost:3000 et notez l'URL affichée
# Rafraîchissez plusieurs fois : l'URL change car le Service
# load-balance entre les différents pods
# Vous pouvez aussi tester avec curl en boucle
for i in {1..10}; do
curl -s http://localhost:3000/env | grep HOSTNAME
done
# Vous verrez différents noms de podsScaling à zéro (arrêt temporaire)
# Mettre l'application en pause sans la supprimer
kubectl scale deployment guestbook --replicas=0
# Vérifier : plus aucun pod
kubectl get pods -l app=guestbook
# No resources found
# Le Deployment existe toujours !
kubectl get deployment guestbook
# NAME READY UP-TO-DATE AVAILABLE AGE
# guestbook 0/0 0 0 20m
# Redémarrer
kubectl scale deployment guestbook --replicas=310.3 Rolling Update : mise à jour sans interruption
Le Rolling Update permet de mettre à jour une application sans downtime.
Concept : mise à jour progressive
État initial (v1) Pendant le rolling update État final (v2)
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ v1 │ │ v1 │ │ v1 │ │ v1 │ │ v1 │ │ v2 │ │ v2 │ │ v2 │ │ v2 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘
│
▼
1. Créer un pod v2
2. Attendre qu'il soit Ready
3. Supprimer un pod v1
4. Répéter jusqu'à 100% v2TP : Observer un Rolling Update
# Terminal 1 : Observer les pods en temps réel
kubectl get pods -l app=guestbook -w
# Terminal 2 : Observer le rollout
kubectl rollout status deployment/guestbook -w
# Terminal 3 : Déclencher la mise à jour (changer l'image)
kubectl set image deployment/guestbook guestbook=omerxx/guestbook-example:latest
# Observer dans Terminal 1 :
# - De nouveaux pods apparaissent (avec un nouveau ReplicaSet hash)
# - Les anciens pods sont progressivement terminés
# - À tout moment, des pods sont disponibles (pas de downtime)TP : Mise à jour avec suivi détaillé
# Voir l'historique des révisions
kubectl rollout history deployment/guestbook
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# Voir les détails d'une révision
kubectl rollout history deployment/guestbook --revision=2
# Voir les ReplicaSets (un par révision)
kubectl get replicasets -l app=guestbook
# NAME DESIRED CURRENT READY AGE
# guestbook-5f4d7b8c9 0 0 0 30m # ancienne version
# guestbook-7a8b9c0d1 3 3 3 2m # nouvelle versionParamètres du Rolling Update
Dans le Deployment, vous pouvez configurer la stratégie :
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Combien de pods EN PLUS peuvent être créés
maxUnavailable: 0 # Combien de pods peuvent être indisponibles| Paramètre | Valeur | Effet |
|---|---|---|
maxSurge: 1, maxUnavailable: 0 | Prudent | Crée d'abord, supprime ensuite. Jamais moins de 3 pods. |
maxSurge: 0, maxUnavailable: 1 | Économe | Supprime d'abord, crée ensuite. Utilise moins de ressources. |
maxSurge: 2, maxUnavailable: 2 | Rapide | Met à jour 2 pods à la fois. Plus rapide mais plus risqué. |
10.4 Rollback : revenir en arrière
Si une mise à jour pose problème, Kubernetes permet de revenir instantanément à la version précédente.
TP : Simuler un déploiement raté et rollback
# Déployer une "mauvaise" version (image qui n'existe pas)
kubectl set image deployment/guestbook guestbook=omerxx/guestbook-example:version-inexistante
# Observer : les nouveaux pods sont en erreur
kubectl get pods -l app=guestbook
# NAME READY STATUS RESTARTS AGE
# guestbook-7a8b9c0d1-abc12 1/1 Running 0 5m # ancien, OK
# guestbook-7a8b9c0d1-def34 1/1 Running 0 5m # ancien, OK
# guestbook-9z8y7x6w5-xyz99 0/1 ImagePullBackOff 0 30s # nouveau, ERREUR
# Le rollout est bloqué
kubectl rollout status deployment/guestbook
# Waiting for deployment "guestbook" rollout to finish: 1 out of 3 new replicas have been updated...
# ROLLBACK !
kubectl rollout undo deployment/guestbook
# Observer : retour à la normale
kubectl get pods -l app=guestbook
# Tous les pods sont Running avec l'ancienne image
# Vérifier l'historique
kubectl rollout history deployment/guestbook
# REVISION CHANGE-CAUSE
# 2 <none>
# 3 <none> # Le rollback crée une nouvelle révisionRollback vers une révision spécifique
# Voir toutes les révisions
kubectl rollout history deployment/guestbook
# Rollback vers la révision 1
kubectl rollout undo deployment/guestbook --to-revision=110.5 Liveness et Readiness Probes en action
Les probes permettent à Kubernetes de savoir si un container est sain.
Concept
| Probe | Question | Action si échec |
|---|---|---|
| Liveness | "Es-tu vivant ?" | Redémarrer le container |
| Readiness | "Es-tu prêt à recevoir du trafic ?" | Retirer du Service (plus de trafic) |
TP : Ajouter des probes au Deployment
Créons une version améliorée du Deployment avec des probes :
# Éditer le deployment
kubectl edit deployment guestbookAjoutez dans spec.template.spec.containers[0] :
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 2
periodSeconds: 5
failureThreshold: 3Observer les probes en action
# Voir les probes dans la description du pod
kubectl describe pod -l app=guestbook | grep -A5 "Liveness\|Readiness"
# Simuler un pod "pas prêt" en tuant Redis
kubectl delete pod -l app=redis
# Les pods guestbook échouent leur probe (ne peuvent plus joindre Redis)
# Ils sont retirés des endpoints du Service
kubectl get endpoints frontend
# Les endpoints disparaissent progressivement
# Recréer Redis
kubectl apply -f k8s-resources/redis-deployment.yaml
# Les probes réussissent à nouveau, les pods reviennent dans les endpoints
kubectl get endpoints frontend10.6 Resource Limits et comportement sous pression
TP : Observer le throttling CPU
# Créer un pod avec une limite CPU très basse
kubectl run stress-test --image=polinux/stress --restart=Never \
--limits="cpu=100m,memory=128Mi" \
-- stress --cpu 2 --timeout 60s
# Observer la consommation (le pod est throttlé à 100m)
kubectl top pod stress-test
# NAME CPU(cores) MEMORY(bytes)
# stress-test 100m 5Mi # Bloqué à 100m malgré stress --cpu 2
# Le pod n'est PAS tué, juste ralenti
kubectl get pod stress-test
# STATUS: RunningTP : Observer un OOMKill
# Créer un pod qui va dépasser sa limite mémoire
kubectl run oom-test --image=polinux/stress --restart=Never \
--limits="memory=64Mi" \
-- stress --vm 1 --vm-bytes 128M --timeout 60s
# Observer : le pod est tué (OOMKilled)
kubectl get pod oom-test -w
# NAME READY STATUS RESTARTS AGE
# oom-test 0/1 OOMKilled 0 5s
# Voir la raison
kubectl describe pod oom-test | grep -A3 "Last State"
# Last State: Terminated
# Reason: OOMKilledRésumé :
- CPU : throttling (ralenti mais pas tué)
- Mémoire : OOMKill (tué et redémarré)
10.7 Service Discovery et DNS
Comment les pods se trouvent-ils ?
Kubernetes fournit un DNS interne. Chaque Service a une entrée DNS.
# Créer un pod de debug
kubectl run debug --image=busybox --restart=Never -- sleep 3600
# Résoudre le nom DNS du service Redis
kubectl exec debug -- nslookup redis
# Server: 10.96.0.10
# Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
#
# Name: redis
# Address 1: 10.96.xxx.xxx redis.default.svc.cluster.local
# Différents formats DNS possibles
kubectl exec debug -- nslookup redis # court
kubectl exec debug -- nslookup redis.default # avec namespace
kubectl exec debug -- nslookup redis.default.svc # avec svc
kubectl exec debug -- nslookup redis.default.svc.cluster.local # FQDNVoir les variables d'environnement injectées
# Kubernetes injecte des variables pour chaque Service
kubectl exec debug -- env | grep -i redis
# REDIS_SERVICE_HOST=10.96.xxx.xxx
# REDIS_SERVICE_PORT=6379
# REDIS_PORT=tcp://10.96.xxx.xxx:637910.8 Que se passe-t-il quand un nœud tombe ?
Sur un cluster multi-nœuds, Kubernetes gère la panne d'un nœud.
TP : Simuler une panne de nœud (Minikube multi-nodes)
# Ajouter un second nœud
minikube node add
# Vérifier les nœuds
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane 1h v1.28.0
# minikube-m02 Ready <none> 1m v1.28.0
# Voir la répartition des pods
kubectl get pods -o wide
# Certains pods sont sur minikube, d'autres sur minikube-m02
# Arrêter le second nœud (simuler une panne)
minikube node stop minikube-m02
# Observer : le nœud passe en NotReady après ~40s
kubectl get nodes -w
# minikube-m02 NotReady <none> 5m v1.28.0
# Après ~5 minutes, les pods du nœud mort sont replanifiés
kubectl get pods -o wide -w
# Les pods qui étaient sur minikube-m02 sont recréés sur minikubeDélais par défaut :
- 40 secondes : le nœud passe en
NotReady - 5 minutes : les pods sont considérés comme perdus et replanifiés
10.9 Événements : l'historique du cluster
Les événements sont la "boîte noire" de Kubernetes.
# Voir tous les événements récents
kubectl get events --sort-by='.lastTimestamp'
# Événements d'un pod spécifique
kubectl describe pod <pod-name>
# Section "Events:" en bas
# Filtrer les événements par type
kubectl get events --field-selector type=Warning
# Événements en temps réel
kubectl get events -wTypes d'événements courants :
| Événement | Signification |
|---|---|
Scheduled | Pod assigné à un nœud |
Pulling | Téléchargement de l'image |
Pulled | Image téléchargée |
Created | Container créé |
Started | Container démarré |
Killing | Container en cours d'arrêt |
Unhealthy | Probe échouée |
FailedScheduling | Impossible de trouver un nœud |
BackOff | Redémarrage après crash |
10.10 Résumé : La philosophie "Cattle not Pets"
Tout ce que nous avons vu illustre cette philosophie :
| Concept | Pets (animaux de compagnie) | Cattle (bétail) |
|---|---|---|
| Identité | Unique, irremplaçable | Anonyme, interchangeable |
| Panne | Réparer à tout prix | Remplacer |
| Scaling | Acheter un plus gros serveur | Ajouter plus d'instances |
| Mise à jour | Modifier en place | Remplacer par une nouvelle version |
| État | Sur le serveur | Externalisé (base de données, stockage) |
Kubernetes automatise la gestion du "bétail" :
- Un pod meurt ? → On en crée un autre
- Besoin de plus de capacité ? → On ajoute des réplicas
- Mise à jour ? → On remplace progressivement
- Nœud en panne ? → On replanifie ailleurs
Récapitulatif des objets
| Objet | Fichier | Rôle |
|---|---|---|
| Pod | guestbook-pod.yaml, redis-pod.yaml | Unité de base, éphémère |
| Deployment | guestbook-deployment.yaml, redis-deployment.yaml | Gère le cycle de vie des Pods |
| Service | guestbook-service.yaml, redis-service.yaml | Expose les Pods, DNS, load balancing |
| ConfigMap | configmap.yaml | Configuration externalisée |
| Secret | secret-configmap.yaml | Données sensibles |
| Job | job.yaml | Tâche ponctuelle |
| CronJob | cronjob.yaml | Tâche planifiée |
| DaemonSet | daemonset.yaml | 1 Pod par nœud |
| StatefulSet | statefulset.yaml | Apps avec état, identité stable |
Nettoyage
# Supprimer tout
kubectl delete -f k8s-resources/
# Ou supprimer par type
kubectl delete deployment guestbook redis
kubectl delete service frontend redis
kubectl delete configmap example-configmap
kubectl delete secret example-secret
kubectl delete job example-job
kubectl delete cronjob example-cronjob
kubectl delete daemonset nginx-daemonset
kubectl delete statefulset web
kubectl delete pvc --all