Erreurs de Planification Kubernetes Expliquées : Solutions et Meilleures Pratiques

Maîtrisez la planification Kubernetes ! Ce guide démystifie pourquoi les Pods restent bloqués dans l'état 'En attente'. Apprenez à diagnostiquer les erreurs avec `kubectl describe`, résoudre les problèmes liés à un CPU/Mémoire insuffisant, surmonter les restrictions d'affinité de nœud et utiliser correctement les Taints et Tolerations pour un placement robuste des charges de travail.

Erreurs de Planification Kubernetes Expliquées : Solutions et Meilleures Pratiques

Les erreurs de planification Kubernetes se manifestent généralement par un pod bloqué dans l'état Pending. Ce statut peut sembler vague, mais il a une signification précise : Kubernetes a accepté l'objet pod, mais le planificateur n'a pas trouvé de nœud qui satisfait aux exigences du pod. Le conteneur n'a pas planté. L'application n'a pas démarré. Dans de nombreux cas, l'image n'a même pas encore été téléchargée.

La façon la plus rapide de résoudre ces problèmes est de comparer ce que le pod demande avec ce que le cluster peut offrir. Les demandes de CPU et de mémoire, les étiquettes de nœud, les règles d'affinité, les taints, les tolérances, les volumes persistants, les règles de répartition topologique et les quotas d'espace de noms peuvent tous bloquer le placement. Le planificateur est strict concernant les contraintes requises. Si une règle requise exclut tous les nœuds, le pod attend.

Diagnostiquer les Pods en Attente : La Première Étape

Avant de tenter des correctifs, vous devez diagnostiquer avec précision pourquoi le planificateur échoue. L'outil principal pour cette enquête est kubectl describe pod.

Lorsqu'un Pod est bloqué dans l'état Pending, la section Events de la sortie de la commande describe contient des informations critiques détaillant le processus de décision de planification et tout rejet.

Utilisation de kubectl describe pod

Ciblez toujours le Pod problématique :

kubectl describe pod <nom-du-pod> -n <espace-de-noms>

Examinez la sortie, en regardant spécifiquement la section Events en bas. Les messages ici indiqueront généralement la contrainte qui a empêché la planification. Les messages courants concernent Insufficient cpu, Insufficient memory, des incompatibilités de sélecteur de nœud, des taints non tolérées ou une liaison de volume.

Catégories Courantes d'Erreurs de Planification et Solutions

Les échecs de planification se répartissent généralement en trois catégories principales : Contraintes de Ressources, Contraintes de Politique (Affinité/Anti-Affinité) et Configuration du Nœud (Taints/Tolerations).

1. Contraintes de Ressources (Ressources Insuffisantes)

C'est la cause la plus fréquente. Le planificateur a besoin d'un nœud qui peut satisfaire les demandes définies dans la spécification du Pod. Si aucun nœud n'a suffisamment de CPU ou de Mémoire allouable disponible, le Pod restera en attente.

Identifier le Problème

La section Events affichera des messages comme :

  • 0/3 nodes are available: 3 Insufficient cpu.
  • 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match node selector.

Ces messages peuvent être combinés. Ne vous arrêtez pas à la première phrase. Si trois nœuds échouent pour trois raisons différentes, corriger une seule raison peut encore laisser le pod en attente.

Solutions pour les Pénuries de Ressources

  1. Réduire les Demandes du Pod : Si les demandes du Pod sont excessivement élevées, essayez de réduire les demandes de CPU ou de Mémoire requests dans le YAML du Pod ou du Déploiement.
  2. Augmenter la Capacité du Cluster : Ajoutez plus de nœuds au cluster Kubernetes.
  3. Nettoyer les Charges de Travail Existantes : Réduisez les charges de travail non essentielles, supprimez les tâches abandonnées ou ajustez les demandes surdimensionnées sur les déploiements existants. Utilisez kubectl drain pour la maintenance des nœuds, pas comme une commande de nettoyage occasionnelle.
  4. Utiliser des Limites de Plage : Si votre espace de noms manque de limites de ressources définies, implémentez des objets LimitRange pour empêcher des Pods uniques d'accaparer les ressources.

2. Sélecteurs de Nœud et Règles d'Affinité/Anti-Affinité

Kubernetes permet un contrôle précis de l'endroit où les Pods peuvent ou doivent être placés en utilisant nodeSelector, nodeAffinity, et podAffinity/podAntiAffinity.

Incompatibilité du Sélecteur de Nœud

Si vous définissez un nodeSelector qui ne correspond à aucune étiquette présente sur un nœud disponible, le Pod ne peut pas être planifié.

Extrait YAML d'Exemple (Cause d'Échec) :

spec:
  nodeSelector:
    disktype: ssd-fast
  containers: [...] # Le Pod reste en attente si aucun nœud n'a disktype=ssd-fast

Solution : Assurez-vous que l'étiquette spécifiée dans nodeSelector existe sur au moins un nœud (kubectl get nodes --show-labels) et que la casse correspond exactement.

Utilisez des vérifications d'étiquettes ciblées lorsque le cluster a de nombreuses étiquettes :

kubectl get nodes -L disktype,topology.kubernetes.io/zone
kubectl describe node <nom-du-nœud>

Une erreur courante consiste à utiliser une étiquette qui existait dans un groupe de nœuds précédent mais pas dans le groupe de nœuds de remplacement. Après une mise à niveau du cluster ou une migration de groupe de mise à l'échelle automatique, les anciennes règles de placement peuvent devenir silencieusement impossibles.

Contraintes d'Affinité de Nœud

nodeAffinity offre des règles plus flexibles (par exemple, requiredDuringSchedulingIgnoredDuringExecution ou preferredDuringSchedulingIgnoredDuringExecution). Si une règle required ne peut pas être satisfaite, le Pod reste en attente.

Conseil de Diagnostic : Lorsque vous utilisez des règles d'affinité complexes, la section Events indique souvent : node(s) didn't match node selector.

Affinité et Anti-Affinité de Pod

Ces règles contrôlent le placement par rapport à d'autres Pods. Si, par exemple, une règle d'Anti-Affinité exige qu'un Pod ne s'exécute pas sur un nœud hébergeant un service spécifique, mais que tous les nœuds hébergent déjà ce service, la planification échouera.

Solution : Examinez attentivement la clé de topologie et le sélecteur dans vos règles d'affinité. Si une règle d'anti-affinité est trop restrictive, assouplissez l'exigence ou vérifiez que les Pods cibles sélectionnés par la règle s'exécutent effectivement sur les nœuds que vous souhaitez éviter.

Préférez preferredDuringSchedulingIgnoredDuringExecution lorsque la règle exprime une préférence plutôt qu'une exigence stricte. L'anti-affinité requise est utile pour répartir les répliques de services critiques, mais elle peut bloquer les déploiements dans les petits clusters. Par exemple, trois répliques avec une anti-affinité stricte une par zone ne peuvent pas se planifier proprement dans un cluster avec seulement deux zones utilisables.

3. Taints et Tolerations

Les Taints sont appliquées directement aux nœuds pour repousser les Pods, tandis que les Tolerations sont ajoutées aux spécifications des Pods pour leur permettre d'accéder aux nœuds taintés.

  • Taint : Repousse les Pods à moins qu'ils n'aient une tolérance correspondante.
  • Toleration : Permet à un Pod d'être planifié sur un nœud avec une taint correspondante.

Identifier le Rejet par Taint

La section Events indiquera explicitement la raison du rejet :

0/3 nodes are available: 2 node(s) had taint {dedicated: special-workload, effect: NoSchedule}, that the pod didn't tolerate.

Solutions pour les Taints et Tolerations

Vous avez deux voies principales :

  1. Modifier le Pod (Recommandé pour les Pods d'Application) : Ajoutez les tolerations requises à la spécification du Pod qui correspondent à la taint du nœud.

    Exemple de Toleration :

    spec:
      tolerations:
      - key: "dedicated"
        operator: "Equal"
        value: "special-workload"
        effect: "NoSchedule"
      containers: [...] 
    
  2. Modifier le Nœud (Recommandé pour les Administrateurs de Cluster) : Supprimez la taint du nœud si la restriction n'est plus nécessaire.

    # Pour supprimer une taint
    kubectl taint nodes <nom-du-nœud> dedicated:special-workload:NoSchedule-
    

Alerte de Meilleure Pratique : Évitez de tolérer la taint globale node-role.kubernetes.io/master:NoSchedule sur les Pods d'application, sauf si vous planifiez intentionnellement des composants de plan de contrôle critiques sur les nœuds maîtres.

Sur les clusters plus récents, les nœuds du plan de contrôle utilisent couramment la taint node-role.kubernetes.io/control-plane au lieu de, ou en plus de, l'ancienne terminologie master. Vérifiez les taints réelles avant de copier une tolérance à partir d'un ancien manifeste :

kubectl describe node <nom-du-nœud> | grep -i taints

Contraintes de Planification Avancées

Des contraintes moins courantes mais importantes peuvent également bloquer la planification :

Contraintes de Volume de Stockage

Si un Pod demande un PersistentVolumeClaim (PVC) qui ne peut pas être lié à un nœud disponible (par exemple, en raison d'exigences spécifiques du fournisseur de stockage ou de l'indisponibilité du volume), le Pod peut rester en attente.

Diagnostic : Vérifiez d'abord l'état du PVC (kubectl describe pvc <nom-du-pvc>). Si le PVC est bloqué dans l'état Pending, la planification du Pod est interrompue jusqu'à ce que le volume soit disponible.

Le stockage peut également être retardé intentionnellement par volumeBindingMode: WaitForFirstConsumer sur la StorageClass. Dans ce mode, la liaison attend que le planificateur choisisse un nœud approprié, car le volume peut devoir être créé dans la même zone que le pod. C'est normal, mais si aucun nœud ne satisfait à la fois aux contraintes du pod et du stockage, le pod reste en attente.

DaemonSets et Répartitions Topologiques

Les DaemonSets ne seront planifiés que sur les nœuds correspondant à leurs critères de sélection (le cas échéant). Si un cluster est partitionné ou si un nouveau nœud ne correspond pas au sélecteur du DaemonSet, il ne s'exécutera pas.

Contraintes de Répartition Topologique (si définies) assurent une distribution uniforme. Si la distribution actuelle empêche le placement sur un nœud tout en respectant les contraintes de répartition, la planification échouera.

Les échecs de répartition topologique apparaissent souvent après une panne partielle. Supposons qu'une zone soit indisponible et qu'un déploiement ait des contraintes de répartition strictes entre les zones. Kubernetes peut refuser de placer de nouvelles répliques dans les zones restantes car cela violerait la règle de déséquilibre. Ce comportement protège les objectifs de distribution, mais pendant une panne, vous devrez peut-être assouplir temporairement la contrainte pour restaurer la capacité.

Quotas d'Espace de Noms et LimitRanges

Un pod peut également être bloqué par la politique de l'espace de noms. ResourceQuota contrôle l'utilisation globale dans un espace de noms. LimitRange peut définir des valeurs par défaut ou des valeurs minimales et maximales de ressources.

Vérifiez-les lorsqu'une spécification de pod semble raisonnable mais que la création ou la planification échoue toujours :

kubectl get resourcequota -n <espace-de-noms>
kubectl describe resourcequota -n <espace-de-noms>
kubectl get limitrange -n <espace-de-noms>
kubectl describe limitrange -n <espace-de-noms>

Les problèmes de quota sont courants dans les clusters de développement partagés. Une équipe peut avoir suffisamment de capacité physique de cluster, mais son quota d'espace de noms est épuisé par d'anciens environnements de prévisualisation ou des tâches terminées qui n'ont jamais été nettoyées.

Une Séquence de Débogage Réaliste

Lorsqu'un pod est en attente, utilisez cet ordre :

  1. Exécutez kubectl describe pod et copiez le dernier événement de planification.
  2. Vérifiez le CPU et la mémoire demandés par rapport à la capacité allouable du nœud avec kubectl describe node.
  3. Vérifiez les étiquettes de nœud si le pod utilise nodeSelector, l'affinité de nœud ou les clés de topologie.
  4. Vérifiez les taints sur les nœuds candidats et les tolérances sur le pod.
  5. Vérifiez les PVC et les StorageClasses si le pod monte un stockage persistant.
  6. Vérifiez les quotas d'espace de noms et les LimitRanges.
  7. Si le Cluster Autoscaler est censé aider, inspectez ses journaux ou événements.

Cet ordre est important car un pod en attente n'est pas un problème d'exécution de l'application. Redémarrer le déploiement aide rarement, sauf si la contrainte sous-jacente a changé.

Meilleures Pratiques pour une Planification Réussie

Pour minimiser les problèmes de planification, adoptez ces meilleures pratiques opérationnelles :

  1. Définir Explicitement les Demandes de Ressources : Fixez toujours des requests (et des limits optionnelles) raisonnables pour le CPU et la mémoire. Cela permet au planificateur d'évaluer avec précision la capacité du nœud.
  2. Utiliser les Étiquettes de Nœud pour le Zonage : Implémentez un étiquetage cohérent des nœuds (par exemple, hardware=gpu, zone=us-east-1a) et utilisez nodeSelector ou nodeAffinity pour diriger les charges de travail vers le matériel approprié.
  3. Documenter les Taints et Tolerations : Si les nœuds sont taintés pour la maintenance ou la ségrégation matérielle, documentez ces taints de manière centralisée. Assurez-vous que les manifestes d'application nécessitant l'accès aux ressources taintées incluent les tolérances correspondantes.
  4. Surveiller le Cluster Autoscaler (si utilisé) : Si vous comptez sur des solutions de mise à l'échelle, assurez-vous qu'elles sont fonctionnelles. Un manque de capacité qui devrait déclencher une mise à l'échelle peut échouer silencieusement, laissant les Pods en attente.
  5. Examiner les Journaux du Planificateur (Avancé) : Pour des diagnostics approfondis, examinez les journaux du composant kube-scheduler lui-même. Dans les clusters gérés, l'accès peut varier selon le fournisseur, alors commencez par les événements de pod et la journalisation du plan de contrôle spécifique au fournisseur.

Corriger la Contrainte, Pas le Symptôme

Le bon correctif dépend du fait que la contrainte soit accidentelle ou intentionnelle. Si le pod demande 8 CPU parce que quelqu'un a copié un manifeste de production dans un petit cluster de staging, réduisez la demande pour cet environnement. Si le pod a besoin d'un GPU et qu'aucun nœud GPU n'existe, ajouter une tolérance n'aidera pas ; le cluster a besoin du matériel approprié. Si une taint protège les nœuds de base de données des charges de travail générales, ne supprimez pas la taint simplement pour planifier un pod non lié.

Pour les changements en production, rendez la raison visible dans Git. Les étiquettes de nœud, les taints, les règles d'affinité et les demandes de ressources sont des contrats de placement. Les futurs opérateurs doivent savoir si une règle existe pour des raisons de performance, de conformité, d'accès matériel, de contrôle des coûts ou simplement par accident historique.

Exemples de Correctifs Rapides Trompeurs

Plusieurs correctifs courants font disparaître l'état Pending immédiat tout en créant un problème plus grave plus tard.

Réduire les demandes de CPU peut aider si la demande initiale était gonflée, mais ce n'est pas un outil de capacité gratuit. Si l'application a vraiment besoin de ce CPU pendant les pics de trafic, le pod peut se planifier puis fonctionner mal sous charge. Vérifiez l'historique d'utilisation et la latence avant de réduire agressivement les demandes.

Ajouter une tolérance large peut permettre à un pod de se planifier, mais il peut atterrir sur des nœuds réservés à un autre usage. Une tolérance dit "ce pod est autorisé ici." Elle ne dit pas "ce pod devrait préférer ici." Si vous avez besoin à la fois de permission et d'intention, combinez les tolérances avec l'affinité de nœud ou les sélecteurs de nœud.

Supprimer une règle d'anti-affinité peut restaurer rapidement les répliques, mais cela peut placer chaque réplique sur un seul nœud ou une seule zone. C'est parfois acceptable pendant une panne, mais cela devrait être un changement temporaire conscient, pas une dérive permanente silencieuse.

Étendre le cluster est souvent la bonne réponse, mais seulement après avoir su que le pod en attente peut utiliser les nouveaux nœuds. Si le pod nécessite une étiquette que le groupe de nœuds mis à l'échelle automatiquement n'aura pas, ajouter des nœuds vous donne simplement plus de nœuds inadaptés.

Vérification Finale

Un pod en attente est un échec de négociation entre le pod et le cluster. Le pod demande des ressources, des étiquettes, du stockage, une topologie et l'autorisation d'atterrir sur certains nœuds. Le cluster répond avec la capacité, les taints, les étiquettes, les quotas et les volumes disponibles. kubectl describe pod montre où cette négociation a échoué. Une fois que vous avez lu attentivement l'événement, la plupart des correctifs deviennent simples : modifier les exigences du pod, modifier la capacité disponible du cluster ou corriger la politique qui ne correspond plus à la réalité.