Cgroups и лимиты памяти: что ломается первым при OOM
Контейнер не получает память изолированно от остальной системы: cgroup учитывает анонимную память, часть кешей и память процессов. Разбираем, как Linux доходит до OOM, что погибает первым и какие метрики помогут найти причину.

OOM в контейнере начинается не обязательно с нехватки RAM на сервере
В cgroup v2 контейнер может упереться в свой лимит памяти, пока на хосте ещё остаётся свободная RAM. При достижении жёсткого лимита ядро пытается освободить память; если это не удаётся, оно может завершить процесс внутри cgroup. Что именно погибнет первым, зависит от состава процессов, настроек cgroup и того, хватило ли системе возможностей для освобождения памяти.
Разбор ниже относится к Linux с cgroup v2. У cgroup v1 другие имена файлов и некоторые отличия поведения, поэтому переносить команды и выводы между версиями без проверки нельзя. В контейнерной платформе лимит и отображение причины завершения также зависят от runtime и оркестратора.
Как лимит превращается в OOM
Контейнерная память — не только память, которую приложение выделило через malloc. Контроллер cgroup учитывает анонимную память процессов, а также файловые страницы, например кеш чтения файлов. Поэтому увеличение файлового кеша или числа процессов может приблизить cgroup к лимиту даже без заметного роста heap приложения.
В cgroup v2 ключевые показатели находятся в файлах контроллера памяти. memory.current показывает текущее потребление cgroup с учётом вложенных cgroup; memory.max задаёт жёсткий предел; memory.high — границу, при которой ядро начинает сильнее ограничивать потребление и возвращать страницы. Значение max в соответствующем файле означает отсутствие заданного лимита.
При давлении памяти ядро сначала пытается reclaim — вернуть память, например освободив часть файлового кеша. Если настроен swap, оно может переместить туда часть страниц, но доступность и предел swap зависят от настройки cgroup и хоста. Когда освободить достаточно памяти не получается, возникает cgroup OOM: ядро выбирает процесс для завершения, чтобы выполнить запрос на память.
OOM — это не сообщение «контейнеру не хватило памяти». Это результат конкретного решения ядра: в какой cgroup возникло давление, удалось ли освободить страницы и какой процесс был выбран для завершения.
Термин. Reclaim — попытка ядра освободить используемые страницы памяти, например отбросить часть файлового кеша или переместить страницы в swap, если он доступен. Это не гарантирует, что приложение продолжит работу без задержек.
Что ломается первым
У cgroup нет универсального правила «сначала убить самый большой процесс». Ядро выбирает жертву по оценке OOM, учитывающей характеристики процесса; результат может измениться из-за привилегий и настроек, влияющих на эту оценку. Поэтому в контейнере с веб-сервером и несколькими worker-процессами погибнуть может один worker, а не главный процесс. Или, если главный процесс критичен для контейнера, его завершение фактически остановит весь сервис.
В cgroup v2 файл memory.oom.group позволяет настроить групповое поведение: при значении 1 OOM рассматривает cgroup как единое целое и может завершить находящиеся в ней задачи вместе, кроме задач, защищённых настройкой oom_score_adj. Это не означает, что любая платформа включает такой режим по умолчанию. Проверяйте фактическую настройку и политику runtime.
Первый симптом часто не сам OOM. До него приложение может замедлиться: ядро тратит время на reclaim, процесс чаще ждёт память, растут задержки запросов. Затем возможны ошибки выделения памяти, перезапуск процесса супервизором или завершение контейнера. Если приложение пережило гибель одного worker, внешне это может выглядеть как краткий всплеск ошибок, а не падение всего сервиса.
Как отличить cgroup OOM от проблем хоста
Одного сообщения «Killed» недостаточно. Сопоставьте состояние cgroup с журналом ядра и событиями контейнерной платформы. В cgroup v2 смотрите memory.events: счётчики high, max, oom и oom_kill помогают понять, было ли давление и завершались ли процессы из-за OOM. Файл memory.events.local показывает события самой cgroup, а не её потомков; для агрегированной картины важен именно контекст иерархии.
Для диагностики фиксируйте показания до инцидента и после него: счётчики событий накапливаются, а снимок после перезапуска может уже не отражать состояние старой cgroup. На хосте проверьте журнал ядра: там может быть запись об OOM и выбранном процессе. В оркестраторе отдельно изучите состояние контейнера и причину завершения — она помогает, но не заменяет журналы ядра и метрики.
| Что проверить | Что показывает | Как трактовать |
|---|---|---|
memory.current | Текущее потребление cgroup | Сравнивайте с memory.max во времени, а не только после сбоя |
memory.high и счётчик high | Давление и ограничение до жёсткого предела | Рост событий вместе с задержками может указывать на reclaim под нагрузкой |
Счётчики max, oom, oom_kill | Попытки превысить предел, OOM и завершения | Снимайте значения до и после; число само по себе не объясняет виновника |
| Журнал ядра и состояние runtime | Выбранный процесс и реакцию платформы | Сверяйте время события с метриками и перезапусками |
Где диагностика ошибается
- Смотреть только на RSS приложения. Он не показывает полную картину учёта cgroup: файловые страницы и другие процессы контейнера тоже могут влиять на потребление.
- Считать
memory.maxединственным важным числом. Ростmemory.eventsпоhighможет сигнализировать о проблеме до жёсткого OOM. Если смотреть только на максимальный лимит, деградацию легко пропустить. - Путать cgroup OOM с глобальным OOM. Глобальный OOM возникает из-за давления на память всего хоста; cgroup OOM — из-за ограничений группы. При этом события могут совпасть по времени, а в контейнере виден только итог — завершившийся процесс.
- Забывать про swap и лимиты родительской cgroup. Локальный контейнер может выглядеть ниже своего предела, но ограничение родителя или настройки swap изменить результат. Проверяйте всю цепочку cgroup и конфигурацию runtime.
Как измерить запас до сбоя
Универсального безопасного запаса в процентах нет: он зависит от пикового числа запросов, размера рабочих наборов, кеша, фоновых задач и того, сколько процессов запускается параллельно. Измеряйте на реалистичной нагрузке и записывайте как минимум пиковые значения memory.current, события high и oom_kill, задержки приложения и число перезапусков.
Перед тестом зафиксируйте версию ядра, режим cgroup, лимиты контейнера и настройки swap. Затем постепенно увеличивайте нагрузку до ожидаемого пика и наблюдайте, что растёт первым: задержки, потребление памяти, события reclaim или число завершений. Без такого контекста цифра «потребление памяти — 900 МБ» мало что говорит: неизвестно, сколько запросов обслуживалось и был ли это краткий пик.
Если cgroup стабильно достигает memory.high, это повод проверить профиль нагрузки и пики, а не просто поднять memory.max. Если растёт oom_kill, выясните, какой процесс завершён и почему другие процессы не удержали сервис. Добавление памяти может устранить дефицит, но не исправит утечку или неконтролируемый рост числа worker-процессов.
Что делать дальше
Настройте сбор метрик cgroup до следующего инцидента: после перезапуска часть доказательств уже нельзя восстановить. Сопоставьте их с графиками задержек, перезапусками и журналом ядра, а затем повторите нагрузочный тест при тех же лимитах и версии ядра. Для конкретной системы отдельно проверьте документацию её runtime: он определяет, как лимиты контейнера соотносятся с cgroup и как сообщает о завершении процесса.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Linux kernel documentation: Control Group v2, Linux kernel documentation: Out Of Memory handling
Частые вопросы
Если на хосте есть свободная память, почему контейнер получил OOM?
Ядро всегда убивает самый прожорливый процесс?
Как узнать, что был именно OOM в cgroup?
Если я использую cgroup v1, подойдут эти имена файлов?
Можно ли просто увеличить memory.max?
Почему приложение тормозит, хотя OOM ещё не случился?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.