L'architecture de Kubernetes vue comme des réconciliateurs & Labels et selectors, le ciment des boucles
L'architecture de Kubernetes comme une collection de réconciliateurs indépendants (API server, scheduler, controller-manager, kubelet) et le rôle des labels et selectors qui relient les boucles — sans prérequis, avec YAML et kubectl.
L’architecture de Kubernetes vue comme des réconciliateurs
L’idée en une phrase
Kubernetes n’est pas un orchestrateur central qui exécute un plan, mais un ensemble de contrôleurs indépendants (chacun un réconciliateur), responsables chacun d’un type d’objet, qui observent l’état désiré et l’état réel par un point unique — l’API server — et agissent pour combler leur écart. L’architecture entière est une composition de boucles de réconciliation faiblement couplées : la même boucle observe → diff → agit, répliquée et spécialisée.
Analogie : Considérons l’équipe de maintenance d’un grand immeuble. Un plombier, un électricien et un technicien d’ascenseur consultent un même registre central où sont consignées les demandes et l’état des installations. Chacun n’intervient que dans sa spécialité, à partir du registre, sans jamais donner d’ordre à un collègue. La coordination n’émerge pas d’un chef qui commande, mais de ce registre partagé que tous lisent et mettent à jour. Le registre est l’API server ; chaque technicien, un contrôleur.
Points clés
- Le plan de contrôle (control plane) héberge l’API server (le point d’entrée unique, vu au chapitre 3-4), etcd (le magasin de l’état, chapitre 3-4), le scheduler et le kube-controller-manager. Les nœuds (worker nodes) exécutent le kubelet et les charges de travail.
- Chaque composant est un réconciliateur à responsabilité unique : le kube-controller-manager maintient les objets de haut niveau (Deployment, ReplicaSet, Node…), le scheduler place les pods (il choisit un nœud), le kubelet garantit que les conteneurs tournent réellement sur son nœud et renseigne le status.
- Toute communication transite par l’API server ; aucun composant n’en appelle un autre directement. Agir, pour un contrôleur, c’est lire et écrire des objets, jamais émettre une commande à un pair. Ce découplage est la traduction architecturale de la boucle.
- Le motif est un hub-and-spoke : l’API server au centre, les réconciliateurs autour. Ajouter un comportement revient à ajouter un contrôleur qui observe l’API server, sans toucher aux autres. L’extensibilité par Operators (chapitre 19-20) repose sur cette propriété.
- Chaque boucle est autonome : si un contrôleur redémarre, les autres continuent de converger ; au redémarrage, il ré-observe l’état et reprend. Aucun plan central n’est conservé, donc aucun plan central ne peut être perdu.
Exemple concret
Un seul kubectl apply -f deployment.yaml (replicas: 3) déclenche une chaîne où chaque réconciliateur réagit à l’état laissé par le précédent. À t₀, kubectl envoie le manifeste à l’API server, qui le valide et l’enregistre dans etcd : un objet Deployment existe, aucun pod encore. Le Deployment controller observe un Deployment sans ReplicaSet associé et crée un ReplicaSet désirant 3 pods. Le ReplicaSet controller observe 0 pod pour 3 désirés et crée 3 objets Pod, encore sans nœud (nodeName vide). Le scheduler observe 3 pods non placés et écrit un nodeName sur chacun. Enfin, sur chaque nœud cible, le kubelet observe un pod qui lui est affecté mais non démarré, lance le conteneur et écrit le status Running. Aucun composant n’a orchestré les autres : la chaîne émerge de quatre boucles réagissant tour à tour à l’état partagé.
Chaque composant, un réconciliateur
| Composant | Objet observé (désiré) | État réel observé | Action pour combler l’écart |
|---|---|---|---|
| Deployment controller | Deployment.spec | Le ReplicaSet courant | Crée ou ajuste un ReplicaSet |
| ReplicaSet controller | ReplicaSet.spec.replicas | Pods correspondant au selector | Crée ou supprime des Pods |
| scheduler | Pods sans nodeName | Nœuds et ressources disponibles | Écrit nodeName sur chaque Pod |
| kubelet | Pods affectés à son nœud | Conteneurs en exécution | Démarre/arrête les conteneurs, écrit le status |
Code kubectl — observer les réconciliateurs du plan de contrôle
# Les composants du plan de contrôle s'exécutent comme des pods système
kubectl -n kube-system get pods
# NAME READY STATUS RESTARTS AGE
# kube-apiserver-node1 1/1 Running 0 7d ← le point d'entrée unique
# etcd-node1 1/1 Running 0 7d ← le magasin (chap. 3-4)
# kube-scheduler-node1 1/1 Running 0 7d ← réconciliateur de placement
# kube-controller-manager-node1 1/1 Running 0 7d ← héberge Deployment, ReplicaSet…
Code kubectl — suivre la chaîne déclenchée par un seul apply
kubectl apply -f deployment.yaml # n'écrit QUE l'objet Deployment dans etcd
# deployment.apps/hello created
# La cascade Deployment → ReplicaSet → Pod (détaillée au chapitre 9-10)
kubectl get deploy,rs,pod -l app=hello
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/hello 3/3 3 3 12s ← Deployment controller
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/hello-7d4b9c 3 3 3 12s ← créé par le Deployment ctrl
# NAME READY STATUS RESTARTS AGE
# pod/hello-7d4b9c-aaa11 1/1 Running 0 12s ← créés par le ReplicaSet ctrl
# Le placement (nodeName) est écrit par le scheduler, pas par le ReplicaSet
kubectl get pods -l app=hello -o wide
# NAME READY STATUS NODE
# hello-7d4b9c-aaa11 1/1 Running node2 ← scheduler : nodeName=node2
# hello-7d4b9c-bbb22 1/1 Running node3
# hello-7d4b9c-ccc33 1/1 Running node2
Piège courant : « Un composant central orchestre et ordonne aux autres ce qu’ils doivent faire » est inexact — aucun composant ne pilote les autres. Chaque réconciliateur réagit indépendamment à l’état partagé dans l’API server, et le comportement global émerge de la composition de ces boucles. L’API server lui-même n’orchestre rien : il stocke l’état et notifie les changements (chapitre 13-14). Il n’existe donc pas de « chef d’orchestre » dont la panne arrêterait tout.
Labels et selectors, le ciment des boucles
L’idée en une phrase
Un label est une paire clé/valeur attachée à un objet ; un selector est une requête qui désigne les objets portant certains labels. C’est par ce couple que chaque boucle identifie l’ensemble des objets dont elle est responsable : le selector définit l’appartenance désirée, et le contrôleur observe en continu quels objets réels y correspondent. Sans labels, une boucle ne saurait pas quels objets compter ni lesquels lui reviennent.
Analogie : Considérons l’acheminement des bagages dans un aéroport. Chaque bagage reçoit des étiquettes — vol, destination, priorité —, et les machines de tri ne consultent aucune liste nominative : elles appliquent une règle (« tout bagage étiqueté vol AF123 part sur ce vol »). Étiqueter un bagage l’intègre au lot ; retirer l’étiquette l’en exclut, sans modifier la règle. Les étiquettes sont les labels ; la règle de tri, le selector.
Points clés
- Un label est une métadonnée clé/valeur placée sous
metadata.labels, par exempleapp: hellooutier: frontend. Un objet peut en porter plusieurs ; ils expriment une identité (quelle application, quel environnement, quelle version), non une simple décoration. - Un selector est une requête sur les labels. Deux formes existent : par égalité (
app=hello) et ensembliste (tier in (frontend, backend), existence d’une clé, négation!canary). Il renvoie un ensemble défini par intention, jamais une liste explicite d’objets. - L’appartenance est dynamique et déclarative : l’ensemble correspondant est recalculé à chaque observation. Ajouter un label fait entrer un objet dans l’ensemble ; le retirer l’en fait sortir. Aucun contrôleur ne tient une liste rédigée à la main.
- C’est le ciment des boucles : un ReplicaSet compte les pods correspondant à son
spec.selector; un Service route vers les pods correspondant à son selector (chapitre 17-18). Le selector délimite « les objets dont je suis responsable » — la portion d’état désiré/réel que la boucle compare. - Dans un Deployment ou un ReplicaSet,
spec.selector.matchLabelsdoit correspondre aux labels despec.template.metadata.labels: le contrôleur crée des pods portant les labels du template, puis les retrouve via le selector. Une incohérence romprait la boucle ; elle est donc rejetée à l’apply.
Exemple concret
Un ReplicaSet déclare spec.replicas: 3 et spec.selector.matchLabels: app=hello ; son template estampille app: hello sur chaque pod créé. À chaque tick, la boucle exécute la requête app=hello, compte le résultat (état réel) et le compare à 3 (état désiré). Supposons qu’un opérateur ajoute à la main le label app: hello à un quatrième pod, sans rapport : au tick suivant, le selector renvoie 4, la boucle calcule un surplus (4 − 3 = 1) et supprime un pod — éventuellement le pod étranger. À l’inverse, retirer le label app: hello d’un pod géré le fait disparaître de la vue du selector : la boucle compte 2, conclut qu’un pod manque et en crée un autre, tandis que le pod « orphelin » continue de tourner, désormais hors gestion. C’est le label, non l’identité ni l’origine du pod, qui décide de l’appartenance.
Label et selector — deux rôles complémentaires
| Critère | Label | Selector |
|---|---|---|
| Rôle | Étiquette posée sur un objet | Requête qui désigne des objets |
| Emplacement | metadata.labels de l’objet | spec.selector d’un contrôleur (ou -l en ligne de commande) |
| Forme | cle: valeur (ex. app: hello) | Égalité (app=hello) ou ensembliste (app in (a, b)) |
| Appartenance | Subsiste tant que le label reste | Recalculée à chaque observation (dynamique) |
Code YAML — le selector doit correspondre aux labels du template
# deployment.yaml — cohérence selector ↔ labels du template
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
labels:
app: hello # label du Deployment lui-même (facultatif mais utile au tri)
spec:
replicas: 3
selector:
matchLabels:
app: hello # le contrôleur cherchera les pods portant app=hello…
template:
metadata:
labels:
app: hello # … et les pods créés portent exactement ce label
spec:
containers:
- name: web
image: nginx:1.27
# Si selector.matchLabels et template.labels divergent, l'apply est rejeté :
# la boucle créerait des pods qu'elle serait incapable de retrouver.
Code kubectl — interroger et faire basculer l’appartenance
# Selector par égalité : tous les pods portant app=hello
kubectl get pods -l app=hello
# NAME READY STATUS AGE
# hello-7d4b9c-aaa11 1/1 Running 3m
# hello-7d4b9c-bbb22 1/1 Running 3m
# hello-7d4b9c-ccc33 1/1 Running 3m
# Selector ensembliste : plusieurs valeurs, existence, négation
kubectl get pods -l 'tier in (frontend, backend),!canary'
# Ajouter un label à la volée fait BASCULER l'appartenance
kubectl label pod legacy-db app=hello
# pod/legacy-db labeled
# → au tick suivant, le ReplicaSet compte ce pod parmi les siens (état réel = 4)
# et supprime un pod en trop pour revenir à l'état désiré (3)
Piège courant : « Un contrôleur reconnaît ses pods parce qu’il les a créés » est inexact — il les retrouve à chaque tick par son selector, non par mémoire de création. Retirer le label correspondant à un pod géré le sort du décompte : le contrôleur le croit manquant et en crée un autre, tandis que le pod dé-labellisé subsiste hors gestion. Un garde-fou supplémentaire, les ownerReferences, affine cette appartenance pour éviter qu’un même pod soit revendiqué par deux contrôleurs ; il est étudié au chapitre 25-26.