OOMKilled в Kubernetes: отделяем лимит контейнера от нехватки памяти узла
API работает несколько часов, затем резко обрывает запросы и запускается заново. В Kubernetes виден OOMKilled. Увеличить память кажется очевидным решением, однако нужно понять, где возник дефицит: внутри ограничения конкретного контейнера или на узле в целом. От этого зависит, исправлять ли настройки приложения, лимит, размещение либо ёмкость кластера.
Сохраняем сведения до следующего перезапуска
kubectl -n demo describe pod api-example
kubectl -n demo get pod api-example -o yaml
kubectl -n demo logs api-example -c api --previousВ примерах namespace — demo, Pod — api-example, контейнер — api. В status.containerStatuses найдите запись этого контейнера и lastState.terminated: reason, exitCode, signal и finishedAt. Код 137 обычно соответствует SIGKILL; подтвердите это полями signal и reason, поскольку процесс может сам вернуть такой код. OOMKilled — более конкретное свидетельство, но контекст узла всё равно нужен. Счётчик restartCount и события помогают сопоставить отказы с графиком нагрузки.
Журнал предыдущего контейнера может закончиться посередине операции: при принудительном завершении процесс не успевает написать объяснение. Поэтому отсутствие traceback не опровергает дефицит памяти. Сохраните время отказа и доступные данные сразу: старые события и журналы не хранятся бесконечно, а новый Pod имеет отдельную историю.
Читаем реальные ограничения контейнера
Посмотрите resources у созданного Pod, а затем у шаблона Deployment. Значения могли быть добавлены политикой namespace. В типовом фрагменте ниже scheduler учитывает request при размещении, а memory limit ограничивает расход контейнера. Это разные числа и разные задачи:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"Сравнивать 512Mi только с размером кучи языка недостаточно. В расход входят и другие области: стеки потоков, нативные библиотеки, буферы, часть файлового кэша и данные memory-backed томов. Если JVM или другой runtime настроен занимать всю доступную границу одной кучей, остальной памяти может не хватить. Определите, что измеряет ваша метрика: RSS, working set и heap отвечают на разные вопросы.
kubectl -n demo top pod api-example --containers kubectl get pod -n demo api-example -o wide kubectl describe node worker-1
Подставьте узел из колонки NODE. kubectl top показывает текущее состояние, обычно уже после перезапуска. Низкая цифра сейчас не доказывает, что перед отказом пика не было. Ищите график по контейнеру в окне перед finishedAt и учитывайте интервал сбора: короткий пик между измерениями может остаться незаметным.
Отделяем узел от контейнера
В describe узла проверьте MemoryPressure, события OOM и одновременные отказы других Pod. При нехватке памяти kubelet может выселять Pod: это отдельный механизм, часто со статусом Failed и причиной Evicted. Kernel OOM и eviction не нужно сводить к одному «приложение превысило limit». Для давления на узел важны пороги kubelet, свободная память и характеристики остальных нагрузок.
Если отказ стабильно возникает около границы одного контейнера, расследуйте его расход: рост при одинаковой нагрузке указывает на возможную утечку, скачок при одном большом файле — на неограниченный вход или буферизацию. Сопоставьте размер задания, число параллельных обработчиков и удерживаемые объекты. Профилирование проводите на воспроизводимом сценарии, чтобы изменение лимита не стало единственным наблюдением.
Выбираем изменение и проверяем его
Увеличение limit имеет смысл, когда измеренная необходимая память выше прежней границы и на узлах есть ёмкость. Одновременно оцените request: прежняя слишком низкая заявка позволяет разместить рядом слишком много таких контейнеров. Ограничение размера запроса, потоковая обработка или уменьшение параллелизма могут устранить пики без постоянного роста резервов. Изменяйте одну причину за раз и фиксируйте ожидаемый результат.
После релиза повторите проблемную нагрузку и наблюдайте дольше обычного интервала до отказа. Успех — отсутствие новых OOM при приемлемых задержках и запасе памяти, а не только Running сразу после старта. Сохранённые график, время отказа и значение memory limit позволяют объяснить, почему выбрана именно эта настройка.
Ещё по теме
- CPU throttling в Kubernetes: почему приложение тормозит при свободном CPU
- DNS в Kubernetes отвечает медленно: проверяем CoreDNS, search и ndots
Продолжить изучение
Хотите увереннее разворачивать приложения и разбирать сбои в кластере? Сети, конфигурация, хранилища и обновления входят в программу «Kubernetes с нуля до PRO».
Продолжить на практике
- Kubernetes с нуля до PRO ↗
Разворачивайте и сопровождайте приложения в Kubernetes: от первого Pod до релизов, сетей, хранилищ, масштабирования и диагностики. Итоговый проект — сервис с базой данных, мониторингом и проверенным откатом.
