TP Kubernetes : placer ses Pods avec Node Affinity, Pod Affinity et Pod Anti-Affinity
Durée estimée : 2 h 30 à 3 h Niveau : débutant à intermédiaire, suite des TP « Pod, Deployment, Service » et « WordPress » Prérequis : minikube et kubectl installés, notions de Pod, Deployment et labels
Objectifs
À la fin de ce TP, vous saurez :
- créer un cluster minikube à plusieurs nœuds et étiqueter ces nœuds ;
- expliquer comment le scheduler choisit le nœud d'un Pod ;
- imposer ou préférer certains nœuds avec nodeSelector et Node Affinity ;
- rapprocher des Pods entre eux avec la Pod Affinity ;
- éloigner des Pods les uns des autres avec la Pod Anti-Affinity ;
- comprendre le rôle du topologyKey (nœud, zone) ;
- diagnostiquer un Pod bloqué en
Pendingà cause d'une règle de placement.
Sommaire
- Partie 0 : Préparation du cluster multi-nœuds
- Partie 1 : Comment le scheduler place un Pod
- Partie 2 : Échauffement avec nodeSelector
- Partie 3 : Node Affinity obligatoire
- Partie 4 : Node Affinity préférentielle
- Partie 5 : Pod Anti-Affinity
- Partie 6 : Pod Affinity
- Partie 7 : Exercice de synthèse
- Partie 8 : Nettoyage
- Annexe A : Aide-mémoire
- Annexe B : Dépannage
- Annexe C : Corrigés
Partie 0 : Préparation du cluster multi-nœuds
0.1 Pourquoi plusieurs nœuds ?
Les règles d'affinité décident sur quel nœud un Pod doit tourner. Avec un seul nœud, il n'y a aucun choix possible. On va donc créer un cluster de 3 nœuds dans un profil minikube séparé, pour ne pas toucher au cluster des TP précédents.
Ressources nécessaires : environ 6 Go de RAM libres et 6 CPU (3 nœuds de 2 CPU et 2 Go).
0.2 Créer le cluster
minikube start -p affinite --nodes=3 --driver=docker --cpus=2 --memory=2048
Comptez 3 à 6 minutes. Vérifiez :
minikube profile list
kubectl config current-context
kubectl get nodes -o wide
Résultat attendu :
NAME STATUS ROLES AGE VERSION
affinite Ready control-plane 3m v1.3x.x
affinite-m02 Ready <none> 2m v1.3x.x
affinite-m03 Ready <none> 1m v1.3x.x
Profil minikube : l'option-p affinitecrée un cluster indépendant nomméaffinite. kubectl bascule automatiquement dessus. Pour revenir plus tard au cluster des TP précédents :kubectl config use-context minikube.
Sur minikube, le nœudcontrol-planeaccepte aussi les Pods applicatifs. On a donc bien 3 nœuds utilisables.
0.3 Donner une « carte d'identité » aux nœuds
Les règles d'affinité s'appuient sur les labels des nœuds. On va simuler un petit datacenter :
| Nœud | Zone | Type de disque |
|---|---|---|
affinite |
zone-a | hdd |
affinite-m02 |
zone-a | ssd |
affinite-m03 |
zone-b | ssd |
kubectl label node affinite topology.kubernetes.io/zone=zone-a disktype=hdd
kubectl label node affinite-m02 topology.kubernetes.io/zone=zone-a disktype=ssd
kubectl label node affinite-m03 topology.kubernetes.io/zone=zone-b disktype=ssd
Affichez les labels sous forme de colonnes :
kubectl get nodes -L topology.kubernetes.io/zone,disktype
Labels standards et labels personnalisés :topology.kubernetes.io/zoneest un label standard. Chez un fournisseur cloud, il est posé automatiquement sur chaque nœud (par exempleeu-west-3a). On le pose ici à la main.kubernetes.io/hostnameest posé automatiquement par Kubernetes sur chaque nœud, avec le nom du nœud.disktypeest un label personnalisé : vous pouvez inventer les vôtres (gpu,env,client...).
Voyez tous les labels d'un nœud :
kubectl get node affinite-m02 --show-labels
0.4 Préparer le dossier et un raccourci d'affichage
mkdir -p ~/tp-affinite && cd ~/tp-affinite
Tout au long du TP, on voudra savoir quel Pod tourne sur quel nœud. Créez cet alias :
alias kpn='kubectl get pods -o custom-columns=POD:.metadata.name,NOEUD:.spec.nodeName,STATUT:.status.phase'
kpn
Ajoutez la lignealias kpn=...dans votre~/.bashrcsi vous voulez la conserver pour une autre session.
Partie 1 : Comment le scheduler place un Pod
Quand un Pod est créé, il n'a pas encore de nœud. Le scheduler (un composant du plan de contrôle) lui en choisit un en deux étapes :
Pod sans nœud
│
▼
┌────────────────────────────────────────┐
│ 1. FILTRAGE │ élimine les nœuds qui ne respectent pas
│ règles "required" / nodeSelector │ les contraintes OBLIGATOIRES
└──────────────────┬─────────────────────┘
▼
┌────────────────────────────────────────┐
│ 2. NOTATION (scoring) │ classe les nœuds restants selon les
│ règles "preferred", charge, etc. │ PRÉFÉRENCES et d'autres critères
└──────────────────┬─────────────────────┘
▼
Le nœud ayant la meilleure note
- Si aucun nœud ne passe le filtrage, le Pod reste en
Pending. Le scheduler réessaiera dès que la situation du cluster change (nouveau nœud, nouveau label, Pod supprimé...). - Les préférences ne bloquent jamais un Pod : elles influencent seulement la note.
Les trois mécanismes de ce TP :
| Mécanisme | Question posée | S'appuie sur |
|---|---|---|
| Node Affinity | « Sur quel type de nœud ce Pod doit-il tourner ? » | les labels des nœuds |
| Pod Affinity | « À côté de quels autres Pods ce Pod doit-il tourner ? » | les labels des Pods déjà en place |
| Pod Anti-Affinity | « Loin de quels Pods ce Pod doit-il tourner ? » | les labels des Pods déjà en place |
Chacun existe en deux versions :
| Version | Nom complet | Effet |
|---|---|---|
| Obligatoire | requiredDuringSchedulingIgnoredDuringExecution |
filtrage : si impossible, le Pod reste Pending |
| Préférentielle | preferredDuringSchedulingIgnoredDuringExecution |
notation : le scheduler fait au mieux |
Lire le nom : « required during scheduling » signifie que la règle est vérifiée au moment du placement. « Ignored during execution » signifie qu'elle n'est plus vérifiée ensuite : si les labels changent après coup, un Pod déjà en place n'est pas déplacé. On le vérifiera en partie 3.
Partie 2 : Échauffement avec nodeSelector
nodeSelector est la forme la plus simple : « ce Pod ne peut tourner que sur un nœud portant exactement ces labels ».
Créez nodeselector.yaml :
apiVersion: v1
kind: Pod
metadata:
name: ns-ssd
spec:
nodeSelector:
disktype: ssd # uniquement les nœuds avec disktype=ssd
containers:
- name: nginx
image: nginx:1.27-alpine
kubectl apply -f nodeselector.yaml
kpn
Le Pod est sur affinite-m02 ou affinite-m03, jamais sur affinite (disque hdd).
Limites de nodeSelector : on ne peut exprimer qu'une égalité stricte, avec un ET entre les labels. Impossible de dire « ssd OU nvme », « pas en zone-b » ou « de préférence ». C'est ce que permet la Node Affinity.
kubectl delete -f nodeselector.yaml
Partie 3 : Node Affinity obligatoire
3.1 Premier exemple
Objectif : un Pod sur un nœud ssd qui n'est pas en zone-b.
Créez na-required.yaml :
apiVersion: v1
kind: Pod
metadata:
name: na-required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: # toutes les expressions d'un terme : ET
- key: disktype
operator: In
values: ["ssd"]
- key: topology.kubernetes.io/zone
operator: NotIn
values: ["zone-b"]
containers:
- name: nginx
image: nginx:1.27-alpine
kubectl apply -f na-required.yaml
kpn
Un seul nœud remplit les deux conditions : affinite-m02.
Structure à retenir :nodeSelectorTermsest une liste de termes : il suffit qu'un terme soit satisfait (OU).Dans un terme,matchExpressionsest une liste d'expressions : toutes doivent être satisfaites (ET).
Opérateurs disponibles :
| Opérateur | Signification | Exemple |
|---|---|---|
In |
la valeur du label fait partie de la liste | disktype In [ssd, nvme] |
NotIn |
la valeur ne fait pas partie de la liste | zone NotIn [zone-b] |
Exists |
le label existe, quelle que soit sa valeur | gpu Exists |
DoesNotExist |
le label n'existe pas | maintenance DoesNotExist |
Gt / Lt |
valeur numérique supérieure / inférieure | cpu-gen Gt 3 |
NotInetDoesNotExistpermettent de repousser des Pods de certains nœuds. Pour interdire un nœud à tous les Pods sauf exceptions, on utilise plutôt les taints et tolerations (hors programme de ce TP).
3.2 Expérience : un Pod qui ne trouve pas de nœud
Créez na-gpu.yaml, un Pod qui exige un nœud avec GPU :
apiVersion: v1
kind: Pod
metadata:
name: na-gpu
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: Exists
containers:
- name: nginx
image: nginx:1.27-alpine
kubectl apply -f na-gpu.yaml
kpn
kubectl describe pod na-gpu | tail -n 5
Le Pod est Pending. Dans Events, un message du type :
0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.
Le scheduler explique pour chaque nœud pourquoi il a été éliminé. C'est le message à savoir lire.
Ajoutez un GPU (fictif) sur un nœud :
kubectl label node affinite-m03 gpu=nvidia
kpn
Quelques secondes plus tard, le Pod passe en Running sur affinite-m03 : le scheduler a réessayé dès que le cluster a changé.
3.3 Expérience : « Ignored During Execution »
Retirez le label GPU (le - final supprime un label) :
kubectl label node affinite-m03 gpu-
kubectl get nodes -L gpu
kpn
Le Pod na-gpu continue de tourner sur un nœud qui ne respecte plus sa règle. La règle n'est vérifiée qu'au placement. Si le Pod est supprimé, il ne pourra pas être replacé :
kubectl delete pod na-gpu
kubectl apply -f na-gpu.yaml
kpn
Il reste Pending. Nettoyez :
kubectl delete -f na-gpu.yaml -f na-required.yaml
✅ Point de contrôle 3
- Dans
nodeSelectorTerms, deux termes sont-ils combinés par ET ou par OU ? Et deuxmatchExpressionsd'un même terme ? - Écrivez l'expression qui accepte les nœuds dont
disktypevautssdounvme. - Un Pod est
Pending. Quelle commande et quelle section consultez-vous en premier ?
Partie 4 : Node Affinity préférentielle
4.1 Notion
Une règle preferred exprime un souhait. Chaque préférence a un poids (weight, de 1 à 100) : si un nœud satisfait la préférence, son poids est ajouté à sa note. Le Pod est ensuite placé sur le nœud le mieux noté.
4.2 Préférer une zone
Créez na-preferred.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: pref-zone
spec:
replicas: 6
selector:
matchLabels:
app: pref-zone
template:
metadata:
labels:
app: pref-zone
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80 # préférence forte
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["zone-b"]
- weight: 20 # préférence faible
preference:
matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
containers:
- name: nginx
image: nginx:1.27-alpine
resources:
requests:
cpu: 50m
memory: 32Mi
kubectl apply -f na-preferred.yaml
kubectl rollout status deployment pref-zone
kpn
Comptez les Pods par nœud :
kubectl get pods -l app=pref-zone -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | sort | uniq -c
La majorité des Pods est sur affinite-m03 (zone-b + ssd = 100 points). Il est possible que quelques Pods atterrissent ailleurs : le scheduler prend aussi en compte d'autres critères, comme l'équilibrage de charge entre nœuds. Une préférence n'est pas une garantie.
Calcul des notes pour ce Deployment :
4.3 Expérience : required contre preferred face à une panne
Simulons l'indisponibilité de affinite-m03. cordon marque un nœud comme « non planifiable » (les Pods existants restent, aucun nouveau n'arrive) :
kubectl cordon affinite-m03
kubectl get nodes
kubectl rollout restart deployment pref-zone
kubectl rollout status deployment pref-zone
kpn
Tous les Pods ont été recréés sur affinite et affinite-m02, en privilégiant affinite-m02 (ssd). Avec une règle preferred, l'application a continué de fonctionner.
Faites le même test avec une règle obligatoire :
cat > na-required-zone.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: req-zone
spec:
replicas: 2
selector:
matchLabels:
app: req-zone
template:
metadata:
labels:
app: req-zone
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["zone-b"]
containers:
- name: nginx
image: nginx:1.27-alpine
EOF
kubectl apply -f na-required-zone.yaml
kpn
Les Pods req-zone sont Pending : le seul nœud de zone-b est indisponible. Remettez le nœud en service :
kubectl uncordon affinite-m03
kpn
Les Pods req-zone démarrent immédiatement sur affinite-m03.
À retenir :requiredprotège une contrainte réelle (licence, matériel, conformité) au prix d'une indisponibilité possible.preferredoptimise le placement sans jamais empêcher l'application de démarrer. Dans le doute, préférezpreferred.
kubectl delete -f na-preferred.yaml -f na-required-zone.yaml
✅ Point de contrôle 4
- Que se passe-t-il pour un Pod dont aucune préférence ne peut être satisfaite ?
- À quoi sert le champ
weight? - Que fait
kubectl cordon? Etkubectl uncordon?
Partie 5 : Pod Anti-Affinity
5.1 Notion
La Pod Anti-Affinity répond à la question : « ne me place pas là où tournent déjà certains Pods ».
Cas d'usage principal : la haute disponibilité. Si les 3 réplicas d'une application tournent sur le même nœud et que ce nœud tombe, l'application est totalement indisponible. On veut donc un réplica par nœud.
Une règle de Pod Affinity ou Anti-Affinity comporte deux éléments :
labelSelector: quels Pods sont concernés (« les Podsapp=web») ;topologyKey: à quelle échelle on compare, désignée par un label de nœud.
topologyKey |
Signification pour l'anti-affinité |
|---|---|
kubernetes.io/hostname |
pas deux Pods sur le même nœud |
topology.kubernetes.io/zone |
pas deux Pods dans la même zone |
Comment le scheduler raisonne : il regroupe les nœuds qui ont la même valeur pour le labeltopologyKey. Aveckubernetes.io/hostname, chaque nœud forme son propre groupe. Avectopology.kubernetes.io/zone,affiniteetaffinite-m02forment un seul groupe (zone-a),affinite-m03un autre (zone-b).
5.2 Un réplica par nœud
Créez anti-web.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web # s'éloigner des autres Pods app=web...
topologyKey: kubernetes.io/hostname # ...à l'échelle du nœud
containers:
- name: nginx
image: nginx:1.27-alpine
Le Deployment s'applique la règle à lui-même : chaque Podapp=webrefuse un nœud qui héberge déjà un Podapp=web.
kubectl apply -f anti-web.yaml
kubectl rollout status deployment web
kpn
Chaque Pod est sur un nœud différent.
5.3 Expérience : un réplica de trop
kubectl scale deployment web --replicas=4
kpn
kubectl describe $(kubectl get pods -l app=web --field-selector=status.phase=Pending -o name | head -n 1) | tail -n 4
Le 4e Pod est Pending, avec un message du type :
0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules.
Avec une anti-affinité obligatoire par nœud, le nombre maximum de réplicas est égal au nombre de nœuds.
Revenez à 3 réplicas :
kubectl scale deployment web --replicas=3
5.4 Expérience : le piège de la mise à jour
Déclenchez une mise à jour du Deployment (recréation de tous les Pods) :
kubectl rollout restart deployment web
kpn
kubectl rollout status deployment web --timeout=30s
La mise à jour reste bloquée. Pourquoi ?
- Par défaut, un Deployment crée d'abord un nouveau Pod (
maxSurge) avant de supprimer un ancien. - Ce nouveau Pod porte
app=web, tout comme les 3 anciens qui occupent les 3 nœuds. - Il ne trouve aucun nœud libre et reste
Pending. Aucun ancien Pod n'est supprimé tant qu'il n'est pas prêt : blocage.
Corrigez en demandant de supprimer un ancien Pod avant d'en créer un nouveau. Ajoutez dans anti-web.yaml, au niveau de spec (à côté de replicas) :
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0 # ne jamais dépasser le nombre de réplicas
maxUnavailable: 1 # supprimer un ancien Pod à la fois
kubectl apply -f anti-web.yaml
kubectl rollout status deployment web
kpn
La mise à jour se termine : les Pods sont remplacés un par un.
5.5 Anti-affinité à l'échelle de la zone
Supprimez le Deployment puis modifiez anti-web.yaml : remplacez topologyKey: kubernetes.io/hostname par topologyKey: topology.kubernetes.io/zone.
kubectl delete deployment web
# modifier le topologyKey dans anti-web.yaml, puis :
kubectl apply -f anti-web.yaml
kpn
Avant de regarder le résultat, prédisez combien de Pods seront Running.
Réponse : 2. Il n'y a que deux zones, et la règle interdit deux Pods app=web dans la même zone. Un Pod tourne en zone-a (sur affinite ou affinite-m02), un en zone-b, le troisième reste Pending.
5.6 Anti-affinité préférentielle
En pratique, on utilise souvent la version préférentielle : « répartis au mieux, mais démarre quand même s'il n'y a pas assez de nœuds ».
Supprimez le Deployment et remplacez la section affinity de anti-web.yaml par :
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm: # attention : niveau supplémentaire
labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
Remettez aussi replicas: 5.
kubectl delete deployment web
kubectl apply -f anti-web.yaml
kubectl rollout status deployment web
kpn
Les 5 Pods tournent : les 3 premiers sont répartis un par nœud, les 2 suivants s'ajoutent là où c'est possible.
Différence de syntaxe : en versionpreferred, la règle est encapsulée danspodAffinityTermavec unweight. En versionrequired, elle est écrite directement dans la liste. Cette différence est une source d'erreurs fréquente.
kubectl delete -f anti-web.yaml
✅ Point de contrôle 5
- Quel est le rôle du
topologyKey? - Avec une anti-affinité obligatoire
topologyKey: kubernetes.io/hostnamesur un cluster de 5 nœuds, combien de réplicas au maximum peuvent tourner ? - Pourquoi un
rollout restartpeut-il se bloquer avec une anti-affinité obligatoire, et comment l'éviter ?
Partie 6 : Pod Affinity
6.1 Notion
La Pod Affinity répond à la question inverse : « place-moi là où tournent déjà certains Pods ».
Cas d'usage : rapprocher deux composants qui échangent beaucoup (une application et son cache), pour réduire la latence réseau ou éviter du trafic entre zones, souvent facturé chez les fournisseurs cloud.
6.2 Déployer le cache
Créez cache.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: cache
spec:
replicas: 1
selector:
matchLabels:
app: cache
template:
metadata:
labels:
app: cache
spec:
containers:
- name: redis
image: redis:7.4-alpine
kubectl apply -f cache.yaml
kubectl rollout status deployment cache
kpn
Notez le nœud du Pod cache. Le scheduler l'a choisi librement.
6.3 Un frontal sur le même nœud que le cache
Créez front.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: front
spec:
replicas: 3
selector:
matchLabels:
app: front
template:
metadata:
labels:
app: front
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache # se rapprocher des Pods app=cache...
topologyKey: kubernetes.io/hostname # ...sur le même nœud
containers:
- name: nginx
image: nginx:1.27-alpine
kubectl apply -f front.yaml
kubectl rollout status deployment front
kpn
Les 3 Pods front sont sur le même nœud que le Pod cache.
Déplacez le cache et observez :
kubectl delete pod -l app=cache
kpn
Le Pod cache a peut-être été recréé sur un autre nœud, mais les Pods front n'ont pas bougé (Ignored During Execution). Seuls les nouveaux Pods front suivront le cache :
kubectl rollout restart deployment front
kubectl rollout status deployment front
kpn
6.4 Élargir à la zone
Placer toutes les instances sur un seul nœud annule la haute disponibilité. Un compromis courant : même zone que le cache, mais pas forcément le même nœud.
Dans front.yaml, remplacez topologyKey: kubernetes.io/hostname par topologyKey: topology.kubernetes.io/zone, puis :
kubectl apply -f front.yaml
kubectl rollout status deployment front
kpn
Selon la zone du cache :
- cache en zone-a : les Pods
frontpeuvent aller suraffiniteetaffinite-m02; - cache en zone-b : ils ne peuvent aller que sur
affinite-m03, seul nœud de cette zone.
6.5 Expérience : l'ordre de déploiement
kubectl delete -f front.yaml -f cache.yaml
kubectl apply -f front.yaml
kpn
Les Pods front sont Pending : aucun Pod app=cache n'existe, la règle obligatoire ne peut pas être satisfaite. Déployez le cache :
kubectl apply -f cache.yaml
kpn
Les Pods front démarrent dès que le cache est placé.
À retenir : une Pod Affinity obligatoire crée une dépendance de démarrage. Si le composant cible est absent (panne, suppression, erreur de label), les nouveaux Pods ne démarrent pas. C'est pourquoi on utilise souvent la version preferred pour la Pod Affinity.6.6 Le schéma classique : combiner affinité et anti-affinité
Voici un besoin très courant en production : « les frontaux doivent être dans la même zone que le cache et jamais deux sur le même nœud ». On combine les deux règles dans le même Pod :
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: front
topologyKey: kubernetes.io/hostname
Question : avec replicas: 3, combien de Pods front seront Running si le cache est en zone-a ? Et s'il est en zone-b ? Vérifiez en appliquant cette version dans front.yaml (pensez à ajouter la strategy de la partie 5.4).
kubectl delete -f front.yaml -f cache.yaml
✅ Point de contrôle 6
- Quelle différence entre Node Affinity et Pod Affinity ?
- Pourquoi une Pod Affinity obligatoire avec
topologyKey: kubernetes.io/hostnamepeut-elle nuire à la haute disponibilité ? - Que se passe-t-il pour les Pods
frontdéjà en place quand le Podcachechange de nœud ?
Partie 7 : Exercice de synthèse
Vous devez déployer trois composants avec les contraintes suivantes. Utilisez l'image nginx:1.27-alpine pour les trois.
| Composant | Réplicas | Contraintes |
|---|---|---|
db |
1 | obligatoirement sur un nœud disktype=ssd ; de préférence en zone-b |
api |
2 | obligatoirement dans la même zone que db ; jamais deux api sur le même nœud |
worker |
3 | jamais sur le même nœud que db |
Travail demandé :
- Avant d'écrire le moindre YAML, prédisez sur papier le nœud de chaque Pod.
- Écrivez un fichier unique
synthese.yamlcontenant les trois Deployments. - Appliquez-le et comparez le résultat à votre prédiction avec
kpn. - Un Pod reste
Pending. Expliquez pourquoi à l'aide dekubectl describe. - Proposez trois corrections différentes qui permettent à tous les Pods de démarrer, et indiquez ce que chacune sacrifie.
Partie 8 : Nettoyage
Supprimer toutes les ressources du TP :
kubectl delete deployment --all
kubectl delete pod --all
Supprimer le cluster multi-nœuds et revenir au cluster des TP précédents :
minikube delete -p affinite
kubectl config use-context minikube
Annexe A : Aide-mémoire
Emplacement des règles
spec: # spec du Pod (ou spec.template.spec d'un Deployment)
affinity:
nodeAffinity: { required..., preferred... }
podAffinity: { required..., preferred... }
podAntiAffinity: { required..., preferred... }
Syntaxe comparée
Obligatoire (required...) |
Préférentielle (preferred...) |
|
|---|---|---|
| nodeAffinity | nodeSelectorTerms: [ { matchExpressions: [...] } ] |
[ { weight, preference: { matchExpressions: [...] } } ] |
| podAffinity / podAntiAffinity | [ { labelSelector, topologyKey } ] |
[ { weight, podAffinityTerm: { labelSelector, topologyKey } } ] |
Commandes utiles
| Action | Commande |
|---|---|
| Ajouter un label à un nœud | kubectl label node <nœud> cle=valeur |
| Modifier un label existant | kubectl label node <nœud> cle=valeur --overwrite |
| Supprimer un label | kubectl label node <nœud> cle- |
| Afficher des labels en colonnes | kubectl get nodes -L cle1,cle2 |
| Voir quel Pod est sur quel nœud | kubectl get pods -o wide |
| Compter les Pods par nœud | kubectl get pods -l app=x -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | sort | uniq -c |
| Rendre un nœud non planifiable | kubectl cordon <nœud> |
| Le remettre en service | kubectl uncordon <nœud> |
| Vider un nœud (maintenance) | kubectl drain <nœud> --ignore-daemonsets |
| Documentation | kubectl explain pod.spec.affinity.podAntiAffinity |
Annexe B : Dépannage
Message dans kubectl describe pod |
Signification | Piste |
|---|---|---|
didn't match Pod's node affinity/selector |
Aucun nœud ne porte les labels exigés | kubectl get nodes --show-labels, vérifier clés, valeurs et majuscules |
didn't match pod anti-affinity rules |
Tous les nœuds (ou zones) hébergent déjà un Pod repoussé | Réduire les réplicas, ajouter des nœuds, passer en preferred |
didn't match pod affinity rules |
Aucun Pod cible (ou pas dans la bonne topologie) | kubectl get pods -l <selector> -o wide, le Pod cible existe-t-il ? |
node(s) were unschedulable |
Nœud en cordon |
kubectl get nodes, puis kubectl uncordon |
Insufficient cpu / Insufficient memory |
Pas assez de ressources sur les nœuds autorisés | Réduire les requests ou élargir les règles |
rollout status bloqué |
Anti-affinité obligatoire + maxSurge par défaut |
maxSurge: 0, maxUnavailable: 1 |
Erreur unknown field "labelSelector" à l'apply |
Oubli de podAffinityTerm en version preferred |
Voir la partie 5.6 |
Annexe C : Corrigés
Point de contrôle 3
- Les termes de
nodeSelectorTermssont combinés par OU. LesmatchExpressionsd'un même terme par ET. kubectl describe pod <nom>, section Events.
- key: disktype
operator: In
values: ["ssd", "nvme"]
Point de contrôle 4
- Il est quand même placé, sur le nœud le mieux noté selon les autres critères du scheduler.
- Il pondère les préférences entre elles : le poids est ajouté à la note des nœuds qui satisfont la préférence.
cordoninterdit le placement de nouveaux Pods sur le nœud sans toucher aux Pods existants.uncordonlève cette interdiction.
Point de contrôle 5
- Il définit l'échelle de comparaison : les nœuds ayant la même valeur pour ce label forment un même domaine (un nœud, une zone...).
- 5 réplicas, un par nœud. Les suivants restent
Pending. - Le nouveau Pod créé par
maxSurgene trouve aucun nœud libre et les anciens ne sont pas supprimés tant qu'il n'est pas prêt. Solution :maxSurge: 0etmaxUnavailable: 1.
Point de contrôle 6
- La Node Affinity s'appuie sur les labels des nœuds. La Pod Affinity s'appuie sur les labels des Pods déjà en place.
- Toutes les instances se retrouvent sur le même nœud : si ce nœud tombe, l'application entière est indisponible.
- Rien : les règles ne sont vérifiées qu'au placement. Seuls les nouveaux Pods suivront le cache.
Question 6.6
- Cache en zone-a : 2 Pods
frontRunning(un suraffinite, un suraffinite-m02), le 3ePending. - Cache en zone-b : 1 seul Pod
frontRunning(suraffinite-m03), les 2 autresPending.
Exercice de synthèse
Prédiction :
db: nœuds ssd possiblesaffinite-m02etaffinite-m03, préférence zone-b, doncaffinite-m03.api: même zone quedb(zone-b), donc uniquementaffinite-m03. L'anti-affinité interdit deuxapisur ce nœud : 1apiRunning, 1apiPending.worker: tous les nœuds sauf celui dedb, doncaffiniteetaffinite-m02, 3 Pods répartis sur ces deux nœuds.
synthese.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["zone-b"]
containers:
- name: nginx
image: nginx:1.27-alpine
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: db
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.27-alpine
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 3
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: db
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.27-alpine
kubectl apply -f synthese.yaml
kpn
kubectl describe $(kubectl get pods -l app=api --field-selector=status.phase=Pending -o name) | tail -n 4
Explication du Pod Pending : zone-b ne contient qu'un nœud. L'affinité de zone impose ce nœud aux deux api, et l'anti-affinité par nœud en interdit un second.
Trois corrections possibles :
| Correction | Effet | Ce qui est sacrifié |
|---|---|---|
Changer la préférence de db en zone-a |
db va sur affinite-m02, les 2 api sur affinite et affinite-m02 |
Le choix initial de zone pour la base |
Passer l'anti-affinité de api en preferred |
Les 2 api démarrent sur affinite-m03 |
La haute disponibilité de api (un seul nœud) |
Ajouter un nœud en zone-b : minikube node add -p affinite, puis le labelliser zone-b / ssd |
Chaque api a son nœud en zone-b |
Le coût d'un nœud supplémentaire |
Pour la 3e correction :
minikube node add -p affinite
kubectl get nodes
kubectl label node affinite-m04 topology.kubernetes.io/zone=zone-b disktype=ssd
kpn