Короткий ответ
OOMKilled подтверждается через kubectl describe pod (поле Last State: Terminated, Reason: OOMKilled) и метрику container_memory_working_set_bytes, которая перед моментом перезапуска устойчиво приближается к значению resources.limits.memory; падение из-за исключения обычно не сопровождается ростом памяти до лимита и видно по exit code (не 137) и stack trace в логах перед остановкой.
Как это работает подробнее
когда контейнер превышает memory limit, ядро Linux (cgroup OOM killer) принудительно завершает процесс сигналом, что Kubernetes отражает как exit code 137 (128 + SIGKILL=9) и причину OOMKilled в статусе контейнера. Обычное падение приложения из-за необработанного исключения завершается с другим exit code (часто 1 или специфичным для языка/фреймворка) и, как правило, логирует stack trace перед остановкой процесса, которого не будет при OOM (процесс убивается ядром снаружи, не успевая даже корректно залогировать причину).
Пример кода
kubectl describe pod checkout-7f9c8 | grep -A5 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# --- promql ---
container_memory_working_set_bytes{pod="checkout-7f9c8"}Что использовать на практике
- Практика. для подтверждения гипотезы OOM смотрят график
container_memory_working_set_bytesдля этого пода за 10–15 минут до момента рестарта — характерный паттерн: плавный или резкий рост до значения, близкого к лимиту, и обрыв графика в момент рестарта. Если рост памяти отсутствует, а в логах есть явный stack trace перед остановкой — это обычное падение приложения, и решение здесь не про ресурсы, а про сам код/необработанное исключение. - Практика. Если график метрики за 10 минут до события показывает рост от 200 МБ до почти 512 МБ (равного заданному
limits.memory: 512Mi), а статус подтверждаетOOMKilledсExit Code: 137— причина установлена достоверно, и дальше нужно разбираться, является ли рост памяти утечкой или просто недооценённым лимитом под нормальную нагрузку.
Подводные камни
- Частые ошибки. увеличивать memory limit "на всякий случай" без проверки реального паттерна потребления, что просто откладывает проблему при утечке памяти; путать OOMKilled уровня контейнера (cgroup limit) с OOM на уровне узла (node-level memory pressure, когда kubelet сам вытесняет поды по приоритету) — у них разная диагностика и разные метрики (
node_memory_MemAvailable_bytesдля второго случая); не проверятьexit codeи полагаться только на факт "под перезапустился", не разбираясь в причине.