PerfectScale

Scaling fin des pods Kubernetes : au-delà du HPA et VPA

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

Jun 25, 20264 min read
Joshua Fox

About Joshua Fox

Joshua Fox has been a software architect in innovative technology companies for 20 years. Now, he advises tech startups and growth companies on architecture and cost optimization for Google Cloud Platform and Amazon Web Services; also publishing articles, and speaking to cloud engineers.

He has a PhD from Harvard University and a BA in math from Brandeis.

My personal page

1. Introduction

L'autoscaling Kubernetes vise à préserver la robustesse tout en réduisant les coûts. Les autoscalers peuvent ajuster le nombre de nœuds via le Cluster Autoscaler, ou bien le nombre et la taille des pods via un Vertical ou Horizontal Pod Autoscaler.

Cette approche a ses limites : les autoscalers standards s'appuient sur des métriques globales comme l'utilisation moyenne du CPU. Le résultat manque de précision et conduit soit à conserver des ressources gaspillées, soit à exposer le système à des risques d'instabilité en charge. Dans cet article, je me concentre sur les pod autoscalers et je décris comment une télémétrie fine, issue de cAdvisor, Kube-State-Metrics et eBPF, permet des décisions de scaling plus précises et adaptées aux workloads. Ces décisions sont ensuite appliquées aux pods en cours d'exécution via l'API Kubernetes InPlacePodVerticalScaling.

2. Les failles de l'autoscaling actuel

L'autoscaling standard souffre de trois limites majeures :

  • Granularité insuffisante du signal : de simples moyennes CPU ne reflètent pas le profil de performance complet d'applications complexes, ce qui entraîne des décisions de scaling tardives ou erronées.
  • Conflit HPA/VPA : le HPA scale en fonction des ressources demandées, alors que le VPA modifie ces mêmes demandes. Lorsque les deux fonctionnent indépendamment, ils peuvent émettre des instructions contradictoires et provoquer des oscillations.
  • Redimensionnement par éviction : le VPA historique impose l'éviction des pods pour appliquer les changements de ressources, ce qui perturbe les workloads stateful ou sensibles au cache, incapables de tolérer la latence d'un démarrage à froid.

3. Le pipeline de télémétrie multi-couches

Nous partons des métriques au niveau du nœud comme socle, puis ajoutons quatre couches de télémétrie supplémentaires pour obtenir un signal plus fin :

  • Couche 0 (socle) : utilisation CPU et mémoire au niveau du nœud, fournie par le Kubernetes Metrics Server, qui constitue la fondation à granularité grossière.
  • Couche 1 : télémétrie au niveau des conteneurs via cAdvisor. Cette couche capture la latence des micro-bursts — de brefs pics CPU qui gonflent les moyennes sans refléter une charge soutenue — ainsi que les working sets mémoire, évitant ainsi des décisions de scaling fondées sur une croissance bénigne du page cache du noyau.
  • Couche 2 : Kube-State-Metrics (KSM) apporte le contexte de santé du workload, comme les répliques en attente et les signaux de saturation du HPA. Le controller peut ainsi déterminer si un scaling est nécessaire ou déjà en cours avant de déclencher de nouvelles actions.
  • Couche 3 : eBPF sonde le noyau pour faire remonter des signaux applicatifs profonds, comme les taux d'allocation du heap JVM ou la pression du Garbage Collection (GC). Les ressources peuvent ainsi être ajustées en amont, avant la survenue d'un événement OOM. Par exemple, une sonde bpftrace exploitant les marqueurs USDT de HotSpot tels que usdt:/path/to/libjvm.so:hotspot:mem__pool__gc__begin peut détecter les pics de fréquence du GC plusieurs secondes avant qu'ils n'affectent la latence, offrant au controller une longueur d'avance.
  • Couche 4 : les GPU sont désormais une ressource critique pour le coût et la performance de l'IA/ML. Les métriques GPU spécifiques exposées via NVIDIA DCGM permettent un scaling fondé sur les goulets d'étranglement GPU, comme l'utilisation des SM ou la saturation de la bande passante mémoire.

4. Construire un controller de scaling unifié

Un controller unifié harmonise ces signaux sous la forme d'une machine à états : il maintient des états explicites (stable, scaling-horizontal, scaling-vertical, cooldown) et n'effectue de transitions que sur des conditions définies. Cela le distingue des scalers existants, qui fonctionnent généralement comme des moteurs de règles évaluant chaque signal indépendamment, au risque d'émettre des instructions contradictoires.

Le controller opère en trois phases :

Phase 1 : agrégation des signaux

Chaque couche de télémétrie alimente, via un score pondéré, une métrique unique de pression par workload. Les signaux applicatifs comme ceux de la Couche 3 (eBPF/pression GC) reçoivent un poids plus élevé.

Phase 2 : arbitrage des axes

Le controller évite les conflits en détectant qu'un scaling horizontal est déjà en cours ; cette situation se manifeste par AbleToScale=True couplé à une différence non nulle entre currentReplicas et desiredReplicas.

À l'inverse, si un workload est déclaré singleton ou stateful (via une annotation ou une appartenance à un StatefulSet), le controller s'oriente systématiquement vers le scaling vertical, jamais horizontal.

Phase 3 : réconciliation et cooldown

Après chaque action de scaling, une période de cooldown évite le thrashing. Le controller ne réévalue qu'après avoir observé au moins deux fenêtres de métriques consécutives au-dessus ou en dessous du seuil, ce qui garantit que des pics transitoires ne déclenchent pas d'événements de scaling répétés.

5. Le redimensionnement vertical in-place des pods

L'API InPlacePodVerticalScaling, passée en disponibilité générale en décembre 2025 avec Kubernetes v1.35, permet de modifier les ressources sans éviction du pod. Le controller patche la spécification du pod, puis le kubelet met à jour dynamiquement les cgroups du conteneur — sans coupure de connexion ni redémarrage de processus.

Un exemple concret : si la couche eBPF détecte une pression GC Java soutenue indiquant que l'épuisement du heap est probable dans les 30 secondes, le controller émet un PATCH /api/v1/namespaces/{ns}/pods/{name} avec des resources.requests.memory et resources.limits.memory actualisés. Le kubelet applique le changement en ajustant la limite mémoire cgroup du conteneur in-place.

C'est particulièrement précieux pour les services aux temps de démarrage à froid significatifs, comme les applications JVM, les sidecars de base de données et les caches en mémoire, pour lesquels un redimensionnement par éviction introduirait des pics de latence de plusieurs dizaines de secondes.

6. Conclusion

Les configurations HPA et VPA standards peinent souvent à concilier haute disponibilité et maîtrise des coûts. En combinant l'observabilité fine d'eBPF et de cAdvisor avec un scaling horizontal/vertical coordonné par une machine à états unifiée, et en appliquant les changements via le redimensionnement in-place des pods, les organisations peuvent bâtir des clusters auto-réparants et correctement dimensionnés, avec un impact minimal.

PerfectScale est une implémentation inspirée des patterns architecturaux décrits ici. Je travaille au sein de l'équipe Forward Deployed Engineering chez DoiT, où j'accompagne les clients sur AWS et Google Cloud Platform. N'hésitez pas à me contacter pour toute question.