La cascade Deployment → ReplicaSet → Pod & Rolling updates : la réconciliation progressive
La cascade de trois réconciliateurs imbriqués Deployment → ReplicaSet → Pod, et le rolling update comme réconciliation progressive entre deux ReplicaSets — sans prérequis, avec YAML et kubectl.
La cascade Deployment → ReplicaSet → Pod
L’idée en une phrase
Un Deployment ne crée pas de pods : il pilote un ReplicaSet, qui pilote les pods. Trois réconciliateurs imbriqués forment une cascade où l’état désiré descend (Deployment.spec → ReplicaSet.spec → Pod.spec) et l’état réel remonte (status du pod → du ReplicaSet → du Deployment) ; chaque échelon exécute sa propre boucle observe → diff → agit sur l’échelon immédiatement inférieur, sans court-circuiter les autres.
Analogie : Considérons une chaîne de délégation dans une organisation. La direction fixe un objectif — « maintenir trois agences ouvertes » — sans recruter personne elle-même : elle mandate un responsable régional. Celui-ci crée et surveille les agences, qui embauchent à leur tour le personnel. Chaque échelon ne pilote que celui immédiatement en dessous et ignore les détails des autres. La direction est le Deployment, le responsable régional le ReplicaSet, le personnel les pods.
Points clés
- Un Deployment décrit l’état désiré d’une application : l’image, le nombre de replicas et la stratégie de mise à jour. Son contrôleur n’observe pas les pods ; il maintient un ReplicaSet conforme à son
spec.template. - Un ReplicaSet a une responsabilité unique : maintenir N pods correspondant à son selector (vu au chapitre 7-8). Il ignore les notions d’image ou de version — il compte et ajuste, rien de plus.
- L’état désiré descend, l’état réel remonte : le Deployment transmet replicas et template au ReplicaSet, qui les transmet aux pods ; en retour, le status des pods agrège celui du ReplicaSet, puis celui du Deployment (
AVAILABLE,UP-TO-DATE). - Chaque révision du
templateengendre un ReplicaSet distinct, identifié par un hash (pod-template-hash) ajouté au selector et aux labels des pods. Les anciens ReplicaSets sont conservés à 0 replica (paramètrerevisionHistoryLimit) pour autoriser un retour arrière. - Les ownerReferences relient chaque pod à son ReplicaSet, et chaque ReplicaSet à son Deployment ; la suppression se propage de haut en bas (garbage collection, chapitre 25-26).
Exemple concret
Un Deployment hello déclare replicas: 3 et l’image nginx:1.27. À t₀, son contrôleur observe qu’aucun ReplicaSet ne correspond au template courant et crée hello-7d4b9c (désirant 3 pods). Le contrôleur de ce ReplicaSet observe 0 pod pour 3 désirés et crée hello-7d4b9c-aaa11, -bbb22, -ccc33. Trois objets, deux boucles : le Deployment n’a jamais « vu » un pod, il a seulement garanti l’existence d’un ReplicaSet conforme ; ce dernier a garanti l’existence des pods. Si l’un des trois pods est supprimé, c’est le contrôleur du ReplicaSet — non celui du Deployment — qui observe 2/3 et en recrée un. Le Deployment ne réagit, lui, qu’à un écart portant sur les ReplicaSets.
Trois échelons, trois réconciliateurs
| Échelon | État désiré (sa spec) | État réel observé | Action pour combler l’écart |
|---|---|---|---|
| Deployment controller | template + replicas + stratégie | Les ReplicaSets qu’il possède | Crée le ReplicaSet courant, pilote la transition entre révisions |
| ReplicaSet controller | replicas + selector | Pods correspondant au selector | Crée ou supprime des pods |
| kubelet (niveau pod) | Pod.spec (conteneurs) | Conteneurs en exécution (chapitre 7-8) | Démarre ou arrête les conteneurs |
Code YAML — l’état désiré au sommet de la cascade
# deployment.yaml — un seul objet déclaré, trois échelons réconciliés
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 3 # transmis tel quel au ReplicaSet créé
selector:
matchLabels:
app: hello
template: # toute modification ici engendre un NOUVEAU ReplicaSet
metadata:
labels:
app: hello
spec:
containers:
- name: web
image: nginx:1.27
Code kubectl — observer la cascade et la filiation
# Un seul apply, trois niveaux d'objets se déploient
kubectl apply -f deployment.yaml
# deployment.apps/hello created
kubectl get deploy,rs,pod -l app=hello
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/hello 3/3 3 3 8s
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/hello-7d4b9c 3 3 3 8s ← créé par le Deployment
# NAME READY STATUS RESTARTS AGE
# pod/hello-7d4b9c-aaa11 1/1 Running 0 8s ← créés par le ReplicaSet
# pod/hello-7d4b9c-bbb22 1/1 Running 0 8s
# pod/hello-7d4b9c-ccc33 1/1 Running 0 8s
# La filiation est inscrite dans les ownerReferences (chap. 25-26)
kubectl get pod hello-7d4b9c-aaa11 -o jsonpath='{.metadata.ownerReferences[0].kind}'
# ReplicaSet ← le pod appartient au ReplicaSet, jamais directement au Deployment
Piège courant : « Le Deployment crée et surveille directement les pods » est inexact — il maintient un ReplicaSet, qui maintient les pods. C’est pourquoi
kubectl scale deploymentmodifie en réalité lereplicasdu ReplicaSet, et pourquoi un pod supprimé est recréé par le ReplicaSet, jamais par le Deployment. Confondre les deux échelons conduit à diagnostiquer un comportement au mauvais niveau de la cascade.
Rolling updates : la réconciliation progressive
L’idée en une phrase
Un rolling update ne remplace pas les pods d’un seul coup : le contrôleur de Deployment fait coexister deux ReplicaSets — l’ancien décroissant, le nouveau croissant — et transfère les replicas de l’un vers l’autre par paliers. L’état désiré final (« N pods de la nouvelle version ») est atteint par une suite d’états intermédiaires, chaque tick respectant deux bornes (maxUnavailable, maxSurge) : la réconciliation est ici progressive et contrainte, non instantanée.
Analogie : Considérons le renouvellement des rames d’une ligne de métro en service. Pour ne pas interrompre le trafic, on n’immobilise pas toutes les rames à la fois : une rame neuve entre en circulation, on vérifie qu’elle roule, puis une ancienne est retirée, et ainsi de suite. À chaque instant, le nombre de rames en service reste suffisant. La cadence — combien de rames en plus ou en moins sont tolérées — est fixée à l’avance.
Points clés
- La stratégie par défaut est RollingUpdate ; l’alternative Recreate supprime tous les anciens pods avant de créer les nouveaux, au prix d’une interruption de service assumée.
- Deux paramètres règlent la cadence : maxUnavailable (combien de pods peuvent manquer sous l’effectif désiré, défaut 25 %) et maxSurge (combien peuvent être créés au-dessus, défaut 25 %). Ils bornent chaque état intermédiaire.
- Une nouvelle version implique un nouveau ReplicaSet. Le contrôleur n’édite jamais un pod existant — les pods sont immuables — il crée des pods neufs et supprime les anciens. La transition n’est qu’un transfert de replicas entre deux ReplicaSets.
- La progression est conditionnée par la disponibilité : un nouveau pod n’est compté disponible qu’une fois Ready (readiness probe, renseignée dans le status vu au chapitre 5-6). Tant que le quota de pods disponibles n’est pas tenu, le contrôleur n’abaisse pas davantage l’ancien ReplicaSet.
- Le retour arrière (
kubectl rollout undo) ne redéploie rien depuis zéro : il réconcilie vers l’ancien ReplicaSet, conservé à 0 replica, en le remontant. L’historique des révisions est précisément la liste des ReplicaSets conservés.
Exemple concret
Un Deployment de replicas: 4, avec maxUnavailable: 1 et maxSurge: 1, passe de nginx:1.27 à nginx:1.28. Le contrôleur crée un second ReplicaSet (version 1.28) et procède par paliers : il porte le nouveau de 0 à 1 (total 5 pods, surge respecté), attend que ce pod soit Ready, puis abaisse l’ancien de 4 à 3 (3 disponibles ≥ 4 − 1). Puis nouveau 1 → 2, ancien 3 → 2 ; nouveau 2 → 3, ancien 2 → 1 ; nouveau 3 → 4, ancien 1 → 0. À aucun instant moins de 3 pods ne sont disponibles, ni plus de 5 au total. L’ancien ReplicaSet subsiste à 0 replica, prêt pour un éventuel rollout undo.
RollingUpdate ou Recreate : deux régimes de transition
| Critère | RollingUpdate (défaut) | Recreate |
|---|---|---|
| Interruption de service | Aucune (chevauchement des versions) | Oui : arrêt complet puis redémarrage |
| Versions coexistantes | Deux, transitoirement | Une seule à la fois |
| Bornes de cadence | maxUnavailable, maxSurge | Sans objet |
| Usage typique | Services web sans état | Cas interdisant toute coexistence de versions |
Code YAML — fixer la cadence de la transition
# deployment.yaml — la stratégie borne chaque état intermédiaire
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # au plus 1 pod sous l'effectif → au moins 3 disponibles
maxSurge: 1 # au plus 1 pod au-dessus → au plus 5 au total
template:
spec:
containers:
- name: web
image: nginx:1.28 # 1.27 → 1.28 : déclenche la création d'un nouveau ReplicaSet
Code kubectl — déclencher, suivre et annuler une transition
# Changer l'image = modifier le template = nouveau ReplicaSet
kubectl set image deployment/hello web=nginx:1.28
# deployment.apps/hello image updated
# Suivre la convergence, palier par palier
kubectl rollout status deployment/hello
# Waiting for deployment "hello" rollout to finish: 2 of 4 updated replicas are available...
# deployment "hello" successfully rolled out
# Pendant la transition, DEUX ReplicaSets coexistent
kubectl get rs -l app=hello
# NAME DESIRED CURRENT READY AGE
# hello-7d4b9c 1 1 1 6m ← ancien (1.27), en décroissance
# hello-5f8d2c 3 3 3 20s ← nouveau (1.28), en croissance
# Retour arrière : réconcilier vers l'ancien ReplicaSet, toujours présent
kubectl rollout undo deployment/hello
# deployment.apps/hello rolled back
Piège courant : «
kubectl applyd’une nouvelle image modifie les pods existants » est inexact — un pod est immuable : le rolling update crée un nouveau ReplicaSet, fait converger les effectifs des deux, et supprime les anciens pods une fois les nouveaux Ready. De même, unrollout undon’est pas un redéploiement neuf mais une réconciliation vers un ReplicaSet déjà présent. Croire que les pods sont « mis à jour sur place » fait mal interpréter l’apparition d’un second ReplicaSet et la coexistence temporaire des versions.