Introduction
Kubernetes planifie les pods sur les nœuds en fonction des demandes et limites de ressources, mais les nœuds ont une mémoire et un disque finis. Lorsque les ressources disponibles d'un nœud tombent en dessous des seuils configurés, le kubelet peut évincer des pods pour récupérer de l'espace et maintenir la stabilité du nœud. Ce processus est appelé éviction sous pression des nœuds, et s'il n'est pas planifié, il peut entraîner des terminaisons inattendues de pods, une dégradation des services et des défaillances en cascade dans votre cluster.
Une planification de capacité efficace vous aide à dimensionner correctement les nœuds, à définir des seuils d'éviction sûrs et à éviter ces perturbations. Dans ce guide, vous apprendrez une approche étape par étape pour estimer les besoins en ressources, ajuster les politiques d'éviction du kubelet, surveiller la santé des nœuds, tester votre configuration et planifier la croissance. Nous utiliserons un exemple pratique de cluster d'application web tout au long pour illustrer les concepts clés.
L'éviction sous pression des nœuds est l'un des multiples mécanismes de Kubernetes pour gérer la pénurie de ressources. Contrairement au planificateur, qui place les pods en fonction des demandes, le kubelet surveille activement l'utilisation réelle sur chaque nœud. Il suit des signaux tels que la mémoire disponible, l'espace disque disponible et les inodes disponibles. Lorsqu'un signal franchit un seuil, le kubelet évince les pods dans un ordre spécifique : d'abord les pods qui dépassent leurs demandes, puis les pods de priorité inférieure, et ainsi de suite. En planifiant la capacité et en ajustant les seuils, vous pouvez minimiser les évictions tout en protégeant la santé du nœud.
Ce guide couvre le cycle de vie complet : inventorier votre environnement, calculer des seuils sûrs, configurer le kubelet, vérifier les paramètres, simuler la pression pour tester le comportement, comprendre les modes de défaillance et maintenir une liste de contrôle opérationnelle continue. Nous aborderons également les pièges courants et comment les éviter, car une politique d'éviction mal configurée peut être pire que l'absence de politique.
Inventaire de la version et de l'environnement
Avant de modifier les paramètres d'éviction, vous devez avoir une vision claire de votre cluster. Le comportement d'éviction a évolué selon les versions de Kubernetes, il est donc essentiel de connaître votre version exacte. Par exemple, Kubernetes 1.23 a introduit des changements dans les seuils d'éviction douce et les périodes de grâce. Les versions plus récentes peuvent avoir des valeurs par défaut ou des dépréciations différentes. Consultez toujours les notes de version de votre version.
Voici l'environnement que nous utiliserons dans ce guide :
| Composant | Version/Détails |
|---|---|
| Kubernetes | v1.25.3 |
| Système d'exploitation des nœuds | Ubuntu 22.04 LTS |
| Runtime de conteneurs | containerd 1.6.9 |
| Kubelet | v1.25.3 |
| Taille du cluster | 3 nœuds control-plane, 5 nœuds workers |
| Spécifications des nœuds workers | 8 vCPU, 32 Go de RAM, 100 Go de disque |
En plus des versions de la plateforme, documentez les charges de travail exécutées sur les nœuds. Notre cluster d'exemple héberge une application web avec les composants suivants :
- Pods frontend : 3 réplicas, chacun demandant 512 Mo de mémoire et 0,5 CPU, limite de 1 Go de mémoire et 1 CPU.
- Pods backend : 5 réplicas, chacun demandant 1 Go de mémoire et 1 CPU, limite de 2 Go de mémoire et 2 CPU.
- Pods de base de données : 2 réplicas (primaire et réplica), chacun demandant 4 Go de mémoire et 1 CPU, limite de 8 Go de mémoire et 2 CPU.
- Pods de cache : 2 réplicas, chacun demandant 2 Go de mémoire et 0,5 CPU, limite de 4 Go de mémoire et 1 CPU.
Tous les pods ont des demandes et limites de ressources définies. Vérifiez cela avec kubectl describe pod <nom-du-pod> en cas de doute. Les demandes non définies sont une cause fréquente d'éviction : le planificateur ne peut pas en tenir compte, et le kubelet peut évincer des pods qui utilisent plus que prévu.
Assurez-vous que la surveillance est en place. Pour ce guide, nous supposons que le metrics-server de Kubernetes est installé, ce qui fournit les commandes kubectl top. Nous recommandons également kube-state-metrics pour les métriques d'état des pods et des nœuds, et un backend de séries temporelles comme Prometheus pour l'analyse historique. Ces outils seront utilisés plus tard pour la vérification et la planification de capacité.
Enfin, localisez le fichier de configuration du kubelet. Sur les nœuds basés sur systemd, il se trouve généralement à /var/lib/kubelet/config.yaml. Sur certaines distributions, il peut s'agir de /etc/kubernetes/kubelet.conf ou être passé via des arguments en ligne de commande. Utilisez ps aux | grep kubelet pour voir les arguments en cours d'exécution. Documentez le chemin pour chaque nœud, car nous le modifierons bientôt.
Prérequis
- Accès SSH à tous les nœuds workers avec les privilèges sudo.
- Permission de modifier la configuration du kubelet (généralement root).
- metrics-server installé et fonctionnel.
- kubectl configuré avec cluster-admin ou des permissions suffisantes.
- Un nœud ou cluster de test ou de staging où vous pouvez simuler la pression en toute sécurité avant de déployer en production.
Chemin de configuration sûr
Commencez par les paramètres d'éviction par défaut et ajustez progressivement. Le kubelet est livré avec des seuils par défaut qui sont prudents pour de nombreux environnements, mais ils peuvent ne pas correspondre à la taille de votre nœud ou au modèle de charge de travail. L'objectif est de définir des seuils qui reflètent la capacité réelle du nœud tout en laissant suffisamment de marge pour les fluctuations et les pics normaux.
Comprendre les signaux d'éviction
L'éviction du kubelet utilise deux types de seuils :
- Seuils d'éviction durs : Lorsqu'une ressource tombe en dessous du seuil, le kubelet évince immédiatement les pods. Il n'y a pas de période de grâce. C'est une mesure réactive pour protéger le nœud d'un épuisement imminent des ressources.
- Seuils d'éviction doux : Lorsqu'une ressource tombe en dessous du seuil, le kubelet démarre un minuteur. Si la ressource remonte au-dessus du seuil avant l'expiration du minuteur (la période de grâce), aucune éviction n'a lieu. Si la ressource reste basse pendant toute la période de grâce, le kubelet évince alors les pods. Les seuils doux sont préférables en production car ils donnent aux charges de travail la possibilité de libérer des ressources naturellement.
Le kubelet surveille plusieurs signaux, notamment :
memory.available: Mémoire disponible en octets (ou en pourcentage de la mémoire totale, selon la configuration).nodefs.available: Espace disque disponible sur le système de fichiers principal du nœud (généralement la partition contenant/var/lib/kubelet).nodefs.inodesFree: Inodes disponibles sur le système de fichiers principal.imagefs.available: Espace disque disponible pour les images de conteneurs (si un système de fichiers séparé est utilisé).imagefs.inodesFree: Inodes disponibles pour le système de fichiers des images.
Pour notre exemple, nous nous concentrons sur memory.available et nodefs.available car ce sont les déclencheurs d'éviction les plus courants en production.
Calculer des seuils sûrs
La première étape consiste à déterminer la mémoire totale du nœud et la mémoire qui doit être réservée aux démons système : le kubelet, le runtime de conteneurs, le noyau du système d'exploitation et les autres services système. Une pratique courante consiste à réserver 20 % de la mémoire pour les surcharges système, mais cela varie selon la taille du nœud. La formule est :
- Réservé au système = mémoire totale × 0,20
- Mémoire disponible pour les pods = mémoire totale - réservé au système
Pour notre nœud de 32 Go :
- Réservé au système = 32 Go × 0,20 = 6,4 Go
- Disponible pour les pods = 32 Go - 6,4 Go = 25,6 Go
Mais attention : le kubelet ne connaît pas automatiquement cette réservation à moins que vous ne la configuriez. Le kubelet a des indicateurs comme --system-reserved et --kube-reserved qui lui indiquent combien soustraire de la capacité totale du nœud lors de la prise de décisions. Dans Kubernetes, ils font partie de la configuration du kubelet. Nous les définirons également.
Ensuite, choisissez les seuils d'éviction. Une approche courante consiste à définir un seuil dur pour memory.available à 10 % de la mémoire totale. Pour un nœud de 32 Go, cela représente 3,2 Go. Cela signifie que lorsque la mémoire disponible tombe à 3,2 Go, le kubelet commencera immédiatement à évincer des pods. Certains opérateurs préfèrent un seuil plus élevé (par exemple, 15 à 20 %) pour fournir plus de tampon, mais cela réduit la capacité effective pour les pods. Choisissez un seuil qui équilibre la sécurité et l'utilisation.
Pour le disque, définissez le seuil nodefs.available à 10 % de la taille du disque. Sur un disque de 100 Go, cela représente 10 Go. Si vous avez un système de fichiers séparé pour les images, définissez imagefs.available de manière similaire. Les inodes sont également importants ; un seuil comme nodefs.inodesFree: "5%" est souvent utilisé, mais la valeur exacte dépend du taux de création de fichiers de votre charge de travail.
En plus des seuils durs, définissez des seuils doux avec des périodes de grâce pour permettre une récupération gracieuse. Par exemple :
- Doux
memory.available: 4 Go avec une période de grâce de 2 minutes. - Doux
nodefs.available: 15 % avec une période de grâce de 5 minutes.
Cela donne au kubelet le temps de réagir aux pics courts sans évincer immédiatement les pods.
Exemple de configuration du kubelet
La configuration du kubelet est en YAML. Voici un extrait pour /var/lib/kubelet/config.yaml sur notre nœud d'exemple :
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
memory.available: "3.2Gi"
nodefs.available: "10%"
nodefs.inodesFree: "5%"
evictionSoft:
memory.available: "4Gi"
nodefs.available: "15%"
evictionSoftGracePeriod:
memory.available: "2m"
nodefs.available: "5m"
evictionMaxPodGracePeriod: 60
systemReserved:
memory: "4Gi"
cpu: "500m"
kubeReserved:
memory: "2Gi"
cpu: "500m"
Dans cet exemple :
- L'éviction dure se déclenche à 3,2 Go de mémoire disponible ou 10 % d'espace disque libre ou 5 % d'inodes libres.
- L'éviction douce se déclenche à 4 Go de mémoire disponible avec une grâce de 2 minutes, ou 15 % d'espace disque libre avec une grâce de 5 minutes.
evictionMaxPodGracePeriodest le temps maximum que le kubelet attendra pour qu'un pod se termine gracieusement pendant l'éviction (en secondes). 60 secondes est raisonnable.- Les valeurs réservées au système et au kubelet indiquent au kubelet de soustraire ces montants de la capacité totale du nœud lors du calcul des ressources disponibles pour les pods. Cela évite que les démons système soient affamés.
Ajustez les nombres en fonction de la taille de votre nœud et de votre charge de travail. La clé est de garantir que la somme réservée au système + réservée au kubelet + seuil d'éviction est inférieure aux ressources totales du nœud, laissant suffisamment pour les pods.
Appliquer la configuration du kubelet
Pour appliquer les modifications sur un seul nœud :
- Connectez-vous en SSH au nœud worker.
- Sauvegardez la configuration existante :
sudo cp /var/lib/kubelet/config.yaml /var/lib/kubelet/config.yaml.bak - Modifiez
/var/lib/kubelet/config.yamlavec vos modifications. - Redémarrez le kubelet :
sudo systemctl restart kubelet - Vérifiez le statut du service :
sudo systemctl status kubelet
Sortie attendue : Le statut doit afficher active (running). Si le service ne démarre pas, vérifiez les journaux avec journalctl -u kubelet -n 50 pour détecter les erreurs. Les erreurs courantes incluent des erreurs de syntaxe YAML ou des valeurs de seuil non valides (par exemple, utiliser Gi avec des champs entiers uniquement).
Pour les modifications à l'échelle du cluster, évitez de modifier manuellement chaque nœud ; utilisez plutôt un outil de gestion de configuration comme Ansible, Puppet ou un opérateur natif Kubernetes comme la configuration kubeadm. Pour les clusters kubeadm, vous pouvez utiliser kubeadm upgrade node phase kubelet-config pour faire tourner la configuration, mais la modification manuelle est acceptable pour un test sur un seul nœud.
Vérification et diagnostics
Après avoir appliqué la configuration, vérifiez que les seuils sont actifs.
Vérifier la configuration effective
Utilisez l'endpoint configz du kubelet pour voir la configuration effective :
kubectl get --raw "/api/v1/nodes/<nom-du-nœud>/proxy/configz" | jq .
Sortie attendue inclut les seuils d'éviction que vous avez définis. Par exemple :
{
"kubeletconfig": {
"evictionHard": {
"memory.available": "3.2Gi",
"nodefs.available": "10%",
"nodefs.inodesFree": "5%"
},
"evictionSoft": {
"memory.available": "4Gi",
"nodefs.available": "15%"
},
"evictionSoftGracePeriod": {
"memory.available": "2m",
"nodefs.available": "5m"
},
"evictionMaxPodGracePeriod": 60
}
}
Si la sortie ne correspond pas, redémarrez à nouveau le kubelet et vérifiez les erreurs.
Surveiller les conditions des nœuds
Le kubelet signale les conditions de nœud MemoryPressure et DiskPressure. Utilisez kubectl describe node <nom-du-nœud> pour les voir. Dans des conditions normales, elles doivent être False. Si elles sont True, le nœud est actuellement sous pression et des évictions peuvent être imminentes.
Simuler une pression mémoire
Pour tester le comportement d'éviction, déployez un pod qui consomme plus de mémoire que sa demande. Créez un fichier memory-demo.yaml :
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "1Gi"
requests:
memory: "1Gi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "2G", "--vm-hang", "1"]
Appliquez-le :
kubectl apply -f memory-demo.yaml
Ce pod demande 1 Go mais tente d'utiliser 2 Go. Si la mémoire disponible du nœud est inférieure à 2 Go à ce moment-là, le pod sera évincé lorsque memory.available tombe en dessous du seuil dur (3,2 Go dans notre exemple). Surveillez l'éviction :
kubectl get pods --watch
Le statut du pod passera à Evicted ou Terminating. Vérifiez les événements :
kubectl describe pod memory-demo
La sortie attendue inclut quelque chose comme : The node was low on resource: memory. Threshold quantity: 3.2Gi, available: 2.8Gi.
Si le pod n'est pas évincé, augmentez la pression mémoire ou abaissez temporairement le seuil sur un nœud de test. Attention : ce test peut perturber d'autres pods sur le nœud. Exécutez-le toujours sur un nœud de test dédié ou un cluster de staging.
Vérifier les périodes de grâce
Pour l'éviction douce, le pod peut ne pas être évincé immédiatement. Après la période de grâce (2 minutes dans notre exemple), si la mémoire reste basse, le kubelet évincera le pod. Vous pouvez observer le timing en notant les horodatages des événements. Si le pod remonte au-dessus du seuil pendant la grâce, il ne sera pas évincé.
Pour tester cela, vous pouvez créer un pod qui augmente temporairement la mémoire puis la libère avant l'expiration de la période de grâce. Le kubelet ne devrait pas l'évincer.
Modes de défaillance et récupération
Une mauvaise configuration de l'éviction peut entraîner des évictions excessives de pods ou une instabilité des nœuds. Voici les modes de défaillance courants :
- Seuils trop élevés (par exemple, memory.available à 50 %) : Les évictions se produisent fréquemment sous charge normale, provoquant une reprogrammation constante des pods et une dégradation des performances de l'application. Cela se produit lorsque les opérateurs comprennent mal la signification du seuil ou essaient d'être trop prudents. Pour éviter cela, calculez la capacité réelle du nœud et définissez les seuils en fonction de cela, pas de la peur.
- Seuils trop bas (par exemple, memory.available à 1 %) : Le nœud peut manquer complètement de mémoire avant que l'éviction ne se déclenche, ce qui conduit le tueur OOM du noyau à terminer les processus de manière imprévisible, y compris éventuellement des services système critiques. C'est plus dangereux que des seuils élevés. Pour récupérer, augmentez immédiatement les seuils et enquêtez sur la raison pour laquelle la mémoire disponible est tombée si bas.
- Réservation système insuffisante : Si vous ne réservez pas de mémoire pour les démons système, le kubelet ou le runtime de conteneurs peut être tué lorsque la mémoire est épuisée, rendant le nœud
NotReady. Définissez toujourssystemReservedetkubeReservedde manière appropriée.
- Périodes de grâce mal configurées : Une période de grâce trop courte évince les pods avant qu'ils ne puissent s'arrêter gracieusement, provoquant des réinitialisations de connexion. Une période de grâce trop longue permet au nœud d'épuiser les ressources avant l'éviction, conduisant à des tueries OOM. Choisissez des périodes de grâce correspondant au temps d'arrêt de votre application (généralement 30 à 120 secondes).
- Ignorer la pression des inodes : De nombreux opérateurs se concentrent uniquement sur la mémoire et l'espace disque, mais l'épuisement des inodes peut provoquer
DiskPressureet des évictions même si l'espace disque est suffisant. Surveillez l'utilisation des inodes et définissez des seuilsnodefs.inodesFree.
- Ne pas tenir compte des pods DaemonSet : Les pods DaemonSet comme les agents de journalisation ou de surveillance consomment des ressources sur chaque nœud. S'ils ne sont pas pris en compte dans la planification de capacité, ils peuvent déclencher des évictions. Assurez-vous que les pods DaemonSet ont des demandes définies et incluez-les dans vos calculs.
Retour en arrière
Si vous devez revenir à la configuration précédente du kubelet, restaurez le fichier de sauvegarde et redémarrez le kubelet :
sudo cp /var/lib/kubelet/config.yaml.bak /var/lib/kubelet/config.yaml
sudo systemctl restart kubelet
Vérifiez que le nœud revient à Ready et que les conditions sont False :
kubectl describe node <nom-du-nœud> | grep -A5 Conditions
Comme la configuration du kubelet est locale à chaque nœud, conservez toujours des sauvegardes et utilisez le contrôle de version pour les fichiers de configuration si possible.
Vérifications de récupération
Après un événement d'éviction, assurez-vous que les pods sont reprogrammés et sains :
kubectl get pods -o wide
Vérifiez les conditions des nœuds et l'utilisation des ressources :
kubectl describe node <nom-du-nœud> | grep -A5 Conditions
kubectl top nodes
kubectl top pods
Si les évictions persistent, examinez les demandes de ressources des pods par rapport à l'utilisation réelle. Utilisez kubectl describe pod pour voir les demandes et limites, et comparez avec kubectl top pod. Ajustez les demandes si elles sont trop basses, ou ajoutez plus de nœuds pour augmenter la capacité du cluster.
Pièges courants et comment les éviter
Au-delà des modes de défaillance, plusieurs pièges subtils peuvent compromettre votre planification d'éviction :
- Mélanger les unités : Le kubelet accepte à la fois des valeurs absolues (par exemple, "2Gi") et des pourcentages (par exemple, "10%") pour les seuils, mais tous les signaux ne prennent pas en charge les deux.
memory.availablepeut être l'un ou l'autre ; les signaux de disque utilisent souvent des pourcentages. Utiliser la mauvaise unité peut faire rejeter la configuration par le kubelet ou se comporter de manière inattendue. Vérifiez toujours la documentation Kubernetes de votre version.
- Ne pas définir de budgets de perturbation des pods : Les évictions sont considérées comme des perturbations volontaires. Si vous avez un PodDisruptionBudget (PDB) pour votre déploiement, l'API d'éviction le respectera, mais l'éviction sous pression du kubelet ne le fait pas. Il évincera les pods même si cela viole un PDB. Pour minimiser l'impact, assurez-vous que votre application peut tolérer la perte d'un pod, ou surprovisionnez les ressources.
- Oublier de mettre à jour la capacité du nœud lors de la mise à l'échelle : Si vous ajoutez des nœuds plus grands à votre cluster, les anciens seuils peuvent être trop prudents ou trop agressifs. Réévaluez les seuils pour chaque taille de nœud.
- Supposer que l'ordre d'éviction est prévisible : Le kubelet classe les pods par priorité, puis par utilisation au-dessus des demandes. Mais cet ordre peut changer avec les classes de QoS et les classes de priorité. Ne vous fiez pas à un pod spécifique évincé en premier ; concevez plutôt tous les pods pour être résilients.
- Négliger de tester en staging : Testez toujours le comportement d'éviction dans un environnement de staging avant la production. Simulez la pression mémoire et disque pour voir comment vos charges de travail réagissent. C'est le seul moyen de valider vos seuils.
Liste de contrôle opérationnelle
Maintenez une liste de contrôle pour la planification de capacité et la gestion des évictions en continu. Attribuez un propriétaire pour chaque élément et définissez une fréquence.
| Tâche | Fréquence | Propriétaire | Commande/Outil |
|---|---|---|---|
| Vérifier l'utilisation des ressources des nœuds | Quotidienne | SRE de garde | kubectl top nodes |
| Vérifier les conditions des nœuds (MemoryPressure, DiskPressure) | Quotidienne | SRE de garde | kubectl describe nodes |
| Examiner les événements d'éviction | Hebdomadaire | Ingénieur plateforme | kubectl get events --field-selector reason=Evicted |
| Vérifier la cohérence de la configuration du kubelet entre les nœuds | Mensuelle | Ingénieur plateforme | Comparer les configurations des nœuds avec l'état souhaité (par exemple, à l'aide d'un script ou d'une ConfigMap) |
| Planifier la capacité en fonction des tendances de croissance | Mensuelle | Planificateur de capacité | Analyser les métriques historiques de Prometheus |
| Tester le comportement d'éviction en staging | Avant tout changement | Responsable AQ | Simuler la pression mémoire/disque avec des outils de stress |
| Auditer les paramètres réservés au système | Trimestrielle | Architecte infrastructure | Examiner les indicateurs réservés du kubelet et l'utilisation réelle du système d'exploitation |
En outre, configurez des alertes de surveillance pour notifier lorsque l'utilisation de la mémoire ou du disque du nœud dépasse 80 % du seuil d'éviction. Par exemple, si le seuil d'éviction de mémoire est de 3,2 Go, alertez lorsque la mémoire disponible tombe en dessous de 4 Go (ou mieux, en dessous de 20 % du total). Utilisez des règles d'alerte Prometheus ou un système similaire. Les tableaux de bord doivent afficher les tendances historiques de l'utilisation de la mémoire et du disque par nœud, ainsi que le nombre d'évictions.
Conclusion
Une planification de capacité efficace pour l'éviction sous pression des nœuds Kubernetes nécessite de comprendre les mécanismes d'éviction, de définir des seuils appropriés et de surveiller en continu la santé des nœuds. Dans ce guide, vous avez appris à inventorier votre environnement, à calculer des seuils d'éviction sûrs, à configurer le kubelet, à vérifier les paramètres, à simuler la pression pour tester le comportement, à gérer les modes de défaillance et à maintenir une liste de contrôle opérationnelle.
Commencez par un projet pilote étroit sur un seul nœud : choisissez un nœud worker, ajustez les seuils, testez le comportement d'éviction avec des pods de stress et observez pendant quelques jours avant de déployer sur l'ensemble du cluster. Cela minimise les risques et vous permet d'ajuster les seuils en fonction des modèles de charge réels.
N'oubliez pas que la planification de capacité n'est pas une tâche ponctuelle. À mesure que votre cluster grandit et que les charges de travail changent, révisez régulièrement les seuils et la capacité. Utilisez la liste de contrôle et les alertes de surveillance pour anticiper les situations de pression. En suivant ces pratiques, vous pouvez minimiser les évictions inattendues et maintenir vos applications en bon état de fonctionnement, même sous forte charge.