
Contraindre les Pods à certains noeuds : Node Selectors et Node Affinity
Apprenez à utiliser nodeSelector et Node Affinity dans Kubernetes pour contraindre ou orienter l'ordonnancement de vos Pods sur des noeuds spécifiques.
Pourquoi cibler des noeuds spécifiques ?
Bien que le Scheduler Kubernetes fasse un excellent travail pour répartir les Pods de manière équilibrée en fonction des ressources disponibles, il existe de nombreuses situations où nous devons explicitement contraindre ou influencer sur quels noeuds un Pod doit (ou ne doit pas) s'exécuter. Le placement ciblé des Pods est souvent nécessaire pour répondre à des exigences spécifiques liées au matériel, à la localisation, à la performance ou à la conformité.
Par exemple, vous pourriez avoir besoin de :
- Placer des Pods nécessitant des GPU sur des noeuds équipés de GPU.
- Exécuter des charges de travail sensibles aux I/O sur des noeuds dotés de disques SSD rapides.
- Contraindre des Pods à s'exécuter dans une zone géographique ou un rack réseau particulier pour des raisons de latence ou de réglementation.
- Dédier certains noeuds à des groupes d'utilisateurs ou à des types d'applications spécifiques.
- Respecter des licences logicielles liées à des noeuds spécifiques.
Kubernetes offre deux mécanismes principaux pour réaliser ce ciblage basé sur les caractéristiques des noeuds : le simple nodeSelector et le plus flexible et expressif Node Affinity.
La méthode simple : `nodeSelector`
Le mécanisme le plus direct pour contraindre un Pod à s'exécuter uniquement sur des noeuds possédant des labels spécifiques est le champ spec.nodeSelector dans la définition du Pod. C'est une simple correspondance de type clé-valeur.
Le processus se déroule en deux étapes :
- Labelliser les noeuds : Vous devez d'abord ajouter des labels personnalisés aux noeuds cibles. Par exemple, pour marquer un noeud comme ayant un disque SSD :
kubectl label nodedisktype=ssd - Spécifier le
nodeSelectordans le Pod : Dans le manifeste YAML du Pod, ajoutez un champnodeSelectorlistant les paires clé-valeur requises. Le Pod ne sera ordonnancé que sur un noeud possédant tous les labels spécifiés.apiVersion: v1 kind: Pod metadata: name: nginx-on-ssd spec: containers: - name: nginx image: nginx nodeSelector: disktype: ssd # Exige que le noeud ait le label disktype=ssd # zone: us-west-1 # Pourrait exiger plusieurs labels (logique ET)
Le nodeSelector est facile à utiliser mais présente des limitations : il ne permet que des correspondances exactes (égalité) et combine les différentes paires clé-valeur avec une logique `ET`. Il ne permet pas d'exprimer des conditions plus complexes comme 'le label doit exister', 'la valeur doit être dans une liste', ou de définir des préférences plutôt que des exigences strictes. C'est là qu'intervient Node Affinity.
Node Affinity : l'approche avancée et flexible
Node Affinity (Affinité de Noeud) est un mécanisme plus puissant et expressif, conçu pour surmonter les limitations de nodeSelector. Il est défini dans le champ spec.affinity.nodeAffinity du Pod et offre plusieurs avantages :
- Langage plus riche : Il permet d'utiliser des opérateurs logiques variés (
In,NotIn,Exists,DoesNotExist,Gt,Lt) pour la correspondance des labels, pas seulement l'égalité. - Distinction entre exigence et préférence : Vous pouvez définir des règles qui doivent absolument être satisfaites (règles requises) et des règles que le Scheduler devrait essayer de satisfaire (règles préférées).
Node Affinity se décline en deux types principaux, qui peuvent être utilisés conjointement :
requiredDuringSchedulingIgnoredDuringExecution: Règles d'affinité requises. Le Pod ne sera ordonnancé que si ces règles sont satisfaites au moment de la décision. Si les labels du noeud changent après l'ordonnancement, le Pod n'est pas expulsé (d'où le `IgnoredDuringExecution`).preferredDuringSchedulingIgnoredDuringExecution: Règles d'affinité préférées. Le Scheduler essaiera de trouver un noeud qui satisfait ces règles, mais si ce n'est pas possible, il ordonnera quand même le Pod sur un autre noeud faisable. Chaque règle préférée est associée à un poids (weight) qui influence le score du noeud lors de la phase de scoring.
Node Affinity : `requiredDuringSchedulingIgnoredDuringExecution`
Ce type d'affinité est utilisé pour définir des contraintes strictes. Le Pod ne démarrera pas si aucun noeud ne correspond aux critères.
La structure YAML implique des nodeSelectorTerms. Si vous spécifiez plusieurs nodeSelectorTerms, un noeud est éligible s'il correspond à au moins un des termes (logique `OU`). A l'intérieur d'un nodeSelectorTerm, si vous spécifiez plusieurs matchExpressions, le noeud doit correspondre à toutes les expressions (logique `ET`).
Chaque matchExpressions contient une `key`, un `operator`, et éventuellement des `values`.
- Opérateurs :
In: La valeur du label doit être dans la liste fournie.NotIn: La valeur du label ne doit pas être dans la liste fournie.Exists: Le label doit exister (aucune valeur n'est spécifiée).DoesNotExist: Le label ne doit pas exister.Gt: La valeur du label (interprétée comme un nombre) doit être supérieure à la valeur fournie.Lt: La valeur du label (interprétée comme un nombre) doit être inférieure à la valeur fournie.
Exemple : Exiger que le Pod soit placé sur un noeud dans la zone `us-east-1a` OU `us-east-1b` ET qui a le label `storage=fast`.
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone # Label standard pour la zone
operator: In
values:
- us-east-1a
- us-east-1b
- key: storage
operator: Exists # Le noeud doit avoir le label 'storage'
- matchExpressions: # Autre terme (logique OU)
- key: gpu-model
operator: In
values:
- nvidia-tesla-k80
- nvidia-tesla-v100
containers:
- name: myapp
image: myapp
Node Affinity : `preferredDuringSchedulingIgnoredDuringExecution`
Ce type d'affinité permet d'exprimer des préférences. Le Scheduler essaiera de satisfaire ces préférences mais placera le Pod même si ce n'est pas possible. Chaque préférence est associée à un poids (weight) entre 1 et 100.
Le Scheduler calcule un score pour chaque noeud faisable en additionnant les poids des préférences satisfaites par ce noeud. Plus le poids est élevé, plus la préférence est forte.
Exemple : Préférer fortement (poids 80) les noeuds avec le label `disktype=ssd` et préférer aussi (poids 20) les noeuds dans la zone `eu-west-1`.
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-preferred
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
- weight: 20
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- eu-west-1
containers:
- name: myapp
image: myapp
Dans cet exemple, un noeud avec `disktype=ssd` dans `eu-west-1` obtiendra un score de 100 (80+20). Un noeud avec `disktype=ssd` mais dans une autre zone obtiendra 80. Un noeud sans SSD mais dans `eu-west-1` obtiendra 20. Le Scheduler choisira le noeud faisable ayant le score le plus élevé.
Choisir entre `nodeSelector` et Node Affinity
Pour des besoins simples de correspondance exacte sur un ou plusieurs labels (logique ET), nodeSelector est plus concis et facile à utiliser.
Pour toutes les autres situations nécessitant des opérateurs plus complexes (In, NotIn, Exists...), une logique OU entre différents critères, ou la distinction entre exigences strictes et préférences, Node Affinity est la solution recommandée et beaucoup plus puissante.
Il est possible d'utiliser les deux dans un même Pod, mais c'est généralement redondant. Les règles définies dans nodeSelector et dans la section `requiredDuringScheduling...` de Node Affinity doivent toutes être satisfaites (logique ET).
En maîtrisant `nodeSelector` et surtout Node Affinity, vous gagnez un contrôle précis sur le placement de vos Pods, vous permettant d'adapter l'ordonnancement aux contraintes et objectifs spécifiques de votre infrastructure et de vos applications.