Deployments et mises à jour progressives
Pourquoi
À la leçon précédente, Signalements tournait dans un Pod. Supprimez-le, et l'application disparaît : personne ne le recrée. Faites tomber le nœud qui l'héberge, et c'est pareil. Voulez-vous trois exemplaires pour absorber la charge ? Il faut écrire trois manifestes, avec trois noms. Et pour passer de la version 1.2.0 à la 1.3.0, il faut supprimer et recréer chaque Pod à la main, en espérant que les utilisateurs ne remarquent rien.
C'est exactement le travail qu'on ne veut plus faire à la main. Kubernetes le confie à des contrôleurs : des boucles qui observent l'état voulu, le comparent à la réalité, et agissent pour combler l'écart. Le Deployment est le contrôleur que l'on utilise pour presque toutes les applications sans état, comme Signalements. Vous lui dites « je veux trois pods de cette image, avec cette configuration », et il les crée, les recrée quand ils disparaissent, et les remplace progressivement quand la description change.
Cette leçon montre comment il fonctionne, comment il remplace une version par une autre sans coupure, et comment revenir en arrière quand la nouvelle version ne tient pas ses promesses. Elle montre aussi pourquoi un déploiement « progressif » peut quand même couper le service, si l'application et le cluster ne sont pas réglés ensemble.
Les concepts
Étiquettes et sélecteurs
Tout ce qui suit repose sur un mécanisme simple. Chaque objet Kubernetes peut porter des étiquettes (labels) : des paires clé-valeur libres, dans metadata.labels. Un sélecteur (selector) est une requête sur ces étiquettes : « tous les pods qui ont app.kubernetes.io/name=signalements ». Les contrôleurs et les Services ne connaissent pas les pods par leur nom, qui change à chaque recréation : ils les retrouvent par sélecteur.
La documentation recommande un jeu d'étiquettes communes, préfixées par app.kubernetes.io/ : name (le nom de l'application), instance (une installation particulière), version, component, part-of, managed-by. Les suivre rend les outils (tableaux de bord, Helm, Argo CD) capables de regrouper les objets d'une même application. Ce cours utilise app.kubernetes.io/name: signalements comme étiquette d'identité.
ReplicaSet, puis Deployment
Le contrôleur de base s'appelle ReplicaSet. Son contrat tient en trois champs : un nombre de répliques, un sélecteur, et un modèle de Pod (Pod template). Il compte en permanence les pods qui correspondent au sélecteur ; s'il en manque, il en crée à partir du modèle ; s'il y en a trop, il en supprime.
Un ReplicaSet ne sait pas faire une chose : changer de version. Si vous modifiez son modèle, les pods existants ne sont pas touchés, puisqu'ils correspondent toujours au sélecteur ; seuls les pods créés ensuite utilisent le nouveau modèle. C'est pourquoi on ne manipule presque jamais un ReplicaSet directement, et que la documentation recommande d'utiliser un Deployment.
Le Deployment gère des ReplicaSets, comme le ReplicaSet gère des pods :
flowchart TB
D["Deployment signalements<br/>replicas: 3, image 1.3.0"]
D --> RS1["ReplicaSet signalements-6d5f8...<br/>image 1.2.0, replicas: 0"]
D --> RS2["ReplicaSet signalements-7c9b4...<br/>image 1.3.0, replicas: 3"]
RS2 --> P1["Pod ...-7c9b4...-abcde"]
RS2 --> P2["Pod ...-7c9b4...-fghij"]
RS2 --> P3["Pod ...-7c9b4...-klmno"]
À chaque changement de modèle, le Deployment crée un nouveau ReplicaSet pour le nouveau modèle, puis fait monter son nombre de répliques pendant qu'il fait descendre celui de l'ancien. Les anciens ReplicaSets, ramenés à zéro, sont gardés : ce sont eux qui permettent de revenir en arrière.
Pour distinguer les pods de deux ReplicaSets qui partagent les mêmes étiquettes, le Deployment ajoute à chacun une étiquette pod-template-hash, calculée à partir du modèle. Elle apparaît dans le nom du ReplicaSet et dans celui de ses pods (signalements-7c9b4d8f6-abcde). Ne la posez jamais vous-même.
Qui possède quoi
Chaque Pod créé par un ReplicaSet porte dans ses métadonnées une référence de propriétaire (metadata.ownerReferences) vers ce ReplicaSet, et chaque ReplicaSet en porte une vers son Deployment. Le ramasse-miettes (garbage collector) de Kubernetes s'en sert : supprimer un Deployment supprime, par défaut, ses ReplicaSets, qui suppriment leurs pods. C'est la suppression en cascade. Avec kubectl delete deployment ... --cascade=orphan, les objets dépendants sont au contraire laissés en place, sans propriétaire.
Le manifeste de Signalements
apiVersion: apps/v1
kind: Deployment
metadata:
name: signalements
namespace: signalements
labels:
app.kubernetes.io/name: signalements
spec:
replicas: 3
revisionHistoryLimit: 5
progressDeadlineSeconds: 300
minReadySeconds: 5
selector:
matchLabels:
app.kubernetes.io/name: signalements
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app.kubernetes.io/name: signalements
app.kubernetes.io/version: "1.2.0"
spec:
terminationGracePeriodSeconds: 40
containers:
- name: api
image: ghcr.io/lyneko-formation/signalements:1.2.0
ports:
- name: http
containerPort: 8000
env:
- name: APP_VERSION
value: "1.2.0"
startupProbe:
httpGet: {path: /, port: http}
periodSeconds: 2
failureThreshold: 30
readinessProbe:
httpGet: {path: /sante, port: http}
periodSeconds: 5
failureThreshold: 2
lifecycle:
preStop:
sleep: {seconds: 5}Le bloc template est exactement un Pod de la leçon 4, sans apiVersion, kind ni nom : les noms des pods sont générés. Autour, trois choses comptent.
Le sélecteur doit correspondre aux étiquettes du modèle. spec.selector.matchLabels dit quels pods appartiennent au Deployment, spec.template.metadata.labels dit quelles étiquettes portent les pods créés. Si les secondes ne satisfont pas le premier, l'API refuse le manifeste. Et le sélecteur est immuable après création, d'après la documentation de apps/v1 : pour le changer, il faut supprimer et recréer le Deployment. Choisissez donc un sélecteur stable (le nom de l'application), et jamais une étiquette qui change, comme la version.
replicas fixe le nombre de pods voulus. La commande kubectl scale le modifie, mais si le manifeste est géré par Git (cours GitOps avec Argo CD), la modification manuelle sera écrasée à la prochaine synchronisation.
strategy décide comment remplacer les pods quand le modèle change : c'est la section suivante.
La génération de départ est facile avec kubectl en mode client, qui ne contacte pas le cluster :
$ kubectl create deployment signalements --image=ghcr.io/lyneko-formation/signalements:1.2.0 \
--replicas=3 --port=8000 --dry-run=client -o yaml
Sortie réelle avec kubectl 1.36.0 :
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: signalements
name: signalements
spec:
replicas: 3
selector:
matchLabels:
app: signalements
strategy: {}
template:
metadata:
labels:
app: signalements
spec:
containers:
- image: ghcr.io/lyneko-formation/signalements:1.2.0
name: signalements
ports:
- containerPort: 8000
resources: {}
status: {}Le squelette est correct mais minimal : une étiquette app au lieu des étiquettes recommandées, pas de sonde, pas de réglage d'arrêt, et les champs vides (strategy: {}, resources: {}, status: {}) qu'il vaut mieux retirer avant de versionner le fichier.
La mise à jour progressive
La stratégie par défaut, RollingUpdate, remplace les pods par vagues, en respectant deux limites :
maxSurge: combien de pods en plus du nombre voulu peuvent exister pendant la mise à jour. Valeur par défaut : 25 %, arrondi au supérieur.maxUnavailable: combien de pods en moins du nombre voulu peuvent être indisponibles. Valeur par défaut : 25 %, arrondi à l'inférieur.
Les deux ne peuvent pas valoir zéro en même temps : le Deployment ne pourrait plus rien faire. Avec les valeurs par défaut et trois répliques, maxSurge vaut 1 (25 % de 3 = 0,75, arrondi à 1) et maxUnavailable vaut 0 (0,75 arrondi à 0) : le Deployment crée un pod neuf, attend qu'il soit disponible, supprime un ancien, et recommence. Le nombre de pods disponibles ne descend jamais sous trois.
Le manifeste de Signalements écrit ces valeurs explicitement, pour qu'elles ne changent pas le jour où le nombre de répliques change : à dix répliques, les valeurs par défaut donneraient maxSurge de 3 et maxUnavailable de 2, soit deux pods de moins pendant la mise à jour.
Que veut dire « disponible » ? Un pod est prêt (ready) quand ses sondes de disponibilité réussissent ; il est disponible (available) quand il est prêt depuis au moins minReadySeconds (0 par défaut). Ce délai protège contre une version qui démarre, passe sa sonde, puis plante au bout de quelques secondes : avec minReadySeconds: 5, le Deployment attend cinq secondes de stabilité avant de considérer le pod comme acquis et de passer au suivant.
C'est ici que la sonde de disponibilité de la leçon 4 change de rôle : elle ne sert plus seulement à router le trafic, elle pilote le déploiement. Une version 1.3.0 qui ne parvient pas à se connecter à la base n'est jamais prête, donc jamais disponible ; avec maxUnavailable: 0, le Deployment ne supprime aucun ancien pod, et la version 1.2.0 continue de servir. Sans sonde, Kubernetes considère un pod prêt dès que son conteneur tourne, et remplace la totalité du parc par une version qui ne fonctionne pas.
L'autre stratégie, Recreate, supprime tous les anciens pods avant de créer les nouveaux. Elle provoque une coupure, et ne sert que lorsque deux versions ne peuvent pas tourner en même temps (un schéma de base incompatible, un fichier verrouillé).
Ce qui déclenche un nouveau déploiement
La documentation est précise : un déploiement est déclenché si et seulement si le modèle de Pod (spec.template) change. Changer l'image, une variable d'environnement, une étiquette du modèle, une sonde : nouvelle révision, nouveau ReplicaSet, remplacement progressif. Changer replicas, strategy ou minReadySeconds : aucune révision, les pods existants restent.
Une conséquence piège les débutants : si la variable APP_VERSION vient d'un ConfigMap (leçon 7) et que vous modifiez le ConfigMap, le modèle de Pod ne change pas : rien n'est redéployé, et les pods gardent l'ancienne valeur. Pour forcer un remplacement sans changer le fond, kubectl rollout restart ajoute au modèle une annotation horodatée (kubectl.kubernetes.io/restartedAt), ce qui suffit à déclencher une nouvelle révision.
Révisions et retour arrière
Chaque modèle de Pod distinct est une révision, numérotée. Le Deployment garde les ReplicaSets des révisions passées, ramenés à zéro réplique, dans la limite de revisionHistoryLimit (10 par défaut). Revenir en arrière, c'est simplement faire remonter un ancien ReplicaSet et descendre le courant, avec la même progressivité qu'une mise à jour.
À revisionHistoryLimit: 0, la documentation prévient que tout l'historique est effacé et que le retour arrière devient impossible. Le cours le règle à 5 : assez pour revenir de quelques versions, sans accumuler des dizaines d'objets inutiles.
Un déploiement qui n'avance plus
Une mise à jour peut se bloquer : image introuvable, quota épuisé, sonde de disponibilité qui n'aboutit jamais. Le Deployment ne le sait pas tout seul. progressDeadlineSeconds (600 secondes par défaut) fixe le délai au-delà duquel, sans progrès, il pose dans son statut une condition Progressing à False avec la raison ProgressDeadlineExceeded. Il n'annule rien : il signale. C'est aux outils de déploiement (une CI qui attend kubectl rollout status, Argo CD qui marque l'application Degraded) de réagir.
Un déploiement vraiment sans coupure
Une mise à jour progressive peut perdre des requêtes, même avec des sondes. Il faut que trois réglages s'emboîtent :
- Une sonde de disponibilité qui ne déclare le nouveau pod prêt que lorsqu'il peut servir : le trafic n'arrive pas trop tôt.
- Un arrêt gracieux de l'application sur
SIGTERM(gunicorn bien lancé en PID 1, leçon 4) : les requêtes en cours se terminent. - Une attente en
preStop, parce que le retrait d'un pod des Services se propage de façon asynchrone sur chaque nœud : pendant quelques secondes après le début de l'arrêt, des connexions peuvent encore arriver. Le tutoriel Pods And Endpoints Termination Flow de la documentation le montre : l'EndpointSlice marque le point d'accèsterminating, puis chaque nœud met ses règles à jour.
Le manifeste réunit les trois. Un quatrième réglage protège contre une autre source d'interruption : les perturbations volontaires.
Le PodDisruptionBudget
Un déploiement n'est pas la seule occasion de perdre des pods. Quand un administrateur vide un nœud pour le mettre à jour (kubectl drain), ou qu'un service managé comme Kapsule remplace ses nœuds lors d'une montée de version, les pods du nœud sont évincés. Si les trois pods de Signalements sont sur deux nœuds et que les deux sont vidés en même temps, l'application s'arrête.
Un PodDisruptionBudget (PDB) fixe une limite à ces perturbations volontaires : « au moins deux pods de Signalements doivent rester disponibles ». L'API d'éviction refuse alors d'évincer un pod si cela ferait passer sous le seuil, et kubectl drain attend. Génération en mode client, sortie réelle :
$ kubectl create poddisruptionbudget signalements \
--selector=app.kubernetes.io/name=signalements --min-available=2 \
--dry-run=client -o yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: signalements
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: signalements
status:
currentHealthy: 0
desiredHealthy: 0
disruptionsAllowed: 0
expectedPods: 0Le PDB ne protège pas contre une panne de nœud (perturbation involontaire), ni contre la suppression directe d'un pod, ni contre une mise à jour du Deployment, qui suit ses propres règles. Il ne vaut que pour les évictions. Le cours Kubernetes : administrer un cluster revient sur le vidage des nœuds.
En pratique
Sur le cluster kind formation, namespace signalements. La leçon décrit ce que vous devez observer, sans reproduire de sortie, à l'exception des citations de la documentation signalées comme telles.
Créer le Deployment
Enregistrez le manifeste dans deployment.yaml, puis :
$ kubectl apply -f deployment.yaml
$ kubectl rollout status deployment/signalements
$ kubectl get deployment,replicaset,pod -l app.kubernetes.io/name=signalements
rollout status attend la fin du déploiement : la documentation donne la forme de ses messages, Waiting for rollout to finish: 2 out of 3 new replicas have been updated..., puis deployment "nginx-deployment" successfully rolled out avec le nom de votre Deployment. Son code de sortie vaut 0 en cas de succès, et différent de zéro si le délai de progression est dépassé : c'est ce qui le rend utile dans un pipeline.
La troisième commande liste en une fois le Deployment (colonnes READY, UP-TO-DATE, AVAILABLE), son ReplicaSet (dont le nom contient le pod-template-hash) et ses trois pods. L'option -l filtre par étiquette, exactement comme le sélecteur du Deployment.
Vérifier l'autoréparation
Dans un terminal, observez les pods ; dans un autre, supprimez-en un, en copiant son nom depuis la liste :
$ kubectl get pods -l app.kubernetes.io/name=signalements --watch
$ kubectl delete pod <nom-d-un-pod> --wait=false
--wait=false rend la main sans attendre la fin de l'arrêt. Pendant que l'ancien pod passe en Terminating, un nouveau apparaît en ContainerCreating, avec un suffixe différent : le ReplicaSet a constaté qu'il en manquait un.
Lisez ensuite les références de propriétaire d'un pod :
$ kubectl get pod <nom-d-un-pod> -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}'
La commande affiche ReplicaSet suivi du nom du ReplicaSet. La même commande sur le ReplicaSet affiche Deployment signalements.
Mettre à l'échelle
$ kubectl scale deployment/signalements --replicas=5
$ kubectl get pods -l app.kubernetes.io/name=signalements
Deux pods supplémentaires apparaissent, du même ReplicaSet : la mise à l'échelle ne crée pas de révision. Revenez à trois, de préférence en modifiant replicas dans le fichier et en le réappliquant : c'est le fichier qui fait foi.
Mettre à jour vers 1.3.0
Dans deployment.yaml, changez l'image en 1.3.0, ainsi que APP_VERSION et l'étiquette app.kubernetes.io/version. Puis, dans un premier terminal :
$ kubectl get pods -l app.kubernetes.io/name=signalements --watch
et dans un second :
$ kubectl apply -f deployment.yaml
$ kubectl rollout status deployment/signalements
Avec maxSurge: 1 et maxUnavailable: 0, vous voyez apparaître un nouveau pod, 0/1 puis 1/1, puis, cinq secondes plus tard (minReadySeconds), un ancien passer en Terminating. Puis le deuxième, puis le troisième. À aucun moment moins de trois pods ne sont prêts.
$ kubectl get replicaset -l app.kubernetes.io/name=signalements
$ kubectl rollout history deployment/signalements
Deux ReplicaSets : le nouveau à 3, l'ancien à 0. L'historique liste deux révisions ; la colonne CHANGE-CAUSE est vide (<none>) tant que vous ne posez pas l'annotation kubernetes.io/change-cause sur le Deployment, comme l'indique la documentation. L'ancienne option --record, qui la remplissait automatiquement, est dépréciée.
Une version qui ne démarre pas
Simulez une mauvaise version : changez l'image en ghcr.io/lyneko-formation/signalements:9.9.9, qui n'existe pas, et appliquez. Dans le terminal qui observe les pods, un nouveau pod apparaît et reste en ErrImagePull, puis ImagePullBackOff. Les trois anciens pods restent Running : le Deployment n'en supprime aucun, puisque le nouveau n'est jamais disponible et que maxUnavailable vaut 0. rollout status attend.
C'est la meilleure démonstration de l'intérêt de maxUnavailable: 0 : une erreur de déploiement ne coupe pas le service. Revenez en arrière :
$ kubectl rollout undo deployment/signalements
$ kubectl rollout status deployment/signalements
undo revient à la révision précédente ; --to-revision=N vise une révision précise. Le pod en échec disparaît, et le Deployment retrouve un état stable. Pensez à corriger aussi le fichier, sinon le prochain apply reproduira l'erreur.
Avec le délai de progression réglé à 300 secondes, si vous ne faites rien, kubectl describe deployment signalements affiche au bout de cinq minutes une condition Progressing à False avec la raison ProgressDeadlineExceeded.
Mettre en pause, redémarrer
$ kubectl rollout pause deployment/signalements
$ kubectl rollout resume deployment/signalements
$ kubectl rollout restart deployment/signalements
pause fige le Deployment : les changements du modèle sont enregistrés, mais aucun nouveau ReplicaSet n'est créé avant resume. Pratique pour grouper plusieurs modifications en un seul déploiement. restart remplace progressivement tous les pods sans rien changer d'autre, par exemple pour qu'ils relisent un Secret modifié.
Sous le capot
Deux contrôleurs, une boucle chacun. Le contrôleur de Deployment et celui de ReplicaSet tournent dans le kube-controller-manager (leçon 2). Ils ne se parlent pas : ils observent l'API. Le contrôleur de Deployment écrit des ReplicaSets et ajuste leur champ replicas ; le contrôleur de ReplicaSet voit ces changements et crée ou supprime des pods ; le planificateur voit des pods sans nœud et leur en attribue un ; le kubelet du nœud voit un pod qui lui est affecté et le démarre. Chacun ne fait qu'une chose, à partir de ce qu'il lit dans l'API.
Le calcul d'une vague. À chaque passage, le contrôleur de Deployment calcule combien il peut créer et combien il peut supprimer. Il peut monter le nouveau ReplicaSet tant que le total des pods ne dépasse pas replicas + maxSurge ; il peut descendre l'ancien tant que les pods disponibles ne tombent pas sous replicas - maxUnavailable. La documentation précise que les pods en cours d'arrêt ne sont pas comptés comme disponibles, si bien que, pendant une mise à jour, le nombre réel de pods peut dépasser replicas + maxSurge le temps que les anciens finissent leur délai de grâce.
Le hash du modèle. Le pod-template-hash est calculé à partir du modèle de Pod. Deux modèles identiques donnent le même hash : c'est ainsi que rollout undo retrouve l'ancien ReplicaSet au lieu d'en créer un nouveau, et qu'un retour à un modèle déjà vu réutilise sa révision (qui reçoit alors un nouveau numéro).
Le statut est la source de vérité des outils. kubectl rollout status ne fait que lire en boucle .status du Deployment (observedGeneration, updatedReplicas, availableReplicas, conditions). Argo CD lit les mêmes champs pour décider qu'une application est Healthy ou Progressing.
Pièges courants
Un sélecteur qui inclut la version. matchLabels: {app: signalements, version: "1.2.0"} semble précis ; il rend la mise à jour impossible, puisque le sélecteur est immuable et que les nouveaux pods ne lui correspondraient plus.
Pas de sonde de disponibilité. Le Deployment considère chaque pod prêt dès son démarrage, et remplace tout le parc par une version cassée en quelques secondes.
Les valeurs par défaut de la stratégie à grande échelle. 25 % d'indisponibilité tolérée, c'est trois pods sur douze. Fixez maxUnavailable explicitement selon la capacité que l'application peut perdre.
Modifier un ConfigMap et attendre un redéploiement. Le modèle de Pod n'a pas changé : rien ne se passe. rollout restart, ou un outil qui change le modèle quand la configuration change (une somme de contrôle dans une annotation, technique courante avec Helm).
kubectl edit ou kubectl set image sur un objet géré par Git. La modification fonctionne, puis disparaît à la synchronisation suivante, ou provoque une dérive signalée par Argo CD. En production, l'image change dans Git ; c'est la règle du cours GitOps.
Un rollout undo sans corriger la source. Le cluster revient en arrière, le fichier non : le déploiement suivant réintroduit la version cassée.
Un PodDisruptionBudget impossible. minAvailable: 3 pour trois répliques n'autorise aucune éviction : kubectl drain attend indéfiniment, et la maintenance des nœuds est bloquée. Un PDB doit laisser au moins une perturbation possible.
Sécurité
- Le modèle de Pod est ce qui s'exécute. Qui peut modifier un Deployment peut changer son image ou sa commande, et donc exécuter n'importe quoi avec les droits de ses pods. Le droit
updatesurdeploymentsvaut presque un accès aux secrets montés dans les pods ; il se donne comme tel. - Une image épinglée par empreinte garantit que la révision 3 exécute toujours le même contenu, et qu'un retour arrière restaure exactement l'ancien code. Une étiquette mutable rend l'historique trompeur : la révision 2 peut tirer aujourd'hui une image différente d'hier.
- Le retour arrière n'efface pas une faille. Revenir à une version précédente peut réintroduire une vulnérabilité corrigée entre-temps ; c'est un geste d'urgence, suivi d'une correction vers l'avant.
- Les annotations de suivi (
kubernetes.io/change-cause, ou mieux, l'historique Git) disent qui a déployé quoi ; sans elles, l'historique des révisions ne dit que « quoi ».
En production
- Le Deployment est décrit dans Git et appliqué par Argo CD ; la CI ne touche pas le cluster, elle écrit la nouvelle étiquette dans le dépôt (cours GitOps avec Argo CD). Chez Lyneko, c'est ainsi que sont déployées les applications du cluster
lyneko-apps, y compris le site que vous lisez. - Au moins deux répliques, réparties sur des nœuds et des zones différents (règles d'anti-affinité et de répartition, cours Kubernetes : workloads avancés, CRD et opérateurs), et un PodDisruptionBudget qui permet une éviction à la fois.
maxUnavailable: 0pour les services exposés aux utilisateurs, avec assez de capacité dans le cluster pour le pod supplémentaire demaxSurge. Sans place pour ce pod, la mise à jour reste bloquée enPending.- Les migrations de base se font avant le déploiement et doivent rester compatibles avec la version précédente, puisque les deux versions tournent ensemble pendant la mise à jour (cours Stratégies de déploiement).
- Au-delà de la mise à jour progressive : déploiements bleu-vert, canari avec analyse automatique des métriques. Ils ne sont pas dans le Deployment de base ; le cours Déploiements progressifs avec Argo Rollouts les présente.
Exercices
1. Calculer une vague (niveau 100). Un Deployment a 10 répliques, maxSurge: 25% et maxUnavailable: 25%. Combien de pods au plus peuvent exister pendant la mise à jour, et combien de pods disponibles au moins ? Même question avec maxSurge: 0 et maxUnavailable: 1.
Solution
maxSurge s'arrondit au supérieur : 25 % de 10 = 2,5, donc 3 ; maxUnavailable s'arrondit à l'inférieur : 2,5, donc 2. Au plus 13 pods (hors pods en cours d'arrêt, qui ne sont pas comptés), au moins 8 disponibles. Avec maxSurge: 0 et maxUnavailable: 1 : jamais plus de 10 pods, au moins 9 disponibles. Le Deployment supprime un ancien pod, attend que son remplaçant soit disponible, et recommence : une mise à jour lente, qui ne demande aucune capacité supplémentaire.
2. Ce qui redéploie (niveau 100). Parmi ces modifications du Deployment de Signalements, lesquelles créent une nouvelle révision ? (a) passer replicas de 3 à 4 ; (b) changer la valeur d'APP_VERSION dans le modèle ; (c) changer minReadySeconds ; (d) ajouter une étiquette equipe: voirie dans metadata.labels du Deployment ; (e) ajouter la même étiquette dans spec.template.metadata.labels.
Solution
Seules (b) et (e) modifient spec.template, et déclenchent donc une nouvelle révision et un remplacement progressif. (a), (c) et (d) modifient le Deployment sans toucher au modèle de Pod : les pods existants restent.
3. Le déploiement qui coupe (niveau 200). Un Deployment a 2 répliques, des valeurs par défaut pour la stratégie, et une sonde de disponibilité. À chaque mise à jour, le tableau de bord montre quelques dizaines d'erreurs 502 pendant une dizaine de secondes. Proposez deux hypothèses et la correction de chacune.
Solution
Première hypothèse : les connexions arrivent sur des pods qui s'arrêtent, parce que l'application ferme ses ports dès SIGTERM alors que les règles réseau des nœuds n'ont pas encore retiré le pod. Correction : un preStop qui attend quelques secondes, et un délai de grâce qui le couvre. Deuxième hypothèse : l'application ne s'arrête pas gracieusement (un shell en PID 1 qui ne transmet pas SIGTERM, puis un SIGKILL qui coupe les requêtes en cours) ; correction : forme exec dans l'image, et graceful_timeout de gunicorn cohérent avec le délai de grâce. On notera aussi qu'avec 2 répliques, les valeurs par défaut donnent maxSurge 1 et maxUnavailable 0 (25 % de 2 arrondi à l'inférieur) : la capacité ne baisse pas, ce n'est donc pas la stratégie qui est en cause.
4. Un PDB raisonnable (niveau 200). Signalements tourne avec 3 répliques. Écrivez un PodDisruptionBudget qui permet de vider les nœuds un par un sans jamais descendre sous deux pods disponibles. Que se passe-t-il si quelqu'un passe le Deployment à 2 répliques sans toucher au PDB ?
Solution
minAvailable: 2 avec le sélecteur app.kubernetes.io/name: signalements (ou, de façon équivalente ici, maxUnavailable: 1). Avec 3 répliques, une éviction est permise à la fois. Si le Deployment passe à 2 répliques avec minAvailable: 2, aucune éviction n'est plus permise : kubectl drain reste bloqué. maxUnavailable: 1 aurait mieux résisté au changement, puisqu'il s'exprime par rapport au nombre de pods attendus.
Récapitulatif
- Les contrôleurs retrouvent les pods par sélecteur d'étiquettes ; le sélecteur d'un Deployment est immuable et ne doit contenir que des étiquettes stables.
- Un ReplicaSet maintient N pods d'un modèle ; un Deployment gère des ReplicaSets, un par modèle, et passe de l'un à l'autre. Les
ownerReferencesrelient le tout, et la suppression se fait en cascade. RollingUpdateremplace par vagues :maxSurge(25 %, arrondi au supérieur) de pods en plus,maxUnavailable(25 %, arrondi à l'inférieur) en moins.Recreatecoupe.- Un pod nouveau doit être disponible (prêt depuis
minReadySeconds) avant que la vague suivante parte : la sonde de disponibilité pilote le déploiement. - Seul un changement de
spec.templatecrée une révision ;rollout restarten force une.rollout status,history,undo,pause,resumepilotent ;revisionHistoryLimit(10) garde l'historique ;progressDeadlineSeconds(600) signale un blocage sans rien annuler. - Sans coupure = sonde de disponibilité + arrêt gracieux sur
SIGTERM+preStopqui attend la propagation. - Un PodDisruptionBudget limite les évictions volontaires, pas les pannes.
- En production, le Deployment vit dans Git ; on ne fait pas
kubectl set image.
Pour aller plus loin
- La page Deployments de la documentation, notamment les sections sur le statut et les déploiements bloqués.
- La tâche Specifying a Disruption Budget for your Application, qui détaille le choix entre
minAvailableetmaxUnavailableselon le type d'application. - Le code du contrôleur de Deployment, dans
pkg/controller/deploymentdu dépôt de Kubernetes, pour qui veut voir le calcul des vagues. - La leçon suivante, Services et DNS, qui donne aux pods de ce Deployment une adresse stable.
Sources
- Kubernetes, Deployments
- Kubernetes, ReplicaSet
- Kubernetes, Labels and Selectors, et Recommended Labels
- Kubernetes, Specifying a Disruption Budget for your Application
- Kubernetes, Garbage Collection (ownerReferences)
- Kubernetes, tutoriel Pods And Endpoints Termination Flow
- kubernetes/kubernetes, contrôleur de Deployment (pkg/controller/deployment)