Aller au contenu
Conteneurs & Orchestration medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Scheduling avancé Kubernetes

14 min de lecture

logo kubernetes

Le scheduler Kubernetes place les Pods sur les nœuds disponibles selon des règles précises. Par défaut, il équilibre la charge automatiquement. Mais en production, vous devez souvent contrôler ce placement : isoler des workloads, garantir la haute disponibilité, ou exploiter du matériel spécifique (GPU, SSD).

MécanismeDirectionUsage principal
nodeSelectorPod → NodePlacement simple par labels
nodeAffinityPod → NodePlacement avancé avec expressions
podAffinityPod → PodCo-localisation de Pods
podAntiAffinityPod ↔ PodSéparation de Pods
taints/tolerationsNode → PodExclusion de nœuds sauf exception
topologySpreadConstraintsPod ↔ TopologyRépartition équilibrée
Fenêtre de terminal
# Voir les labels et taints d'un nœud
kubectl describe node <node-name> | grep -A10 Labels
kubectl describe node <node-name> | grep -A5 Taints
# Lister les nœuds avec leurs labels
kubectl get nodes --show-labels

Le nodeSelector est le mécanisme le plus simple : il place le Pod uniquement sur des nœuds ayant tous les labels spécifiés.

Fenêtre de terminal
# Ajouter un label
kubectl label node worker-1 disktype=ssd
# Vérifier
kubectl get nodes -l disktype=ssd
# Supprimer un label
kubectl label node worker-1 disktype-
apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
nodeSelector:
disktype: ssd
containers:
- name: app
image: nginx:1.25

nodeAffinity offre plus de flexibilité que nodeSelector avec des expressions logiques (In, NotIn, Exists, DoesNotExist, Gt, Lt).

TypeComportement
requiredDuringSchedulingIgnoredDuringExecutionObligatoire — le Pod ne démarre pas si aucun nœud ne matche
preferredDuringSchedulingIgnoredDuringExecutionPréféré — le scheduler essaie, mais place ailleurs si nécessaire
apiVersion: v1
kind: Pod
metadata:
name: affinity-required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- eu-west-1a
- eu-west-1b
containers:
- name: app
image: nginx:1.25

Le Pod ne démarrera que sur un nœud dans la zone eu-west-1a ou eu-west-1b.

apiVersion: v1
kind: Pod
metadata:
name: affinity-preferred
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
- weight: 20
preference:
matchExpressions:
- key: disktype
operator: In
values:
- hdd
containers:
- name: app
image: nginx:1.25

Le scheduler préfère les nœuds SSD (poids 80) mais accepte les HDD (poids 20) si nécessaire.

OpérateurDescriptionExemple
InValeur dans la listezone In [a, b]
NotInValeur hors de la listeenv NotIn [prod]
ExistsLa clé existe (valeur ignorée)gpu Exists
DoesNotExistLa clé n’existe passpot DoesNotExist
GtValeur supérieure (numérique)cores Gt 4
LtValeur inférieure (numérique)memory Lt 32

Ces mécanismes définissent des règles de placement entre Pods, pas entre Pod et Node.

Placer un Pod sur le même nœud (ou dans la même zone) qu’un autre Pod :

apiVersion: v1
kind: Pod
metadata:
name: cache-client
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: redis
topologyKey: kubernetes.io/hostname
containers:
- name: client
image: busybox:1.36
command: ['sh', '-c', 'sleep 3600']

Ce Pod sera placé sur le même nœud (topologyKey: kubernetes.io/hostname) qu’un Pod ayant le label app=redis.

Placer un Pod sur un nœud différent des autres réplicas (haute disponibilité) :

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
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.25

Chaque réplica sera placé sur un nœud différent. Si vous n’avez que 2 nœuds, le 3ᵉ Pod restera Pending.


Les taints sont appliqués aux nœuds pour repousser les Pods. Les tolerations permettent aux Pods de tolérer ces taints.

Fenêtre de terminal
# Syntaxe : kubectl taint nodes <node> <key>=<value>:<effect>
kubectl taint nodes worker-1 dedicated=database:NoSchedule
# Vérifier
kubectl describe node worker-1 | grep Taints
# Supprimer un taint (ajout du tiret final)
kubectl taint nodes worker-1 dedicated=database:NoSchedule-
EffectComportement
NoScheduleNouveaux Pods sans toleration ne sont pas schedulés
PreferNoScheduleLe scheduler évite ce nœud mais l’utilise si nécessaire
NoExecutePods existants sans toleration sont évincés
apiVersion: v1
kind: Pod
metadata:
name: db-pod
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "database"
effect: "NoSchedule"
containers:
- name: postgres
image: postgres:15

Pour tolérer tous les taints d’un nœud (utile pour DaemonSets) :

tolerations:
- operator: "Exists"
TaintUsage
node-role.kubernetes.io/control-plane:NoScheduleProtège le control plane
dedicated=gpu:NoScheduleRéserve des nœuds GPU
maintenance=true:NoExecuteÉvince les Pods avant maintenance

topologySpreadConstraints — Répartition équilibrée

Section intitulée « topologySpreadConstraints — Répartition équilibrée »

Ce mécanisme répartit les Pods de manière équilibrée sur une topologie (zones, nœuds).

apiVersion: apps/v1
kind: Deployment
metadata:
name: balanced-app
spec:
replicas: 6
selector:
matchLabels:
app: balanced
template:
metadata:
labels:
app: balanced
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: balanced
containers:
- name: app
image: nginx:1.25
ParamètreDescription
maxSkewDifférence maximale de Pods entre topologies (1 = équilibré)
topologyKeyLabel définissant la topologie (zone, nœud, rack)
whenUnsatisfiableDoNotSchedule (strict) ou ScheduleAnyway (best-effort)
labelSelectorPods concernés par la contrainte
ZonePods
eu-west-1a2
eu-west-1b2
eu-west-1c2

Kubernetes peut évincer des Pods de basse priorité pour faire de la place à des Pods de haute priorité.

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Pour les workloads critiques"
apiVersion: v1
kind: Pod
metadata:
name: critical-pod
spec:
priorityClassName: high-priority
containers:
- name: app
image: nginx:1.25

Labellisez un nœud avec tier=frontend et créez un Pod qui ne s’exécute que sur ce nœud.

Solution
Fenêtre de terminal
# Labelliser
kubectl label node worker-1 tier=frontend
# Pod
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: frontend-pod
spec:
nodeSelector:
tier: frontend
containers:
- name: nginx
image: nginx:1.25
EOF
# Vérifier
kubectl get pod frontend-pod -o wide
  1. Appliquez un taint workload=ml:NoSchedule sur un nœud
  2. Créez un Pod sans toleration (doit rester Pending)
  3. Créez un Pod avec toleration (doit démarrer)
Solution
Fenêtre de terminal
# Taint
kubectl taint nodes worker-1 workload=ml:NoSchedule
# Pod sans toleration
kubectl run no-toleration --image=nginx -o yaml --dry-run=client | \
kubectl apply -f -
kubectl get pod no-toleration
# Pending (si worker-1 est le seul nœud disponible)
# Pod avec toleration
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: ml-pod
spec:
tolerations:
- key: "workload"
operator: "Equal"
value: "ml"
effect: "NoSchedule"
containers:
- name: app
image: nginx:1.25
EOF
# Nettoyage
kubectl taint nodes worker-1 workload=ml:NoSchedule-

Créez un Deployment de 3 réplicas avec une anti-affinité garantissant qu’aucun Pod ne partage le même nœud.

Solution
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: ha-app
spec:
replicas: 3
selector:
matchLabels:
app: ha-app
template:
metadata:
labels:
app: ha-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: ha-app
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.25
EOF
# Vérifier la distribution
kubectl get pods -l app=ha-app -o wide

Quand un Pod reste Pending, le scheduler indique pourquoi dans les events :

Fenêtre de terminal
# Events du Pod
kubectl describe pod <pod-name> | grep -A10 Events
# Messages courants
# 0/3 nodes are available: 1 node(s) had untolerated taint, 2 node(s) didn't match Pod's node affinity
# 0/3 nodes are available: 3 Insufficient cpu
# 0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules
  1. Vérifier les events du Pod

    Fenêtre de terminal
    kubectl describe pod <name> | tail -20
  2. Vérifier les ressources disponibles

    Fenêtre de terminal
    kubectl describe nodes | grep -A5 "Allocated resources"
  3. Vérifier les taints des nœuds

    Fenêtre de terminal
    kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
  4. Vérifier les labels des nœuds

    Fenêtre de terminal
    kubectl get nodes --show-labels | tr ',' '\n'

  1. nodeSelector : placement simple, tous les labels doivent matcher
  2. nodeAffinity : placement avancé avec expressions (In, NotIn, Exists, Gt, Lt)
  3. podAffinity/AntiAffinity : placement relatif entre Pods (co-localisation ou séparation)
  4. Taints repoussent les Pods, tolerations permettent aux Pods de passer
  5. topologySpreadConstraints : répartition équilibrée sur zones/nœuds
  6. PriorityClass : permet la preemption des Pods de basse priorité
  7. Un Pod Pending → kubectl describe pod pour voir la raison

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn