Aller au contenu principal

Ressources

HPA Kubernetes : quand l'autoscaling aggrave l'incident et déplace le goulot

HPA Kubernetes : formule, tolérance, fenêtres et politiques, délai des nœuds, plafond de réplicas tiré des dépendances et lecture d'un incident amplifié.

L'équipe CTN Solutions · publié le · 20 min de lecture

Faits vérifiés sur les sources primaires le

Le trafic monte, la latence suit, et le HorizontalPodAutoscaler (HPA) de Kubernetes ajoute des pods, comme prévu. Quelques minutes plus tard, la base de données refuse des connexions, une API (interface de programmation) tierce répond 429 (trop de requêtes) et une partie des nouveaux pods attend un nœud. Le service va plus mal qu'avant. Le HPA n'est pas forcément en défaut : il lit une métrique sur les pods qu'il pilote et ignore la capacité de ce qui se trouve derrière eux.

Cet article décrit l'algorithme du HPA tel que le documente Kubernetes 1.37 (version mineure la plus récente au ), le délai qu'ajoutent Cluster Autoscaler et Karpenter, une méthode pour fixer maxReplicas à partir des dépendances, et une grille pour lire la chronologie d'un incident amplifié.

Pourquoi plus de pods peut aggraver un incident

Le HPA répond à une question précise : combien de réplicas faut-il pour ramener une métrique à sa cible ? Il ne répond pas à celle qui compte pendant un incident : la ressource qui sature est-elle celle que les nouveaux réplicas apportent ? Ajouter des pods ajoute du CPU, de la mémoire et du parallélisme côté application. Si la ressource rare se trouve ailleurs, chaque pod supplémentaire augmente la demande qui pèse sur elle.

Quatre goulots que les réplicas ne desserrent pas

  • Les connexions à la base de données. Dans PostgreSQL 18, max_connections fixe le nombre maximal de connexions simultanées (en général 100 par défaut, modifiable seulement au démarrage du serveur), dont trois réservées par défaut aux superutilisateurs (superuser_reserved_connections). Si chaque pod ouvre son propre pool, le total croît avec les réplicas. Au-delà de la limite, le serveur n'accepte plus de connexion ; en deçà, davantage de requêtes concurrentes peut encore allonger leur durée sur un serveur déjà saturé.
  • Les quotas d'API tierces. Un fournisseur de paiement ou d'envoi de messages peut appliquer son quota par compte ou par clé. Une limite de débit posée dans chaque pod, multipliée par le nombre de réplicas, le dépasse alors dès que le HPA monte, et les tentatives qui suivent les réponses 429 consomment le reste.
  • Les files et les traitements en aval. Un frontal mis à l'échelle accepte plus de travail ; si le consommateur de la file ne suit pas, l'attente se déplace sans disparaître.
  • Les nœuds. Un pod créé par le HPA n'apporte rien tant qu'il n'est pas planifié, démarré et prêt. Sans place, il reste en attente (état Pending) et la charge continue de peser sur les pods existants.

La boucle de rétroaction

Le livre de Google consacré à l'ingénierie de fiabilité des sites (Site Reliability Engineering, SRE) définit une défaillance en cascade comme une défaillance qui grandit avec le temps par rétroaction positive. La mise à l'échelle automatique peut fermer une telle boucle : la dépendance sature, les requêtes échouent, clients et bibliothèques réessaient, ce travail perdu fait monter le CPU, et le HPA ajoute des pods qui ouvrent à leur tour connexions et tentatives. À l'inverse, des pods qui attendent la base sans consommer de CPU gardent une métrique basse et le HPA ne fait rien : la métrique choisie décide du sens de l'erreur.

La boucle d'amplification par la mise à l'échelle

Le schéma montre comment une dépendance saturée peut ramener la charge vers la métrique que le HPA surveille.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. Un pic de trafic porte le CPU moyen au-dessus de la cible.
  2. Le HPA ajoute des réplicas.
  3. Chaque réplica ouvre des connexions et appelle les dépendances.
  4. La base ou l'API tierce atteint sa limite.
  5. Les requêtes échouent ou ralentissent.
  6. Clients et bibliothèques réessaient.
  7. Ces tentatives font remonter le CPU et relancent l'étape 2.
Modèle explicatif, d'après la définition de la défaillance en cascade du livre SRE de Google et l'algorithme documenté du HPA.

Le même livre recommande de borner les nouvelles tentatives : backoff exponentiel aléatoire, limite par requête, budget de tentatives par processus, une seule couche de la pile qui réessaie. Ces protections appartiennent à l'application ; le HPA ne les remplace pas, et un plafond de réplicas ne tient que si elles existent.

L'algorithme du HPA dans Kubernetes 1.37

Le HPA est une ressource de l'API autoscaling/v2, stable, et un contrôleur exécuté par le kube-controller-manager. Il agit sur la sous-ressource scale d'une cible comme un Deployment ou un StatefulSet ; il ne s'applique pas à un DaemonSet. Ce n'est pas un processus continu : la boucle s'exécute à l'intervalle fixé par --horizontal-pod-autoscaler-sync-period, 15 secondes par défaut.

La formule et la tolérance

À chaque passage, pour chaque métrique, la documentation donne la formule suivante : réplicas souhaités = ceil(réplicas actuels × valeur actuelle / valeur cible). Pour une cible en utilisation (Utilization), la valeur actuelle est la moyenne, sur les pods ciblés, de la consommation rapportée à la request de chaque conteneur. Si un conteneur n'a pas de request pour cette ressource, l'utilisation n'est pas définie et le HPA n'agit pas sur cette métrique. Dans le code du contrôleur (tag v1.37.2), la multiplication porte sur le nombre de pods prêts dont la métrique est disponible.

Si le rapport entre valeur actuelle et cible reste dans la tolérance, le HPA ne fait rien. Elle vaut 10 % par défaut pour tout le cluster, réglable par --horizontal-pod-autoscaler-tolerance. Depuis Kubernetes 1.37, la fonctionnalité HPAConfigurableTolerance est stable : un champ tolerance se règle par HPA et par direction, sous behavior.scaleUp et behavior.scaleDown. Une tolérance de montée plus faible fait réagir plus tôt, mais elle fait aussi réagir au bruit de la métrique.

Métriques manquantes et pods pas encore prêts

Avant de conclure, le contrôleur ignore les pods en cours de suppression et écarte les pods en échec. Il met de côté les pods dont la métrique manque et, pour le CPU, ceux qui ne sont pas encore prêts ou dont le dernier échantillon précède le passage à l'état prêt. Deux options du kube-controller-manager, réglables uniquement pour tout le cluster, encadrent cette période de démarrage : --horizontal-pod-autoscaler-cpu-initialization-period (5 minutes par défaut) et --horizontal-pod-autoscaler-initial-readiness-delay (30 secondes par défaut).

Le calcul est ensuite refait de façon prudente. Pour une descente, les pods sans métrique sont supposés consommer 100 % de la cible ; pour une montée, 0 %. Pour le CPU, si une montée est proposée alors que des pods ne sont pas encore prêts, ceux-ci comptent pour 0 %. Si le nouveau rapport inverse le sens ou retombe dans la tolérance, le HPA ne change rien. Le statut publié garde pourtant la moyenne d'origine : la valeur que montre kubectl get hpa peut rester au-dessus de la cible sans que le HPA ajoute de pods.

Calcul du HPA à chaque passage

Le schéma suit les étapes que le contrôleur du HPA enchaîne à chaque passage, de la formule à la décision de changer ou non le nombre de réplicas.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. Formule : réplicas souhaités = ceil(réplicas actuels × valeur actuelle / valeur cible). Pour une cible en utilisation, la valeur actuelle est la moyenne de la consommation rapportée à la request, sur les pods ciblés.
  2. Les pods en cours de suppression et ceux en échec sont écartés. Les pods dont la métrique manque sont mis de côté ; pour le CPU, les pods pas encore prêts, ou dont le dernier échantillon précède le passage à l'état prêt, aussi.
  3. Recalcul prudent : les pods sans métrique comptent pour 100 % de la cible en descente et pour 0 % en montée. Pour le CPU, les pods pas encore prêts comptent pour 0 % en montée.
  4. Décision : si le sens du changement s'inverse ou si le rapport revient dans la tolérance de 10 % par défaut, aucun changement.
  5. Sinon, le changement est borné par les politiques de behavior et par maxReplicas.
Modèle explicatif, d'après l'algorithme du HPA documenté par Kubernetes 1.37.

Deux conséquences comptent pendant un incident. Une sonde de disponibilité (readiness probe) qui réussit trop tôt laisse entrer le pic de CPU du démarrage dans le calcul ; la documentation conseille une startupProbe, ou une readinessProbe qui ne réussit qu'après ce pic. Et quand cette sonde échoue, le contrôleur EndpointSlice retire le pod des Services : si elle interroge la base, une base saturée peut sortir des pods du trafic et reporter la charge sur les autres.

Plusieurs métriques, métriques illisibles

Avec plusieurs métriques, le HPA retient la plus grande proposition, dans la limite de maxReplicas. Si une métrique est illisible et que les autres proposent une descente, il ne descend pas ; il peut encore monter. S'il ne parvient plus à calculer, par exemple quand Metrics Server ou l'adaptateur ne répond plus, la condition ScalingActive passe à False, signe en général d'un problème de métriques, et les réplicas restent au dernier ajustement.

Fenêtres de stabilisation et politiques

Le champ behavior, stable depuis Kubernetes 1.23, décrit séparément la montée (scaleUp) et la descente (scaleDown). Les valeurs par défaut, fusionnées avec ce que vous déclarez, sont les suivantes :

Valeurs par défaut du champ behavior du HPA pour la montée et la descente : fenêtre de stabilisation, politiques, sélection et tolérance
RéglageMontée (scaleUp)Descente (scaleDown)
Fenêtre de stabilisation0 seconde300 secondes, ou la valeur de --horizontal-pod-autoscaler-downscale-stabilization
Politiques100 % des réplicas ou 4 pods par période de 15 secondes100 % des réplicas par période de 15 secondes
Sélection (selectPolicy)Max : la politique qui autorise le plus grand changementune seule politique
Tolérance10 % pour le cluster, sauf champ tolerance10 % pour le cluster, sauf champ tolerance

En descente, la fenêtre retient la plus haute recommandation de la période, pour éviter de retirer des pods qu'il faudrait recréer peu après. Une politique Pods ou Percent borne le changement sur periodSeconds (1 800 secondes au plus) ; selectPolicy: Min retient la plus restrictive, Disabled interdit la direction. Quand une politique bride la montée, la condition ScalingLimited passe à True avec la raison ScaleUpLimit ; quand c'est maxReplicas, avec TooManyReplicas.

Les types de métriques

  • Resource : CPU ou mémoire des pods, lus par l'API metrics.k8s.io, que fournit en général Metrics Server, installé séparément.
  • ContainerResource : la même mesure pour un seul conteneur, stable depuis Kubernetes 1.30, utile quand un conteneur annexe fausse la moyenne du pod.
  • Pods : une métrique applicative par pod, lue par custom.metrics.k8s.io via un adaptateur.
  • Object : une métrique d'un autre objet Kubernetes, comme les requêtes par seconde d'un Ingress.
  • External : une métrique hors de Kubernetes, comme la profondeur d'une file, lue par external.metrics.k8s.io.

Pour Object et External, le calcul s'appuie sur les pods prêts. Avec au moins une métrique de ce type, minReplicas: 0 devient possible : HPAScaleToZero est en bêta et activée par défaut depuis Kubernetes 1.37, sur l'API server comme sur le kube-controller-manager, et la condition ScaledToZero signale l'état.

Les nœuds : le délai qu'ajoutent Cluster Autoscaler et Karpenter

Le HPA change un nombre de réplicas ; il ne crée pas de capacité. Si les nouveaux pods ne tiennent pas sur les nœuds existants, le planificateur les laisse en attente et un autoscaler de nœuds doit provisionner. La documentation de Kubernetes cite deux projets parrainés par le SIG Autoscaling (le groupe d'intérêt du projet Kubernetes consacré à l'autoscaling), Cluster Autoscaler et Karpenter. Tous deux réagissent aux pods non planifiables et dimensionnent les nœuds d'après les requests des pods, pas d'après leur consommation réelle.

Cluster Autoscaler

Cluster Autoscaler agrandit des groupes de nœuds préconfigurés et recherche les pods non planifiables toutes les 10 secondes par défaut (--scan-interval). Sa FAQ décompose le délai en trois parts : réaction du HPA, réaction de Cluster Autoscaler, provisionnement du nœud, qui dépend surtout du fournisseur cloud. Les événements TriggeredScaleUp et NotTriggerScaleUp sur le pod disent s'il a demandé un nœud, ou pourquoi pas.

Karpenter

Karpenter (v1.15 au ) crée des nœuds directement à partir des contraintes d'un NodePool, sans groupe homogène préalable. Il regroupe les pods en attente par lot : la fenêtre se ferme après 1 seconde sans nouveau pod en attente (BATCH_IDLE_DURATION) et dure au plus 10 secondes (BATCH_MAX_DURATION). Le champ spec.limits d'un NodePool plafonne les ressources qu'il peut consommer ; sans ce champ, seuls les quotas du fournisseur cloud bornent la croissance, et une fois la limite atteinte, aucun nœud n'est créé tant que d'autres ne sont pas terminés. Ce plafond protège un budget de calcul, pas une dépendance : il ignore tout de la base de données.

Ce que le délai change pendant l'incident

Du pic de charge au pod qui sert

Le schéma montre les étapes qui séparent la hausse d'une métrique d'un pod réellement capable de servir.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. La charge monte sur les pods existants.
  2. Metrics Server ou un adaptateur collecte la métrique.
  3. Le HPA recalcule à son prochain passage (toutes les 15 secondes par défaut).
  4. Les nouveaux pods sont créés.
  5. Si un nœud a de la place, le pod démarre.
  6. Sinon, il attend qu'un autoscaler de nœuds obtienne un nœud du fournisseur cloud.
  7. Le pod ne reçoit du trafic qu'après le succès de sa sonde de disponibilité.
Modèle explicatif, d'après la documentation Kubernetes du HPA et de l'autoscaling des nœuds.

Pendant ce délai, le HPA a déjà compté les nouveaux réplicas : la cible peut en compter nettement plus que les pods qui servent réellement. Pour une métrique CPU, les pods pas encore prêts comptent pour 0 % dans un calcul de montée, ce qui freine la surenchère. Pour une métrique Object ou External, le rapport est multiplié par le nombre de pods prêts : si la métrique reste haute, d'autres montées restent possibles.

Quand les nœuds arrivent, les pods en attente démarrent ensemble et ouvrent leurs connexions en même temps, caches vides. Le livre SRE de Google rappelle qu'un service adossé à un cache ne tient pas sa charge avec un cache vide, et conseille d'augmenter la charge progressivement. Une politique de montée plus lente, ou une capacité tampon, adoucit ce front. La FAQ de Cluster Autoscaler décrit un tel tampon : des pods « pause » à très basse priorité réservent de la place, sont préemptés par les pods normaux (mécanisme de priorité et de préemption de Kubernetes), puis deviennent non planifiables et déclenchent l'ajout d'un nœud. Sa taille se décide avant le pic.

Fixer maxReplicas et les politiques à partir des dépendances

maxReplicas n'est pas une estimation de ce que le cluster peut héberger. C'est le nombre de réplicas au-delà duquel une dépendance cesse de suivre. Le calcul part donc de la dépendance la plus étroite du chemin critique.

  1. Lister les dépendances du chemin critique et la ressource limitante de chacune : connexions, requêtes par seconde, quota d'API, consommateurs d'une file.
  2. Lire la limite réelle à sa source : SHOW max_connections; sur la base, le quota affiché par le fournisseur ou inscrit au contrat.
  3. Retirer ce que consomment les autres : connexions réservées, autres services, workers, tâches planifiées, migrations, outils d'administration, marge pour une intervention humaine.
  4. Diviser par la consommation maximale d'un pod : taille maximale de son pool, débit sortant autorisé.
  5. Réserver la place du surplus de déploiement. maxSurge vaut 25 % par défaut, arrondi au supérieur : pendant un déploiement, la cible peut compter maxReplicas plus ce surplus. Un pod en cours d'arrêt peut aussi garder ses connexions pendant son délai de grâce, 30 secondes par défaut.
  6. Arrondir à l'inférieur. Si le plafond reste sous le besoin, la réponse est dans la dépendance (pooler, réplicas en lecture, quota renégocié, cache), pas dans le HPA.

Exemple : un service, sa base et son quota d'API

Un service api tourne avec 6 réplicas, une cible CPU de 60 % (averageUtilization: 60) et une utilisation moyenne de 90 %.

  • Rapport : 90 / 60 = 1,5, hors de la tolérance de 10 %.
  • Proposition : ceil(6 × 1,5) = 9 réplicas.
  • Plafond de vitesse sur un passage : 6 × 2 = 12 (la politique Percent, 100 %, autorise 12 ; la politique Pods, 4 pods, autorise 10 ; Max retient 12). Les 9 réplicas sont autorisés.
  • Au passage suivant, les 3 nouveaux pods ne sont pas encore prêts et les 6 anciens restent à 90 %. Sans eux, le rapport serait encore 1,5 ; en les comptant à 0 %, la moyenne devient (6 × 90 + 3 × 0) / 9 = 60 %, soit un rapport de 1,0, dans la tolérance. Le HPA attend.

Budget de connexions de la base PostgreSQL qu'utilise ce service :

  • max_connections configuré : 400.
  • Réservées aux superutilisateurs (valeur par défaut) : 3.
  • Workers : 40 ; tâches planifiées : 20 ; marge d'administration et de migration : 17.
  • Reste pour api : 400 - 3 - 40 - 20 - 17 = 320 connexions.
  • Pool maximal par pod : 10 connexions, soit 32 pods au plus en même temps.
  • Avec un surplus de 25 % arrondi au supérieur, il faut R + ceil(R / 4) ≤ 32. Pour R = 25 : 25 + 7 = 32, le budget tient. Pour R = 26 : 26 + 7 = 33, il ne tient plus. Le plafond est maxReplicas: 25.

Du budget de connexions à maxReplicas

Le schéma part de la limite de connexions de la base et aboutit au plafond de réplicas du service, dans l'exemple chiffré de l'article.

Schéma défilable horizontalement ; sa version textuelle complète suit.

Lire le schéma sous forme textuelle
  1. max_connections configuré : 400.
  2. Retirées : 3 connexions réservées aux superutilisateurs, 40 pour les workers, 20 pour les tâches planifiées, 17 pour l'administration et les migrations. Reste pour api : 400 - 3 - 40 - 20 - 17 = 320 connexions.
  3. Pool maximal de 10 connexions par pod : 320 / 10 = 32 pods au plus en même temps.
  4. Surplus de déploiement de 25 % arrondi au supérieur : R + ceil(R / 4) ≤ 32.
  5. Pour R = 25 : 25 + 7 = 32, le budget tient. Pour R = 26 : 26 + 7 = 33, il ne tient plus. Le plafond est maxReplicas: 25.
D'après l'exemple chiffré de l'article sur le budget de connexions de la base PostgreSQL.

Si la charge porte ensuite le CPU à 95 % sur ces 25 pods, le HPA propose ceil(25 × 95 / 60) = 40. Il reste à 25 et affiche ScalingLimited à True, raison TooManyReplicas. Pendant l'incident, c'est le signal attendu d'un plafond qui protège la base, pas une invitation à le relever.

Même raisonnement pour une API tierce dont le quota est de 50 requêtes par seconde pour le compte : avec une limite locale de 5 requêtes par seconde par pod, le quota est atteint dès 10 pods. Le onzième n'ajoute aucun débit, seulement des refus 429 et des tentatives. Une limite partagée entre tous les pods, ou un plafond de réplicas sur le service appelant, protège le quota ; une limite par pod ne le fait pas.

Régler la montée et la descente

Par défaut, la montée peut doubler les réplicas toutes les 15 secondes. Si la dépendance a besoin de temps pour absorber de nouveaux clients (connexions, caches), une politique Pods qui limite les ajouts par minute étale le front ; avec une politique Percent et selectPolicy: Min, la plus restrictive l'emporte. La contrepartie, une réponse plus lente à un vrai pic, est une décision à écrire.

À la descente, la fenêtre de 300 secondes garde la capacité pendant qu'une file se vide. En incident, selectPolicy: Disabled sur scaleDown bloque toute descente ; retirez ce réglage ensuite, sinon le HPA ne redescend plus.

À adapter à votre environnement : remplacez les valeurs entre chevrons par celles de votre calcul de budget et de vos essais.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: <NOM_HPA>
  namespace: <NAMESPACE>
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: <NOM_DEPLOYMENT>
  minReplicas: <MINIMUM_DE_SERVICE>
  maxReplicas: <PLAFOND_ISSU_DU_BUDGET>
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: <CIBLE_CPU>
  behavior:
    scaleUp:
      tolerance: <TOLERANCE_MONTEE>
      stabilizationWindowSeconds: 0
      policies:
        - type: Pods
          value: <PODS_AJOUTES_PAR_PERIODE>
          periodSeconds: 60
        - type: Percent
          value: <POURCENTAGE_AJOUTE_PAR_PERIODE>
          periodSeconds: 60
      selectPolicy: Min
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: <POURCENTAGE_RETIRE_PAR_PERIODE>
          periodSeconds: 60

Quand le goulot est le nombre de pools plutôt que le CPU, une autre option existe : le redimensionnement en place des ressources d'un conteneur, stable depuis Kubernetes 1.35, change CPU et mémoire sans recréer le pod, donc sans ouvrir de nouveau pool. Il modifie aussi les requests, donc le dénominateur de l'utilisation que lit le HPA : la cible doit être relue en même temps.

Dégrader plutôt que multiplier

Un plafond protège la dépendance, mais une partie des requêtes reste alors sans réponse rapide. Le choix de ce qui échoue se prépare : délester les requêtes les moins utiles quand la file interne dépasse un seuil, couper un appel vers une dépendance qui ne répond plus (coupe-circuit), désactiver une fonction coûteuse par un interrupteur de configuration. Le livre SRE de Google demande que cette dégradation reste rare et qu'elle puisse être désactivée ou réglée rapidement.

Files d'attente : KEDA en bref

KEDA (Kubernetes Event-driven Autoscaling, v2.21 au ) met à l'échelle des consommateurs d'événements. Il ne remplace pas le HPA : son opérateur gère seul le passage de zéro à un réplica et de un à zéro, et il crée un HPA qui gère la plage de un à N à partir des métriques externes que publie son serveur de métriques. Une valeur d'activation décide du réveil, une valeur cible pilote ensuite le nombre de réplicas. La mise à zéro n'est pas possible avec des déclencheurs CPU ou mémoire, faute de pod à mesurer. Le budget s'applique de la même façon : maxReplicaCount vaut 100 par défaut dans un ScaledObject, et chaque consommateur ouvre lui aussi ses connexions.

Lire la chronologie d'un incident où la mise à l'échelle a amplifié la charge

Après l'incident, une question doit recevoir une réponse fondée sur des horodatages : la mise à l'échelle a-t-elle soulagé le service, l'a-t-elle aggravé, ou n'a-t-elle simplement pas eu le temps d'agir ? La réponse se lit dans l'ordre des événements, pas dans un graphique isolé.

Recueillir avant que les preuves disparaissent

Par défaut, l'API server ne conserve les événements Kubernetes qu'une heure (--event-ttl). Exportez donc les événements du HPA et des pods dès le début de l'incident, ou envoyez-les en continu vers la chaîne de journalisation. À adapter à votre environnement : remplacez les noms entre chevrons.

# Conditions et derniers événements du HPA
kubectl describe hpa <NOM_HPA> -n <NAMESPACE>

# Décisions du HPA (SuccessfulRescale : "New size: N; reason: ...")
kubectl get events -n <NAMESPACE> \
  --field-selector involvedObject.kind=HorizontalPodAutoscaler,involvedObject.name=<NOM_HPA> \
  --sort-by=.lastTimestamp

# Pods en attente de nœud et raisons de non planification
kubectl get pods -n <NAMESPACE> --field-selector=status.phase=Pending -o wide
kubectl get events -n <NAMESPACE> --field-selector reason=FailedScheduling --sort-by=.metadata.creationTimestamp

# Réaction de l'autoscaler de nœuds
kubectl get events -A --field-selector reason=TriggeredScaleUp --sort-by=.lastTimestamp
kubectl get nodeclaims -o wide
kubectl get nodes --sort-by=.metadata.creationTimestamp

La commande kubectl get nodeclaims ne concerne que Karpenter ; l'événement TriggeredScaleUp, que Cluster Autoscaler. Le planificateur écrit ses événements par l'API events.k8s.io/v1 : il renseigne eventTime, et le champ lastTimestamp que lit kubectl get events reste vide ; le tri des événements FailedScheduling se fait donc sur leur date de création. Pour un événement répété, cette date est celle de la première occurrence, et la dernière figure dans series.lastObservedTime.

Aligner les signaux sur un seul axe de temps

Signaux de la chronologie d'un incident : où les lire et ce qu'ils établissent
SignalOù le lireCe qu'il établit
Réplicas souhaitésévénements SuccessfulRescale du HPAle moment et l'ampleur de chaque décision
Bridageconditions ScalingLimited (ScaleUpLimit, TooManyReplicas) et ScalingActivesi une politique, le plafond ou une panne de métriques a borné le HPA
Pods prêts et pods en attentestatut des pods, événements FailedSchedulingl'écart entre réplicas comptés et réplicas qui servent
Nœudsévénements de l'autoscaler, date de création et passage à l'état prêt des nœudsla part du délai due à la capacité
Connexions à la basenombre de connexions actives rapporté à max_connections, refus de connexionsi chaque montée a rapproché la base de sa limite
Appels sortantsréponses 429 ou 503 (service indisponible), nombre de tentatives par appelsi les tentatives ont multiplié la charge
Symptôme utilisateurtaux d'erreur et latence du parcours critiquesi le service rendu s'est amélioré ou dégradé après chaque décision

Toutes les horloges sont ramenées à un même fuseau, de préférence UTC, et chaque ligne de la chronologie note l'heure, la décision du HPA, les pods prêts, les pods en attente, les connexions, les erreurs et ce qui a été fait à la main.

Reconnaître le scénario

  • Amplification : chaque hausse des réplicas souhaités précède une hausse des connexions ou des refus 429, puis des erreurs, du CPU et une nouvelle décision. Si les erreurs précèdent la première décision, la mise à l'échelle n'est pas la cause première ; si la saturation suit chaque décision, elle l'a aggravée.
  • Retard de capacité : les réplicas souhaités montent, les pods prêts stagnent, les pods en attente s'accumulent. Le délai de provisionnement est en cause, pas le calcul.
  • Gel : ScalingActive passe à False alors que la cible compte au moins un réplica. La raison de la condition dit pourquoi : en général une métrique illisible (raisons commençant par FailedGet) ; sinon un sélecteur invalide ou ambigu (InvalidSelector, AmbiguousSelector). Le HPA n'a plus pu calculer.
  • Plafond atteint : ScalingLimited avec TooManyReplicas. Dépendance à sa limite : le plafond a joué son rôle. Dépendance saine et symptôme persistant : le goulot est dans le service.
  • Descente prématurée : les réplicas baissent alors que la file n'est pas vide ; la descente est à revoir.

Une bascule de la base coupe les connexions de tous les pods à la fois : plus il y a de réplicas, plus la vague de reconnexion est large ; ce point se prépare avec la topologie décrite dans PostgreSQL sur Kubernetes en haute disponibilité : topologies, bascule, RPO et tests de panne. Pour décider lesquels de ces signaux méritent une alerte, voir Des alertes qui sonnent pour rien : un socle d'observabilité pour une scale-up sans SRE.

La chronologie se termine par des décisions écrites : budget de connexions et maxReplicas recalculés, politiques revues, règles de tentatives, dégradation prévue, alertes sur la saturation des dépendances. Quand une mise en production approche, nous traitons ces plafonds comme des critères de passage : seuils d'alerte posés avant l'exposition, critères d'arrêt explicites pendant la montée du trafic, chemin de retour exécuté. C'est la démarche de Mise en production Kubernetes : ouvrir par étapes.

Sources officielles et limites de lecture

Faits datés

Les comportements décrits ont été vérifiés le sur la documentation de Kubernetes 1.37 (dernière version corrective : 1.37.2, publiée le ), le code du contrôleur HPA au tag v1.37.2, la FAQ de Cluster Autoscaler, la documentation de Karpenter v1.15, de KEDA 2.21 et de PostgreSQL 18. Les valeurs par défaut sont celles du projet amont : une distribution managée peut en fixer d'autres pour le kube-controller-manager, ou empêcher de les modifier ; la documentation de votre fournisseur fait foi.

Vérifié le

Sources

  1. Horizontal Pod Autoscaling, documentation Kubernetes : https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/
  2. HorizontalPodAutoscaler Walkthrough, documentation Kubernetes : https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
  3. Feature Gates (HPAConfigurableTolerance, HPAScaleToZero, HPAContainerMetrics, InPlacePodVerticalScaling), documentation Kubernetes : https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/
  4. Contrôleur podautoscaler (horizontal.go, replica_calculator.go), dépôt kubernetes/kubernetes, tag v1.37.2 : https://github.com/kubernetes/kubernetes/tree/v1.37.2/pkg/controller/podautoscaler
  5. Releases, Kubernetes : https://kubernetes.io/releases/
  6. Kubernetes v1.37.2, note de version : https://github.com/kubernetes/kubernetes/releases/tag/v1.37.2
  7. Node Autoscaling, documentation Kubernetes : https://kubernetes.io/docs/concepts/cluster-administration/node-autoscaling/
  8. Cluster Autoscaler FAQ, dépôt kubernetes/autoscaler : https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md
  9. Settings (v1.15), documentation Karpenter : https://karpenter.sh/v1.15/reference/settings/
  10. NodePools (v1.15), documentation Karpenter : https://karpenter.sh/v1.15/concepts/nodepools/
  11. KEDA Concepts (2.21), documentation KEDA : https://keda.sh/docs/2.21/concepts/
  12. Scaling Deployments, StatefulSets and Custom Resources (2.21), documentation KEDA : https://keda.sh/docs/2.21/concepts/scaling-deployments/
  13. ScaledObject specification (2.21), documentation KEDA : https://keda.sh/docs/2.21/reference/scaledobject-spec/
  14. Deployments, documentation Kubernetes : https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
  15. Pod Lifecycle, documentation Kubernetes : https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
  16. Pod Priority and Preemption, documentation Kubernetes : https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
  17. Resize CPU and Memory Resources assigned to Containers, documentation Kubernetes : https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/
  18. kube-apiserver (option --event-ttl), documentation Kubernetes : https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/
  19. Connections and Authentication (PostgreSQL 18), documentation PostgreSQL : https://www.postgresql.org/docs/18/runtime-config-connection.html
  20. Addressing Cascading Failures, Site Reliability Engineering, Google : https://sre.google/sre-book/addressing-cascading-failures/

Parlons de votre contexte.

Cet article expose une pratique générale ; votre situation a ses propres contraintes. Décrivez-la, nous répondons avec un périmètre.

Ouvrir le formulaire