Read in English · Leer en español · Auf Deutsch lesen
Section 10.2 sur 10.8 · Avancé · 25 min de lecture
Conteneurs et orchestration avec Kubernetes
Leçon vidéo
5 min · narré en anglais, avec sous-titresÉcouter seulement (audio)
Lire la transcription
Exécuter un conteneur tient en une seule commande. Faire tourner quarante services, avec plusieurs copies chacun, sur des dizaines de machines, c’est une autre affaire. Voici comment Kubernetes s’y prend, et quand il en fait plus que ce dont vous avez besoin.
Pensez à un grand port. Les marchandises arrivent dans des conteneurs maritimes standard, si bien que chaque grue, chaque navire et chaque camion peut les manipuler sans savoir ce qu’ils contiennent. Le capitaine du port ne déplace pas les caisses lui-même. Il tient un tableau qui indique ce qui doit se trouver où. Son équipe compare sans cesse le quai au tableau, et corrige toute différence. Les conteneurs sont les caisses standard, et Kubernetes est la capitainerie.
Une image de conteneur est un empilement de couches en lecture seule, plus une petite configuration et un manifeste qui les répertorie. Chaque couche est identifiée par un digest, une empreinte de son contenu, si bien que les couches identiques ne sont stockées et téléchargées qu’une seule fois. Un tag, comme la version 2.4.1, est une étiquette mobile : toute personne disposant des droits de push peut la faire pointer vers une autre image dès demain. Un digest ne change jamais de sens, donc la production doit exécuter des images épinglées par digest. Et grâce aux normes ouvertes, une image construite par n’importe quel outil peut être stockée dans n’importe quel registre conforme et exécutée par n’importe quel runtime conforme.
Un cluster Kubernetes comporte deux moitiés : un plan de contrôle, qui prend les décisions, et des nœuds de travail, qui exécutent les charges de travail. Vous, un pipeline ou un agent GitOps déclarez l’état souhaité au serveur d’API, la porte d’entrée. Il vérifie qui vous êtes et valide chaque requête. Seul le serveur d’API communique avec le magasin qui contient l’état du cluster. L’ordonnanceur surveille l’apparition de nouveaux pods et choisit un nœud pour chacun. Le gestionnaire de contrôleurs exécute des dizaines de contrôleurs, chacun responsable d’un type d’objet, par exemple pour maintenir en fonctionnement le bon nombre de pods. Sur chaque nœud de travail, un agent surveille le serveur d’API pour repérer les pods qui lui sont attribués, demande au runtime de conteneurs de les démarrer, vérifie leur santé et renvoie leur état. Remarquez le sens des flèches. Le plan de contrôle ne se connecte jamais à un nœud. Les nœuds surveillent l’état dans lequel ils doivent se trouver et convergent vers lui, si bien qu’un nœud qui a été coupé se remet simplement à niveau.
L’idée centrale est déclarative. Vous ne dites pas au cluster de démarrer trois conteneurs. Vous enregistrez l’état souhaité : trois copies doivent exister. Des contrôleurs exécutent alors une boucle sans fin : observer l’état réel, le comparer à l’état souhaité, et agir pour supprimer la différence. Si un nœud tombe en panne pendant le coup de feu du soir, le contrôleur voit deux pods là où trois sont souhaités, et en crée un ; l’ordonnanceur le place sur un nœud sain. Comme les contrôleurs agissent sur la différence actuelle, un événement manqué ou un plantage ne cause aucun dommage durable.
Deux réglages comptent plus que tout. Une requête est ce que l’ordonnanceur réserve : un pod qui demande un demi-CPU ne va que sur un nœud disposant d’autant de capacité non réservée. Une limite est un plafond. Un conteneur qui dépasse sa limite de CPU est ralenti ; un conteneur qui dépasse sa limite de mémoire est tué puis redémarré. Les sondes informent Kubernetes de la santé des conteneurs. Tant qu’une sonde de disponibilité échoue, le pod ne reçoit aucun trafic, mais continue de tourner. Quand la sonde de vivacité échoue de façon répétée, le conteneur est redémarré. Ne laissez donc jamais la sonde de vivacité vérifier la base de données : un bref basculement de la base redémarrerait tous les pods en même temps, transformant un incident passager en panne.
Dimensionnons le service des commandes pour le coup de feu. Chaque pod demande un demi-CPU, et l’autoscaler vise soixante pour cent de cette valeur, avec trois à douze répliques. Chaque nœud dispose d’environ trois virgule huit CPU, il peut donc accueillir sept de ces pods. À dix-huit heures, quatre répliques sont à quatre-vingt-dix pour cent de leur requête. Quatre fois quatre-vingt-dix, divisé par soixante, donne six répliques. Vingt minutes plus tard, six répliques sont à quatre-vingt-cinq pour cent. Six fois quatre-vingt-cinq, divisé par soixante, donne huit virgule cinq, arrondi à neuf. Si les nœuds sont déjà pleins, trois nouveaux pods attendent, et un nouveau nœud met peut-être deux à quatre minutes à rejoindre le cluster. L’autoscaling réagit à une charge déjà arrivée. Relevez donc le minimum avant un coup de feu prévisible, gardez de la capacité de nœuds en réserve, et gardez des images petites pour que les nouveaux pods démarrent vite.
Kubernetes est une plateforme pour construire des plateformes, et il le fait payer. Il faut que quelqu’un conçoive le réseau, l’identité, les secrets, les politiques et l’observabilité. Il connaît trois versions mineures par an, chacune prise en charge pendant environ quatorze mois, et les plans de contrôle managés ne retirent qu’une partie du travail. Pour une petite équipe qui exploite quelques services web sans état, un PaaS ou une plateforme de conteneurs serverless offre l’essentiel des avantages pour une fraction du travail. Il devient rentable avec de nombreux services, plusieurs équipes et une équipe plateforme pour l’exploiter. Sinon, les organisations finissent souvent par construire, en pratique, un PaaS moins bon par-dessus.
La section complète présente les objets de base, des exemples de manifestes et l’arrêt propre. Ensuite, dans la section dix point trois, nous verrons l’application douze facteurs. À très vite.
Ce qu’il faut savoir
- Une image de conteneur est un empilement de couches adressées par leur contenu, plus une configuration et un manifeste ; les tags peuvent changer, les digests non, donc la production doit exécuter des images épinglées par digest.
- Les spécifications OCI de runtime, d’image et de distribution permettent de stocker dans n’importe quel registre des images construites par n’importe quel outil et de les exécuter avec n’importe quel runtime conforme.
- Kubernetes comporte un plan de contrôle (serveur d’API, etcd, ordonnanceur, gestionnaire de contrôleurs) et des nœuds de travail (kubelet, runtime de conteneurs, kube-proxy) ; seul le serveur d’API communique avec etcd, et les nœuds tirent leur travail.
- Les contrôleurs exécutent des boucles de réconciliation déclenchées par niveau qui comparent l’état réel à l’état souhaité et agissent sur la différence, ce qui rend le système robuste face aux pannes et aux événements manqués.
- Les pods sont jetables ; les Deployments les déploient, les Services leur donnent des noms stables, Gateway API y fait entrer le trafic, les StatefulSets apportent identité et stockage, les Jobs s’exécutent jusqu’à leur terme et le HPA ajuste le nombre de répliques.
- Les requêtes pilotent la planification et l’autoscaling, les limites plafonnent la consommation (le CPU est bridé, un conteneur qui dépasse sa mémoire est tué) ; les sondes de vivacité ne doivent pas vérifier les dépendances partagées.
- Kubernetes est rentable avec de nombreux services, plusieurs équipes et une équipe plateforme ; pour quelques services sans état, un PaaS ou une plateforme de conteneurs serverless est généralement le meilleur choix.
Images, couches et registres
Le chapitre 0 a présenté les conteneurs comme des processus isolés qui partagent le noyau de l’hôte, empaquetés sous forme d’images construites à partir d’un Dockerfile (les serveurs et le cloud). Un architecte a besoin d’un peu plus de détails, car la conception des images influe sur la vitesse de build, sur la sécurité et sur la fiabilité avec laquelle le même logiciel atteint chaque environnement.
Une image de conteneur est un empilement de couches en lecture seule, chacune étant un ensemble compressé de modifications de fichiers, plus un petit document de configuration (la commande à exécuter, les variables d’environnement, l’utilisateur) et un manifeste qui les répertorie. Chaque couche et chaque manifeste sont identifiés par un digest, une empreinte SHA-256 de leur contenu. Les couches identiques ne sont stockées et téléchargées qu’une fois, si bien qu’une centaine d’images construites sur la même base partagent ses couches. Comme chaque instruction d’un Dockerfile produit une couche et que les outils de build réutilisent les couches en cache tant que rien ne change, on ordonne les instructions de la moins souvent modifiée à la plus souvent modifiée : image de base, puis dépendances, puis code applicatif. Les builds multi-étapes compilent dans une étape et ne copient que le résultat dans une petite image finale, en laissant derrière eux compilateurs et outils de build.
Les images résident dans un registre : Docker Hub, GitHub Container Registry, Amazon ECR, Azure Container Registry, Google Artifact Registry, ou un registre auto-hébergé comme Harbor. Un tag comme orders:2.4.1 est une étiquette mobile ; toute personne disposant des droits de push peut la faire pointer vers une autre image dès demain. Un digest comme orders@sha256:3f1c... ne peut jamais changer de sens. Les déploiements en production doivent donc résoudre les tags en digests, pour que ce qui a été testé soit exactement ce qui s’exécute, et ne jamais utiliser latest.
Les petites images se téléchargent plus vite et comportent moins de composants à corriger. La pratique courante consiste à partir d’une base minimale (une distribution allégée ou une image distroless ne contenant que l’environnement d’exécution), à s’exécuter sous un utilisateur non root, à reconstruire régulièrement pour intégrer les correctifs de sécurité et à analyser les images dans le pipeline pour y détecter les vulnérabilités connues. La signature des images et l’ajout d’une nomenclature logicielle relèvent de la sécurité de la chaîne d’approvisionnement, traitée au chapitre 8 (sécurité de la chaîne d’approvisionnement logicielle).
Les normes OCI
Docker a popularisé les conteneurs à partir de 2013 et, en 2015, Docker et d’autres acteurs ont fondé l’Open Container Initiative (OCI) sous l’égide de la Linux Foundation pour éviter la fragmentation du format. L’OCI maintient trois spécifications. La spécification de runtime indique comment exécuter un conteneur décompressé (espaces de noms, groupes de contrôle, montages, processus à lancer) ; runc en est l’implémentation de référence. La spécification d’image définit les couches, les manifestes et la configuration. La spécification de distribution définit l’API HTTP que les registres exposent pour le push et le pull. La version 1.1 des spécifications d’image et de distribution (2024) a ajouté des façons standard de stocker d’autres artefacts dans les registres et de les lier à une image : c’est ainsi que les signatures et les nomenclatures logicielles voyagent désormais avec les images qu’elles décrivent.
Le résultat pratique est l’interchangeabilité. Une image construite par Docker, Podman, Buildah ou un service de build cloud peut être stockée dans n’importe quel registre conforme et exécutée par n’importe quel runtime conforme : containerd et CRI-O sur un nœud Kubernetes, une plateforme de conteneurs serverless ou l’ordinateur portable d’un développeur. C’est pourquoi les conteneurs, contrairement à la plupart des formats d’empaquetage qui les ont précédés, sont devenus une langue commune entre le développement, l’exploitation et tous les grands clouds.
Pourquoi l’orchestration est nécessaire
Exécuter un conteneur tient en une seule commande. Faire tourner la plateforme de livraison de repas, non : quarante services, de trois à trente copies chacun, répartis sur des dizaines de machines dans trois zones de disponibilité, avec une nouvelle version de quelque chose mise en production toutes les heures. Il faut que quelqu’un décide sur quelle machine tourne chaque copie, redémarre les copies qui plantent, remplace les machines qui meurent, ajoute des copies pour le coup de feu du soir et les retire ensuite, déploie les nouvelles versions sans perdre de requêtes, permette aux services de se trouver mutuellement alors que les copies vont et viennent, leur fournisse configuration et secrets, et attache du stockage aux quelques-uns qui en ont besoin.
L’orchestration de conteneurs est le logiciel qui fait tout cela à partir d’une déclaration de ce qui doit tourner. Google a utilisé pendant plus de dix ans un système interne appelé Borg à cette fin ; en 2014, l’entreprise a annoncé Kubernetes, un système open source qui s’inspire de cette expérience, a publié la version 1.0 en juillet 2015 et l’a donné à la Cloud Native Computing Foundation, alors tout juste créée, où il est devenu en 2018 le premier projet à atteindre le statut « graduated ». Il existe des alternatives (HashiCorp Nomad, Amazon ECS, les plateformes de conteneurs serverless), mais Kubernetes est désormais le socle commun proposé par tous les grands fournisseurs et par la plupart des plateformes sur site.
Architecture de Kubernetes
Comment fonctionne Kubernetes : plan de contrôle et nœuds
Un cluster Kubernetes comporte un plan de contrôle (control plane), qui prend les décisions, et des nœuds de travail (worker nodes), qui exécutent les charges de travail. Le serveur d’API est la porte d’entrée : chaque client, du kubectl d’un développeur aux propres composants du cluster, lit et écrit des objets via son API REST ; il authentifie l’appelant, vérifie les autorisations et exécute des contrôles d’admission qui peuvent valider ou modifier chaque requête (par exemple, rejeter les conteneurs qui s’exécutent en root). C’est le seul composant qui communique avec etcd, un magasin clé-valeur fortement cohérent qui réplique l’état du cluster avec le protocole de consensus Raft sur, en général, trois ou cinq membres, afin qu’une majorité survive à la perte d’un ou deux d’entre eux (cohérence, CAP et PACELC).
L’ordonnanceur (scheduler) surveille l’apparition de nouveaux pods sans nœud attribué, écarte les nœuds qui ne peuvent pas les exécuter (pas assez de CPU ou de mémoire non réservés, mauvaise zone, matériel manquant, taints que le pod ne tolère pas), note les autres (en répartissant les copies entre zones, en les regroupant efficacement) et enregistre son choix via le serveur d’API. Le gestionnaire de contrôleurs (controller manager) exécute des dizaines de contrôleurs, chacun responsable d’un type d’objet : le contrôleur ReplicaSet maintient le bon nombre de pods, le contrôleur Node repère les nœuds qui cessent de se signaler, le contrôleur Job exécute des tâches jusqu’à leur terme. Dans un cloud, un gestionnaire de contrôleurs cloud crée aussi des équilibreurs de charge et attache des disques via l’API du fournisseur.
Chaque nœud de travail exécute un kubelet, un agent qui surveille le serveur d’API pour repérer les pods attribués à son nœud, demande au runtime de conteneurs (généralement containerd ou CRI-O, via la Container Runtime Interface ; la couche d’adaptation intégrée pour Docker Engine a été supprimée dans la version 1.24 en 2022) de les démarrer, exécute leurs sondes de santé et renvoie leur état. kube-proxy programme les règles de transfert de paquets du nœud pour que le trafic envoyé à l’adresse virtuelle d’un Service atteigne l’un de ses pods ; certains plug-ins réseau, comme Cilium, le remplacent par un plan de données eBPF. Un plug-in réseau donne à chaque pod sa propre adresse IP, et un service DNS de cluster (généralement CoreDNS) permet aux pods de trouver les Services par leur nom.
Remarquez le sens des flèches. Le plan de contrôle ne se connecte jamais à un nœud pour y lancer une commande ; les nœuds surveillent l’état dans lequel ils doivent se trouver et convergent vers lui. Cette conception en mode pull, fondée sur la surveillance et la convergence, est ce qui rend Kubernetes robuste : un nœud déconnecté pendant dix minutes se remet simplement à niveau à son retour. Avec un Kubernetes managé (Amazon EKS, Azure Kubernetes Service, Google Kubernetes Engine et d’autres), le fournisseur exploite et met à niveau le plan de contrôle ; le client choisit toujours les types de nœuds, met à niveau les pools de nœuds et reste responsable de tout ce qui est déployé dans le cluster.
État souhaité déclaratif et boucles de réconciliation
L’idée centrale de Kubernetes est la configuration déclarative. Vous ne dites pas au cluster de démarrer trois conteneurs ; vous enregistrez un objet indiquant que trois copies d’une image donnée doivent exister : c’est l’état souhaité. Des contrôleurs exécutent alors des boucles de réconciliation : observer l’état réel, le comparer à l’état souhaité et agir pour supprimer la différence, indéfiniment.
La boucle de réconciliation
Suivez ce qui se passe quand un nœud de la zone B tombe en panne pendant le coup de feu du soir. Le contrôleur Node cesse de recevoir des heartbeats et marque le nœud comme non prêt ; après un délai d’expiration, ses pods sont marqués pour éviction. Le contrôleur ReplicaSet du service des commandes observe deux pods en cours d’exécution là où trois sont souhaités et crée un nouvel objet pod. L’ordonnanceur voit un pod non planifié et l’attribue à un nœud sain, en privilégiant les nœuds survivants de la zone B ou une autre zone selon les règles de répartition. Le kubelet de ce nœud voit l’attribution, télécharge l’image et démarre le conteneur. Quand sa sonde de disponibilité réussit, les endpoints derrière le Service des commandes sont mis à jour et le trafic lui parvient. Aucun composant n’a dit à un autre quoi faire ; chacun a réagi à l’état qu’il observait.
Comme les contrôleurs sont déclenchés par niveau (ils réagissent à la différence actuelle) plutôt que déclenchés par front (en réagissant à des événements individuels), un contrôleur qui plante, manque une notification ou s’exécute deux fois converge tout de même vers le bon résultat. Le même patron peut être étendu : les définitions de ressources personnalisées (custom resource definitions) ajoutent de nouveaux types d’objets, et le patron opérateur, introduit par CoreOS en 2016, empaquette un contrôleur qui encode un savoir-faire d’exploitation, par exemple créer, sauvegarder et faire basculer un cluster PostgreSQL à partir d’un seul objet déclaré. La section 10.4 montre la même boucle appliquée à des environnements entiers grâce à GitOps.
Le contrôle déclaratif a un coût : vous devez raisonner en termes d’états finaux et renoncer à un séquencement exact. Quand deux contrôleurs veulent des états souhaités différents pour un même champ, ils se battent. Un exemple classique est un manifeste de Deployment dans Git qui indique replicas: 3 alors qu’un autoscaler le fixe à neuf ; chaque synchronisation GitOps réduit le service en plein coup de feu. La solution consiste à confier chaque champ à un seul propriétaire, ici en retirant replicas du manifeste.
Les objets de base de Kubernetes
| Objet | Ce que c’est | Exemple de livraison de repas |
|---|---|---|
| Pod | Un ou plusieurs conteneurs planifiés ensemble sur un même nœud, qui partagent une adresse IP et des volumes. Les pods sont jetables : on les remplace, on ne les répare jamais. | Une copie du service des commandes, éventuellement avec un petit sidecar d’expédition des journaux. |
| ReplicaSet | Maintient en fonctionnement un nombre donné de pods identiques. Rarement écrit à la main. | Créé et géré par le Deployment des commandes. |
| Deployment | Déclare le modèle de pod et le nombre de répliques d’une application sans état, et déploie les changements en faisant passer les pods d’un ancien ReplicaSet à un nouveau, dans des limites de dépassement et d’indisponibilité. | Services des commandes, de la carte et de la tarification. |
| Service | Un nom stable et une IP virtuelle pour un ensemble changeant de pods sélectionnés par label, avec une répartition de la charge entre eux. | orders.default.svc, utilisé par le service de paiement. |
| Ingress et Gateway API | Acheminent le trafic HTTP(S) externe vers les Services selon l’hôte et le chemin. Gateway API (disponible en version stable depuis la 1.0 en octobre 2023) est le successeur plus expressif et orienté rôles : l’équipe plateforme possède la Gateway, les équipes applicatives possèdent leurs HTTPRoutes. | api.example.com/orders acheminé vers le Service des commandes, avec TLS. |
| ConfigMap | Configuration non secrète fournie aux pods sous forme de variables d’environnement ou de fichiers. | Réglages de fonctionnalités, délais d’expiration, URL du service de la carte. |
| Secret | Valeurs sensibles fournies comme les ConfigMaps. Stockées dans etcd seulement encodées en base64, sauf si le chiffrement au repos est configuré ; de nombreuses équipes les synchronisent depuis un gestionnaire de secrets externe. | La clé d’API du prestataire de paiement, le mot de passe de la base de données. |
| StatefulSet | Pods dotés de noms stables, d’identités réseau stables et de leurs propres volumes persistants, démarrés et arrêtés dans l’ordre. | Un cluster Kafka ou Redis autogéré, si l’on n’utilise pas un service managé. |
| DaemonSet | Exactement un pod sur chaque nœud (ou sur chaque nœud sélectionné). | Collecteur de journaux ou agent de supervision des nœuds. |
| Job et CronJob | Pods qui s’exécutent jusqu’à leur terme, une fois ou selon un planning, avec de nouvelles tentatives. | Migration de base de données avant une mise en production ; rapport nocturne des versements aux restaurants. |
| HorizontalPodAutoscaler | Ajuste le nombre de répliques d’un Deployment pour maintenir une métrique proche d’une cible. | Faire passer le service des commandes de 3 à 12 répliques à mesure que l’utilisation du CPU augmente le soir. |
Un Deployment et un Service en YAML
Les manifestes ci-dessous déclarent le service des commandes : configuration issue d’un Secret, requêtes de ressources et sondes, un Service stable placé devant et un autoscaler qui maintient entre trois et douze répliques. Le Deployment n’a délibérément pas de champ replicas, afin que l’autoscaler soit le seul propriétaire de ce nombre. Ils sont appliqués avec kubectl apply ou, mieux, par un pipeline ou un agent GitOps à partir d’un dépôt.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
# no replicas field: the autoscaler below owns the count
selector:
matchLabels: {app: orders}
template:
metadata:
labels: {app: orders}
spec:
containers:
- name: orders
image: registry.example.com/orders:2.4.1
ports: [{containerPort: 8080}]
env:
- name: DATABASE_URL
valueFrom: {secretKeyRef: {name: orders-db, key: url}}
resources:
requests: {cpu: 500m, memory: 512Mi}
limits: {memory: 512Mi} # no CPU limit: may burst
readinessProbe:
httpGet: {path: /ready, port: 8080}
periodSeconds: 5
livenessProbe:
httpGet: {path: /live, port: 8080}
periodSeconds: 10
failureThreshold: 3apiVersion: v1
kind: Service
metadata:
name: orders
spec:
selector: {app: orders}
ports: [{port: 80, targetPort: 8080}]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: orders
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: orders}
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target: {type: Utilization, averageUtilization: 60}Requêtes de ressources, limites et qualité de service
Les requêtes et limites (requests and limits) sont les lignes les plus lourdes de conséquences d’un manifeste. Une requête est ce que l’ordonnanceur réserve : un pod qui demande 500 millicœurs (un demi-CPU) et 512 Mio de mémoire n’est placé que sur un nœud disposant d’autant de capacité non réservée, et cette part lui est garantie en cas de contention. Une limite est un plafond imposé par le noyau : un conteneur qui dépasse sa limite de CPU est bridé (ralenti), et celui qui dépasse sa limite de mémoire est tué puis redémarré, avec le statut bien connu OOMKilled. Kubernetes déduit de ces réglages une classe de qualité de service : Guaranteed (requêtes égales aux limites pour chaque ressource), Burstable (requêtes définies, limites plus élevées ou absentes) et BestEffort (rien de défini, premier à être évincé quand un nœud manque de ressources).
Les praticiens ne s’accordent pas sur les limites de CPU. Un camp les définit partout, pour la prévisibilité et l’équité entre équipes ; l’autre définit des requêtes de CPU précises et aucune limite de CPU, car le bridage provoque des pics de latence même quand le nœud a du CPU inactif, et les requêtes garantissent déjà sa part à chaque pod. Pour la mémoire, le débat est mince : fixez la limite égale à la requête, car la mémoire ne peut pas être reprise à un processus sans le tuer, et un pod qui utilise plus que sa requête est le premier candidat à l’éviction. Dans tous les cas, les requêtes doivent reposer sur des mesures ; des requêtes gonflées gaspillent de l’argent (le nœud est réservé mais inactif), et des requêtes sous-estimées provoquent des problèmes de voisins bruyants et un autoscaling trompeur.
Le HorizontalPodAutoscaler calcule répliques souhaitées = plafond(répliques actuelles × métrique actuelle ÷ cible), en mesurant l’utilisation du CPU en pourcentage de la requête. Il augmente rapidement le nombre de répliques mais, par défaut, attend une fenêtre de stabilisation de cinq minutes avant de le réduire, pour éviter les oscillations. Quand les nouveaux pods ne tiennent pas sur les nœuds existants, ils restent Pending, et un autoscaler de nœuds (le Cluster Autoscaler, ou Karpenter chez certains fournisseurs) ajoute des machines, ce qui prend des minutes plutôt que des secondes. L’autoscaling des pods et celui des nœuds sont des boucles distinctes, avec des délais distincts, et toutes deux doivent être prévues pour le coup de feu.
Exemple commenté
Exemple commenté : dimensionner le service des commandes pour le coup de feu du soir
Chaque pod du service des commandes demande 500m de CPU et 512 Mio, avec une limite de mémoire de 512 Mio et aucune limite de CPU. Les nœuds ont 4 vCPU et 16 Gio, dont environ 3,8 vCPU et 14,5 Gio sont allouables après les réservations du système (chiffres illustratifs). Le HPA vise 60 % de CPU, avec 3 à 12 répliques.
| Pods par nœud | CPU : 3,8 ÷ 0,5 = 7 pods. Mémoire : 14,5 Gio ÷ 0,5 Gio = 29 pods. Le CPU est la contrainte déterminante : 7 pods de commandes par nœud si rien d’autre n’y tourne. |
|---|---|
| 18:00, début du coup de feu | 4 répliques à 90 % en moyenne de leur requête de CPU. Souhaité = plafond(4 × 90 ÷ 60) = 6 répliques. |
| 18:20, la charge monte encore | 6 répliques à 85 %. Souhaité = plafond(6 × 85 ÷ 60) = plafond(8,5) = 9 répliques, soit 4,5 vCPU demandés au total. |
| Capacité des nœuds | Si d’autres services remplissent déjà les nœuds, trois des nouveaux pods restent Pending ; l’autoscaler de nœuds ajoute un nœud, qui met peut-être deux à quatre minutes à rejoindre le cluster et à télécharger l’image. |
| Réponse de conception | Relevez minReplicas avant le coup de feu prévisible (mise à l’échelle planifiée), gardez de la capacité de nœuds en réserve et gardez l’image petite pour que les nouveaux pods démarrent vite. |
| Effet d’une requête erronée | Si chaque pod n’avait réellement besoin que de 250m mais que la requête restait à 500m, la même charge de coup de feu (225m par pod) apparaîtrait à 45 % au lieu de 90 % : le HPA n’augmenterait pas le nombre de répliques avant que l’utilisation dépasse 300m par pod, et le cluster réserverait deux fois le CPU dont il a besoin. |
L’autoscaling réagit à une charge déjà arrivée ; pour un pic quotidien prévisible, une mise à l’échelle planifiée à l’avance coûte moins cher que des commandes perdues.
Les requêtes pilotent à la fois l’ordonnanceur et l’autoscaler : les mesurer est donc une tâche architecturale de premier ordre, et non un détail de réglage.
Sondes de vivacité, de disponibilité et de démarrage
Les sondes sont le moyen par lequel Kubernetes s’informe de la santé des conteneurs. Une sonde de disponibilité (readiness) indique si ce pod doit recevoir du trafic maintenant ; tant qu’elle échoue, le pod est retiré des endpoints de son Service mais continue de tourner. Une sonde de vivacité (liveness) indique si le processus est bloqué sans espoir de rétablissement ; quand elle échoue de façon répétée, le kubelet redémarre le conteneur. Une sonde de démarrage (startup) retient les deux autres jusqu’à ce qu’une application lente à démarrer ait fini de s’initialiser.
L’erreur la plus dommageable est une sonde de vivacité qui vérifie les dépendances. Si l’endpoint de vivacité du service des commandes interroge la base de données, un basculement de trente secondes de la base fait échouer en même temps la sonde de vivacité de tous les pods des commandes, Kubernetes les redémarre tous, ils se reconnectent tous au même instant, et un bref incident devient une panne. La sonde de vivacité ne doit vérifier que la capacité du processus lui-même à répondre ; la sonde de disponibilité peut tenir compte de la capacité du pod à faire un travail utile, même si, là encore, de nombreuses équipes évitent de rendre tous les pods indisponibles à cause d’une dépendance partagée, et préfèrent faire échouer rapidement les requêtes avec un disjoncteur (voir patrons de résilience).
Les sondes fonctionnent de concert avec l’arrêt propre (graceful shutdown). Quand un pod est supprimé pendant un déploiement ou une réduction du nombre de répliques, Kubernetes le retire des endpoints et envoie SIGTERM au processus ; celui-ci doit cesser d’accepter du nouveau travail, terminer les requêtes en cours et s’arrêter dans le délai de grâce (30 secondes par défaut) avant d’être tué. Comme le retrait des endpoints se propage de façon asynchrone, un court délai preStop évite d’envoyer des requêtes à un pod qui a déjà commencé à s’arrêter. La section 10.3 y revient avec le principe de jetabilité de l’application douze facteurs.
Quand Kubernetes est surdimensionné
Kubernetes est une plateforme généraliste pour construire des plateformes, et il fait payer cette généralité. Il faut que quelqu’un conçoive le réseau, l’entrée du trafic, l’identité, les secrets, les politiques, l’observabilité et la multilocation ; suive trois versions mineures par an, chacune prise en charge pendant environ quatorze mois, ainsi que les extensions qui en dépendent ; et assure l’astreinte pour le cluster en plus des applications. Les plans de contrôle managés retirent une partie de ce travail, pas l’essentiel.
Pour une petite équipe qui exploite une poignée de services web sans état, un PaaS ou une plateforme de conteneurs serverless (Google Cloud Run, Azure Container Apps, Amazon ECS avec Fargate et autres) offre l’essentiel des avantages (conteneurs, autoscaling, déploiements progressifs, TLS managé) pour une fraction de la charge d’exploitation. Kubernetes commence à être rentable quand il y a de nombreux services et plusieurs équipes, une équipe plateforme pour l’exploiter, des charges de travail aux besoins inhabituels (systèmes avec état, GPU, traitements par lots, contrôleurs personnalisés), une obligation de faire tourner la même plateforme sur site et dans plusieurs clouds, ou un écosystème d’outils que l’organisation veut utiliser. Beaucoup d’organisations l’ont adopté parce qu’il était à la mode, puis ont passé des années à construire, en pratique, un PaaS moins bon par-dessus. La section 10.7 explique comment les équipes plateforme évitent ce sort (ingénierie de plateforme).
Vérifiez votre compréhension
Pourquoi un déploiement en production doit-il désigner une image par son digest plutôt que par son tag ?
Un tag est une étiquette mobile qui peut être redirigée vers un autre contenu ; un digest est une empreinte du contenu et identifie toujours exactement l’image qui a été testée.
Un nœud meurt. Quels composants agissent, et dans quel ordre, pour rétablir la troisième réplique ?
Le contrôleur Node marque le nœud comme non prêt et ses pods sont évincés ; le contrôleur ReplicaSet constate qu’il manque des pods et en crée un ; l’ordonnanceur l’attribue à un nœud sain ; le kubelet de ce nœud le démarre via le runtime ; quand la sonde de disponibilité réussit, le pod rejoint les endpoints du Service.
Qu’arrive-t-il à un conteneur qui dépasse sa limite de mémoire, et à un conteneur qui dépasse sa limite de CPU ?
Dépasser la limite de mémoire entraîne l’arrêt forcé du conteneur (OOMKilled) puis son redémarrage ; dépasser la limite de CPU ne fait que le brider.
Pourquoi est-il dangereux de vérifier la base de données dans une sonde de vivacité ?
Un bref problème de base de données fait échouer ensemble la sonde de vivacité de tous les pods, si bien que Kubernetes les redémarre tous en même temps, transformant un court incident de dépendance en panne complète.
Sources et lectures complémentaires
Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.
Demander : « La plateforme de livraison de repas doit-elle définir des limites de CPU sur ses pods Kubernetes, ou seulement des requêtes ? »