Что означает ошибка OOMKilled
OOMKilled (Out Of Memory Killed) — это состояние контейнера в Kubernetes, означающее, что процесс внутри контейнера был принудительно завершён ядром Linux из-за превышения выделенного лимита памяти (cgroup memory limit). В выводе kubectl get pods pod обычно переходит в статус CrashLoopBackOff или Error, а в логах или событии kubectl describe pod вы увидите последнюю причину завершения: State: Terminated Reason: OOMKilled.
Это системная ошибка, а не ошибка приложения. Kubernetes (через контейнерный рантайм, например, containerd) просто выполняет политику лимитов ресурсов, установленную вами в манифесте.
Причины возникновения
- Слишком низкий лимит памяти (
limits.memory). Самая частая причина. В манифесте указано, например,512Mi, а приложение в пике требует700Mi. - Утечка памяти (memory leak) в приложении. Приложение постепенно потребляет всё больше RAM, пока не достигнет лимита.
- Несоответствие
requestsиlimits. Еслиrequestsсильно нижеlimits, pod может быть запланирован на узел с малым количеством свободной памяти, что приведёт к быстрому исчерпанию лимита. - Неучтённые процессы внутри контейнера. Фоновые процессы (например,
cron,sidecar-контейнеры) могут потреблять дополнительную память. - Проблемы с настройкой JVM/рантайма. Для Java-приложений неправильно заданный
-Xmxможет привести к тому, что JVM попытается зарезервировать больше памяти, чем разрешено лимитом контейнера, и будет убита сразу. - Использование памяти ядром (kernel memory) или page cache. В некоторых конфигурациях cgroup учитывается не только память пользовательского пространства (RSS), но и кэш. Интенсивная файловая операция может "съесть" лимит.
Способы решения
Способ 1: Увеличение лимита памяти в манифесте
Это прямое и часто самое быстрое решение.
- Найдите манифест, управляющий pod'ом (Deployment, StatefulSet, DaemonSet).
- В секции
spec.template.spec.containers[].resourcesувеличьте значенияlimits.memoryи, что важно,requests.memory.apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: template: spec: containers: - name: myapp-container image: myapp:latest resources: requests: memory: "512Mi" # Увеличьте это значение limits: memory: "1Gi" # Увеличьте это значение - Примените изменения:
kubectl apply -f deployment.yaml. - Следите за перезапуском pod'а:
kubectl rollout status deployment/myapp.
💡 Совет: Устанавливайте
limitsв 1.5-2 раза выше пикового потребления, которое вы оценили черезkubectl top.requestsдолжен быть близок к среднему потреблению для корректного планирования.
Способ 2: Оптимизация приложения и образа
Если вы не хотите бесконечно увеличивать лимиты, нужно уменьшать потребление.
- Профилирование памяти. Запустите контейнер с отладкой.
- Для Java:
jcmd <PID> VM.native_memory summaryилиjmap -histo. - Для Go:
go tool pprof http://<pod-ip>:6060/debug/pprof/heap. - Для Python:
tracemallocилиmemory-profiler.
- Для Java:
- Настройка JVM. Если приложение на Java, явно задайте максимальный размер heap через
-Xmx, например,-Xmx800m. Убедитесь, что-Xmx+ метапространство + стек потока <limits.memory. - Уменьшение размера образа. Используйте multi-stage сборки и легковесные базовые образы (
alpine,distroless). Меньше слоёв — меньше накладных расходов на память. - Оптимизация кода и конфигурации. Уменьшите размеры кэшей (Redis, встроенные), настройте пуллы соединений, проверьте на утечки (открытые файловые дескрипторы, неосвобождаемые объекты).
Способ 3: Проверка и корректировка конфигурации узла и cgroup
Иногда проблема не в pod'е, а в конфигурации узла.
- Проверьте swap на узле. Kubernetes по умолчанию не рекомендует использовать swap. Если swap включён, система может "подтормаживать", а не убивать процессы, что сложнее диагностировать. Лучше отключить:
sudo swapoff -a. - Узнайте версию cgroup.
stat -fc %T /sys/fs/cgroup/. Еслиcgroup2, убедитесь, что Docker/containerd корректно настроен для работы с ним. Некоторые старые версии рантаймов могут некорректно считать память в cgroup v2. - Проверьте общую нагрузку на узел. Возможно, на узле запущено слишком много pod'ов с высокими лимитами, и физической памяти просто не хватает. Используйте
kubectl top node. Рассмотрите увеличение узлов или настройкуResourceQuota.
Способ 4: Настройка oomScoreAdj (выживание при нехватке на узле)
Этот шаг не решит проблему внутри контейнера, но поможет критически важным pod'ам выжить, если память на узле закончится и ядро начнёт убивать процессы.
В манифесте pod'а добавьте securityContext:
spec:
containers:
- name: myapp
# ...
securityContext:
oomScoreAdj: -999 # Минимальный приоритет на убийство (только для Linux)
Чем меньше значение oomScoreAdj (от -1000 до 1000), тем меньше шансов, что процесс будет убит первым. По умолчанию у контейнера 0. У kubelet и системных процессов — отрицательные значения.
⚠️ Важно: Если pod всё равно получает OOMKilled, это значит, что он превысил свой собственный
limit, а не был жертвой общей нехватки на узле.oomScoreAdjздесь не поможет.
Профилактика
- Всегда устанавливайте и
requests, иlimits. Никогда не оставляйтеlimitsнеограниченными (unlimited). - Настройте мониторинг. Используйте Prometheus +
kube-state-metricsдля сбора метрикkube_pod_container_resource_limitsиcontainer_memory_usage_bytes. Настройте алерты (Alertmanager) на приближение использования к лимиту (например, >85%). - Проводите нагрузочное тестирование. Перед запуском в продакшене протестируйте приложение с инструментами вроде
stress-ngилиheyвнутри контейнера, чтобы найти реальные пики потребления памяти. - Используйте вертикальный автоскейлинг (VPA) в режиме
AutoилиInitial. VPA может автоматически анализировать историческое потребление и рекомендовать (или применять) оптимальные значенияrequestsиlimits. Внимание: VPA не должен использоваться вместе с HPA на CPU/Memory одновременно без осторожности. - Регулярно обновляйте образы. Новые версии приложений и их зависимостей часто содержат исправления утечек памяти и оптимизации.