Veröffentlicht am

OOMKilled debuggen: Memory-Limits und Leaks finden

Teilen:
Authors

TL;DR

  • OOMKilled (Exit Code 137) bedeutet, dass der Linux Kernel einen Container wegen Speicherüberschreitung beendet hat
  • Die 5 häufigsten Ursachen: zu niedriges Memory Limit, Memory Leaks, JVM/Container Mismatch, Sidecar-Verbrauch und Burst-Traffic
  • Faustregel: Memory Limit auf 1,5x bis 2x des normalen Verbrauchs setzen; bei Java: Container Limit = Xmx + ca. 150 MB
  • Prometheus-Query container_memory_usage_bytes / container_spec_memory_limit_bytes zeigt die Memory-Auslastung relativ zum Limit
  • Immer mindestens Burstable QoS verwenden; für kritische Workloads Guaranteed (Request = Limit)

Kubernetes OOMKilled - Kompletter Troubleshooting Guide

OOMKilled (Out of Memory Killed) bedeutet, dass Ihr Container mehr Speicher verbraucht hat als erlaubt und vom Linux Kernel beendet wurde. Dieser Guide zeigt die Ursachen, Diagnose und Lösungen.

Was bedeutet OOMKilled?

kubectl describe pod my-pod
Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
  • Exit Code 137 = Container wurde mit SIGKILL (9) beendet
  • Formel: 128 + 9 = 137
  • Linux Kernel beendet Prozess, der Memory Limit überschreitet

Schnelle Diagnose

# 1. OOMKilled bestätigen
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'

# 2. Memory Limit prüfen
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].resources.limits.memory}'

# 3. Aktueller Memory-Verbrauch (wenn Pod läuft)
kubectl top pod <pod-name>

# 4. Node Memory-Status
kubectl top nodes

Die 5 häufigsten Ursachen

1. Memory Limit zu niedrig

Problem: Anwendung braucht mehr Memory als konfiguriert.

Diagnose:

# Aktuelles Limit
kubectl get pod <pod> -o yaml | grep -A 3 limits

# Historischer Verbrauch (wenn Prometheus vorhanden)
# container_memory_usage_bytes{pod="<pod>"}

Lösung:

spec:
  containers:
  - name: app
    resources:
      requests:
        memory: "256Mi"   # Garantierter Speicher
      limits:
        memory: "512Mi"   # Maximum (erhöhen!)

Empfehlung: Limit = 1.5x bis 2x des normalen Verbrauchs.


2. Memory Leak in der Anwendung

Problem: Anwendung gibt Speicher nicht frei.

Symptome:

  • Memory steigt kontinuierlich
  • OOMKilled nach Stunden/Tagen
  • Neustart "behebt" Problem temporär

Diagnose:

# Memory-Verlauf über Zeit beobachten
kubectl top pod <pod> --containers

# Alle 30 Sekunden
watch -n 30 "kubectl top pod <pod>"

Java-Anwendungen:

spec:
  containers:
  - name: java-app
    env:
    - name: JAVA_OPTS
      value: "-Xms256m -Xmx384m -XX:+HeapDumpOnOutOfMemoryError"
    resources:
      limits:
        memory: "512Mi"  # Muss größer als Xmx sein!

Formel für Java: Container Limit = Xmx + ~150MB (für Non-Heap)

Node.js-Anwendungen:

env:
- name: NODE_OPTIONS
  value: "--max-old-space-size=384"

3. Container Memory vs. Limit Mismatch

Problem: Anwendung kennt ihr Limit nicht und allokiert zu viel.

Java ohne Limits:

# JVM sieht Node-Memory, nicht Container-Limit!
java -jar app.jar  # Allokiert 25% des Node-RAM

Lösung - Container-aware JVM (Java 11+):

env:
- name: JAVA_OPTS
  value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

Oder explizite Heap-Größe:

env:
- name: JAVA_OPTS
  value: "-Xms256m -Xmx384m"

4. Sidecar/Init-Container verbrauchen Memory

Problem: Gesamtes Pod-Memory wird von allen Containern geteilt.

Diagnose:

# Memory aller Container im Pod
kubectl top pod <pod> --containers
POD         NAME       CPU    MEMORY
my-pod      app        10m    256Mi
my-pod      sidecar    5m     128Mi   # <-- Zusätzlicher Verbrauch!

Lösung: Limits für jeden Container setzen:

spec:
  containers:
  - name: app
    resources:
      limits:
        memory: "256Mi"
  - name: sidecar
    resources:
      limits:
        memory: "64Mi"

5. Burst-Traffic verursacht Spike

Problem: Kurzzeitig hoher Traffic führt zu Memory-Spike.

Diagnose:

# Events während OOMKill-Zeit prüfen
kubectl get events --field-selector reason=OOMKilling

Lösungen:

  1. Limit erhöhen für Peak-Zeiten
  2. HPA einrichten für Auto-Scaling:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70

Memory Requests vs. Limits

┌─────────────────────────────────────────────────┐
NODE MEMORY├───────────────────────┬─────────────────────────┤
REQUESTSLIMITS   (Guaranteed)              (Maximum)├───────────────────────┴─────────────────────────┤
│                                                 │
│   ┌──────────┐         ┌──────────────────┐    │
│   │ Pod A    │         │ Pod A            │    │
│   │ 256Mi    │ ───────▶│ kann bis 512Mi   │    │
│   │ Request  │         │ nutzen (Limit)   │    │
│   └──────────┘         └──────────────────┘    │
│                                                 │
│   ┌──────────┐         ┌──────────────────┐    │
│   │ Pod B    │         │ Pod B            │    │
│   │ 128Mi    │ ───────▶│ kann bis 256Mi   │    │
│   │ Request  │         │ nutzen (Limit)   │    │
│   └──────────┘         └──────────────────┘    │
│                                                 │
└─────────────────────────────────────────────────┘

Best Practices:

SzenarioRequestLimit
Stabile WorkloadsGleich wie Limit= Request
Variable Workloads50% des Limits2x Request
Batch JobsNiedrigHoch

Memory richtig dimensionieren

Schritt 1: Baseline ermitteln

# 1. Pod ohne Limits deployen (nur in Dev!)
# 2. Last simulieren
# 3. Verbrauch messen

kubectl top pod <pod> --containers

# Über Zeit messen
for i in {1..60}; do
  kubectl top pod <pod> >> memory-log.txt
  sleep 60
done

Schritt 2: Limits berechnen

Request = P95 Memory (95. Perzentil des normalen Verbrauchs)
Limit = Peak Memory + 20% Buffer

Schritt 3: Monitoring einrichten

# Prometheus Alert für Memory > 80%
- alert: PodMemoryHigh
  expr: |
    container_memory_usage_bytes / container_spec_memory_limit_bytes > 0.8
  for: 5m
  labels:
    severity: warning

QoS-Klassen verstehen

Kubernetes hat 3 Quality of Service Klassen:

QoS KlasseBedingungEviction-Priorität
GuaranteedRequest = Limit (für alle Container)Niedrig (zuletzt)
BurstableRequest < LimitMittel
BestEffortKeine Requests/LimitsHoch (zuerst)

Empfehlung für Production: Immer mindestens Burstable, besser Guaranteed.

# Guaranteed QoS
resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "256Mi"  # Gleich wie Request!
    cpu: "100m"      # Gleich wie Request!

Debugging mit Tools

1. kubectl-top

kubectl top pod <pod> --containers

2. Prometheus + Grafana

# Memory-Verbrauch pro Pod
container_memory_usage_bytes{namespace="production"}

# Memory vs. Limit
container_memory_usage_bytes / container_spec_memory_limit_bytes

# OOMKill Events
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}

3. Java JVM Analyse

# Heap Dump erstellen
kubectl exec <pod> -- jcmd 1 GC.heap_dump /tmp/heap.hprof

# Kopieren
kubectl cp <pod>:/tmp/heap.hprof ./heap.hprof

# Analysieren mit Eclipse MAT oder VisualVM

4. Debug Container

# Ephemeral Debug Container (K8s 1.25+)
kubectl debug -it <pod> --image=busybox --target=app

# Memory des Host-Systems prüfen
cat /proc/meminfo

Häufige Fehler

Fehler 1: Nur Limit setzen, kein Request

# SCHLECHT
resources:
  limits:
    memory: "512Mi"
# Request wird automatisch = Limit
# Überschätzt den Bedarf, verschwendet Ressourcen

Fehler 2: Request zu hoch

# SCHLECHT - Pod kann nicht geschedult werden
resources:
  requests:
    memory: "4Gi"  # Kein Node hat so viel frei

Fehler 3: JVM Heap = Container Limit

# SCHLECHT - JVM braucht auch Non-Heap!
env:
- name: JAVA_OPTS
  value: "-Xmx512m"  # Gleich wie Limit
resources:
  limits:
    memory: "512Mi"  # OOMKilled garantiert!

Korrekt:

env:
- name: JAVA_OPTS
  value: "-Xmx384m"  # 75% des Limits
resources:
  limits:
    memory: "512Mi"

Checkliste bei OOMKilled

# 1. ✅ OOMKilled bestätigen
kubectl describe pod <pod> | grep -A 5 "Last State"

# 2. ✅ Aktuelles Limit prüfen
kubectl get pod <pod> -o yaml | grep -A 5 resources

# 3. ✅ Memory-Verbrauch analysieren
kubectl top pod <pod> --containers

# 4. ✅ Memory Leak ausschließen
# - Verbrauch über Zeit beobachten
# - Heap Dump analysieren

# 5. ✅ Limit angemessen erhöhen
kubectl set resources deployment/<name> --limits=memory=512Mi

# 6. ✅ Monitoring einrichten
# - Alert bei 80% Memory-Nutzung

CKA-Prüfungstipp

In der CKA-Prüfung werden Sie:

  1. OOMKilled-Pods identifizieren müssen
  2. Memory Limits anpassen
  3. Resource Quotas verstehen

Wichtige Befehle:

kubectl top pod
kubectl describe pod | grep -A 5 "Last State"
kubectl set resources deployment/<name> --limits=memory=512Mi

Mehr zur Prüfung: CKA Zertifizierung Guide


Zusammenfassung

ProblemDiagnoseLösung
Limit zu niedrigkubectl top podLimit erhöhen
Memory LeakMemory steigt über ZeitApp fixen, Heap begrenzen
JVM zu großXmx ≥ Container LimitXmx = 75% des Limits
Sidecar Memorytop --containersLimits pro Container
Burst TrafficEvents prüfenHPA einrichten

Wichtigster Tipp: Container Limit muss immer größer sein als der Peak-Verbrauch der Anwendung!


Dieser Troubleshooting-Guide wird regelmäßig aktualisiert. Letzte Aktualisierung: Januar 2026.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen