Exit code 137, OOMKilled и лимиты CPU/RAM
Автор: Казачкин Даниил Михайлович · Обновлено
Код 137 часто связывают с нехваткой памяти, но сам по себе он не устанавливает причину. В shell-соглашении это 128 + 9, то есть завершение SIGKILL. Такой сигнал может прийти от OOM killer, оператора или механизма остановки после таймаута. Разберём, какие наблюдения позволяют различить эти случаи.
Собрать несколько признаков
Для завершившегося контейнера проверьте:
docker inspect --format '{{json .State}}' NAME
docker inspect --format '{{json .HostConfig.Memory}}' NAME
docker logs --tail 100 --timestamps NAMENAME замените именем контейнера. В State смотрите ExitCode, OOMKilled, время завершения и Error. OOMKilled: true — сильный дополнительный признак, но для полной картины сопоставьте события ядра/daemon и состояние хоста. Один код не доказывает утечку в приложении и не объясняет, какой процесс потреблял память.
Контролируемый опыт с лимитом
Создайте только одноразовый контейнер с жёстким небольшим ограничением. Команда выделяет память до завершения; не запускайте её без --memory.
docker run --name yk-oom-demo --memory 64m --memory-swap 64m python:3.13-alpine python -c 'chunks=[]; exec("while True:\n chunks.append(bytearray(8 * 1024 * 1024))")'
docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}}' yk-oom-demoНа обычном Linux Engine ожидается завершение из-за установленного лимита; зафиксируйте фактические значения вместо подмены результата ожидаемым. Поддержка ограничения swap зависит от конфигурации ядра. Одинаковые значения memory и memory-swap в поддерживаемом окружении запрещают контейнеру дополнительный swap. Параметры памяти.
Сравните с контейнером alpine:3.22 sleep 300, которому вы вручную отправили docker kill. Он тоже может получить 137, но OOMKilled не должен стать true только от вашего сигнала. В обоих опытах используйте разные учебные имена и удалите их после inspect.
CPU — другой вид ограничения
--cpus 0.5 ограничивает доступный процессорный бюджет, а не гарантирует постоянно занятое пол-ядра. При превышении квоты процесс ждёт, что может увеличить задержку. Это не то же самое, что OOM и уничтожение процесса. Для наблюдения используйте docker stats, а для разбора throttling — счётчики cgroup, если они доступны в вашей конфигурации.
Память Docker Desktop выделяется внутри Linux VM: большой суммарный расход может упереться и в бюджет VM, и в отдельный container limit. На сервере учитывайте daemon, page cache и соседние процессы. Отсутствие собственного лимита не означает бесконечный ресурс.
Исправление зависит от профиля нагрузки
Если рабочему набору обоснованно нужен больший объём, увеличьте бюджет после проверки ёмкости хоста. Если растёт очередь, ограничьте параллелизм и буферизацию. Если память монотонно удерживается, исследуйте heap/ссылки. Увеличение лимита откладывает падение при утечке, но не устраняет её. При Java дополнительно сопоставьте heap с памятью native, потоков и контейнерным бюджетом.
Измеряйте типичный и пиковый сценарии, а не только idle. Сохраните версию образа, параметры лимита и входные данные рядом с результатом: так повторный тест покажет эффект изменения. docker stats --no-stream даёт снимок, а не историю пика; для истории нужен последовательный сбор.
Самостоятельная диагностика
Получите два случая 137 разного происхождения, выпишите признаки и предложите действие для каждого. Для случая таймаута stop проверьте [обработку сигналов](/lessons/without-university/docker-containerization/docker-developer-26), для OOM — рабочий набор и лимиты. Удалите только yk-oom-demo и второй контейнер опыта. Проверяемый результат — вывод, основанный на нескольких наблюдениях, а не на одном числе.