DevOps · C5 Orchestration · Chapitre 1 · 8 h
Kubernetes
Pourquoi orchestrer, architecture en nœuds et plan de contrôle, les cinq objets à maîtriser — pod, deployment, service, ingress, configmap — mise à l'échelle, sondes et déploiement progressif.
Une mise en garde avant de commencer, et elle fait partie du cours.
Kubernetes est le point qui coince de ce semestre. Sa complexité conceptuelle dépasse largement ce qu'un module de huit heures peut absorber, et c'est vrai aussi en entreprise : des équipes entières y consacrent leur métier. Ce chapitre est donc explicitement une découverte — déployer, exposer, mettre à l'échelle, revenir en arrière. Cinq objets maîtrisés valent mieux que vingt survolés.
Une seconde mise en garde, plus rare dans les cours : vous n'en avez peut-être pas besoin. Un service qui tient sur deux machines et se déploie en bleu-vert avec Compose n'a rien à gagner à l'orchestration, et beaucoup à perdre en complexité. Kubernetes se justifie quand on gère beaucoup de services sur beaucoup de machines. En dessous, il ajoute un système à exploiter — et ce système tombe en panne, lui aussi.
Ce que Compose ne sait pas faire
Le chapitre 4 fait tenir plusieurs conteneurs ensemble sur une machine. Quatre besoins le dépassent.
Plusieurs machines. Répartir des conteneurs sur un parc, en tenant compte de la place disponible.
L'auto-réparation. Une machine tombe à trois heures du matin : quelque chose doit redémarrer ses conteneurs ailleurs, sans réveiller personne.
La mise à l'échelle. Passer de trois à vingt exemplaires pour absorber une pointe, puis redescendre.
Les déploiements progressifs sans interruption, avec les stratégies du chapitre 6, intégrés plutôt que scriptés.
Un orchestrateur fait ces quatre choses. Kubernetes est celui qui s'est imposé.
L'architecture, en deux minutes
PLAN DE CONTRÔLE NŒUDS DE TRAVAIL ┌──────────────────────┐ ┌────────────┐ ┌────────────┐ │ serveur d'API │◄───────────┤ kubelet │ │ kubelet │ │ ordonnanceur │ │ ┌────┬───┐ │ │ ┌────┐ │ │ gestionnaire de │ │ │pod │pod│ │ │ │pod │ │ │ contrôleurs │ │ └────┴───┘ │ │ └────┘ │ │ etcd (l'état voulu) │ └────────────┘ └────────────┘ └──────────────────────┘Le serveur d'API est le seul point d'entrée : tout passe par lui, y compris les composants internes. L'ordonnanceur décide sur quel nœud placer un nouveau conteneur. Le gestionnaire de contrôleurs fait tourner les boucles de la section suivante. etcd stocke l'état déclaré — c'est la base de données du cluster, et sa sauvegarde est la seule chose vraiment critique.
Sur chaque nœud de travail, un agent, le kubelet, reçoit ses instructions et fait tourner les conteneurs. C'est tout ce qu'il faut retenir de l'architecture à ce niveau.
La seule idée à retenir : la réconciliation
Si vous ne deviez garder qu'une chose de ce chapitre, ce serait celle-ci.
On ne donne pas d'ordres à Kubernetes. On lui déclare un état, et des contrôleurs passent leur temps à réduire l'écart entre cet état et la réalité.
Animation · 8 étapes
On ne commande pas Kubernetes : on lui déclare un état, il le maintient
- Le manifeste est appliqué — On n'a écrit aucune commande de démarrage. On a DÉCLARÉ qu'il devait exister trois exemplaires de l'application en version 1.4 — et l'on a laissé le contrôleur trouver comment y parvenir.
- L'écart est résorbé — Le contrôleur continue de tourner, et ne fait rien — c'est l'état normal. Une boucle qui compare en permanence, plutôt qu'une commande exécutée une fois : toute la différence entre déclaratif et impératif tient là.
- Un nœud tombe — Une machine s'arrête, un exemplaire disparaît. Personne n'a rien demandé : la boucle constate simplement que le réel ne vaut plus le désiré. C'est l'auto-réparation, et elle n'est pas une fonctionnalité en plus — c'est la conséquence directe du modèle.
- Réparé, sans intervention — Quelques dizaines de secondes plus tard, l'écart est de nouveau nul. À trois heures du matin, cela fait la différence entre une astreinte réveillée et une ligne dans les journaux.
- Mise à l'échelle : on change le nombre désiré — Une seule valeur modifiée dans le manifeste, ou une commande de mise à l'échelle. On ne dit toujours pas COMMENT créer les exemplaires, ni où les placer : ce sont les décisions de l'ordonnanceur.
- Mise à jour : le déploiement progressif — Changer l'image relance la même boucle, avec une contrainte de plus : ne jamais descendre sous un nombre minimal d'exemplaires prêts. Le contrôleur crée un pod en 1.5, attend qu'il réponde, puis supprime un pod en 1.4 — et recommence.
- La sonde refuse le nouveau pod — Le pod 1.5 démarre mais sa sonde de disponibilité échoue : il ne reçoit donc AUCUN trafic. Le contrôleur ne peut pas poursuivre sans casser la contrainte de disponibilité, alors il s'arrête. Quatre exemplaires en 1.4 continuent de servir.
- Retour arrière : redéclarer l'état précédent — Une commande d'annulation remet l'image à 1.4, et la boucle fait le reste. C'est le point qu'il faut retenir de tout le chapitre : la sonde de disponibilité vous a protégé, pas votre vigilance — encore fallait-il l'avoir écrite.
Ce modèle explique tout le reste, et notamment des comportements qui paraissent magiques.
L'auto-réparation n'est pas une fonctionnalité ajoutée : c'est la conséquence directe du modèle. Un pod disparaît, l'écart réapparaît, la boucle le comble.
La mise à l'échelle n'est pas une opération : c'est un changement de la valeur déclarée.
Le retour arrière n'est pas un mécanisme spécial : c'est redéclarer l'état précédent.
Et une conséquence moins agréable : modifier quelque chose à la main ne tient pas. Supprimer un pod le fait revenir ; corriger un conteneur en direct disparaît au prochain remplacement. C'est la dérive du chapitre 7, activement combattue — ce qui est une excellente propriété, et une source de perplexité pour qui vient du monde des serveurs.
Cinq objets, et pas un de plus
| Objet | Répond à |
|---|---|
| Pod | l'unité déployée : un ou plusieurs conteneurs qui partagent réseau et stockage |
| Deployment | combien d'exemplaires, quelle version, comment les remplacer |
| Service | une adresse stable devant des pods qui vont et viennent |
| Ingress | comment le trafic extérieur entre dans le cluster |
| ConfigMap / Secret | la configuration et les secrets, séparés de l'image |
Le pod est l'unité de base — et non le conteneur. On ne le crée presque jamais directement : il est jetable, remplacé à chaque déploiement, et son adresse change à chaque fois.
Le deployment est ce qu'on écrit réellement. Il déclare un nombre d'exemplaires et un gabarit de pod, et c'est son contrôleur qui tient la boucle.
apiVersion: apps/v1kind: Deploymentmetadata: name: applispec: replicas: 3 selector: matchLabels: { app: appli } template: metadata: labels: { app: appli } spec: containers: - name: appli image: registre.exemple.net/appli:1.4.2 ports: [{ containerPort: 3000 }] readinessProbe: httpGet: { path: /pret, port: 3000 } livenessProbe: httpGet: { path: /vivant, port: 3000 }Le service résout le problème que les pods jetables créent : leurs adresses changent. Il fournit un nom stable et répartit le trafic sur les pods portant une étiquette donnée — c'est la découverte de service du chapitre 3, à l'échelle du cluster. Et c'est ici que les étiquettes prennent leur sens : rien n'est relié par un identifiant, tout est relié par « tous les objets qui portent telle étiquette ». Une étiquette mal recopiée donne un service qui ne pointe sur rien, sans aucune erreur.
L'ingress fait entrer le trafic extérieur, avec le routage par nom de domaine et la terminaison TLS.
Les ConfigMap et Secret portent ce qui change d'un environnement à l'autre, conformément à la règle du chapitre 6. Un avertissement s'impose : un Secret Kubernetes n'est pas chiffré par défaut, seulement encodé en base64 — c'est-à-dire lisible par quiconque a accès à l'objet ou à etcd. Le chiffrement au repos et le contrôle d'accès s'activent, et c'est le sujet du chapitre 11.
Les sondes : la distinction qui coûte cher
Trois sondes, trois questions différentes, et les confondre produit des pannes spectaculaires.
| Sonde | Question | Si elle échoue |
|---|---|---|
| readiness | ce pod peut-il recevoir du trafic ? | il est retiré du service, sans redémarrage |
| liveness | ce pod est-il encore vivant ? | il est redémarré |
| startup | a-t-il fini de démarrer ? | les deux autres sont suspendues jusque-là |
La faute classique consiste à pointer les deux premières sur la même adresse, laquelle vérifie souvent la base de données.
Déroulons ce qui se passe alors quand la base ralentit. La sonde de disponibilité échoue : les pods sortent du service — comportement correct, ils ne peuvent effectivement pas servir. Mais la sonde de vivacité échoue aussi : Kubernetes redémarre les pods. Ils redémarrent tous en même temps, se reconnectent tous à la base au même instant, et l'achèvent. Une lenteur passagère est devenue une panne totale, causée par l'orchestrateur.
La règle qui l'évite : la sonde de vivacité ne teste que le processus lui-même — répond-il encore ? — et ne doit jamais dépendre d'un service extérieur. La sonde de disponibilité, elle, peut légitimement en dépendre : c'est son rôle de dire « je ne peux pas servir en ce moment ».
Quant à la sonde de démarrage, elle évite qu'une application lente à s'initialiser soit tuée
avant d'avoir fini — c'est le start_period du chapitre 4, avec les mêmes conséquences si on
l'oublie.
Mise à l'échelle et déploiement progressif
La mise à l'échelle manuelle est un changement de valeur. La mise à l'échelle automatique ajoute une boucle de plus : un contrôleur observe une métrique — l'usage processeur, par exemple — et ajuste le nombre d'exemplaires entre deux bornes. Deux mises en garde : la métrique doit refléter la charge réelle, et il faut des bornes, faute de quoi une boucle de rétroaction mal réglée peut multiplier les exemplaires jusqu'à épuiser le cluster.
Le déploiement progressif est le comportement par défaut d'un deployment : changer l'image déclenche un remplacement pod par pod, sous deux contraintes réglables — combien d'exemplaires peuvent manquer, et combien peuvent être créés en plus temporairement.
C'est ici que la sonde de disponibilité prend toute son importance. Un nouveau pod n'entre dans le service que lorsqu'elle réussit : si la nouvelle version est défaillante, elle ne reçoit jamais de trafic, et le déploiement s'arrête de lui-même avec l'ancienne version toujours en place. Sans cette sonde, Kubernetes remplace joyeusement des pods sains par des pods cassés, puisque rien ne lui dit qu'ils le sont.
Le retour arrière, enfin, tient en une commande d'annulation : le deployment garde ses révisions précédentes, et redéclarer l'ancienne relance la même boucle.
Ce que ce cours ne traite pas
Trois sujets sont volontairement laissés de côté, et il vaut mieux savoir qu'ils existent.
Les opérateurs étendent Kubernetes avec des contrôleurs sur mesure, pour gérer des objets métier — une base de données répliquée, par exemple. C'est la suite logique du modèle, et c'est un métier.
Le maillage de services ajoute une couche réseau pour le chiffrement, l'observation et le routage fin entre services. Puissant, et d'une complexité qui dépasse largement ce module.
Le stockage persistant avancé — volumes répliqués, classes de stockage, sauvegardes. Le sujet est vaste, et l'usage courant reste de confier les données à un service géré hors du cluster, ce qui est souvent le bon choix.
Un cluster local — k3s, kind, minikube — suffit à tout ce chapitre, sans un centime d'hébergement.
Quiz · 1 question
Un pod est supprimé à la main avec kubectl delete pod. Il réapparaît quelques secondes plus tard. Pourquoi ?
- Une protection contre les suppressions accidentelles empêche de retirer un pod en service — protection
- Le deployment déclare un nombre d'exemplaires voulu : le contrôleur constate l'écart entre 3 voulus et 2 réels, et le comble. On ne supprime pas un pod, on change l'état déclaré — boucle de réconciliation
- Le pod a été mis en cache par le kubelet, qui le restaure automatiquement — cache local
Réponse : C'est la conséquence directe du modèle déclaratif, et la source de perplexité la plus fréquente pour qui vient du monde des serveurs. Le deployment déclare « il doit exister trois exemplaires » ; son contrôleur compare en permanence ce nombre au réel. Supprimer un pod crée un écart, que la boucle comble en quelques secondes — c'est exactement le même mécanisme que l'auto-réparation après la panne d'un nœud, et ce n'est pas une fonctionnalité en plus mais la même. La leçon pratique : on ne donne pas d'ordres, on modifie l'état DÉCLARÉ. Pour retirer réellement des exemplaires, on change le nombre voulu ; pour arrêter l'application, on supprime le deployment. Corollaire moins agréable : corriger un conteneur à la main ne tient pas non plus, la correction disparaissant au prochain remplacement — ce qui est une excellente protection contre la dérive du chapitre 7.
Quiz · 1 question
Les sondes de vivacité et de disponibilité pointent toutes deux sur une adresse qui vérifie la base de données. La base ralentit. Que se passe-t-il ?
- Les pods sortent du service le temps que la base réponde, puis y reviennent : le comportement est correct — comportement correct
- Les pods sortent du service — correct — MAIS sont aussi redémarrés par la sonde de vivacité. Ils se reconnectent tous en même temps à une base déjà en difficulté et l'achèvent : une lenteur devient une panne totale, causée par l'orchestrateur — cascade de redémarrages
- Kubernetes détecte que la cause est externe et suspend les deux sondes — détection automatique
Réponse : C'est la panne classique, et elle est provoquée par la supervision elle-même. Les deux sondes répondent à des questions différentes : la DISPONIBILITÉ demande « ce pod peut-il servir ? » et son échec retire le pod du service, sans le redémarrer — dépendre de la base y est légitime. La VIVACITÉ demande « ce processus est-il encore vivant ? » et son échec provoque un REDÉMARRAGE. Pointer les deux sur la même vérification transforme un incident distant en redémarrage massif : tous les pods repartent, se reconnectent simultanément, et la base qui ralentissait s'effondre. La règle est donc absolue : la sonde de vivacité ne teste que le processus lui-même et NE DÉPEND JAMAIS d'un service extérieur. Kubernetes ne peut rien détecter de tel — il applique ce qu'on a déclaré, et c'est bien le problème.
À vous
L'exercice fait tourner la boucle de réconciliation, et c'est le seul moyen de se convaincre qu'elle explique tout le reste.
Vous écrirez le contrôleur : comparer le nombre voulu au nombre réel, agir, recommencer. Puis vous lui enverrez des événements — une panne de nœud, une mise à l'échelle, une modification d'image — et vous constaterez qu'aucun n'a demandé de code supplémentaire.
La seconde partie porte sur le déploiement progressif avec une sonde de disponibilité qui échoue : le déploiement doit s'arrêter de lui-même, l'ancienne version restant majoritaire. Vous relancerez ensuite le même scénario sans sonde, et vous compterez les requêtes perdues.
La troisième reproduit la cascade du second quiz : sondes mal configurées, base qui ralentit, et le nombre de redémarrages que l'orchestrateur provoque. Vous corrigerez la configuration et relancerez le même incident.
Exercice de code
Écrivez la boucle de réconciliation, éprouvez un déploiement progressif sans sonde, puis provoquez la cascade de redémarrages.
Point de départ
// ── L'état déclaré, et le monde réel ──────────────────────────────────────
const desire = { replicas: 3, image: "appli:1.4" };
let pods = []; // le réel
let horloge = 0;
let prochainId = 1;
const nouveauPod = (image) => ({
id: "pod-" + (prochainId++), image, pret: false, ne: horloge, redemarrages: 0,
});
// ── La boucle du contrôleur ───────────────────────────────────────────────
function reconcilier(journal) {
const vivants = pods.filter((p) => p.image === desire.image);
const ecart = desire.replicas - vivants.length;
// ← à écrire : créer si l'écart est positif, supprimer s'il est négatif,
// et ne RIEN faire s'il est nul. C'est toute la boucle.
return "aucune action";
}
// Les pods deviennent prêts quelques instants après leur création.
function avancer(secondes, sondePret) {
horloge += secondes;
for (const p of pods) {
if (!p.pret && horloge - p.ne >= 2) p.pret = sondePret(p);
}
}
function etat() {
const prets = pods.filter((p) => p.pret).length;
return "réel " + pods.length + " (" + prets + " prêts) | désiré " + desire.replicas +
" | images " + [...new Set(pods.map((p) => p.image))].join(", ");
}
// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Écrivez reconcilier(). Envoyez-lui quatre événements : démarrage,
// panne d'un nœud, mise à l'échelle, changement d'image. Combien de
// lignes de code chaque événement a-t-il demandées ?
// 2. Déploiement progressif avec une sonde qui ÉCHOUE sur la nouvelle
// version : le déploiement doit s'arrêter. Relancez sans sonde et
// comptez les requêtes perdues.
// 3. Reproduisez la cascade : vivacité et disponibilité pointant toutes
// deux sur la base. La base ralentit — combien de redémarrages ?
console.log(reconcilier([]));
Solution
let desire = { replicas: 3, image: "appli:1.4" };
let pods = [];
let horloge = 0, prochainId = 1;
const nouveauPod = (image) => ({
id: "pod-" + (prochainId++), image, pret: false, ne: horloge, redemarrages: 0,
});
function reconcilier() {
// Toute la boucle tient en trois lignes : compter, comparer, agir.
const conformes = pods.filter((p) => p.image === desire.image);
const ecart = desire.replicas - conformes.length;
if (ecart > 0) { for (let i = 0; i < ecart; i++) pods.push(nouveauPod(desire.image)); return "créé " + ecart; }
if (ecart < 0) { pods = pods.slice(0, desire.replicas); return "supprimé " + -ecart; }
// Reste à retirer les pods d'une ancienne image, un par un (progressif).
const anciens = pods.filter((p) => p.image !== desire.image);
if (anciens.length && conformes.filter((p) => p.pret).length >= desire.replicas - 1) {
pods = pods.filter((p) => p !== anciens[0]);
return "retiré un pod de l'ancienne version";
}
return "aucune action";
}
function avancer(secondes, sondePret) {
horloge += secondes;
for (const p of pods) if (!p.pret && horloge - p.ne >= 2) p.pret = sondePret(p);
}
function etat() {
const parImage = {};
for (const p of pods) parImage[p.image] = (parImage[p.image] ?? 0) + 1;
return "réel " + pods.length + " (" + pods.filter((p) => p.pret).length + " prêts)" +
" | désiré " + desire.replicas +
" | " + Object.entries(parImage).map(([i, n]) => i + " ×" + n).join(", ");
}
const tourner = (n, sonde = () => true) => {
for (let i = 0; i < n; i++) { const a = reconcilier(); avancer(2, sonde);
if (a !== "aucune action") console.log(" t=" + horloge + "s " + a.padEnd(38) + etat()); }
};
console.log("— 1. quatre événements, aucun code supplémentaire —");
console.log(" [démarrage]");
tourner(4);
console.log(" [un nœud tombe : deux pods disparaissent]");
pods.splice(0, 2);
tourner(4);
console.log(" [mise à l'échelle : replicas 3 → 5]");
desire.replicas = 5;
tourner(4);
console.log(" [nouvelle image : 1.4 → 1.5]");
desire.image = "appli:1.5";
tourner(12);
console.log(" Aucun de ces quatre événements n'a demandé une ligne de plus : la");
console.log(" même boucle « comparer, agir » les traite tous.");
console.log("");
console.log("— 2. déploiement progressif avec une sonde qui échoue —");
function scenarioDeploiement(avecSonde) {
pods = []; horloge = 0; prochainId = 1;
desire = { replicas: 4, image: "appli:1.4" };
tourner(6);
desire.image = "appli:1.5";
// La 1.5 est défaillante : sa sonde de disponibilité ne passera jamais.
const sonde = (p) => (avecSonde ? p.image !== "appli:1.5" : true);
let perdues = 0;
for (let i = 0; i < 20; i++) {
reconcilier(); avancer(2, sonde);
// Le trafic va aux pods PRÊTS ; sans sonde, un pod cassé reçoit du trafic.
const servants = pods.filter((p) => p.pret);
const casses = servants.filter((p) => p.image === "appli:1.5").length;
if (servants.length) perdues += Math.round(100 * casses / servants.length);
}
const anciens = pods.filter((p) => p.image === "appli:1.4").length;
return { perdues, anciens, total: pods.length };
}
for (const avec of [true, false]) {
const r = scenarioDeploiement(avec);
console.log(" " + (avec ? "AVEC sonde de disponibilité " : "SANS sonde ") +
": " + r.anciens + "/" + r.total + " pods encore en 1.4 | " +
r.perdues + " requêtes perdues (sur 2000)");
}
console.log(" Avec la sonde, aucun pod de la version défaillante n'entre dans le");
console.log(" service : le déploiement S'ARRÊTE DE LUI-MÊME et l'ancienne version");
console.log(" continue de servir. Sans elle, Kubernetes remplace joyeusement des");
console.log(" pods sains par des pods cassés — rien ne lui dit qu'ils le sont.");
console.log("");
console.log("— 3. la cascade de redémarrages —");
function scenarioSondes(vivaciteDependDeLaBase) {
let pods = Array.from({ length: 6 }, (_, i) => ({ id: i, redemarrages: 0, dansLeService: true }));
let chargeBase = 0.5; // la base ralentit
const journal = [];
for (let t = 0; t < 10; t++) {
const baseLente = chargeBase > 0.8;
for (const p of pods) {
// Disponibilité : dépend légitimement de la base.
p.dansLeService = !baseLente;
// Vivacité : ne DOIT tester que le processus.
const vivaciteEchoue = vivaciteDependDeLaBase ? baseLente : false;
if (vivaciteEchoue) { p.redemarrages++; }
}
// Chaque redémarrage rouvre des connexions et charge davantage la base.
const redemarres = vivaciteDependDeLaBase && baseLente ? pods.length : 0;
chargeBase = Math.min(1, chargeBase + 0.12 + redemarres * 0.05);
journal.push("t=" + t + " charge base " + (chargeBase * 100).toFixed(0) + " %" +
" redémarrages cumulés " + pods.reduce((s, p) => s + p.redemarrages, 0));
}
return { journal, total: pods.reduce((s, p) => s + p.redemarrages, 0) };
}
for (const [nom, dep] of [["vivacité DÉPEND de la base", true], ["vivacité teste le processus seul", false]]) {
const r = scenarioSondes(dep);
console.log(" " + nom.padEnd(34) + r.total + " redémarrages");
if (dep) for (const l of r.journal.slice(4, 8)) console.log(" " + l);
}
console.log(" Dans le premier cas, la supervision a transformé une lenteur en");
console.log(" panne totale : tous les pods redémarrent, se reconnectent en même");
console.log(" temps, et achèvent la base. Kubernetes ne peut rien détecter — il");
console.log(" applique ce qu'on a déclaré.");
En travaux pratiques
Travaux pratiques 9 · 5 h
Déployer, exposer, mettre à l'échelle — et rien de plus
Manipuler les cinq objets du chapitre sur un cluster local, jusqu'à voir la boucle de réconciliation réparer, déployer et refuser d'elle-même.
Avant de commencer
- Un cluster local : k3s, kind ou minikube — aucun hébergement payant
- kubectl configuré, et kubectl get nodes qui répond
- L'image du TP 3 chargée dans le cluster
Énoncé
- Un premier déploiement — Écrivez un Deployment de trois exemplaires de l'application. Appliquez-le, puis listez les pods et notez leurs noms.
- Éprouver la réconciliation — Supprimez un pod à la main. Relistez immédiatement, puis dix secondes plus tard. Expliquez ce que vous observez, puis dites comment retirer RÉELLEMENT un exemplaire. Indice : Vous n'avez pas donné d'ordre, vous avez déclaré un nombre.
- Exposer — Ajoutez un Service devant les pods, puis un Ingress pour y accéder depuis votre machine. Vérifiez que le trafic se répartit sur les trois exemplaires. Indice : Rien n'est relié par identifiant : tout l'est par ÉTIQUETTE. Une étiquette mal recopiée donne un service qui ne pointe sur rien, sans aucune erreur.
- Sortir la configuration de l'image — Déplacez l'adresse de la base dans un ConfigMap et le mot de passe dans un Secret, puis injectez-les dans le conteneur. Affichez ensuite le contenu du Secret et commentez.
- Mettre à l'échelle — Passez à dix exemplaires, puis redescendez à deux. Chronométrez chaque opération et observez où les pods sont placés.
- Déployer une nouvelle version — Changez l'étiquette d'image et suivez le remplacement en direct. Combien d'exemplaires sont indisponibles au pire moment ?
- Déployer une version cassée, AVEC sonde — Ajoutez une sonde de disponibilité, puis déployez une version dont cette sonde échoue. Que fait le déploiement ? Combien de requêtes sont perdues ?
- Recommencer SANS sonde — Retirez la sonde et refaites l'essai. Comparez, puis exécutez un retour arrière.
- Provoquer la cascade — Faites pointer la sonde de vivacité sur une adresse qui vérifie la base, puis arrêtez la base. Comptez les redémarrages. Corrigez et refaites l'essai.
C'est réussi quand
- Un pod supprimé revient seul, et vous savez expliquer pourquoi
- Le déploiement de la version cassée s'arrête TOUT SEUL, sans requête perdue
- Vous savez dire ce que le Secret protège réellement
- La correction des sondes fait tomber le nombre de redémarrages à zéro
Correction
apiVersion: apps/v1
kind: Deployment
metadata:
name: tickets
spec:
replicas: 3
selector:
matchLabels: { app: tickets }
template:
metadata:
labels: { app: tickets } # DOIT correspondre au selector
spec:
containers:
- name: tickets
image: registre/tickets:1.0
ports: [{ containerPort: 3000 }]
envFrom:
- configMapRef: { name: tickets-config }
- secretRef: { name: tickets-secret }
readinessProbe:
httpGet: { path: /pret, port: 3000 }
periodSeconds: 3
livenessProbe:
httpGet: { path: /vivant, port: 3000 }
periodSeconds: 10Deux adresses DISTINCTES pour les deux sondes, et c'est tout l'enjeu de l'étape 9. /pret peut légitimement vérifier la base — c'est son rôle de dire « je ne peux pas servir » ; /vivant ne doit tester que le processus.
kubectl delete pod tickets-7d4b9-x2k4 kubectl get pods # tickets-7d4b9-x2k4 Terminating # tickets-7d4b9-m9p1 ContainerCreating <-- déjà remplacé # pour en retirer un RÉELLEMENT : kubectl scale deployment tickets --replicas=2
Le contrôleur compare en permanence trois exemplaires voulus à deux réels, et comble l'écart. C'est exactement le mécanisme qui répare une panne de nœud — ce n'est pas une fonctionnalité en plus, c'est la même. Corollaire : corriger un conteneur à la main ne tient pas non plus.
kubectl get secret tickets-secret -o jsonpath='{.data.MOT_DE_PASSE}' | base64 -d
# secret
kubectl auth can-i get secrets --as=system:serviceaccount:default:defaultUn Secret n'est PAS chiffré par défaut : il est encodé en base64, c'est-à-dire lisible par quiconque a le droit de le lire ou l'accès à etcd. Ce qui le protège est le contrôle d'accès, plus le chiffrement au repos si on l'a activé. La commande auth can-i est le bon réflexe : elle dit qui peut réellement lire quoi.
AVEC sonde de disponibilité kubectl rollout status deployment/tickets Waiting for 1 pod to be ready... (bloqué) → 3 pods en 1.0 servent toujours, 0 requête perdue → kubectl rollout undo deployment/tickets SANS sonde rollout terminé en 12 s → 3 pods en 1.1 « prêts » alors qu'ils renvoient des erreurs → ~100 % des requêtes en échec jusqu'au retour arrière
Un nouveau pod n'entre dans le Service que si sa sonde de disponibilité réussit. Sans elle, Kubernetes remplace des pods sains par des pods cassés en toute confiance — rien ne lui dit qu'ils le sont. La sonde n'est pas un confort : c'est ce qui rend le déploiement progressif sûr.
# la faute : les deux sondes vérifient la base
livenessProbe:
httpGet: { path: /sante-complete, port: 3000 } # interroge la base
# base arrêtée :
kubectl get pods
# tickets-… 0/1 CrashLoopBackOff RESTARTS: 7
# la correction
livenessProbe:
httpGet: { path: /vivant, port: 3000 } # ne teste QUE le processusUne lenteur de la base devient une panne totale, causée par l'orchestrateur : tous les pods redémarrent, se reconnectent en même temps et achèvent la base. La règle est absolue — la sonde de vivacité ne dépend jamais d'un service extérieur. Après correction, les pods sortent du Service le temps que la base revienne, et aucun ne redémarre.
Ce que la suite en fait
Le bloc VI ajoute ce sans quoi tout ce qui précède est aveugle. Kubernetes redémarre des pods, retire du trafic, arrête des déploiements — et rien de tout cela ne se voit sans métriques ni journaux centralisés. Le chapitre 10 fournit les trois piliers, et rend enfin possible la décision automatique du canari du chapitre 6.
Le chapitre 11, lui, reprendra le Secret encodé en base64, les images tirées de registres publics et les droits d'accès au cluster — trois sujets que ce chapitre n'a fait qu'effleurer.
À retenir
Flashcards · 5 cartes
- Quels quatre besoins dépassent Compose, et quelle est la mise en garde ?
- 1) PLUSIEURS MACHINES : répartir des conteneurs sur un parc. 2) L'AUTO-RÉPARATION : redémarrer ailleurs quand une machine tombe. 3) LA MISE À L'ÉCHELLE : passer de trois à vingt exemplaires. 4) LES DÉPLOIEMENTS PROGRESSIFS intégrés. MISE EN GARDE : vous n'en avez peut-être pas besoin — un service sur deux machines déployé en bleu-vert avec Compose n'a rien à gagner et beaucoup à perdre en complexité. Kubernetes ajoute un système à exploiter, qui tombe en panne lui aussi.
- Énoncez l'idée centrale de Kubernetes, et les quatre comportements qu'elle explique.
- ON NE DONNE PAS D'ORDRES : on DÉCLARE un état, et des contrôleurs réduisent en permanence l'écart avec la réalité. Elle explique : l'AUTO-RÉPARATION (un pod disparaît, l'écart réapparaît, la boucle le comble — ce n'est pas une fonctionnalité ajoutée) ; la MISE À L'ÉCHELLE (changer une valeur déclarée) ; le RETOUR ARRIÈRE (redéclarer l'état précédent) ; et le fait que MODIFIER À LA MAIN NE TIENT PAS — excellente protection contre la dérive, et source de perplexité.
- Citez les cinq objets et ce que chacun résout.
- POD : l'unité déployée, un ou plusieurs conteneurs partageant réseau et stockage — jetable, on ne le crée presque jamais directement. DEPLOYMENT : combien d'exemplaires, quelle version, comment les remplacer — c'est ce qu'on écrit. SERVICE : une adresse STABLE devant des pods qui vont et viennent, reliés par ÉTIQUETTES. INGRESS : l'entrée du trafic extérieur, routage par domaine et TLS. CONFIGMAP / SECRET : la configuration séparée de l'image — et un Secret n'est PAS chiffré par défaut, seulement encodé en base64.
- Distinguez les trois sondes, et quelle faute produit une panne totale ?
- READINESS : « puis-je recevoir du trafic ? » — l'échec retire du service, SANS redémarrage, et dépendre d'un service externe y est légitime. LIVENESS : « suis-je encore vivant ? » — l'échec REDÉMARRE le pod, et elle ne doit JAMAIS dépendre d'un service extérieur. STARTUP : « ai-je fini de démarrer ? » — elle suspend les deux autres. LA FAUTE : pointer vivacité et disponibilité sur la même vérification de base de données — une lenteur de la base redémarre alors tous les pods, qui se reconnectent en même temps et l'achèvent.
- Pourquoi la sonde de disponibilité est-elle essentielle au déploiement progressif ?
- Parce qu'un nouveau pod n'entre dans le service QUE lorsqu'elle réussit. Si la nouvelle version est défaillante, elle ne reçoit jamais de trafic et le déploiement S'ARRÊTE DE LUI-MÊME, l'ancienne version restant en place. Sans cette sonde, Kubernetes remplace joyeusement des pods sains par des pods cassés, puisque rien ne lui dit qu'ils le sont. Le retour arrière tient ensuite en une commande d'annulation : le deployment conserve ses révisions.