Introduction
Le Helm capacity planning consiste à prévoir et à contrôler combien de CPU, de mémoire et de réplicas vos workloads Kubernetes nécessitent, puis à encoder ces décisions dans vos charts Helm. Bien appliqué, il réduit les coûts, évite les indisponibilités et rend la montée en charge prévisible. Ce guide met l'accent sur la pratique : un workflow reproductible, des extraits values.yaml et HPA concrets, et un pilote sûr à exécuter dans un environnement isolé avant un déploiement plus large. Les termes Helm capacity planning, Helm resources, Helm limits, Helm sizing et Helm scaling sont utilisés là où ils sont pertinents.
Vue d'ensemble du workflow
Utilisez cette séquence répétable pour chaque service :
- Définir les cibles : RPS de crête attendu, concurrence, SLO de latence et budget d'erreur.
- Établir une base locale : lancer le service dans Docker ou un namespace de test et mesurer l'utilisation CPU%, la mémoire RSS et la latence P95 sous une charge représentative.
- Estimer le coût par requête : dériver les ms CPU/req et la mémoire stable par worker.
- Choisir la forme de pod : définir requests et limits pour s'aligner sur la taille des nœuds et les objectifs QoS.
- Dimensionner les réplicas : calculer pour le pic avec marge, puis configurer les cibles HPA.
- Instrumenter : exposer les métriques de l'app et de Nginx ; scrapper avec Prometheus.
- Valider : exécuter une charge contrôlée et vérifier throttling, OOM et latence.
- Déployer progressivement : augmenter le trafic tout en observant les signaux clés.
Concepts et signaux clés
Définitions essentielles :
- CPU request et limit : millicœurs réservés et plafond. Le risque de throttling augmente à l'approche du limit.
- Mémoire request et limit : MiB réservés et plafond. Un dépassement provoque un OOMKill.
- Classes QoS : Guaranteed (requests = limits), Burstable (requests < limits), BestEffort (sans requests). Guaranteed réduit le risque d'éviction ; Burstable permet du burst CPU.
Signaux principaux à suivre :
- Utilisation CPU par pod (cible 50-70% au pic pour garder de la marge)
- Mémoire RSS et comportement du GC (cible <80% du limit au pic)
- Latence P95 et taux d'erreur
- Profondeur de file ou backlog pour les workers
- Connexions actives Nginx et taux de 5xx
Formules rapides (back-of-the-envelope)
- rps_per_pod ~= concurrency_per_pod / p95_service_time_sec
- replicas_needed = ceil((peak_rps / rps_per_pod) * safety_factor)
- cpu_request_per_pod ~= p95_cpu_ms_per_req * rps_per_pod / 1000 (en cœurs)
Choisissez un safety_factor entre 1,3 et 2,0 selon la burstiness du trafic.
Exemples pratiques
Exemple A : Nginx en frontal d'un site statique ou d'une API upstream
Hypothèses : p95 à 10 ms par requête sur cache hit, pic cible 2000 rps, Nginx consomme ~60-100 Mi de mémoire stable par worker.
- rps_per_pod : avec worker_processes = 2, concurrency_per_pod ~= 2 × 1024 via epoll. La concurrence est souvent limitée par le CPU ; prenez une valeur prudente rps_per_pod = 1500.
- replicas_needed = ceil((2000 / 1500) × 1,4) = 2
- cpu_request_per_pod : supposez 0,2 CPU à la cible ; memory_request 128Mi.
values.yaml
replicaCount: 2
resources:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
nginx:
workerProcesses: 2
workerConnections: 2048
hpa:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 60
HPA template (autoscaling/v2)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "chart.fullname" . }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "chart.fullname" . }}
minReplicas: {{ .Values.hpa.minReplicas }}
maxReplicas: {{ .Values.hpa.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.hpa.targetCPUUtilizationPercentage }}
Exemple B : API liée au CPU (Go ou Node.js)
Hypothèses : p95 50 ms/req à saturation de 1 vCPU ; pic cible 600 rps.
- rps_per_pod à 60% de CPU : 0,6 CPU × (1 / 0,050) = 12 rps si monothread. Go peut utiliser plusieurs threads ; avec 4 workers → ~48 rps.
- replicas_needed = ceil((600 / 48) × 1,5) = 19
Pour réduire le nombre de réplicas, augmentez le CPU par pod : demandez 2 vCPU.
- Nouveau rps_per_pod ~= 2 × 0,6 / 0,050 = 24 rps par worker ; avec 4 workers ~96 rps.
- replicas_needed = ceil((600 / 96) × 1,4) = 9
values.yaml
replicaCount: 9
resources:
requests:
cpu: "2"
memory: 512Mi
limits:
cpu: "3"
memory: 1Gi
env:
- name: GOMAXPROCS
value: "4"
- name: NODE_OPTIONS
value: "--max-old-space-size=512"
hpa:
enabled: true
minReplicas: 6
maxReplicas: 20
targetCPUUtilizationPercentage: 65
Exemple C : Worker en arrière-plan tirant d'une file
Hypothèses : chaque job consomme 200 ms CPU et 300 ms mur ; cible 500 jobs/s ; concurrence 10.
- jobs_per_pod = 10 / 0,300 ~= 33 jobs/s (borné par le temps mur)
- cpu_request_per_pod = (0,200 s × 33) = 6,6 secondes CPU/s → 6,6 cœurs à 100% ; à 70% cible, demandez ~4,6 cœurs. Trop élevé ; réduisez la concurrence à 4 par pod ou augmentez le nombre de pods.
- Avec concurrence 4 : jobs_per_pod ~= 13 ; cpu_request ~= (0,200 × 13) = 2,6 cœurs ; request 2, limit 3.
- replicas_needed = ceil((500 / 13) × 1,3) = 50
values.yaml
replicaCount: 50
resources:
requests:
cpu: "2"
memory: 512Mi
limits:
cpu: "3"
memory: 768Mi
env:
- name: WORKER_CONCURRENCY
value: "4"
hpa:
enabled: true
minReplicas: 20
maxReplicas: 120
targetCPUUtilizationPercentage: 60
Prometheus : requêtes pour valider la capacité
- RPS :
sum(rate(http_requests_total[5m])) by (pod) - Latence :
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, pod)) - CPU :
sum(rate(container_cpu_usage_seconds_total{image!=""}[5m])) by (pod) - Mémoire :
container_memory_working_set_bytes{image!=""}
Exemple GitLab CI/CD pour un déploiement Helm
deploy:
stage: deploy
image: alpine/helm:3.14.0
script:
- helm upgrade --install api charts/api \
--namespace prod \
--set resources.requests.cpu=2 \
--set resources.requests.memory=512Mi \
--set hpa.targetCPUUtilizationPercentage=65
rules:
- if: "$CI_COMMIT_BRANCH == 'main'"
Plan pilote local
Gardez le premier pilote étroit, mesurable et simple à inspecter localement :
- Choisir un service au trafic stable et aux SLO clairs.
- Ajouter l'instrumentation : taux de requêtes, latence P95, CPU, RSS.
- Implémenter un values.yaml avec des requests/limits explicites et une HPA.
- Dans un namespace sandbox, exécuter une courte charge tout en observant Prometheus.
- Vérifier le throttling, les OOM et les régressions de latence.
- Geler la version du chart et documenter la forme de pod et la politique de scaling retenues.
- Augmenter progressivement le trafic et re-vérifier les mêmes tableaux de bord.
Marges de sécurité opérationnelles
Valeurs pratiques par défaut :
- Marge CPU : viser 60-70% d'utilisation au pic ; cible HPA 60-65%.
- Marge mémoire : dimensionner pour rester sous 70-80% du limit au pic.
- N+1 : garder au moins une réplique de réserve au-dessus du minimum.
- Readiness : configurer readinessProbe pour protéger la latence pendant le warmup.
- QoS : pour les services sensibles à la latence, rapprocher requests des limits ; pour le trafic web bursty, autoriser un limit CPU 1,5×-2× au-dessus du request.
Risques courants :
- OOMKill dû à un pic soudain ou à des fuites : surveiller le working set, éviter des limits mémoire trop proches de l'état stable.
- Throttling CPU : une charge proche du limit induit des pics de latence ; baisser la cible HPA ou augmenter les limits.
- Fragmentation de densité des pods : des pods surdimensionnés limitent le scheduling ; privilégier des tailles modérées.
- Sur-engagement des nœuds : aligner requests avec la capacité des nœuds et le cluster autoscaler.
- I/O et stockage éphémère : définir requests/limits si le workload écrit sur disque.
Contrôles à ajouter :
- PodDisruptionBudget pour maintenir un minimum de réplicas lors des maintenances.
- ResourceQuota et LimitRange par namespace pour éviter les pods incontrôlés.
- Alertes : CPU élevé sur 10 min, mémoire >80% du limit, burn rate SLO de latence, taux de 5xx.
Conclusion
Le Helm capacity planning consiste à mesurer, modéliser puis encoder les décisions de ressources comme du code. Partez d'objectifs clairs, établissez une base locale, choisissez une forme de pod avec marge, fixez des cibles HPA et validez sous charge. Maintenez une marge de sécurité, suivez les bons signaux et faites croître de manière itérative. Les exemples et snippets fournis permettent de lancer un petit pilote sûr, d'en tirer des enseignements et d'évoluer en confiance.