Aller au contenu
Pools de nœuds, autoscaling et autoréparation

Pools de nœuds, autoscaling et autoréparation

300 Concevoir ⏱ 1 h 30 kuberneteskapsulescalewayterraform

À la fin, vous saurez

  • Répartir des charges sur des pools distincts (général, mémoire, GPU) avec étiquettes, teintes et sélecteurs
  • Concevoir un cluster réparti sur plusieurs zones avec un pool par zone et des contraintes de répartition des pods
  • Régler l'autoscaler de cluster (bornes, expander, délais) et protéger des pods ou des nœuds de la réduction
  • Prévoir ce que fait l'autoréparation et ce qui la déclenche
  • Choisir le type et la taille du volume système d'un pool, et un groupe de placement
  • Remplacer un pool sans interruption avec cordon, drain et PodDisruptionBudget, et estimer le coût des nœuds inactifs

Prérequis

Testé avec kubernetes 1.36 scaleway-cli 2.62.0 terraform 1.16 , vérifié le 5 octobre 2026

Pourquoi

Le cluster signalements-prod existe, avec un pool de trois nœuds. Trois besoins arrivent en peu de temps :

  • l'API doit supporter les pics d'une intempérie sans que l'équipe redimensionne à la main, et ne pas payer ces nœuds le reste de l'année ;
  • le rapport mensuel de chaque métropole, qui charge des millions de lignes en mémoire, a besoin de nœuds très différents de ceux de l'API, et ne doit pas se retrouver à côté d'elle ;
  • la direction veut que la perte d'une zone ne coupe pas le service.

Un pool unique de nœuds identiques répond mal à tout cela : trop petit pour le rapport, trop gros et trop cher pour l'API la nuit, dans une seule zone. La réponse tient en un mot, pools, et en quelques mécanismes qui s'emboîtent : l'autoscaler, l'autoréparation, les étiquettes et teintes, les contraintes de répartition, le drain. Chacun a des comportements par défaut qui surprennent quand on ne les connaît pas : un autoscaler qui refuse de retirer un nœud pour un détail, une autoréparation qui remplace un nœud que l'on débogue, un drain bloqué par un budget de perturbation. Cette leçon les prend un par un, avec leurs valeurs par défaut.

Les concepts

Qu'est-ce qu'un pool

Un pool est un groupe de nœuds identiques : même type d'instance, même zone, même image, mêmes étiquettes et teintes, même volume système. Un cluster en a un ou plusieurs ; Scaleway crée, remplace et met à jour les nœuds d'un pool comme une unité. Ce que vous faites au pool s'applique à ses nœuds : changez les étiquettes, elles sont réconciliées sur chacun d'eux.

Deux conséquences structurent la conception :

  • Un pool vit dans une zone. Le champ zone du pool fixe l'emplacement de tous ses nœuds. Un cluster réparti sur trois zones a donc au moins trois pools, un par zone. Il n'y a pas de pool « multi-zone » ;
  • Un type, un profil. Tout ce qui diffère (mémoire, GPU, architecture, volume système) impose un autre pool.

Les types de nœuds

Les nœuds sont des instances Scaleway, facturées au prix des instances sous-jacentes, d'après la FAQ. Le type se choisit dans le catalogue des instances (scw instance server-type list), à une restriction près : les types à mémoire insuffisante (DEV1-S, PLAY2-PICO, STARDUST) ne sont pas éligibles. Quelques repères de choix, à vérifier dans le catalogue du jour :

  • général : rapport mémoire/processeur équilibré (les gammes PRO2 et POP2 figurent dans les exemples de Scaleway), pour l'API, les outils, l'ingress ;
  • mémoire : variantes POP2-HM-… (high memory), pour les traitements qui chargent beaucoup de données ;
  • GPU : l'opérateur GPU de NVIDIA est installé par défaut sur chaque pool GPU, et ses pilotes arrivent après la création du nœud : un pod ordonnancé aussitôt les manque. D'où l'intérêt d'un sélecteur ou d'une teinte de démarrage ;
  • architecture : une image qui n'existe qu'en x86 échoue sur un nœud Arm (exec format error) : publiez des images multi-architectures ou ciblez kubernetes.io/arch.

Tous les types ne sont pas offerts dans toutes les zones, et les stocks peuvent être limités (la documentation cite les nœuds GPU, absents de certaines zones) : vérifiez le type dans chaque zone visée.

Pools multiples et zones

Le cours Le cloud : les fondamentaux, leçon 3 a posé la conception pour la panne. Sur Kapsule, elle se réalise ainsi : des pools dans plusieurs zones de la région, rattachés au même réseau privé (condition nécessaire), et des pods répartis entre elles. La documentation recommande au moins trois nœuds répartis sur au moins deux zones. Elle liste aussi ce qui ne devient pas résilient :

  • le plan de contrôle reste unique et son accès passe par la zone principale (leçon 1) ;
  • les volumes de Block Storage sont liés à leur zone : un pod qui en dépend ne redémarre que dans cette zone, et l'application doit répliquer ses données entre zones elle-même ;
  • en isolation complète, la passerelle publique est unique par réseau privé (leçon 2) ;
  • le répartiteur de charge est zonal (Le répartiteur de charge en profondeur).

Pour répartir les pods, on utilise les contraintes de répartition topologique (topology spread constraints) de Kubernetes, avec la clé topology.kubernetes.io/zone posée sur chaque nœud : la documentation de Scaleway les recommande pour ne pas laisser une zone porter tous les pods d'un service. L'autoscaler sait aussi équilibrer des pools similaires : l'option balance-similar-node-groups (désactivée par défaut) répartit la création de nœuds entre eux.

Étiquettes et teintes d'un pool

Pour que le rapport mensuel aille sur les nœuds à mémoire, et que l'API n'y aille pas, on pose sur le pool :

  • des étiquettes (labels), réconciliées sur chaque nœud, que les pods ciblent par nodeSelector ou nodeAffinity ;
  • des teintes (taints, taints), que seuls les pods qui les tolèrent peuvent franchir ; l'effet est NoSchedule, PreferNoSchedule ou NoExecute ;
  • des teintes de démarrage (startup-taints), appliquées à la création du nœud et non réconciliées ensuite : elles servent à interdire le placement tant que quelque chose n'est pas prêt (le CNI de Kapsule en pose une et la retire une fois initialisé).

L'étiquette et la teinte jouent deux rôles différents, et il faut souvent les deux : la teinte repousse les pods qui ne sont pas concernés, l'étiquette attire ceux qui le sont. Une teinte seule empêche l'API d'aller sur les nœuds à mémoire, mais n'oblige pas le rapport à y aller (il pourrait atterrir sur un nœud général). Le cours Ressources, planification et qualité de service a présenté ces mécanismes ; l'apport de Kapsule est que le pool les applique à tous ses nœuds, y compris ceux que l'autoscaler crée demain.

Scaleway pose aussi l'étiquette k8s.scaleway.com/pool-name, que la documentation emploie pour cibler un pool entier avec kubectl cordon -l. Un ancien mécanisme passe par les tags du pool (clé=valeur devient l'étiquette k8s.scaleway.com/clé, taint=clé=valeur:Effet une teinte) : les champs labels et taints sont la voie à employer, l'autre sert à lire d'anciens clusters.

L'autoscaler de cluster

L'autoscaler de cluster (Cluster Autoscaler, un projet de Kubernetes) ajuste le nombre de nœuds d'un pool :

  • il ajoute un nœud quand des pods sont en attente (Pending) faute de ressources sur les nœuds existants, et qu'ajouter un nœud du pool leur permettrait de s'exécuter ;
  • il retire un nœud quand celui-ci est inutile depuis assez longtemps : peu utilisé, et ses pods importants peuvent être replacés ailleurs.

Il raisonne sur les requêtes (resources.requests) des pods, pas sur leur consommation réelle. Un pod sans requête de CPU et de mémoire est invisible pour lui : le nœud paraît vide, les pods en attente ne se calculent pas. C'est la raison pour laquelle les requêtes sont la première condition d'un autoscaling fonctionnel, et l'objet de la leçon 8 du cours Kubernetes.

Ne le confondez pas avec l'autoscaling horizontal des pods (HPA), qui ajuste le nombre de réplicas d'après une métrique : les deux se complètent. Le HPA ajoute des pods, qui restent Pending faute de place ; l'autoscaler de cluster voit ces pods en attente et ajoute des nœuds.

Sur Kapsule, on active l'autoscaler par pool (autoscaling=true, avec min-size et max-size), et on règle son comportement pour tout le cluster par le champ autoscaler-config. Les valeurs par défaut documentées par l'aide de la CLI et du fournisseur Terraform :

RéglageSensDéfaut
scale-down-unneeded-timedurée pendant laquelle un nœud doit être inutile avant d'être retiré10 minutes
scale-down-delay-after-adddélai sans réduction après une augmentation10 minutes
scale-down-utilization-thresholdutilisation (requêtes / capacité) sous laquelle un nœud est candidat0,5
max-graceful-termination-secattente maximale de l'arrêt des pods d'un nœud retiré600 secondes
expanderstratégie pour choisir le pool à agrandir : random, most_pods, least_waste, priority, pricerandom
estimatorestimation de ce qui tient dans un nouveau nœudbinpacking
balance-similar-node-groupsrépartir entre pools similairesnon
ignore-daemonsets-utilizationignorer les DaemonSets dans le calcul d'utilisationnon
expendable-pods-priority-cutoffles pods de priorité inférieure sont « jetables » : ils ne déclenchent rien-10 (Terraform)
skip-nodes-with-local-storagene jamais retirer un nœud avec des pods à stockage local (emptyDir, hostPath)oui (fixé à la création)
scale-down-disabledinterdire toute réductionnon

Avec l'expander par défaut random, l'autoscaler choisit au hasard parmi les pools qui conviennent ; avec des pools de prix différents, least_waste ou price sont plus raisonnables. skip_nodes_with_local_storage et log_level ne se changent plus après la création : les modifier recrée le cluster (fournisseur Terraform).

Ce qui empêche un nœud d'être retiré, d'après la FAQ de l'autoscaler, et ce que vous pouvez faire :

  • des pods dont le PodDisruptionBudget est trop strict ;
  • des pods sans contrôleur (un pod nu, créé par kubectl run) ;
  • des pods avec stockage local, sauf annotation cluster-autoscaler.kubernetes.io/safe-to-evict-local-volumes ;
  • des pods de kube-system sans budget de perturbation ;
  • des pods qui ne peuvent pas être replacés (aucun autre nœud n'a les ressources, le port d'hôte ou l'étiquette qu'ils exigent) ;
  • des pods annotés cluster-autoscaler.kubernetes.io/safe-to-evict: "false", l'annotation qui protège volontairement un pod (un traitement long qu'on ne veut pas interrompre).

L'annotation inverse, "safe-to-evict": "true", permet de lever certains de ces blocages pour un pod précis. Au niveau du nœud, cluster-autoscaler.kubernetes.io/scale-down-disabled=true l'exclut de toute réduction : c'est la parade quand on garde un nœud pour l'étudier.

L'autoréparation

L'autoréparation (autohealing, option autohealing du pool) surveille l'état des nœuds. Le document de Scaleway décrit une boucle de réconciliation toutes les cinq minutes : un nœud qui reste NotReady plus de 15 minutes est redémarré (une seule fois), et au bout de 30 minutes, il est remplacé. Scaleway la recommande en production ; les cas où la désactiver sont les environnements de test, les mécanismes de récupération maison, et la volonté de garder le contrôle manuel.

C'est aussi un piège pour qui déboguera : un nœud que l'on laisse volontairement NotReady pour l'examiner sera redémarré, puis remplacé, et les traces disparaissent. On le sait avant d'intervenir (désactiver l'option sur le pool pendant l'investigation).

Le volume système

Le volume système est celui du système d'exploitation du nœud ; la FAQ le décrit comme un volume dont le contenu est immuable, à ne pas confondre avec le stockage de vos applications. Il existe, selon le type de nœud, en deux familles :

  • stockage local : le système est sur l'hyperviseur du nœud ; c'est rapide, mais lié à la machine ;
  • stockage bloc (Block Storage) : le système est sur un volume distant, sur un cluster de stockage résilient.

L'aide de la CLI propose pour root-volume-type les valeurs default_volume_type, l_ssd, b_ssd, sbs_5k et sbs_15k, et pour root-volume-size une taille. La FAQ recommande au moins 20 Go de volume système, et 100 Go pour stocker confortablement images de conteneurs et journaux. Un volume système saturé se traite en créant un autre pool avec un volume plus grand, pas en agrandissant l'existant.

Les groupes de placement

Un groupe de placement contraint l'emplacement physique des machines : en mode max_availability, deux instances du groupe ne partagent pas un hyperviseur ; en low_latency, elles sont rapprochées. Un pool y rattache ses nœuds (placement-group-id), dans la limite de 20 instances par groupe. On l'emploie pour qu'une panne de machine ne prenne pas deux nœuds du pool ; changer de groupe recrée le pool.

Mettre à jour et remplacer un pool

Deux notions distinctes :

  • mettre à jour la version d'un pool : scw k8s pool upgrade, avec une politique de mise à jour (upgrade-policy.max-unavailable, valeur par défaut 1 d'après le fournisseur, et max-surge, 0 par défaut) qui borne le nombre de nœuds mis à jour en même temps et le nombre de nœuds supplémentaires créés pendant l'opération ; c'est le sujet de la leçon 6 ;
  • remplacer un pool par un autre, quand le type de nœud, le groupe de placement ou le nom change (ces champs recréent le pool). La documentation de Scaleway décrit la méthode : créer le nouveau pool, vérifier que ses nœuds sont Ready, isoler (cordon) puis vider (drain) les nœuds de l'ancien, vérifier le replacement des pods, supprimer l'ancien pool.

Le délai de grâce compte ici : le champ max-termination-grace-period du pool, 15 minutes par défaut, jusqu'à une heure, est la durée maximale avant que l'API force le drain et la suppression d'un nœud en deleting. La documentation précise qu'il prime sur le PodDisruptionBudget et sur le terminationGracePeriodSeconds des pods. Autrement dit, un budget protège contre un drain volontaire (kubectl drain, autoscaler), mais pas indéfiniment contre une suppression de nœud ordonnée par la plateforme.

En pratique

Les commandes ont été vérifiées avec l'aide de la CLI 2.62.0 ; les ressources créées sont facturées, et la dernière sous-section les supprime. Les sorties dépendent de votre compte et ne sont pas reproduites.

Trois pools pour Signalements

On part du pool créé à la leçon 2 (nommé general, en fr-par-1). On ajoute un pool général dans la seconde zone, puis un pool à mémoire, qui peut descendre à zéro nœud :

$ scw k8s pool create cluster-id=<identifiant> region=fr-par \
    name=general-2 node-type=POP2-4C-16G zone=fr-par-2 \
    size=2 min-size=1 max-size=4 autoscaling=true autohealing=true \
    root-volume-type=sbs_5k root-volume-size=50G \
    labels.lyneko.com/profil=general
$ scw k8s pool create cluster-id=<identifiant> region=fr-par \
    name=memoire node-type=POP2-HM-8C-64G zone=fr-par-1 \
    size=0 min-size=0 max-size=3 autoscaling=true autohealing=true \
    labels.lyneko.com/profil=memoire \
    taints.0.key=lyneko.com/profil taints.0.value=memoire taints.0.effect=NoSchedule
  • zone=fr-par-2 : un pool par zone ; la contrainte de répartition de la sous-section suivante répartit les pods entre eux.
  • min-size=1 max-size=4 autoscaling=true : l'autoscaler garde entre un et quatre nœuds. size=2 ne sert qu'à la création : le fournisseur Terraform le précise, avec l'autoscaling activé, une modification de la taille n'est plus prise en compte.
  • root-volume-type=sbs_5k root-volume-size=50G : volume système en Block Storage de 50 Go, au-dessus du minimum de 20 Go de la FAQ (vérifiez dans l'aide la syntaxe de taille attendue).
  • size=0 min-size=0 : aucun nœud tant qu'aucun pod ne le demande ; rien au repos, mais le premier pod attend la création d'une instance.
  • taints.0.* et labels.* : la teinte repousse les autres pods, l'étiquette sert au nodeSelector du rapport.

Le fournisseur Terraform expose les mêmes champs (labels, blocs taints, min_size, max_size), que la leçon 8 emploie.

Des charges qui utilisent les pools

Voici l'API (quatre réplicas, répartis entre les zones, avec requêtes et budget de perturbation) et le rapport, qui cible le pool à mémoire :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: signalements-api
  namespace: signalements
spec:
  replicas: 4
  selector:
    matchLabels: {app: signalements-api}
  template:
    metadata:
      labels: {app: signalements-api}
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: {app: signalements-api}
      containers:
        - name: api
          image: rg.fr-par.scw.cloud/lyneko-apps/signalements-api:1.4.0
          resources:
            requests: {cpu: 250m, memory: 256Mi}
            limits: {memory: 256Mi}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: signalements-api
  namespace: signalements
spec:
  minAvailable: 2
  selector:
    matchLabels: {app: signalements-api}
---
apiVersion: v1
kind: Pod
metadata:
  name: rapport-memoire
  namespace: signalements
spec:
  nodeSelector:
    lyneko.com/profil: memoire
  tolerations:
    - key: lyneko.com/profil
      operator: Equal
      value: memoire
      effect: NoSchedule
  containers:
    - name: rapport
      image: rg.fr-par.scw.cloud/lyneko-apps/signalements-rapport:1.4.0
      resources:
        requests: {cpu: "1", memory: 20Gi}
  • maxSkew: 1 sur la zone : l'écart de pods entre deux zones ne dépasse pas un. ScheduleAnyway place le pod même si la contrainte ne peut être tenue ; DoNotSchedule la rend stricte, au risque de pods en attente.
  • requests : sans elles, l'autoscaler ne voit rien.
  • minAvailable: 2 : sur quatre réplicas, un drain évince au plus deux pods à la fois (voir Deployments et mises à jour progressives).
  • rapport-memoire demande 20 Gio : il ne tient sur aucun nœud général, reste Pending, et ce Pending fait monter le pool memoire de zéro à un. Quand il se termine, l'autoscaler retire le nœud devenu inutile.

Les manifestes ont été vérifiés pour leur syntaxe YAML, pas appliqués ; les noms d'images sont illustratifs.

Régler l'autoscaler

$ scw k8s cluster update <identifiant> region=fr-par \
    autoscaler-config.expander=least_waste \
    autoscaler-config.scale-down-unneeded-time=15m \
    autoscaler-config.balance-similar-node-groups=true
  • least_waste : le pool qui laisse le moins de ressources inutilisées. scale-down-unneeded-time=15m évite les allers-retours quand la charge oscille.
  • balance-similar-node-groups=true équilibre les créations entre general et general-2 (même type, zones différentes) ; sans lui, une zone pourrait se remplir et l'autre rester vide.

Pour exclure un nœud de la réduction pendant une investigation, ou protéger un pod d'une éviction :

$ kubectl annotate node <nœud> cluster-autoscaler.kubernetes.io/scale-down-disabled=true
$ kubectl annotate pod <pod> cluster-autoscaler.kubernetes.io/safe-to-evict=false

Pour un contrôleur, mettez l'annotation dans le modèle de pod (spec.template.metadata.annotations), sinon elle disparaît à la recréation.

Observer ce que fait l'autoscaler

Lisez les décisions dans les événements, que l'autoscaler publie sur les pods et les nœuds :

$ kubectl get events -A --field-selector reason=TriggeredScaleUp
$ kubectl get events -A --field-selector reason=NotTriggerScaleUp
$ kubectl get nodes -L k8s.scaleway.com/pool-name,topology.kubernetes.io/zone,lyneko.com/profil
$ scw k8s node list cluster-id=<identifiant> region=fr-par

TriggeredScaleUp et NotTriggerScaleUp sont les raisons de la FAQ de l'autoscaler. La dernière commande montre les nœuds du point de vue de Scaleway (création, prêt, remplacement).

Remplacer un pool

Pour passer, par exemple, general à un type plus récent : on crée general-v2, puis on vide l'ancien pool, en isolant d'abord tous ses nœuds (afin qu'un pod évincé n'atterrisse pas sur un autre nœud de l'ancien pool), puis un par un :

$ kubectl get nodes -l k8s.scaleway.com/pool-name=general
$ kubectl cordon -l k8s.scaleway.com/pool-name=general
$ kubectl drain <nœud> --ignore-daemonsets --delete-emptydir-data
$ kubectl get pods -A -o wide
$ scw k8s pool delete <identifiant du pool> region=fr-par
  • cordon -l marque tous les nœuds du pool comme non ordonnançables.
  • --ignore-daemonsets : les DaemonSets (CNI, CSI) ne se replacent pas, sans l'option le drain refuse. --delete-emptydir-data accepte de perdre les emptyDir.
  • kubectl drain respecte les PodDisruptionBudgets et attend si l'éviction passerait sous le minimum : kubectl get pdb -A montre alors ALLOWED DISRUPTIONS à 0.

Nettoyer

$ scw k8s pool delete <identifiant du pool> region=fr-par
$ scw k8s cluster delete <identifiant> with-additional-resources=true region=fr-par

Sous le capot

Comment l'autoscaler ajoute un nœud. À chaque cycle, il cherche les pods Unschedulable et simule, pour chaque pool, l'arrivée d'un nœud neuf : les pods en attente s'y placeraient-ils, compte tenu des étiquettes, teintes, requêtes et affinités ? Les pools qui conviennent sont candidats, l'expander choisit. Un pod qui exige une étiquette qu'aucun pool ne porte déclenche NotTriggerScaleUp : il restera en attente, et l'événement le dit.

Comment il en retire un. Il calcule l'utilisation du nœud (le plus grand des rapports entre requêtes de CPU ou de mémoire et capacité allouable). Sous le seuil pendant scale-down-unneeded-time, et si ses pods importants peuvent être replacés (simulation de l'ordonnanceur), il isole le nœud, évince les pods en respectant les budgets, puis le supprime.

Pourquoi size est ignoré avec l'autoscaling. L'état voulu d'un pool autoscalé est une plage, pas un nombre : si Terraform réimposait size à chaque application, il défairait la décision de l'autoscaler.

Le remplacement d'un nœud. Autoréparation, mise à jour ou suppression : Scaleway crée une instance du même type avec la même configuration de pool, la joint au cluster, puis supprime l'ancienne en vidant ses pods. C'est pourquoi rien ne doit exister sur un nœud qui ne se reconstruise pas, et pourquoi la documentation recommande une durée de vie maximale de 30 jours pour les nœuds.

Les teintes de démarrage. Tant qu'un contrôleur ne les retire pas, aucun pod ordinaire ne s'installe sur le nœud neuf : c'est ce qui protège d'un CNI pas prêt ou d'un opérateur GPU qui n'a pas fini ses pilotes. Retirées à la main, elles ne reviennent pas.

Pièges courants

Aucune requête, aucun autoscaling. Sans resources.requests, les pods sont invisibles : le nœud paraît vide et est retiré au mauvais moment, ou des pods en attente ne déclenchent rien.

Un pod nu bloque la réduction. Un kubectl run oublié empêche de retirer son nœud. Cherchez-les : kubectl get pods -A -o json | jq -r '.items[] | select(.metadata.ownerReferences == null) | .metadata.namespace + "/" + .metadata.name'.

Une teinte sans étiquette, ou l'inverse. Pour un pool réservé il faut les deux : la teinte seule n'oblige pas le pod ciblé à y aller, l'étiquette seule laisse les autres pods s'y placer.

Un PodDisruptionBudget qui bloque tout. Avec minAvailable égal au nombre de réplicas, ou un seul réplica et minAvailable: 1, aucune éviction n'est jamais permise : kubectl drain attend sans fin (Cannot evict pod as it would violate the pod's disruption budget) et l'autoscaler ne retire pas le nœud.

L'autoréparation qui remplace le nœud qu'on examinait. Après 30 minutes de NotReady, ses traces disparaissent. Désactivez l'option sur le pool avant d'investiguer.

Des pods multi-zones avec des volumes mono-zone. Un pod à volume ne va que dans la zone de son volume : avec DoNotSchedule sur la zone, il peut rester Pending indéfiniment (leçon 4).

Changer un champ qui recrée le pool. Nom, type de nœud, groupe de placement : l'ancien pool est supprimé puis recréé. Avec Terraform, utilisez create_before_destroy et un nom généré (deux pools ne peuvent pas porter le même nom).

Un volume système trop petit. Images et journaux le remplissent, le nœud passe en pression disque et évince les pods. On ne l'agrandit pas : créez un autre pool.

Sécurité

  • Un pool réservé limite les voisins d'une charge sensible, mais une teinte n'est pas une frontière de sécurité : un pod peut tolérer ce qu'il veut si une politique d'admission ne contrôle pas les tolérations.
  • Les pods d'un nœud partagent un noyau. Pour des locataires qui ne se font pas confiance, des pools séparés sont plus solides que des namespaces.
  • Une durée de vie courte des nœuds est une mesure de sécurité : correctifs de l'image reçus, traces d'une compromission persistante effacées (leçon 6).
  • Les données utilisateur du pool exécutent du code sur chaque nœud : relisez-les comme du code de production ; un cloud-init erroné peut casser les nœuds, dépannage à votre charge.
  • Le groupe de sécurité : par défaut, celui, partagé, de la zone ; security-group-id pour un groupe propre au pool.
  • Un pool GPU sans max-size est une cible de détournement de ressources et une dépense sans plafond.

En production

  • Posez max-size sur chaque pool autoscalé, et un quota d'instances sur le projet : seul plafond de dépense si un pod mal écrit génère mille pods en attente.
  • Le plancher (min-size) couvre le trafic normal et la perte d'une zone. L'autoscaler ajoute des nœuds en minutes, pas en secondes : il n'absorbe pas un pic brutal, la marge de capacité et le HPA s'en chargent.
  • Pas d'autoscaling pour les charges à état sans réflexion : retirer un nœud évince des pods à volume, qui ne reviennent que dans leur zone. Les bases restent sur des pools stables ou hors du cluster : sig-db reste PostgreSQL managé.
  • Un pool par profil, pas par application : cinq pools bien nommés se gèrent, trente non.
  • Remplacez les nœuds régulièrement et réglez max-surge pour garder de la capacité pendant l'opération.
  • Coût des nœuds inactifs : un nœud allumé coûte son prix d'instance, utilisé ou non ; le plan de contrôle mutualisé est gratuit. Le coût d'un cluster est son plancher plus ses pics. Pour le réduire : requêtes justes (des requêtes surévaluées font créer des nœuds pour rien), scale-down-unneeded-time raisonnable, pools à zéro pour le calcul occasionnel, préproduction arrêtée la nuit (leçon 7).
  • Chez Lyneko, un pool de calcul à zéro nœud sert aux travaux ponctuels de lyneko-apps : rien entre deux exécutions, et le premier démarrage ajoute le temps de création d'une instance.

Exercices

1. Pourquoi ce pod reste en attente (niveau 300). rapport-memoire reste Pending depuis 20 minutes, le pool memoire est à zéro nœud, et kubectl describe pod montre NotTriggerScaleUp. Citez trois causes possibles et la vérification de chacune.

Solution

(1) Le pod demande une étiquette ou une teinte que le pool n'a pas : comparer nodeSelector et tolerations du pod aux labels et taints du pool (scw k8s pool get). (2) Ses requêtes dépassent la capacité allouable d'un nœud du pool (20 Gio demandés, mais type plus petit, ou mémoire réservée au système) : comparer avec kubectl describe node d'un nœud de ce type. (3) Le pool est à son max-size, n'a pas l'autoscaling activé, ou le type n'est pas disponible dans la zone ou dépasse un quota. L'événement signifie que l'autoscaler a simulé l'ajout d'un nœud de chaque pool sans que le pod puisse s'y placer.

2. Le drain bloqué (niveau 300). Pendant le remplacement du pool general, kubectl drain reste bloqué. kubectl get pdb -n signalements montre signalements-api avec MIN AVAILABLE 2 et ALLOWED DISRUPTIONS 0, et 2 pods Ready sur 4. Expliquez et débloquez sans risque.

Solution

Deux réplicas sur quatre seulement sont Ready (les autres sont en attente, par exemple parce que la contrainte de répartition les empêche de se placer ou que le nouveau pool n'a pas encore de nœuds) : évincer un pod passerait sous minAvailable: 2, donc le drain attend. Il faut rétablir la capacité, pas contourner le budget : comprendre pourquoi deux pods ne sont pas prêts (kubectl describe pod), vérifier que le nouveau pool a des nœuds Ready, attendre les réplicas. Contourner (--disable-eviction, suppression de pods) ôterait la garantie du budget, et la plateforme forcerait de toute façon le drain après le délai de grâce du pool.

Récapitulatif

  • Un pool est un groupe de nœuds identiques dans une zone ; un cluster multi-zone a un pool par zone.
  • Étiquette (attire) et teinte (repousse) se posent sur le pool et couvrent tous ses nœuds, présents et futurs.
  • L'autoscaler de cluster raisonne sur les requêtes, ajoute des nœuds pour des pods Pending, en retire ceux inutilisés depuis 10 minutes sous 50 % d'utilisation (défauts) ; safe-to-evict et scale-down-disabled protègent pod et nœud.
  • L'autoréparation redémarre un nœud NotReady après 15 minutes et le remplace après 30.
  • Volume système immuable : 20 Go au minimum, 100 Go confortablement. Groupe de placement : 20 instances au plus.
  • Remplacer un pool : création, cordon, drain, vérification, suppression ; le délai de grâce de la plateforme (15 minutes par défaut) finit par primer sur les budgets.
  • Le coût d'un cluster est son plancher de nœuds plus ses pics.

Pour aller plus loin

  • La FAQ de l'autoscaler de Kubernetes (« What types of pods can prevent CA from removing a node ? », « What are expanders ? »), source de référence de tout ce que fait le réglage fin.
  • La leçon 4, pour les volumes liés aux zones.
  • La leçon 6, pour la politique de mise à jour des pools.
  • Le cours Ressources, planification et qualité de service, pour les requêtes, limites et affinités.
  • Le cours Kubernetes : administrer un cluster, pour le drain, les priorités et la préemption en détail.
Voir ma constellation →

Sources