Aller au contenu
English
Conteneurs & Orchestration medium

Requests et Limits Kubernetes : Évitez les pièges !

30 min de lecture

logo kubernetes

Vos Pods Kubernetes se font tuer avec un mystérieux message OOMKilled ? Ou ils semblent tourner au ralenti malgré un serveur bien dimensionné ? Le problème vient probablement d'une mauvaise configuration des requests et limits, les paramètres qui définissent combien de CPU et de mémoire chaque Pod peut utiliser.

Ce guide vous explique comment configurer ces paramètres pour éviter les ralentissements, les crashs et la surconsommation de ressources.

Prérequis : concepts Kubernetes de base et un cluster fonctionnel.

  • Comprendre la différence entre requests (ressources garanties) et limits (plafonds)
  • Identifier les symptômes d'une mauvaise configuration (OOMKilled, throttling)
  • Choisir la bonne classe de QoS selon le type d'application
  • Configurer les ressources pour des workloads critiques, élastiques et non critiques
  • Diagnostiquer les problèmes avec kubectl et Prometheus

Les requests et limits sont les deux paramètres qui contrôlent la consommation de ressources d'un conteneur.

Les requests définissent la quantité minimale de ressources (CPU/mémoire) dont un conteneur a besoin pour fonctionner. Kubernetes réserve ces ressources sur le nœud où le Pod est déployé.

Exemple : si un Pod demande 500m CPU (soit 50% d'un CPU), Kubernetes s'assure qu'un nœud ayant au moins cette capacité disponible l'accueille.

Les limits définissent la quantité maximale de ressources qu'un conteneur peut consommer. Si un Pod dépasse cette limite, Kubernetes applique des restrictions :

  • CPU : throttling (ralentissement forcé)
  • Mémoire : OOMKilled, le conteneur est tué et redémarré selon la restartPolicy

Exemple : si un Pod a une limit de 1 CPU, il ne pourra jamais dépasser cette valeur, même en cas de forte demande.

Deux choses différentes se vérifient, et les confondre fait tourner en rond : ce que le Pod déclare, qui se lit dans son manifeste, et ce qu'il consomme réellement, qui demande le serveur de métriques. Le premier explique un placement, le second explique une lenteur.

Fenêtre de terminal
kubectl describe pod mon-pod | grep -A 10 "Limits:"

Sortie attendue :

Limits:
cpu: 500m
memory: 256Mi
Requests:
cpu: 250m
memory: 128Mi

Une surprise attend ceux qui n'ont déclaré que des limits : Kubernetes recopie la limit dans la request au moment d'enregistrer le Pod. Un manifeste qui ne porte que limits: {cpu: 300m, memory: 128Mi} se relit ainsi :

{"limits":{"cpu":"300m","memory":"128Mi"},"requests":{"cpu":"300m","memory":"128Mi"}}

Le scheduler réserve donc bien 300m sur le nœud, et le Pod se retrouve en classe Guaranteed sans que personne l'ait demandé. Relisez toujours le Pod après création plutôt que votre manifeste :

Fenêtre de terminal
kubectl get pod mon-pod -o jsonpath='{.spec.containers[0].resources}'

Schéma requests vs limits Kubernetes

Le request (vert) est l'espace garanti par le scheduler. La zone burst (jaune) est disponible si le nœud a de la capacité. La limit (rouge) est le plafond absolu, au-delà, c'est le throttling CPU ou l'OOMKilled mémoire.

Si vous définissez mal requests et limits, vous risquez trois types de problèmes.

Le throttling CPU se produit lorsque votre conteneur atteint sa limit CPU. Kubernetes utilise le planificateur CFS (Completely Fair Scheduler) de Linux pour le ralentir.

Symptômes :

  • L'application devient lente et peu réactive
  • Des timeouts apparaissent sur les requêtes
  • Une baisse de performance inattendue en période de charge

Comment détecter le throttling :

Fenêtre de terminal
kubectl top pod

Deux précisions avant de la taper. D'abord, cette commande ne marche pas sur un cluster neuf : elle interroge l'API de métriques, fournie par metrics-server, qui n'est installé ni sur kind ni sur la plupart des distributions minimales.

Sortie sans metrics-server
error: Metrics API not available

Ensuite, kubectl top rend des valeurs absolues, en millicores et en octets, jamais un pourcentage. Comparez vous-même la valeur affichée à la limits.cpu du conteneur : un Pod qui plafonne à 500m avec une limite à 500m est en throttling.

La mesure qui ne se discute pas vient de Prometheus, car elle compte le temps réellement volé au conteneur plutôt que de l'inférer :

rate(container_cpu_cfs_throttled_seconds_total[5m])

Une valeur non nulle signifie que le noyau a effectivement suspendu le conteneur pendant ses quotas.

Solutions :

  • Évitez de définir une limit CPU si ce n'est pas nécessaire
  • Augmentez la limit CPU si votre application a besoin de plus de puissance
  • Utilisez le Horizontal Pod Autoscaler (HPA) pour ajouter des Pods au lieu de restreindre un seul

Contrairement au CPU, la mémoire ne se ralentit pas : on ne peut pas rendre un octet déjà alloué. Le CPU se bride, la mémoire se reprend, et la seule façon de la reprendre est de tuer le processus. C'est le noyau qui s'en charge quand le cgroup atteint sa limite, avec le message OOMKilled.

Deux précisions que les schémas simplifiés escamotent. C'est le conteneur qui est tué, pas le Pod : ce que vous observez ensuite dépend de la restartPolicy et du contrôleur. Et le déclenchement est réactif, lié à la pression mémoire du cgroup : une charge peut brièvement frôler sa limite avant que le noyau n'intervienne.

Symptômes :

  • Redémarrages en boucle (CrashLoopBackOff)
  • Pertes de données si l'application ne gère pas bien les interruptions
  • Instabilité du service si plusieurs Pods critiques sont affectés

Comment détecter un OOMKilled :

Le plus simple d'abord, la colonne STATUS le dit directement :

Fenêtre de terminal
kubectl get pod mon-pod
Sortie
NAME READY STATUS RESTARTS AGE
oom 0/1 OOMKilled 0 3s

Attention au champ que vous interrogez si vous scriptez. Beaucoup d'exemples lisent lastState, qui ne contient la raison qu'après un redémarrage. Sur un Pod en restartPolicy: Never, il est vide et la raison vit dans state :

Fenêtre de terminal
# Le conteneur tourne encore après avoir été tué et relancé
kubectl get pod mon-pod \
-o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
# Le conteneur est mort et n'a pas redémarré
kubectl get pod mon-pod \
-o jsonpath='{.status.containerStatuses[0].state.terminated.reason}'

Interrogez les deux, ou lisez simplement kubectl describe pod, qui affiche la Reason quel que soit le cas.

Le code de sortie lève la dernière ambiguïté, et c'est lui qu'on retrouve dans les journaux d'une chaîne de déploiement qui n'a plus accès au cluster :

CodeRaisonCe qui s'est passé
137OOMKilledLe noyau a tué le processus par SIGKILL, 128 + 9
1ErrorL'application s'est arrêtée en erreur, la mémoire n'y est pour rien
Fenêtre de terminal
kubectl get pod mon-pod \
-o jsonpath='{.status.containerStatuses[0].state.terminated.exitCode}'

Un 137 oriente vers limits.memory ou vers une fuite mémoire ; un 1 oriente vers les journaux de l'application. Confondre les deux fait augmenter une limite mémoire qui n'était pas en cause.

Solutions :

  • Augmentez la limite mémoire
  • Surveillez la consommation mémoire pour détecter des fuites éventuelles
  • Utilisez Prometheus pour monitorer container_memory_working_set_bytes

Pod Pending : des requests que personne ne peut servir

Section intitulée « Pod Pending : des requests que personne ne peut servir »

Le troisième symptôme ne fait ni crasher ni ralentir : le Pod ne démarre jamais. Des requests supérieures à ce qu'un nœud peut offrir laissent le scheduler sans candidat, et le Pod reste en Pending indéfiniment.

Fenêtre de terminal
kubectl describe pod mon-pod

Le message d'échec nomme la ressource qui manque, nœud par nœud :

Sortie
0/2 nodes are available: 1 Insufficient cpu, 1 Insufficient memory,
1 node(s) had untolerated taint(s).

Lisez-le comme un décompte : sur 2 nœuds, aucun ne convient, et le motif diffère d'un nœud à l'autre. Le mot Insufficient désigne toujours les requests, jamais les limits : c'est la preuve que le placement se joue sur ce que le Pod demande, pas sur son plafond.

Si un nœud manque de ressources, Kubernetes doit faire de la place pour les Pods les plus importants. Il utilise un mécanisme d'éviction pour supprimer les Pods non prioritaires.

L'ordre d'éviction dépend de la classe de QoS (Quality of Service) de chaque Pod.

Kubernetes attribue automatiquement une classe de QoS à chaque Pod en fonction de sa configuration de ressources.

ClasseConditionPriorité à l'éviction
Guaranteedrequests = limits, CPU et mémoire, sur tous les conteneursDerniers évincés
Burstableau moins un conteneur avec des requests, sans remplir la condition ci-dessusÉvincés après BestEffort
BestEffortaucun conteneur ne déclare ni requests ni limitsPremiers évincés

Les mots « tous les conteneurs » ne sont pas une précaution de rédaction, c'est le piège de cette classification. Un Pod dont le conteneur principal est parfaitement réglé et dont le sidecar ne déclare rien n'est pas Guaranteed :

Fenêtre de terminal
kubectl get pod qos-deux-conteneurs -o jsonpath='{.status.qosClass}'
Sortie
Burstable

Vous croyez être protégé de l'éviction, vous ne l'êtes pas, et c'est le conteneur oublié qui a fait basculer le Pod entier.

Priorité d'éviction selon la QoS Kubernetes

En cas de pression mémoire sur un nœud, Kubernetes évince d'abord les Pods BestEffort, puis les Burstable, et en dernier recours les Guaranteed.

Vérifier la QoS d'un Pod :

Fenêtre de terminal
kubectl get pod mon-pod -o jsonpath='{.status.qosClass}'

Il n'existe pas de valeur universellement bonne : le réglage dépend de ce que vous protégez. Un service critique veut des garanties, un traitement par lots veut de l'élasticité, un environnement de test veut surtout ne pas gaspiller.

  1. Identifier le type de workload

    Type de workloadQoS recommandéePourquoi
    Base de données, API critiqueGuaranteedProtégé contre l'éviction
    Serveur web avec pics de chargeBurstableDoit pouvoir monter en charge
    Job batch temporaireBurstableA besoin de plus de ressources si disponible
    Tâches non critiquesBestEffortPeut être arrêté en cas de besoin
  2. Configurer selon la QoS choisie

Pour les applications critiques (bases de données, APIs essentielles), configurez requests = limits :

resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "2"
memory: "4Gi"

Pour les applications qui supportent les variations de charge :

resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"

Cette configuration garantit 250m CPU et 128Mi en permanence, mais autorise jusqu'à 500m CPU et 256Mi en cas de pic.

Pour les tâches qui peuvent être interrompues sans conséquence :

resources: {}

Ces quatre lignes suffisent à décider dans la plupart des cas. Retenez surtout la colonne de droite : c'est le risque encouru qui doit guider le réglage, pas une valeur recopiée d'un exemple.

Type d'applicationQoSRequests CPULimits CPURequests MémoireLimits Mémoire
Base de données, API critiqueGuaranteed2 CPU2 CPU4Gi4Gi
Serveur webBurstable250m500m128Mi256Mi
Batch jobBurstable500m1000m512Mi1024Mi
Pods de testBestEffort----

Les trois symptômes ci-dessous se distinguent par ce que fait le Pod. Un Pod qui ne démarre pas renvoie aux requests et au placement ; un Pod qui redémarre renvoie à la limite mémoire ; un Pod qui ralentit renvoie à la limite CPU. Identifier lequel des trois vous avez sous les yeux fait l'essentiel du diagnostic.

SymptômeCause probableSolution
Pod OOMKilledLimit mémoire trop basseAugmenter limits.memory
Application lenteThrottling CPUAugmenter limits.cpu ou supprimer la limit
Pod Pending, Insufficient cpu ou Insufficient memoryRequests > ressources disponiblesRéduire requests ou ajouter des nœuds
Éviction fréquenteQoS BestEffortAjouter des requests pour passer en Burstable
  • Requests = ressources garanties, utilisées par le scheduler pour placer le Pod
  • Limits = plafond maximal, déclenche throttling (CPU) ou OOMKill (mémoire)
  • QoS Guaranteed (requests = limits) : pour les workloads critiques
  • QoS Burstable (requests < limits) : pour les workloads avec pics de charge
  • QoS BestEffort (aucune config) : premiers évincés, à éviter en production
  • Le throttling CPU ralentit l'application ; l'OOMKilled la tue
  • Guaranteed exige requests = limits sur tous les conteneurs, sidecars compris
  • kubectl top réclame metrics-server et ne rend pas de pourcentage
  • Pour un OOMKill, lisez la colonne STATUS, ou state.terminated.reason si le Pod n'a pas redémarré

Sept questions sur ce qui coûte le plus cher : la différence entre garantie et plafond, la condition exacte de la classe Guaranteed, et ce qui distingue le bridage CPU de l'arrêt pour dépassement mémoire.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn