Kubernetes
4 min de lecture

Java sur Kubernetes : booster le CPU au démarrage, pas après

Les applications Java ont besoin de plus de CPU au démarrage qu'en nominal. kube-startup-cpu-boost de Google résout ce dilemme avec un boost temporaire automatique.

Arthur Zinck
Arthur Zinck
Expert DevOps Kubernetes & Cloud

Une application Spring Boot qui met 45 secondes à démarrer. Le développeur a suivi les bonnes pratiques : request CPU à 500m, pas de limit. Le problème : la JVM a besoin de 2 vCPU pendant les 10 premières secondes pour charger les classes, compiler le bytecode, initialiser le contexte Spring. Après, elle tourne à 200m.

On met quoi en request ? La consommation au démarrage ou la consommation nominale ?

Si on met 2 vCPU en request, le pod réserve ces ressources en permanence. Sur un cluster de 20 pods Java, ça fait 40 vCPU réservés pour des applications qui en utilisent réellement 4 au total. 90% de gaspillage.

Si on met 500m en request, le scheduler place le pod sur un node avec 500m de disponible. Au démarrage, la JVM réclame ses 2 vCPU. Si le node est sous pression, le kernel Linux limite le CPU alloué au conteneur à hauteur de sa request. Le démarrage passe de 10 secondes à 45 secondes. Multiplié par 20 pods qui redémarrent pendant un rolling update, le déploiement prend 15 minutes au lieu de 2.

Et dans le pire des cas : la liveness probe est configurée avec un délai de 30 secondes. Le pod met 45 secondes à démarrer. Kubernetes le considère comme mort et le tue. Le pod redémarre, reprend 45 secondes, se fait tuer à nouveau. CrashLoopBackOff. L’application ne démarre jamais.

J’ai longtemps jonglé avec ce compromis. Augmenter les requests pour le démarrage et accepter le gaspillage. Ou optimiser pour le nominal et accepter les démarrages lents. Pas de bonne réponse.

Jusqu’à kube-startup-cpu-boost.

C’est un projet Google, open source. Le principe : gonfler temporairement les ressources CPU au démarrage, puis les remettre à leur valeur normale une fois l’application prête.

Le fonctionnement repose sur deux mécanismes Kubernetes.

Premier mécanisme : un mutating admission webhook. Quand on crée un pod qui correspond aux critères du boost, le webhook intercepte la requête et modifie les specs à la volée. Une request CPU de 500m ? Le webhook la passe à 1500m avant que le pod soit schedulé. Le pod démarre avec les ressources gonflées.

Deuxième mécanisme : l’in-place resource resize. Introduit en Kubernetes 1.27, ce mécanisme permet de modifier les ressources d’un pod sans le recréer. Une fois la condition de fin atteinte, le controller remet les valeurs originales. Le pod continue de tourner. Pas de restart, pas de disruption.

Prérequis : Kubernetes 1.33 ou supérieur. Ou 1.27+ avec le feature gate InPlacePodVerticalScaling activé.

L’installation se fait via Helm :

helm repo add kube-startup-cpu-boost https://google.github.io/kube-startup-cpu-boost
helm repo update
helm install kube-startup-cpu-boost kube-startup-cpu-boost/kube-startup-cpu-boost \
  -n kube-startup-cpu-boost-system \
  --create-namespace

Le controller se déploie dans le namespace kube-startup-cpu-boost-system.

Ensuite, on définit une ressource StartupCPUBoost :

apiVersion: autoscaling.x-k8s.io/v1alpha1
kind: StartupCPUBoost
metadata:
  name: java-apps-boost
  namespace: production
spec:
  selector:
    matchExpressions:
      - key: app.kubernetes.io/runtime
        operator: In
        values: ["java"]
  resourcePolicy:
    containerPolicies:
      - containerName: app
        percentageIncrease:
          value: 200
  durationPolicy:
    podCondition:
      type: Ready
      status: "True"

Cette configuration applique un boost de +200% sur les CPU requests (une request de 500m passe à 1500m) à tous les pods avec le label app.kubernetes.io/runtime: java dans le namespace production. Le boost s’arrête quand le pod passe Ready.

On peut aussi définir une durée fixe :

durationPolicy:
  fixedDuration:
    unit: Seconds
    value: 60

60 secondes de boost, puis retour à la normale.

Un point important : par défaut, le webhook supprime les CPU limits pendant le boost. C’est cohérent avec la recommandation de ne pas définir de limits CPU. Pour conserver les limits, il faut passer la variable d’environnement REMOVE_LIMITS à false sur le controller, via les values Helm :

helm install kube-startup-cpu-boost kube-startup-cpu-boost/kube-startup-cpu-boost \
  -n kube-startup-cpu-boost-system \
  --create-namespace \
  --set env.removeLimits=false

Les métriques Prometheus sont exposées :

  • boost_containers_total : nombre total de conteneurs boostés
  • boost_containers_active : conteneurs actuellement en boost
  • boost_configurations : configurations actives par namespace

Sur un projet récent, j’ai testé cette solution sur un cluster avec 15 microservices Spring Boot. Avant : démarrage moyen de 38 secondes par pod, rolling update complet en 12 minutes. Après activation du boost à 200% : démarrage moyen de 14 secondes, rolling update en 4 minutes.

Le calcul FinOps est direct. Avant, pour avoir des démarrages rapides, les équipes avaient configuré 1 vCPU en request sur tous les pods Java. 15 pods × 1 vCPU = 15 vCPU réservés en permanence. Consommation réelle en nominal : 3 vCPU.

Après : request à 300m, boost à 200% au démarrage. 15 pods × 0.3 vCPU = 4.5 vCPU réservés. 70% d’économie sur les requests CPU, sans sacrifier les temps de démarrage.

Pour ceux qui gèrent des applications Java sur Kubernetes, kube-startup-cpu-boost résout un vrai problème. On dimensionne pour le nominal, le boost gère le démarrage. Plus de compromis.

Points clés à retenir

  • Les apps Java ont besoin de 3-4x plus de CPU au démarrage qu'en nominal
  • kube-startup-cpu-boost booste les CPU requests temporairement via mutating webhook
  • Le boost s'arrête automatiquement (temps ou condition Ready)
  • Résultat : 70% d'économie sur les requests CPU sans sacrifier les temps de démarrage
kubernetes java cpu startup finops jvm spring-boot

Partager cet article

Twitter LinkedIn