Lexikon · O

OOM Kill (OOMKilled)

Das erzwungene Beenden eines Prozesses durch den Linux-OOM-Killer, wenn Speicher fehlt oder ein Container sein Limit überschreitet.

Von Obseria-RedaktionAktualisiert

Definition

Ein OOM Kill (OOMKilled) ist das erzwungene Beenden eines Prozesses durch den Out-of-Memory-Killer von Linux, wenn dem System oder einer cgroup der Speicher ausgeht. In Kubernetes passiert das, wenn ein Container sein Memory-Limit überschreitet: Er wird per SIGKILL gestoppt und meldet Exit-Code 137 mit dem Grund OOMKilled.

Was ist ein OOM Kill?

Ein OOM Kill tritt ein, wenn Linux eine Speicheranforderung nicht erfüllen kann und Speicher gewaltsam freigeben muss. Der Out-of-Memory-Killer des Kernels wählt einen Prozess aus und sendet ihm SIGKILL, ein Signal, das sich weder abfangen noch behandeln lässt. Der Prozess endet sofort, ohne Shutdown-Hooks, ohne Puffer zu leeren und ohne laufende Requests abzuschließen.

In Containern wird Speicher über cgroups begrenzt. Erreicht ein Container sein Limit (memory.max in cgroup v2) und kann der Kernel nicht genug zurückgewinnen, greift der OOM-Killer nur innerhalb dieser cgroup. Kubernetes meldet den Container-Status dann als OOMKilled mit Exit-Code 137, also 128 plus Signalnummer 9.

So wählt der OOM-Killer sein Opfer

Bei systemweitem Speichermangel berechnet der Kernel für jeden Prozess einen oom_score, vor allem anhand des Speicherverbrauchs, korrigiert um oom_score_adj (von -1000 bis 1000). Der Prozess mit dem höchsten Wert wird beendet. Kubernetes setzt oom_score_adj abhängig von der Quality-of-Service-Klasse des Pods:

QoS-KlasseBedingungOOM-Priorität
GuaranteedRequests gleich Limits für CPU und Speicher in jedem ContainerWird zuletzt beendet
BurstableMindestens ein Request oder Limit gesetzt, aber nicht GuaranteedDazwischen, abhängig von den Requests
BestEffortWeder Requests noch LimitsWird zuerst beendet

Ein OOM Kill wegen des eigenen Container-Limits ist etwas anderes als eine Eviction durch das Kubelet. Dabei steht der Node unter Speicherdruck und das Kubelet entfernt Pods vorsorglich. Beides endet in einem Neustart oder einer Neuplanung, erscheint aber mit unterschiedlichen Gründen und erfordert unterschiedliche Maßnahmen.

Typische Ursachen

  • Zu niedriges Limit für das tatsächliche Working Set, oft nachdem Traffic oder Datenvolumen gewachsen sind.
  • Ein Speicherleck, das den Verbrauch langsam an das Limit heranschiebt.
  • Runtime kennt das Container-Limit nicht. Ein JVM-Heap größer als das Limit oder Heap, Metaspace, Thread-Stacks und Direct Buffers, die zusammen das Limit übersteigen.
  • Lastspitzen mit vielen gleichzeitigen Requests, die jeweils große Puffer halten.
  • Page Cache und tmpfs, die gegen die cgroup zählen, etwa Dateien auf einem In-Memory-Volume.

Was gegen das Memory-Limit zählt

Viele OOM Kills überraschen, weil Teams das Limit mit der falschen Zahl vergleichen. Der Kernel rechnet einer cgroup anonymen Speicher (Heap, Stacks, native Allokationen) zu, außerdem den Page Cache aus Lese- und Schreibzugriffen auf Dateien sowie Kernel-Speicher wie Socket-Puffer. Rückgewinnbarer Page Cache wird unter Druck meist freigegeben. Kubernetes und das Kubelet nutzen daher das Working Set als maßgebliche Größe, grob der Gesamtverbrauch abzüglich inaktiven Datei-Caches. Deshalb sagt container_memory_working_set_bytes einen OOM Kill besser voraus als container_memory_usage_bytes.

Bei verwalteten Runtimes ist der Heap nur ein Teil des Bildes. Eine JVM belegt zusätzlich Metaspace, Code Cache, Thread-Stacks, Strukturen des Garbage Collectors und Direct Buffers. Ein Node.js-Prozess hält Puffer außerhalb des V8-Heaps. Eine Heap-Einstellung, die sicher wirkt, kann den Container also trotzdem über sein Limit treiben. Wer Heap-Metriken und Container-Working-Set nebeneinander beobachtet, vermeidet diesen blinden Fleck und kann ein langsames Leck von einem Dimensionierungsproblem unterscheiden.

Beispiel: OOMKilled in Kubernetes analysieren

Eine illustrative Prüfung eines neu startenden Pods:

kubectl describe pod checkout-7d9f
  Last State:  Terminated
    Reason:    OOMKilled
    Exit Code: 137
  Limits:      memory: 512Mi

Eine PromQL-Abfrage wie max_over_time(container_memory_working_set_bytes{container="checkout"}[1h]) / on(pod) group_left kube_pod_container_resource_limits{resource="memory", container="checkout"} zeigt, wie nah der Container seinem Limit kommt. Steigt das Working Set zwischen den Neustarts kontinuierlich, suchen Sie nach einem Leck. Springt es nur bei Lastspitzen, sind Limit oder Parallelität zu knapp bemessen. kube-state-metrics stellt den letzten Beendigungsgrund bereit, sodass Sie OOM Kills pro Workload zählen können, statt sie zufällig zu entdecken.

OOM Kills vorbeugen

  • Bemessen Sie Memory-Limits aus dem beobachteten Spitzen-Working-Set plus Reserve und setzen Sie Requests nahe am realen Verbrauch.
  • Machen Sie Runtimes container-bewusst, etwa mit -XX:MaxRAMPercentage für die JVM oder GOMEMLIMIT für Go, und lassen Sie Platz für Speicher außerhalb des Heaps.
  • Begrenzen Sie Parallelität über Pools und Queues, damit Spitzen den Speicherbedarf nicht vervielfachen.
  • Alarmieren Sie, wenn der Speicher sich dem Limit nähert und bei OOMKilled-Ereignissen, nicht nur bei Neustarts. Ein Pod im CrashLoopBackOff verdeckt sonst schnell die eigentliche Ursache.
  • Behandeln Sie wiederholte OOM Kills als Zuverlässigkeitsproblem: Für Nutzer wirken sie wie zufällige Fehler und können sich kaskadierend ausbreiten. Das gehört zum Monitoring der Sättigung.

Wie Obseria hilft

Das Kubernetes-Monitoring von Obseria zeigt Container-Speicher im Verhältnis zu Requests und Limits, Beendigungsgründe und Neustarts pro Workload, verknüpft mit Traces und Logs der betroffenen Pods. Continuous Profiling hilft zu erkennen, welcher Code vor dem Kill Speicher anlegt. Eine umfassendere Liste der Signale im Cluster finden Sie in der Kubernetes-Observability-Checkliste.

Häufig gestellte Fragen

Exit-Code 137 heißt, dass der Prozess durch Signal 9 (SIGKILL) beendet wurde, denn 128 + 9 = 137. In Kubernetes ist das meist ein OOM Kill, kann aber auch ein erzwungenes Beenden nach Ablauf der Grace Period sein. Prüfen Sie daher das Feld Reason.

Nein. OOMKilled bedeutet, dass der Kernel einen Container beendet hat, der sein Memory-Limit überschritten hat. Eine Eviction ist eine Entscheidung des Kubelets, Pods zu entfernen, wenn dem Node selbst Speicher oder andere Ressourcen ausgehen.

In der Regel nicht. Ohne Limits kann ein einzelner Pod mit Speicherleck den Speicher des Nodes erschöpfen und systemweite OOM Kills anderer Workloads auslösen. Setzen Sie realistische Limits und beheben Sie die Ursache des hohen Verbrauchs.

Vergleichen Sie das Working Set über die Zeit mit Traffic und Deployments und nutzen Sie dann Heap-Dumps oder Allokationsprofile der Runtime, um zu sehen, welche Objekte oder Codepfade den Speicher halten.

Setzen Sie das Wissen in die Praxis um

Testen Sie Obseria 14 Tage kostenlos, mit OpenTelemetry und ohne proprietäre Agenten.