Le scheduling dans Kubernetes est le processus qui consiste à assigner les Pods nouvellement créés à des nœuds appropriés pour leur exécution par le Kubelet. Le scheduler est le composant du control plane responsable de cette tâche cruciale.
Le processus de scheduling se déroule typiquement en deux phases :
nodeSelector
non correspondant, taints non tolérés, etc.).Le scheduler prend en compte divers facteurs :
requests
) comparées aux ressources disponibles sur les nœuds.taints
et tolerations
.nodeSelector
et nodeName
.nodeSelector
et nodeName
Ce sont les méthodes les plus simples pour contraindre un Pod à s'exécuter sur un ou des nœuds spécifiques.
1. nodeSelector
Permet de cibler des nœuds possédant des labels spécifiques. Il faut d'abord ajouter un label au nœud :
# Ajouter le label 'disktype=ssd' au nœud 'k8s-worker1'
kubectl label nodes k8s-worker1 disktype=ssd
Ensuite, spécifier ce label dans le nodeSelector
du Pod :
apiVersion: v1
kind: Pod
metadata:
name: nodeselector-pod
spec:
containers:
- name: nginx
image: nginx:1.27.4
nodeSelector:
disktype: "ssd" # Le Pod ne sera schedulé que sur les nœuds avec ce label
2. nodeName
Permet de cibler directement un nœud par son nom. C'est une méthode plus directe mais moins flexible que nodeSelector
. Si le nœud spécifié n'est pas disponible ou n'existe pas, le Pod ne sera pas schedulé.
apiVersion: v1
kind: Pod
metadata:
name: nodename-pod
spec:
containers:
- name: nginx
image: nginx:1.27.4
nodeName: k8s-worker2 # Force le Pod à s'exécuter sur ce nœud spécifique
L'affinité de nœud est une généralisation de nodeSelector
qui offre plus d'expressivité et de flexibilité. Elle permet de définir des règles requises (requiredDuringScheduling...
) ou préférées (preferredDuringScheduling...
).
Il existe deux types principaux d'affinité de nœud :
requiredDuringSchedulingIgnoredDuringExecution
: Le Pod doit être placé sur un nœud qui satisfait les règles spécifiées. Si aucun nœud ne correspond, le Pod ne sera pas schedulé. IgnoredDuringExecution
signifie que si les labels du nœud changent après le scheduling, le Pod continue de s'exécuter sur ce nœud.preferredDuringSchedulingIgnoredDuringExecution
: Le scheduler essaiera de placer le Pod sur un nœud qui satisfait les règles, mais si ce n'est pas possible, il pourra le placer sur un autre nœud. Chaque règle préférée est associée à un weight
(poids entre 1 et 100) qui influence le score du nœud.Ces règles utilisent des nodeSelectorTerms
, qui contiennent des matchExpressions
(ou matchFields
) permettant des opérations plus complexes que la simple égalité de nodeSelector
(In
, NotIn
, Exists
, DoesNotExist
, Gt
, Lt
).
Exemple combinant requis et préféré :
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-pod
spec:
containers:
- name: myapp-container
image: myapp
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- europe-west1-b
- europe-west1-c
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1 # Poids faible
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-value
- weight: 50 # Poids plus élevé
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
Ce Pod :
europe-west1-b
ou europe-west1-c
.disktype=ssd
dans ces zones.another-node-label-key=another-value
.Alors que l'affinité de nœud concerne la relation entre un Pod et un nœud, l'affinité/anti-affinité de Pods concerne la relation entre Pods. Elle permet d'influencer le scheduler pour placer (ou ne pas placer) un Pod sur un nœud où d'autres Pods spécifiques sont déjà présents.
Le concept clé ici est la topologie (topologyKey
). Elle définit le "domaine" sur lequel la règle d'affinité/anti-affinité s'applique (ex: le même nœud, la même zone de disponibilité, la même région).
La topologyKey
utilise un label présent sur les nœuds pour définir ce domaine. Exemples courants :
kubernetes.io/hostname
: Applique la règle au niveau du nœud.topology.kubernetes.io/zone
: Applique la règle au niveau de la zone de disponibilité.topology.kubernetes.io/region
: Applique la règle au niveau de la région.custom.domain/rack
pour représenter des racks de serveurs dans un centre de données.Comme pour l'affinité de nœud, il existe des types requis (requiredDuringScheduling...
) et préférés (preferredDuringScheduling...
).
Affinité de Pods (Pod Affinity) :
Anti-Affinité de Pods (Pod Anti-Affinity) :
Exemple d'Affinité de Pods (Requis) :
apiVersion: v1
kind: Pod
metadata:
name: web-server
spec:
containers:
- name: web-app
image: web-app-image
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache # Cible les pods avec le label app=cache
topologyKey: kubernetes.io/hostname # Doit être sur le MÊME nœud
Ce Pod web-server
doit être schedulé sur un nœud où au moins un Pod avec le label app=cache
est déjà en cours d'exécution.
Exemple d'Anti-Affinité de Pods (Préféré) :
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-critical-app
template:
metadata:
labels:
app: my-critical-app
spec:
containers:
- name: app-container
image: my-critical-app-image
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100 # Forte préférence
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-critical-app # Cible les autres pods de ce même Deployment
topologyKey: topology.kubernetes.io/zone # Essayer de les placer dans des ZONES différentes
Ce Deployment essaiera (avec une forte préférence) de scheduler ses 3 replicas dans des zones de disponibilité différentes pour maximiser la résilience.
Points importants :
requiredDuringScheduling...
peut empêcher le scheduling si les conditions ne sont pas remplies.topologyKey
est obligatoire.Un DaemonSets permet de copier un pod sur chaque noeud. Un pod sera automatiquement rajouté si on rajoute un noeud.
Cependant le DaemonSet respecte les labels, taints, tolérances. Si un pod ne devrait pas être sur ce noeud, le DaemonSet ne créera pas de copie du pod sur ce noeud.
La configuration se fait de la façon suivante :
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: my-daemonset
spec:
selector: # Selector obligatoire
matchLabels:
app: my-daemonset # Match les pods ayant ce label
template:
metadata:
labels:
app: my-daemonset # Ajoute le label app=my-daemonset au pod
spec:
containers:
- name: nginx
image: nginx:1.27.4
Une note sur les stratégies de mise à jour des DaemonSets (.spec.updateStrategy
) :
RollingUpdate
(défaut) : Met à jour les Pods un par un, nœud par nœud. Permet de contrôler le nombre de Pods indisponibles (maxUnavailable
).OnDelete
: Les Pods du DaemonSet ne sont mis à jour que lorsque vous supprimez manuellement les anciens Pods.Les Taints (marques) sont appliqués aux nœuds, tandis que les Tolerations (tolérances) sont appliquées aux Pods. Ce mécanisme permet aux nœuds de "repousser" certains Pods.
Le scheduler ne placera un Pod sur un nœud avec un Taint que si le Pod a une Toleration correspondante.
Appliquer un Taint à un nœud :
# Appliquer un Taint au nœud 'node1'
# Clé=key1, Valeur=value1, Effet=NoSchedule
# kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node-with-gpu gpu=true:NoSchedule
# Appliquer un Taint sans valeur (vérifie juste l'existence de la clé)
kubectl taint nodes node2 dedicated:NoExecute
Un Taint est composé de trois parties : key=value:effect
.
key
et value
sont des chaînes arbitraires.effect
définit ce qui arrive aux Pods qui ne tolèrent pas ce Taint :
NoSchedule
: Le scheduler n'essaiera pas de placer de nouveaux Pods (non tolérants) sur ce nœud. Les Pods déjà présents ne sont pas affectés.PreferNoSchedule
: Le scheduler essaiera d'éviter de placer des Pods (non tolérants) sur ce nœud, mais ce n'est pas une obligation stricte.NoExecute
: Effet le plus fort. Non seulement les nouveaux Pods (non tolérants) ne seront pas schedulés, mais les Pods (non tolérants) déjà en cours d'exécution sur ce nœud seront expulsés (evicted). On peut spécifier une durée (tolerationSeconds
) dans la Toleration du Pod pour retarder l'expulsion après l'ajout du Taint NoExecute
.Définir une Toleration dans un Pod :
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: tensorflow/tensorflow:latest-gpu
resources:
limits:
nvidia.com/gpu: 1 # Limite l'utilisation à 1 GPU
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
- key: "dedicated" # Taint sans valeur
operator: "Exists" # Vérifie juste l'existence de la clé
effect: "NoExecute" # Effet du Taint
tolerationSeconds: 3600 # Permet au Pod de continuer à s'exécuter pendant 1 heure avant d'être expulsé
operator
: Peut être Equal
(la key
et la value
doivent correspondre) ou Exists
(seule la key
doit correspondre, la value
est ignorée).operator: Exists
sans key
spécifiée tolère tous les Taints.effect
spécifié tolère tous les effets pour la key:value
correspondante.Cas d'usage courants :
node.kubernetes.io/unreachable
, node.kubernetes.io/not-ready
sont des Taints ajoutés automatiquement par Kubernetes avec l'effet NoExecute
).Supprimer un Taint d'un nœud :
# Supprimer le Taint avec la clé 'key1' et l'effet 'NoSchedule'
kubectl taint nodes node1 key1:NoSchedule-
# Supprimer tous les taints avec la clé 'dedicated'
kubectl taint nodes node2 dedicated-
Un pod statique (static pod) est un pod géré directement par le démon Kubelet sur un nœud spécifique, sans intervention de l'API Server du control plane. Cela signifie qu'un Pod statique peut démarrer même si le control plane est indisponible, il suffit que le Kubelet soit fonctionnel sur le nœud.
Le Kubelet surveille un répertoire spécifique sur le système de fichiers du nœud (configuré via l'argument --pod-manifest-path
du Kubelet, souvent /etc/kubernetes/manifests
par défaut avec kubeadm
) ou une URL de configuration.
Le redémarrage explicite du Kubelet n'est généralement pas nécessaire car il surveille activement le répertoire des manifestes.
Pour chaque Pod statique qu'il gère, le Kubelet tente de créer un objet Pod miroir (mirror Pod) sur l'API Server de Kubernetes. Ce Pod miroir rend le Pod statique visible via kubectl get pods
et d'autres outils interagissant avec l'API. Cependant, ce Pod miroir est en lecture seule du point de vue de l'API :
kubectl get
, kubectl describe
).kubectl delete pod <mirror-pod-name>
sera ignoré ou le Pod sera immédiatement recréé par le Kubelet).La seule façon de gérer le cycle de vie d'un Pod statique est d'agir sur son fichier manifeste dans le répertoire surveillé par le Kubelet sur le nœud hôte.
Cas d'usage principal :
Les Pods statiques sont la méthode standard pour exécuter les composants auto-hébergés du control plane (comme etcd
, kube-apiserver
, kube-controller-manager
, kube-scheduler
) sous forme de containers gérés par le Kubelet sur les nœuds maîtres. kubeadm
utilise ce mécanisme lors de l'initialisation du cluster.
Création (Exemple) :
Se connecter au nœud où l'on veut exécuter le Pod statique (ex: un nœud maître).
Créer un fichier YAML dans le répertoire des manifestes (ex: /etc/kubernetes/manifests/my-static-web.yaml
):
apiVersion: v1
kind: Pod
metadata:
name: my-static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP
Le Kubelet détectera automatiquement ce fichier et démarrera le container Nginx.
Depuis un poste ayant accès à l'API (ou le master), on peut voir le Pod miroir :
kubectl get pods
# On devrait voir un pod nommé my-static-web-<nom-du-noeud>
Suppression :
Supprimer simplement le fichier /etc/kubernetes/manifests/my-static-web.yaml
du nœud. Le Kubelet arrêtera le Pod et supprimera le Pod miroir de l'API Server.
Cette fonctionnalité permet un contrôle plus fin sur la manière dont les Pods sont répartis (spread) à travers différentes topologies du cluster (nœuds, zones, régions, etc.) pour améliorer la haute disponibilité et l'utilisation des ressources.
Elle complète l'anti-affinité de Pods en offrant plus de flexibilité, notamment pour obtenir une répartition équilibrée.
On définit ces contraintes dans spec.topologySpreadConstraints
.
Champs clés d'une contrainte :
maxSkew
: Décrit le degré maximal autorisé d'inégalité dans la répartition des Pods. C'est la différence maximale permise entre le nombre de Pods correspondants dans deux domaines topologiques quelconques.topologyKey
: Le label de nœud qui définit le domaine topologique (ex: kubernetes.io/hostname
, topology.kubernetes.io/zone
).whenUnsatisfiable
: Indique comment gérer un Pod si la contrainte ne peut être satisfaite :
DoNotSchedule
(Défaut) : Le Pod ne sera pas schedulé.ScheduleAnyway
: Le Pod sera schedulé, en priorisant les domaines qui minimisent le skew
.labelSelector
: Pour identifier les Pods concernés par cette contrainte de répartition.Exemple : Répartir équitablement les Pods d'un service entre les zones
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-balanced-app
spec:
replicas: 6
selector:
matchLabels:
app: balanced-app
template:
metadata:
labels:
app: balanced-app
spec:
topologySpreadConstraints:
- maxSkew: 1 # La différence entre le nb de pods dans 2 zones ne doit pas dépasser 1
topologyKey: topology.kubernetes.io/zone # Répartir par zone
whenUnsatisfiable: DoNotSchedule # Ne pas scheduler si la contrainte n'est pas respectée
labelSelector:
matchLabels:
app: balanced-app # Concerne les pods de cette application
containers:
- name: app-container
image: my-app-image
Avec 6 replicas et maxSkew: 1
sur la topologie zone
, Kubernetes essaiera de placer les Pods de manière à ce que le nombre de Pods dans chaque zone ne diffère pas de plus de 1. Par exemple, s'il y a 3 zones, il placera idéalement 2 Pods par zone. S'il n'y a que 2 zones, il pourrait en placer 3 dans chaque.