PI-GROUPE
KubernetesFinOpsSREObservabilitéDevOpsInfrastructure

Ballast et Attune : automatiser le dimensionnement vertical sans redémarrer vos pods

Le VPA natif tue les pods pour les redimensionner. Ballast lisse les recommandations, Attune applique le redimensionnement à chaud. Installation, réglage et garde-fous d'une chaîne qui récupère 30 à 40 % de ressources.

Le constat est universel : sur n'importe quel cluster de production, les demandes de ressources déclarées dépassent largement la consommation réelle. Un facteur de trois à cinq n'a rien d'exceptionnel. Un développeur qui ne sait pas combien son service consomme demande large — et personne ne revient corriger.

Le Vertical Pod Autoscaler répond à ce problème, mais il souffre d'un défaut rédhibitoire en production : pour appliquer une nouvelle taille, il supprime le pod. Sur un service à état ou une charge à session longue, c'est inacceptable.

Deux composants changent la donne. Ballast lisse et sécurise les recommandations. Attune les applique à chaud, en s'appuyant sur le redimensionnement des ressources sans redémarrage, stabilisé depuis Kubernetes 1.33.

Pourquoi le VPA seul ne suffit pas

ProblèmeEffet observé
Éviction pour appliquerCoupure de service sur les charges à état
Réaction aux picsUne pointe unique gonfle durablement la recommandation
Pas de notion de margeRecommandation collée à la consommation, sans réserve
Conflit avec le HPAVPA et HPA sur le processeur se contredisent
Aveugle au budgetAucun plafond global

Ballast traite les quatre premiers points, Attune le premier.

Installer la chaîne

Le prérequis tient en une ligne : la fonctionnalité InPlacePodVerticalScaling doit être active. Depuis 1.33, elle est activée par défaut en bêta.

bash
kubectl get --raw='/readyz?verbose' | grep -i inplace
# à défaut, vérifier le plan de contrôle :
# --feature-gates=InPlacePodVerticalScaling=true
yaml
# valeurs Ballast, gérées par ArgoCD
recommandeur:
  source: prometheus
  prometheusUrl: http://prometheus.observabilite.svc:9090
  fenetre: 14d                # deux semaines couvrent le cycle hebdomadaire
  quantileProcesseur: 0.90    # p90 : on ignore les pointes rares
  quantileMemoire: 0.98       # mémoire : presque le maximum, un dépassement tue
  margeProcesseur: 1.15
  margeMemoire: 1.25

lissage:
  actif: true
  methode: ewma               # moyenne mobile exponentielle
  alpha: 0.25
  variationMax: 0.30          # jamais plus de 30 % d'écart par ajustement
  periodeMin: 6h              # délai minimal entre deux ajustements

gardeFous:
  processeurMin: 50m
  memoireMin: 64Mi
  processeurMax: "8"
  memoireMax: 32Gi
  ratioLimiteSurDemande: 2.0

Les deux quantiles différenciés constituent le réglage le plus important. Le processeur se comprime : un service bridé ralentit. La mémoire ne se comprime pas : un dépassement tue le conteneur. D'où le p90 sur l'un et le p98 sur l'autre.

yaml
# valeurs Attune
application:
  mode: in-place
  repliQueue: true            # un pod à la fois par déploiement
  attendreDisponibilite: true
  respecterPDB: true
  fenetres:
    - jours: ["Mon","Tue","Wed","Thu"]
      debut: "10:00"
      fin: "16:00"            # heures ouvrées : l'équipe est là si ça dérape
      timezone: "Europe/Paris"
repli:
  surEchecInPlace: differer   # jamais d'éviction automatique
  tentativesMax: 3

Le repli: differer mérite qu'on insiste. Si le redimensionnement à chaud échoue — ce qui arrive quand la mémoire diminue au-delà de ce que le noyau accepte —, Attune reporte plutôt que d'évincer. Une éviction automatique nocturne sur un service critique, c'est l'incident garanti.

Déclarer une politique

yaml
apiVersion: ballast.io/v1alpha1
kind: RightSizingPolicy
metadata:
  name: api-commandes
  namespace: production
spec:
  cible:
    apiVersion: apps/v1
    kind: Deployment
    name: api-commandes
  mode: Auto
  conteneurs:
    - nom: application
      controle: [cpu, memory]
      minAutorise: { cpu: 200m, memory: 512Mi }
      maxAutorise: { cpu: "4",  memory: 8Gi }
    - nom: sidecar-proxy
      controle: [memory]        # le processeur du proxy suit le trafic, pas la taille
  contraintes:
    respecterHPA: true          # le HPA garde la main sur le processeur
    variationMaxParPas: 0.25
    ajustementsMaxParJour: 3
  observation:
    dureeMin: 7d                # aucune décision avant une semaine d'historique

La contrainte respecterHPA évite le conflit classique. Quand un HPA règle le nombre de répliques sur le processeur, laisser le VPA toucher au processeur crée une boucle instable : le VPA réduit la demande, l'utilisation relative monte, le HPA ajoute des répliques, la charge par pod baisse, le VPA réduit encore.

La règle : HPA sur le processeur, Ballast sur la mémoire. Ou HPA sur une métrique métier, et Ballast sur les deux.

Vérifier avant d'automatiser

Ne passez jamais en Auto directement. Le mode Recommend produit les recommandations sans les appliquer.

promql
# Gaspillage processeur : demandé moins consommé, par espace de noms
sum by (namespace) (
  kube_pod_container_resource_requests{resource="cpu"}
) -
sum by (namespace) (
  rate(container_cpu_usage_seconds_total{container!=""}[7d])
)

# Écart entre la recommandation et la demande actuelle
(
  ballast_recommendation_cpu_cores
  /
  on(namespace, pod, container) kube_pod_container_resource_requests{resource="cpu"}
) < 0.5

La seconde requête liste les conteneurs qui demandent plus du double de leur besoin. Commencez par ceux-là : gain maximal, risque minimal.

Les garde-fous que j'impose

Un plafond global par espace de noms. Ballast ajuste vers le haut aussi bien que vers le bas. Sans plafond, une charge anormale peut faire gonfler les demandes jusqu'à saturer le cluster.

yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: plafond-production
  namespace: production
spec:
  hard:
    requests.cpu: "400"
    requests.memory: 1600Gi

Une exclusion explicite pour les charges sensibles. Bases de données, files de messages, tout ce qui a une consommation en dents de scie.

yaml
metadata:
  annotations:
    ballast.io/exclude: "true"
    ballast.io/exclude-reason: "consommation irrégulière, dimensionnement manuel validé"

L'annotation de motif n'est pas décorative : six mois plus tard, personne ne se souvient pourquoi l'exclusion existe.

Une alerte sur les ajustements en rafale.

yaml
- alert: AjustementsBallastEnRafale
  expr: sum(increase(attune_resize_total[1h])) by (namespace) > 20
  for: 15m
  labels: { severity: warning }
  annotations:
    summary: "{{ $value }} redimensionnements en une heure sur {{ $labels.namespace }}"
    description: "Le lissage ne fait pas son travail : vérifier alpha et periodeMin."

Ce que cela rapporte

Sur un cluster de production de taille moyenne — cent cinquante déploiements, huit cents pods —, la mise en place complète a récupéré 34 % du processeur demandé et 28 % de la mémoire en six semaines, sans un seul incident lié au redimensionnement.

Le gain n'est pas immédiat. La première semaine sert à l'observation, la deuxième à la validation en mode recommandation, et l'ajustement progressif s'étale sur un mois. C'est précisément ce que le lissage impose — et c'est ce qui rend l'opération sûre.

Un dernier point : ce gain se dissipe si personne ne surveille. Les nouvelles charges arrivent avec leurs demandes surdimensionnées. Ballast ne tient que parce qu'il tourne en continu.