Aller au contenu
Conteneurs & Orchestration medium

kubectl cordon, drain et taint : maintenance et planification des nœuds Kubernetes

21 min de lecture

logo kubernetes

Vous devez mettre un nœud en maintenance, mise à jour de l'OS, remplacement de disque, patching de sécurité, sans interrompre les services qui tournent dessus. Kubernetes fournit un workflow en 3 étapes : kubectl cordon empêche les nouveaux pods d'arriver, kubectl drain évacue les pods existants vers d'autres nœuds, et kubectl uncordon réactive le nœud après la maintenance. Pour un contrôle plus fin et permanent, kubectl taint restreint quels pods peuvent tourner sur quels nœuds via un système de taints et tolerations.

  • Mettre un nœud en maintenance proprement avec le workflow cordondrain → maintenance → uncordon
  • Évacuer les pods d'un nœud en gérant les DaemonSets, les PodDisruptionBudgets et les volumes éphémères
  • Contrôler le placement des pods avec les taints et les tolerations
  • Distinguer les 3 effets de taint : NoSchedule, PreferNoSchedule, NoExecute
  • Diagnostiquer les problèmes de drain bloqué ou de pods non planifiés

Ces quatre commandes agissent toutes sur les nœuds, mais elles ne se remplacent pas. Les trois premières servent une opération ponctuelle de maintenance et se défont aussitôt après ; kubectl taint pose au contraire une contrainte durable, qui survit aux redémarrages du nœud tant que personne ne la retire. Le tableau donne l'effet exact de chacune, à lire avant de choisir.

BesoinCommandeEffet
Empêcher les nouveaux pods d'arriver sur un nœudkubectl cordonMarque le nœud SchedulingDisabled
Réautoriser les pods sur un nœudkubectl uncordonRetire le marquage SchedulingDisabled
Évacuer tous les pods d'un nœud (maintenance)kubectl drainCordon + éviction de tous les pods
Restreindre durablement quels pods tournent sur un nœudkubectl taintSeuls les pods avec la toleration correspondante sont acceptés

Voici la procédure standard pour sortir un nœud du cluster, faire la maintenance, puis le réintégrer. Elle suppose que le cluster dispose de la capacité restante pour absorber les pods évacués : sur un cluster à trois nœuds saturés, le drain se bloquera faute de place. Les sorties affichées viennent d'un cluster à trois nœuds en v1.30.2, les vôtres différeront sur les noms et les âges mais pas sur la structure.

  1. Vérifiez l'état actuel du nœud

    Fenêtre de terminal
    kubectl get nodes
    NAME STATUS ROLES AGE VERSION
    node-1 Ready <none> 90d v1.30.2
    node-2 Ready <none> 90d v1.30.2
    node-3 Ready <none> 90d v1.30.2
  2. Identifiez les pods qui tournent sur le nœud

    Fenêtre de terminal
    kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=node-1

    Cela vous donne la liste complète de ce qui sera évacué.

  3. Empêchez les nouveaux pods d'arriver (cordon)

    Fenêtre de terminal
    kubectl cordon node-1
    node/node-1 cordoned

    Vérification, le nœud affiche SchedulingDisabled :

    Fenêtre de terminal
    kubectl get node node-1
    NAME STATUS ROLES AGE VERSION
    node-1 Ready,SchedulingDisabled <none> 90d v1.30.2
  4. Évacuez les pods existants (drain)

    Fenêtre de terminal
    kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
    node/node-1 already cordoned
    evicting pod default/api-gateway-7d4b8c
    evicting pod default/worker-9f2a1e
    pod/api-gateway-7d4b8c evicted
    pod/worker-9f2a1e evicted
    node/node-1 drained
  5. Faites votre maintenance (mise à jour OS, reboot, remplacement matériel…)

  6. Réactivez le nœud (uncordon)

    Fenêtre de terminal
    kubectl uncordon node-1
    node/node-1 uncordoned
  7. Vérifiez que le nœud est de nouveau prêt

    Fenêtre de terminal
    kubectl get node node-1
    NAME STATUS ROLES AGE VERSION
    node-1 Ready <none> 90d v1.30.2

kubectl cordon et uncordon : le verrou de planification

Section intitulée « kubectl cordon et uncordon : le verrou de planification »

Ces deux commandes forment une paire : elles basculent le champ spec.unschedulable du nœud, rien de plus. Aucun pod n'est déplacé, aucune donnée n'est touchée, l'opération est instantanée et réversible. C'est le geste le plus sûr du guide, et celui par lequel commencer toute intervention sur un nœud.

La commande prend le nom exact du nœud tel que renvoyé par kubectl get nodes.

Fenêtre de terminal
kubectl cordon <nom-du-noeud>

Le nœud passe en SchedulingDisabled. Les pods déjà en cours d'exécution ne sont pas affectés, seuls les nouveaux pods ne seront plus planifiés dessus.

C'est l'opération inverse, à lancer une fois la maintenance terminée et le nœud revenu en Ready.

Fenêtre de terminal
kubectl uncordon <nom-du-noeud>

Le nœud redevient éligible à la planification. Les pods existants sur d'autres nœuds ne sont pas automatiquement redéplacés, Kubernetes ne rééquilibre pas les pods tout seul.

kubectl drain fait 2 choses : il cordon le nœud (s'il ne l'est pas déjà), puis il évacue tous les pods en demandant leur suppression. Le scheduler recrée les pods (gérés par un controller) sur d'autres nœuds.

Le nom du nœud est le seul argument obligatoire, mais la commande sans option échoue dès qu'un DaemonSet tourne sur le nœud, ce qui est le cas de tous les clusters réels.

Fenêtre de terminal
kubectl drain <nom-du-noeud> [options]

En pratique, un drain nu échoue presque toujours. Voici les options que vous utiliserez systématiquement :

OptionPourquoi c'est nécessaire
--ignore-daemonsetsLes DaemonSets sont liés au nœud, ils ne peuvent pas être évacués. Sans cette option, drain refuse de continuer
--delete-emptydir-dataLes pods avec des volumes emptyDir perdront leurs données. Cette option confirme que vous acceptez cette perte
--forceSupprime les pods non gérés par un controller (pods créés manuellement). Ces pods ne seront pas recréés
--grace-period=<seconds>Durée laissée aux pods pour s'arrêter proprement (override du terminationGracePeriodSeconds du pod)
--timeout=<duration>Durée maximale du drain. Par défaut : infini
--pod-selector=<selector>N'évacue que les pods matchant le selector
--disable-evictionUtilise delete au lieu de l'API d'éviction (ignore les PodDisruptionBudgets)

Voici la combinaison à retenir. Le --timeout=300s est le seul ajout par rapport au minimum vital : sans lui, un drain bloqué par un PodDisruptionBudget attend indéfiniment et fige le script de maintenance. Notez l'absence volontaire de --force, on veut être averti si un pod n'est géré par aucun controller.

Fenêtre de terminal
kubectl drain node-1 \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=300s

Un PodDisruptionBudget (PDB) empêche le drain de supprimer trop de pods d'un même service en même temps. Si votre PDB exige minAvailable: 2 et qu'il ne reste que 2 pods disponibles, le drain bloquera tant qu'un pod n'est pas remonté sur un autre nœud.

Fenêtre de terminal
# Voir les PDB en place
kubectl get pdb --all-namespaces
NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
prod api-gateway-pdb 2 N/A 1 30d

Si le drain bloque à cause d'un PDB :

Fenêtre de terminal
# Vérifier quel PDB bloque
kubectl get pdb -n prod -o wide
# Si c'est un drain d'urgence (perte de nœud), contourner le PDB
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --disable-eviction

Pour un rolling upgrade, traitez les nœuds un par un : la boucle ci-dessous ne passe au suivant qu'une fois le précédent revenu en Ready, grâce au kubectl wait final qui bloque jusqu'à la condition ou l'expiration du délai. Le sélecteur -l role=worker évite d'attraper les nœuds de control plane.

Fenêtre de terminal
for node in $(kubectl get nodes -l role=worker -o name); do
echo "=== Draining $node ==="
kubectl drain "$node" --ignore-daemonsets --delete-emptydir-data --timeout=300s
echo "=== Maintenance sur $node ==="
# Votre commande de maintenance ici (SSH, reboot, etc.)
echo "=== Uncordoning $node ==="
kubectl uncordon "$node"
echo "=== Attente que le nœud soit Ready ==="
kubectl wait --for=condition=Ready "$node" --timeout=120s
echo ""
done

Les taints sont un mécanisme permanent pour contrôler quels pods peuvent tourner sur quels nœuds. Contrairement à cordon (qui bloque tout), un taint est sélectif : seuls les pods avec la toleration correspondante sont acceptés.

Le mécanisme repose sur une correspondance vérifiée par le scheduler à chaque tentative de placement. Le taint vit sur le nœud, la toleration vit dans le manifest du pod, et rien ne se passe tant que les deux ne se correspondent pas :

  1. Vous posez un taint sur un nœud (« ce nœud est spécial »)
  2. Seuls les pods qui déclarent une toleration correspondante peuvent y être planifiés
  3. Les pods sans toleration sont rejetés (ou évincés, selon l'effet)

Un taint se compose de trois parties : une clé, une valeur facultative et un effet obligatoire. Le tiret final de la seconde forme est ce qui distingue une suppression d'un ajout.

Fenêtre de terminal
# Ajouter un taint
kubectl taint nodes <noeud> <clé>=<valeur>:<effet>
# Supprimer un taint
kubectl taint nodes <noeud> <clé>-

L'effet décide de ce qui arrive aux pods, et la différence porte surtout sur les pods déjà en cours d'exécution. NoSchedule et PreferNoSchedule ne touchent que les placements futurs, NoExecute évacue immédiatement l'existant. C'est le seul des trois qui provoque une interruption de service s'il est posé sans précaution.

EffetComportement
NoScheduleLes nouveaux pods sans toleration ne sont pas planifiés. Les pods déjà en place ne sont pas touchés
PreferNoScheduleLe scheduler évite ce nœud si possible, mais y planifie un pod s'il n'y a pas d'autre choix
NoExecuteLes pods existants sans toleration sont évacués. Les nouveaux sans toleration sont refusés

Voici quatre situations réelles, classées du besoin le plus permanent au plus urgent. Elles illustrent chacune un effet différent, ce qui aide à choisir : réservation de matériel coûteux, cloisonnement par environnement, mise en observation d'un nœud suspect, et évacuation immédiate.

Les nœuds GPU coûtent cher et rien n'empêche par défaut un pod quelconque de s'y installer. Le taint NoSchedule les rend inaccessibles à tout ce qui ne le tolère pas explicitement.

Fenêtre de terminal
# Taint sur les nœuds GPU
kubectl taint nodes gpu-node-1 gpu=true:NoSchedule
kubectl taint nodes gpu-node-2 gpu=true:NoSchedule

Seuls les pods avec cette toleration seront acceptés :

apiVersion: v1
kind: Pod
metadata:
name: training-job
spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
containers:
- name: trainer
image: ml-training:1.4.2

Nœud dédié à un environnement (prod uniquement)

Section intitulée « Nœud dédié à un environnement (prod uniquement) »

Même principe pour empêcher des pods de test ou de recette d'atterrir sur du matériel réservé à la production. Attention, ce taint isole le placement mais ne constitue pas une frontière de sécurité : il n'empêche personne d'ajouter la toleration à son manifest.

Fenêtre de terminal
kubectl taint nodes prod-node-1 env=production:NoSchedule
tolerations:
- key: "env"
operator: "Equal"
value: "production"
effect: "NoSchedule"

Utilisez PreferNoSchedule pour que le scheduler évite ce nœud sans évacuer les pods existants :

Fenêtre de terminal
kubectl taint nodes node-3 stability=suspect:PreferNoSchedule

À réserver aux situations où vous n'avez pas le temps d'un drain propre, matériel qui lâche ou incident de sécurité. Contrairement à drain, cet effet ne respecte aucun PodDisruptionBudget et ne laisse pas de délai d'arrêt gracieux négocié.

Fenêtre de terminal
kubectl taint nodes node-1 maintenance=urgent:NoExecute

Tous les pods sans toleration pour maintenance=urgent:NoExecute sont immédiatement évacués. C'est plus radical que drain car c'est instantané.

Les pods avec toleration peuvent définir un délai d'expulsion :

tolerations:
- key: "maintenance"
operator: "Equal"
value: "urgent"
effect: "NoExecute"
tolerationSeconds: 300 # Le pod a 5 min avant d'être évacué

Une toleration se déclare sous spec.tolerations dans le manifest du pod. Deux comportements par défaut méritent d'être connus car ils élargissent la correspondance sans qu'on le demande : un operator omis vaut Equal, et un effect laissé vide accepte les trois effets du taint correspondant.

ChampValeurDescription
key"gpu", "env"Doit correspondre à la clé du taint
operatorEqual (défaut)Clé + valeur doivent matcher
operatorExistsSeule la clé doit exister (valeur ignorée)
value"true", "production"Doit correspondre à la valeur du taint (si operator=Equal)
effectNoSchedule, PreferNoSchedule, NoExecuteDoit correspondre à l'effet du taint. Si vide : matche tous les effets
tolerationSeconds300Durée avant évacuation (uniquement avec NoExecute)

Toleration universelle, accepte tous les taints d'un nœud :

tolerations:
- operator: "Exists"

Un nœud peut porter plusieurs taints simultanément, y compris des taints posés par Kubernetes lui-même. Commencez donc toujours par les lister avant d'en retirer un, sinon vous corrigerez un symptôme sans débloquer le placement.

Fenêtre de terminal
# Voir les taints d'un nœud
kubectl describe node node-1 | grep -A5 Taints
# Ou en JSON
kubectl get node node-1 -o jsonpath='{.spec.taints}' | python3 -m json.tool
# Supprimer un taint par sa clé
kubectl taint nodes node-1 gpu-
# Supprimer un taint par clé + effet
kubectl taint nodes node-1 gpu:NoSchedule-

Kubernetes pose automatiquement des taints sur les nœuds dans certaines situations. Les connaître aide au diagnostic :

Taint automatiquePosé quandEffet
node.kubernetes.io/not-readyLe nœud n'est pas ReadyNoExecute
node.kubernetes.io/unreachableLe nœud n'est plus joignableNoExecute
node.kubernetes.io/disk-pressureEspace disque insuffisantNoSchedule
node.kubernetes.io/memory-pressureMémoire insuffisanteNoSchedule
node.kubernetes.io/pid-pressureTrop de processusNoSchedule
node.kubernetes.io/unschedulableNœud cordonéNoSchedule

Les règles ci-dessous viennent des erreurs qui coûtent le plus cher en exploitation : un drain lancé sans filet dans un script automatisé, et un taint posé un jour de crise que plus personne ne sait interpréter six mois après. Elles sont séparées en deux blocs, car maintenance et placement n'ont ni la même temporalité ni les mêmes conséquences.

Ces cinq réflexes valent pour toute intervention sur un nœud en production, du patch mensuel à la mise à jour de version du cluster.

  • Suivez toujours le workflow cordondrain → maintenance → uncordon
  • Mettez un --timeout sur le drain pour éviter un blocage infini dans vos scripts
  • Vérifiez les PodDisruptionBudgets avant de drainer, un PDB trop strict peut bloquer tout le drain
  • Drainez les nœuds un par un dans un rolling upgrade pour maintenir la capacité du cluster
  • Après uncordon, vérifiez que le nœud est Ready avec kubectl wait --for=condition=Ready

Un taint est une décision d'architecture, pas un réglage d'exploitation : il change durablement où les pods peuvent tourner et se répercute sur chaque manifest de l'équipe.

  • Utilisez les taints pour les cas permanents : nœuds GPU, nœuds dédiés par environnement, nœuds spot

  • Documentez vos conventions de taints dans un document partagé, les taints sans documentation deviennent des mystères

  • Préférez PreferNoSchedule quand vous voulez orienter le placement sans être strict

  • Testez vos tolerations en dry-run avant de les appliquer :

    Fenêtre de terminal
    kubectl run test-toleration --image=busybox --dry-run=client -o yaml -- sleep 3600
    # Ajoutez la toleration dans le YAML généré, puis apply

Les pannes se répartissent en deux familles : un drain qui refuse d'aller au bout, et un pod qui ne trouve plus de nœud. Dans les deux cas, le message d'erreur exact est la clé du diagnostic, c'est pourquoi la première colonne reprend les chaînes renvoyées par kubectl. Cherchez la vôtre plutôt que de partir des symptômes.

SymptômeCause probableSolution
drain bloque indéfinimentUn PDB empêche l'évictionVérifiez kubectl get pdb -A. Augmentez le nombre de réplicas ou le maxUnavailable du PDB
drain échoue : cannot delete DaemonSet-managed podDaemonSets non ignorésAjoutez --ignore-daemonsets
drain échoue : cannot delete pod with local dataPods avec emptyDirAjoutez --delete-emptydir-data (les données seront perdues)
drain échoue : pod not managed by a controllerPods créés manuellement (sans Deployment)Ajoutez --force (le pod ne sera pas recréé)
Pod non planifié : node(s) had taints that the pod didn't tolerateLe nœud a un taint, le pod n'a pas la tolerationAjoutez la toleration dans le manifest du pod
Pod évacué d'un nœud sans explicationUn taint NoExecute a été ajouté au nœudVérifiez avec kubectl describe node NOEUD puis cherchez la section Taints
Taints not-ready ou unreachable apparaissentLe nœud a un problème (réseau, kubelet…)Vérifiez avec kubectl describe node NOEUD, conditions et events
Pods ne reviennent pas après uncordonLe scheduler ne rééquilibre pas les pods existantsRedémarrez les Deployments avec kubectl rollout restart deploy/NOM
taint supprimé mais pods toujours rejetésPlusieurs taints sur le nœudListez les taints avec kubectl get node NOEUD -o jsonpath='{.spec.taints}', supprimez tous les taints concernés
  • kubectl cordon empêche les nouveaux pods d'arriver sur un nœud, mais ne touche pas les pods existants.
  • kubectl drain évacue les pods du nœud (et fait un cordon automatique). Options quasi obligatoires : --ignore-daemonsets et --delete-emptydir-data.
  • kubectl uncordon réactive le nœud, mais les pods ne reviennent pas automatiquement (le scheduler ne rééquilibre pas).
  • Les PodDisruptionBudgets protègent vos services pendant un drain, ne les contournez qu'en urgence avec --disable-eviction.
  • Les taints sont des restrictions permanentes sur les nœuds, 3 effets : NoSchedule (bloque les nouveaux), PreferNoSchedule (évite si possible), NoExecute (évacue les existants).
  • Un pod doit déclarer une toleration correspondante pour être accepté sur un nœud tainté.
  • Kubernetes pose automatiquement des taints quand un nœud est en difficulté (not-ready, disk-pressure, memory-pressure…).

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn