OOMKiller : Comment identifier et stopper vos fuites mémoire

Vos pods Kubernetes sont tués sans prévenir ? Apprenez à traquer les fuites mémoires, configurer correctement les limits et utiliser les outils de profiling pour stabiliser votre production.

Traquer et éradiquer les fuites de mémoire sous Kubernetes

Visualisation d'un pod Kubernetes subissant un crash de type Out Of Memory (OOMKilled) avec une transition claire entre la saturation de la RAM et l'arrêt brutal du conteneur.

Votre téléphone vibre en pleine nuit : une alerte de production signale qu'un microservice critique vient de s'effondrer. En ouvrant vos tableaux de bord, vous constatez que le conteneur a été tué brutalement, laissant pour seule trace un statut laconique. Ce phénomène d'extinction soudaine, redouté par tous les administrateurs système, est la signature classique d'une saturation de mémoire physique gérée par la plateforme d'orchestration.

Comprendre et résoudre ces pannes nécessite de plonger sous le capot de la gestion des ressources de l'orchestrateur. Ce tutoriel vous guidera pas à pas pour identifier les fuites de mémoire, configurer des limites adaptées à la charge réelle et mettre en place des outils d'investigation directement exploitables sur vos clusters de production.

Pourquoi vos conteneurs meurent en silence

Pour comprendre le cycle de vie de la mémoire, imaginez que votre conteneur est un locataire dans un immeuble de bureaux. L'orchestrateur Kubernetes agit comme le gérant de l'immeuble. Lorsque vous définissez des limites de ressources, vous passez un contrat d'occupation.

Si votre application consomme plus de mémoire que la limite autorisée, le système d'exploitation de la machine hôte intervient pour protéger la stabilité globale. Le mécanisme OOMKilled (pour Out of Memory Killed) est le dispositif de sécurité ultime du noyau Linux qui élimine instantanément le processus le plus gourmand pour éviter un crash complet de la machine physique. Contrairement à une exception logicielle classique, ce signal ne laisse pas le temps à l'application de fermer proprement ses connexions actives.

Préparation de notre boîte à outils de diagnostic

Avant de pouvoir soigner un système malade, nous devons installer les instruments de mesure appropriés. Nous allons utiliser l'outil de ligne de commande standard et nous assurer que le serveur de métriques du cluster est opérationnel.

Vérifiez d'abord la présence du service d'agrégation des métriques en interrogeant l'API du cluster. Cette commande permet de valider que vos nœuds transmettent correctement les données d'utilisation en temps réel.

kubectl get apiservices v1beta1.metrics.k8s.io

Si le service est actif, le terminal doit renvoyer une confirmation de disponibilité. Dans le cas contraire, vous devrez déployer le serveur de métriques officiel dans votre cluster à l'aide de la commande suivante :

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Cette installation fournit l'infrastructure minimale pour que la commande de surveillance rapide de l'état des ressources puisse interroger l'état de consommation de vos namespaces.

Identifier le coupable : de la détection à l'analyse initiale

Un tableau de bord Grafana affichant les métriques de consommation mémoire d'un pod, mettant en évidence une courbe de croissance linéaire ininterrompue caractéristique d'une fuite.

Une fois les outils installés, l'étape suivante consiste à intercepter les signaux faibles d'une dégradation de mémoire. Une application saine présente une courbe de consommation en dents de scie : la mémoire monte lors des traitements, puis redescend après le passage du ramasse-miettes. Une fuite de mémoire se caractérise par une croissance constante et rectiligne, insensible aux phases d'inactivité.

Analyser les événements système et les codes d'erreur

Le premier réflexe face à un conteneur défaillant est d'inspecter l'historique de son cycle de vie. L'orchestrateur garde en mémoire les raisons de la mort du précédent processus sous forme de métadonnées standardisées.

Lancez l'inspection du pod suspect en ciblant précisément les statuts de terminaison des conteneurs via la commande de description détaillée :

kubectl describe pod -n production payment-service-7f89b4b5c-2x9v4

Résultat:

Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
  Started:      Mon, 25 May 2026 14:32:01 +0200
  Finished:     Mon, 25 May 2026 18:14:22 +0200

L'élément clé ici est le Exit Code: 137. En informatique, un code de sortie supérieur à 128 indique que le processus s'est arrêté suite à la réception d'un signal système d'arrêt d'urgence. Le code 137 correspond précisément au signal de fin de tâche immédiate (Signal 9, ou SIGKILL) envoyé par le gestionnaire de mémoire du système d'exploitation.

Une configuration de test pour provoquer le crash

Pour apprendre à manipuler et observer ce comportement sans impacter vos utilisateurs, nous allons déployer un conteneur volontairement instable. Ce fichier de configuration décrit un pod utilisant une image de test configurée pour allouer de la mémoire de façon continue jusqu'à saturer sa limite matérielle.

Créez un fichier nommé memory-leak-demo.yaml contenant les spécifications de ressources restrictives suivantes :

apiVersion: v1
kind: Pod
metadata:
  name: memory-leak-demo
  namespace: default
spec:
  containers:
  - name: leak-detector
    image: polylux/stress-ng
    args: ["--vm", "1", "--vm-bytes", "150M", "--timeout", "60s"]
    resources:
      requests:
        memory: "50Mi"
        cpu: "100m"
      limits:
        memory: "100Mi"
        cpu: "200m"

Dans cette configuration, l'application demande une réservation garantie de 50 Mégasegundes de mémoire physique via le paramètre requests.memory. Cependant, le paramètre limits.memory fixe un plafond infranchissable de 100 Mégasegundes. Le programme de test stress-ng tente de consommer immédiatement 150 Mégasegundes, ce qui dépasse largement le cadre autorisé.

Appliquez cette configuration sur votre cluster pour observer la réaction immédiate de l'ordonnanceur de tâches :

kubectl apply -f memory-leak-demo.yaml

Surveillez en continu le statut du pod en observant l'évolution de son état grâce à l'option de suivi en direct :

kubectl get pods --watch

Résultat:

NAME                 READY   STATUS      RESTARTS   AGE
memory-leak-demo     1/1     Running     0          5s
memory-leak-demo     0/1     OOMKilled   1          12s

Le système a détecté le dépassement en moins de dix secondes. Le conteneur a été éliminé sur-le-champ, puis l'orchestrateur a incrémenté le compteur de redémarrages pour tenter de rétablir le service.

Configurer des garde-fous robustes et collecter des dumps en production

Pour éviter que ces interruptions brutales ne nuisent à la disponibilité de vos applications en production, vous devez mettre en place une stratégie de défense en profondeur. Cela implique un dimensionnement intelligent et la capture automatique de l'état interne de la mémoire logicielle juste avant le crash.

Schéma décisionnel et flux d'exécution lors de la détection d'une fuite de mémoire et d'un crash OOMKilled sur un nœud Kubernetes

Le schéma ci-dessus illustre la chaîne de réactions lors d'une saturation de mémoire. Lorsque le conteneur atteint le plafond physique autorisé, le mécanisme d'éviction du noyau Linux intervient immédiatement. Pour résoudre efficacement le problème, notre architecture de production doit d'une part écrire un cliché de diagnostic (Heap Dump) sur un espace de stockage persistant, et d'autre part notifier l'équipe d'exploitation via la chaîne d'alerte branchée sur l'agent de supervision.

L'art du dimensionnement des ressources mémoire

L'un des pièges les plus courants consiste à copier-coller les configurations de ressources d'un service à un autre sans analyser le comportement réel de l'environnement d'exécution de votre langage de programmation (machine virtuelle Java, moteur d'exécution Node.js ou binaire compilé en Go).

Paramètre Kubernetes Rôle opérationnel Comportement en cas de saturation du nœud Comportement en cas de dépassement individuel
Requests (Demandes) Garantit la réservation minimale d'espace physique sur le nœud lors de l'ordonnancement. Le pod est protégé tant que sa consommation globale reste sous cette valeur de réservation. Aucune sanction immédiate si l'hôte possède encore de la mémoire disponible.
Limits (Limites) Fixe le plafond absolu d'allocation de mémoire virtuelle pour le conteneur. Le pod est prioritaire pour l'expulsion si le nœud physique s'approche de la saturation. Le processus principal est immédiatement tué par le noyau (OOMKilled - Code 137).

La règle d'or pour la mémoire physique est d'éviter le sur-engagement massif. Contrairement au processeur (CPU) qui peut être ralenti ou partagé dans le temps, la mémoire physique ne peut pas être compressée. Si deux conteneurs réclament en même temps de la mémoire physique indisponible, l'un d'eux doit mourir.

Le danger du Swap sous Kubernetes

Par défaut, la mémoire d'échange (Swap) sur le disque est désactivée sur la majorité des nœuds Kubernetes pour des raisons de performances prévisibles. Tout dépassement de la limite physique déclarée se traduira donc inévitablement par un arrêt immédiat de votre processus, sans avertissement préalable.

Automatiser la capture de Heap Dumps en cas de détresse

Pour corriger une fuite de mémoire applicative, les journaux d'erreurs textuels classiques sont souvent insuffisants. Vous devez analyser l'état interne de la mémoire, appelé Heap Dump. Cette section présente une configuration de production pour une application Java Spring Boot, configurée pour sauvegarder son état sur un espace de stockage persistant avant d'être arrêtée.

Créez la configuration de production suivante sous le nom production-app-recovery.yaml :

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: diagnostics-storage-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secured-payment-gateway
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: payment-gateway
  template:
    metadata:
      labels:
        app: payment-gateway
    spec:
      volumes:
      - name: heap-dumps-volume
        persistentVolumeClaim:
          claimName: diagnostics-storage-pvc
      containers:
      - name: java-application
        image: eclipse-temurin:17-jdk
        env:
        - name: JAVA_TOOL_OPTIONS
          value: "-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/diagnostics/dumps/pay-err.hprof -XX:InitialRAMPercentage=40.0 -XX:MaxRAMPercentage=75.0"
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "1000m"
        volumeMounts:
        - name: heap-dumps-volume
          mountPath: /diagnostics/dumps

Analysons en détail les choix d'ingénierie appliqués dans ce déploiement de production :

  • JAVA_TOOL_OPTIONS : Nous transmettons des options système directement à la machine virtuelle. L'option -XX:+HeapDumpOnOutOfMemoryError demande l'écriture automatique du fichier d'analyse dès que la mémoire de l'application sature.
  • -XX:HeapDumpPath : Oriente le fichier généré vers le point de montage de notre volume persistant pour garantir que les données survivront à la destruction et au redémarrage du conteneur.
  • -XX:MaxRAMPercentage=75.0 : C'est une mesure de sécurité cruciale. Nous limitons l'usage de la mémoire dynamique de l'application à 75 % de l'espace total du conteneur (2Gi). Les 25 % restants sont laissés au système d'exploitation interne, aux agents de supervision et aux outils de diagnostic pour éviter que le conteneur ne soit tué par l'orchestrateur avant d'avoir fini d'écrire son rapport d'erreur.
  • diagnostics-storage-pvc : Un volume de stockage durable qui permet aux développeurs de venir récupérer le fichier d'analyse après l'incident pour l'ouvrir dans un outil d'analyse comme Eclipse Memory Analyzer.

Pour vérifier que vos fichiers d'analyse sont bien sauvegardés après un incident de production, vous pouvez lister le contenu du volume partagé à l'aide de la commande d'exécution à distance :

kubectl exec -it -n production deployment/secured-payment-gateway -- ls -lh /diagnostics/dumps

Résultat:

total 1.2G
-rw-r--r-- 1 root root 1.2G May 26 15:44 pay-err.hprof

Vous disposez maintenant d'un fichier de diagnostic complet et inestimable pour isoler la structure de données ou la boucle infinie responsable de la fuite de mémoire de votre application.

Dompter la mémoire pour stabiliser votre plateforme

La gestion de la mémoire au sein d'un cluster d'orchestration ne s'improvise pas. En appliquant une stratégie stricte de définition de vos besoins matériels, couplée à des mécanismes d'écriture automatique d'états d'erreur sur des volumes de stockage persistants, vous transformez des pannes système imprévisibles en sessions de correction de bugs structurées.

En tant qu'ingénieur de production, votre rôle est d'établir ce cadre de confiance pour vos équipes de développement. Configurez vos sondes, ajustez vos marges de sécurité applicative, et offrez à vos services la robustesse nécessaire pour absorber les variations de charge en toute sérénité.

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

29 commentaires

De rien. Le but, c'est que ça marche en prod, pas de faire de la théorie. Bon debug à tous.

27/05/2026 à 01:48

Merci l'auteur, le tuto est clair et concis. Ça change des articles de 50 pages sans aucune commande utile.

26/05/2026 à 21:14

Regarde tes graphs dans Prometheus. Si ça monte en escalier sans jamais redescendre même quand y'a 0 trafic, c'est une fuite.

Si ça monte avec le trafic et que ça redescend après, c'est juste que ton application a besoin de plus de ressources pour scaler.

26/05/2026 à 14:48
mhuet
Membre
Avatar de mhuet
mhuet
Membre

Comment je sais si c'est une fuite mémoire réelle ou juste un besoin de plus de RAM ?

26/05/2026 à 10:44

C'est du Linux pur. 128 + 9 (SIGKILL) = 137. Kubernetes ne fait que remonter ce que le noyau lui dit.

26/05/2026 à 05:13

Le Exit Code: 137, c'est spécifique à Kubernetes ou c'est Linux ?

25/05/2026 à 21:17

N'oublie pas de bien tester le montage du volume avant. Un dump de 2Go sur un volume mal monté, c'est la perte de données assurée.

25/05/2026 à 16:02
cribeiro
Membre
Avatar de cribeiro
cribeiro
Membre

Merci, je vais appliquer la config des JAVA_TOOL_OPTIONS dès demain.

25/05/2026 à 11:56

C'est ce qu'on appelle le Guaranteed QoS class. C'est bien pour la stabilité, mais c'est rigide.

Si ton appli a des pics, elle sera limitée très vite. C'est un compromis à faire selon ton besoin.

25/05/2026 à 05:51
xavier-lambert
Membre Actif
Avatar de xavier-lambert
xavier-lambert
Membre Actif

Je comprends mieux pourquoi mes pods tombaient. J'avais mis requests = limits. C'est bien ou pas ?

24/05/2026 à 19:19

C'est une excellente pratique. Tu peux mettre un sidecar qui surveille le répertoire des dumps et qui les envoie sur un S3 ou un bucket.

Ça évite de devoir faire des kubectl exec manuellement.

24/05/2026 à 11:41

Est-ce qu'on peut automatiser la récup des dumps via un sidecar ?

24/05/2026 à 06:41

Surtout pas ! Activer le swap sur un cluster Kubernetes, c'est le meilleur moyen d'avoir des perfs totalement imprévisibles.

Le pod ne sera plus OOMKilled, mais il va ramer comme pas possible dès qu'il swap sur le disque. Règle ton problème de fuite mémoire, ne cherche pas à contourner avec du swap.

24/05/2026 à 00:07
helene65
Membre
Avatar de helene65
helene65
Membre

Salut, très utile. J'ai une question sur le Swap. Si je l'active sur mes nœuds, ça règle le souci de OOMKilled ?

23/05/2026 à 13:52

Probablement parce que ton conteneur est tué trop vite. Vérifie tes limits.

Si la limite est trop serrée, le kernel coupe tout avant que la JVM finisse d'écrire. Augmente temporairement ta limite pour laisser le temps au dump de s'écrire.

23/05/2026 à 08:08
xreynaud
Membre
Avatar de xreynaud
xreynaud
Membre

J'ai testé la commande kubectl exec pour voir le dump, mais le fichier est vide. Pourquoi ?

23/05/2026 à 01:39

Oui, les quotas au niveau du namespace vont juste empêcher le pod de démarrer si tu dépasses les limites globales du namespace.

Mais ça ne remplace pas une bonne configuration de limits dans ton spec de déploiement.

22/05/2026 à 14:02
martin-vincent
Membre Actif
Avatar de martin-vincent
martin-vincent
Membre Actif

Ça marche aussi avec les ResourceQuotas ?

22/05/2026 à 04:52

Utilise Eclipse Memory Analyzer (MAT). C'est la référence pour parser les dumps Java.

Tu peux le lancer en local et importer ton fichier pay-err.hprof. Il va te sortir direct la liste des objets qui bouffent toute la RAM.

22/05/2026 à 00:28
mallet-noel
Membre
Avatar de mallet-noel
mallet-noel
Membre

Top, j'ai récupéré mon fichier .hprof. Maintenant, quel outil tu conseilles pour lire ça sans galérer ?

21/05/2026 à 20:27

Oui, le principe reste le même. Pour Node.js, tu peux utiliser --max-old-space-size pour limiter la heap.

Pour le dump, installe heapdump dans ton code et déclenche-le sur un signal système ou une erreur.

21/05/2026 à 09:53
isaac-roger
Membre Actif Secouriste
Avatar de isaac-roger
isaac-roger
Membre Actif Secouriste

Est-ce qu'on peut faire la même chose pour du Node.js ? Le tuto est très orienté Java.

21/05/2026 à 04:38

Vérifie tes permissions sur le host si tu es sur du NFS ou du local path.

Vérifie aussi que ton namespace est bien le même pour le PVC et le Deployment. C'est l'erreur classique.

20/05/2026 à 23:07
mdenis
Membre Actif
Avatar de mdenis
mdenis
Membre Actif

J'ai un souci avec mon volume PersistentVolumeClaim. Il ne monte pas correctement dans le pod, j'ai une erreur MountVolume.SetUp failed.

20/05/2026 à 16:35

Pas du tout. Si tu donnes 100% à la JVM, elle va bouffer toute la mémoire du conteneur.

Résultat : le système d'exploitation n'aura plus rien pour ses propres processus internes et Kubernetes va te kill le pod avant même que la JVM puisse écrire son dump. Garde toujours une marge.

20/05/2026 à 10:23

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire