OOMKilled signifie qu’un pod a dépassé sa limite de mémoire et le kernel l’a terminé (code 137). C’est parce que soit votre limite est trop basse, soit votre app a une fuite mémoire. Ce guide vous montre comment diagnostiquer, dimensionner correctement les limites, et comprendre les classes QoS.

Symptôme

Le pod redémarre à plusieurs reprises et kubectl describe pod affiche :

Name:           example-app-5f7c9d4bfb-7x8kq
Namespace:      default
Status:         Running
Restarts:       12
Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
  Signal:       9
  Last Probe:   2m30s ago
  Reason:       None

Alternativement, le pod n’affiche pas la raison OOMKilled mais se termine constamment avec le code 137 :

Exit Code:    137
Signal:       9

Dans kubectl get events :

pod-name   OOMKilled   The container was OOMKilled

L’utilisation mémoire peut être visible dans kubectl top pod :

NAME                       CPU(cores)   MEMORY(bytes)
example-app-5f7c9d4bfb    100m         1536Mi    # À ou près de la limite

Pourquoi ça arrive

Classées par probabilité :

  1. La limite mémoire est trop basse pour le workload normal de l’app — L’app a besoin de plus de mémoire pour stocker les données, mettre en cache, ou traiter les requêtes, mais la limite ne le permet pas.
  2. Fuite mémoire dans l’app — L’app alloue de la mémoire au fil du temps sans la libérer, finissant par atteindre la limite même si le besoin initial était plus petit.
  3. Heap JVM ou runtime non configuré — Java, Node.js, ou Python utilise toute la mémoire système disponible au lieu de respecter une taille de heap explicite.
  4. Pic soudain dans le traitement des données — Un travail batch ou la génération de rapport nécessite temporairement plus de mémoire que la limite ne le permet.
  5. Aucune memory request définie — Le pod a une limite mais pas de request, donc Kubernetes l’ordonnance sur un nœud qui semble libre mais est réellement sous pression.
  6. La classe QoS est Burstable, pas Guaranteed — Le pod peut être expulsé si le nœud manque de mémoire, même s’il n’a pas techniquement dépassé la limite.

Correction

1. La limite mémoire est trop basse

Tout d’abord, mesurez combien de mémoire votre app utilise réellement :

# Installer metrics-server si absent (généralement pré-installé sur les clusters cloud)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# Attendre 1-2 minutes pour que les métriques soient collectées
sleep 120

# Vérifier l'utilisation en temps réel
kubectl top pod <pod-name>

# Surveiller au fil du temps (regarder les pics)
kubectl top pod <pod-name> --containers

Enregistrez l’utilisation mémoire maximale. Puis augmentez la limite de 20–50% comme marge de sécurité :

# Si le pic est 512 Mi, définir la limite à 768 Mi
kubectl set resources deployment <deployment-name> \
  --limits=memory=768Mi \
  --requests=memory=512Mi

Ou éditez directement le Deployment :

kubectl edit deployment <deployment-name>

Et mettez à jour la section resources :

containers:
- name: app
  resources:
    requests:
      memory: "512Mi"
      cpu: "100m"
    limits:
      memory: "768Mi"        # Augmenté de 512 Mi
      cpu: "500m"

Appliquez et vérifiez :

kubectl rollout restart deployment/<deployment-name>
kubectl top pod <pod-name>    # Vérifier que le nouveau pod a assez de mémoire

2. Fuite mémoire dans l’app

Si la mémoire de l’app grandit au fil du temps jusqu’à atteindre la limite, il y a une fuite. Surveillez la mémoire sur plusieurs heures ou jours :

# Collecter l'utilisation mémoire toutes les 30 secondes et sauvegarder dans un fichier
watch -n 30 "kubectl top pod <pod-name> >> memory-log.txt"

Si la mémoire grandit constamment, l’app a une fuite. Corrigez la fuite dans le code de l’application :

  • Java: Utilisez des heap dumps pour trouver les objets retenus :
    jmap -dump:live,format=b,file=heap.bin <pid>
    
  • Node.js: Utilisez clinic.js ou des heap snapshots :
    node --inspect=0.0.0.0:9229 app.js
    # Puis connectez DevTools et prenez des heap snapshots
    
  • Python: Utilisez memory_profiler ou objgraph pour tracer les allocations.

Pour une solution de contournement rapide en attendant de corriger la fuite, définissez une limite mémoire plus basse et augmentez la policy de redémarrage :

restartPolicy: Always
containers:
- name: app
  resources:
    limits:
      memory: "512Mi"    # Limite plus basse force un redémarrage plus rapide

Ce n’est pas une correction mais gagne du temps pendant que vous debuggez la fuite réelle.

3. Heap JVM ou runtime non configuré

Si votre app s’exécute sur une JVM, Node.js, ou Python, configurez explicitement le heap ou la limite mémoire :

Java/JVM :
Définissez la taille du heap dans votre Dockerfile ou spec pod :

FROM openjdk:17-slim
ENV JAVA_OPTS="-Xmx512m -Xms256m"
COPY app.jar /app.jar
ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", "/app.jar"]

Ou passez-la comme variable d’environnement :

containers:
- name: app
  image: myapp:jvm
  env:
  - name: JAVA_OPTS
    value: "-Xmx512m -Xms256m"

Node.js :
Définissez max-old-space-size :

FROM node:20-alpine
ENV NODE_OPTIONS="--max-old-space-size=512"
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]

Ou :

containers:
- name: app
  env:
  - name: NODE_OPTIONS
    value: "--max-old-space-size=512"

Python :
Certains frameworks pré-allouent de la mémoire. Vérifiez s’il y a une configuration mémoire :

containers:
- name: app
  env:
  - name: PYTHONUNBUFFERED
    value: "1"    # Aide avec la journalisation dans les conteneurs

Redéployez :

kubectl rollout restart deployment/<deployment-name>

4. Pic soudain dans le traitement des données

Pour les travaux batch ou la génération de rapports, utilisez une classe de ressource séparée avec des limites plus hautes :

apiVersion: batch/v1
kind: Job
metadata:
  name: heavy-report
spec:
  template:
    spec:
      containers:
      - name: report
        image: myapp:latest
        resources:
          requests:
            memory: "2Gi"
            cpu: "1000m"
          limits:
            memory: "3Gi"
            cpu: "1500m"
      restartPolicy: Never

Pour les apps de longue durée qui pic occasionnellement, envisagez d’utiliser un Vertical Pod Autoscaler (VPA) pour ajuster automatiquement les limites basé sur l’utilisation historique :

# Installer VPA (s'il n'est pas déjà installé)
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh

# Puis annoter votre Deployment
kubectl annotate deployment <deployment-name> \
  vpa.k8s.io/updateMode=auto \
  vpa.k8s.io/updatePolicy.updateMode=auto

5. Pas de memory request définie

Si vous définissez une limite mais pas de request, Kubernetes traite la request comme égale à la limite. Cela cause des problèmes de scheduling. Définissez toujours les deux :

resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"
    cpu: "500m"

La règle du pouce :
– Request = mémoire minimale nécessaire (pour l’ordonnancement)
– Limit = maximum que l’app devrait jamais utiliser (pour l’application)
– Ratio : la limite devrait être 1.5–2x la request

Mettez à jour votre Deployment :

kubectl set resources deployment <deployment-name> \
  --requests=memory=256Mi,cpu=100m \
  --limits=memory=512Mi,cpu=500m

6. Classe QoS est Burstable au lieu de Guaranteed

Vérifiez la classe QoS :

kubectl get pod <pod-name> -o yaml | grep qosClass

Trois classes QoS existent :
– Guaranteed: requests == limits (priorité la plus haute, jamais expulsé pour la mémoire)
– Burstable: requests < limits (priorité moyenne, peut être expulsé)
– BestEffort: pas de requests ni de limits (priorité la plus basse, expulsé en premier)

Pour les apps de production, utilisez Guaranteed :

resources:
  requests:
    memory: "512Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"    # Même que la request
    cpu: "100m"

Appliquez et redémarrez :

kubectl apply -f deployment.yaml
kubectl rollout restart deployment/<deployment-name>

Éviter la récidive

  1. Mesurez toujours avant de définir les limites :

    kubectl top pod <pod-name>
    

    Définissez les requests et limites basé sur l’utilisation réelle observée, pas sur des suppositions.

  2. Définissez toujours les requests et limites, jamais juste une :

    resources:
      requests:
        memory: "256Mi"
      limits:
        memory: "512Mi"
    

  3. Configurez explicitement le heap JVM/runtime :

    ENV JAVA_OPTS="-Xmx512m -Xms256m"
    ENV NODE_OPTIONS="--max-old-space-size=512"
    

  4. Activez metrics-server pour surveiller l’utilisation mémoire :

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

  5. Utilisez un Vertical Pod Autoscaler (VPA) pour le dimensionnement automatique :

    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: example-vpa
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: example-app
      updatePolicy:
        updateMode: "Auto"
    

  6. Profilez votre app pour les fuites mémoire pendant le développement :
    – Java: heap dumps
    – Node.js: clinic.js ou DevTools
    – Python: memory_profiler

  7. Définissez les startup probes pour donner du temps à l’app d’allouer la mémoire initiale :

    startupProbe:
      httpGet:
        path: /health
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    

FAQ

Comment savoir si un pod a été OOMKilled ?

Exécutez kubectl describe pod <pod> et cherchez ‘Reason: OOMKilled’ dans Last State, ou le code 137. Aussi vérifiez kubectl get events.

Pourquoi la limite de mémoire de mon pod est-elle différente de ce que j’ai défini ?

Kubernetes définit les limites et les requests séparément. Si vous définissez uniquement une limite, la request par défaut est la limite. Voyez les deux avec kubectl get pod -o yaml | grep -A 3 resources:.

Comment mesurer combien de mémoire mon app utilise réellement ?

Utilisez kubectl top pod <pod> pour voir l’utilisation de mémoire en temps réel. Si metrics-server n’est pas installé, installez-le d’abord (généralement pré-installé sur les clusters cloud).

Quelle est la différence entre requests et limits ?

Les requests sont des garanties (pour l’ordonnancement) ; les limits sont des plafonds (pour l’application). Si un pod dépasse sa limite, il est OOMKilled. Définissez toujours les deux, avec une limite 1.5–2x la request.

Comment ce guide a été vérifié

Rédigé à la main à partir de la documentation officielle, chaque commande revérifiée, puis relu une seconde fois avant publication. Dernière relecture le .

Sources officielles consultées pour cet article

Share.

Les commentaires sont fermés.