Контейнер завершился с кодом 137: как отличить OOM от принудительной остановки

Задача в контейнере оборвалась, а в списке остановленных контейнеров стоит Exited (137). Легко сразу увеличить лимит памяти. Обычно 137 означает, что основной процесс завершился после SIGKILL (сигнал 9): 128 + 9. Такой сигнал может прийти из-за нехватки памяти, от docker kill или после тайм-аута штатной остановки. Сначала выясним, кто и когда прервал процесс.

Что осталось после остановки

Возьмите ID нужного контейнера из docker ps -a --filter exited=137. Если система пересоздала контейнер, прежнее имя уже может указывать на новый экземпляр, поэтому дальше лучше использовать именно ID. Для него посмотрите состояние и заданный предел памяти:

docker inspect --format '{{json .State}}' <container-id>
docker inspect --format '{{.HostConfig.Memory}}' <container-id>
docker logs --timestamps --tail 100 <container-id>

В State сравните ExitCode, OOMKilled и FinishedAt. Если OOMKilled равен true, Docker зафиксировал остановку из-за нехватки памяти. HostConfig.Memory показывает жёсткий лимит в байтах; значение 0 означает, что такой лимит для контейнера явно не задан. Это ещё не отменяет нехватки памяти на самом хосте. Последние строки логов помогают понять, на какой операции всё оборвалось, но процесс, убитый SIGKILL, обычно не успевает записать объяснение.

Сопоставьте время с событиями

Для остановки около известного времени запросите события Docker по ID контейнера. В примере окно задано в UTC; подставьте своё время вокруг FinishedAt:

docker events --since 2026-10-22T10:00:00Z --until 2026-10-22T10:10:00Z --filter container=<container-id>

Событие oom рядом с die поддерживает версию OOM. Запись kill с сигналом 9 подтверждает принудительное завершение, но не называет его причину. При обычной остановке Docker сначала отправляет сигнал завершения, а после тайм-аута — SIGKILL. Если рядом видны kill с сигналами 15 и 9, это возможная улика такой остановки, но сверяйте её со временем деплоя, вызовом docker stop или остановкой Compose. Отсутствие первой записи не исключает этот сценарий: начальный сигнал может быть настроен иначе, а Docker хранит лишь ограниченную историю событий. По той же причине отсутствие строки oom само по себе ничего не доказывает.

Если OOMKilled=false, проверьте источник внешнего SIGKILL в журналах оркестратора и хоста. Ядро Linux может убить процесс при общем дефиците памяти на машине. Здесь особенно полезно сопоставить системный журнал OOM с ID процесса и временем остановки. Если контейнером управляет платформа, её события и настройки памяти важнее догадок по одной строке Docker.

Если подтвердился OOM: исправьте причину нагрузки

Представим импорт файла: на небольшой пачке он проходит, на большой OOMKilled=true, а на графике перед падением растёт память. Посмотрите, держит ли код весь файл или все результаты запроса в списке. Потоковое чтение и обработка меньшими пачками уменьшают пик; если памяти объективно нужно больше, измерьте пик с запасом и после этого меняйте лимит и ёмкость хоста. Постоянное повышение лимита без измерения способно лишь перенести падение на соседние сервисы.

Для нового запуска можно наблюдать текущую память командой docker stats <container-id>. У уже остановленного контейнера она не покажет прошлый пик. Поэтому запишите расход во время контролируемого повторения задачи или используйте имеющиеся метрики. Сравните рост по мере обработки одной пачки и по мере повторения многих пачек: первый случай похож на слишком крупную операцию, второй — на накопление памяти. После изменения повторите тот же вход и проверьте одновременно завершение задачи, пик памяти и скорость обработки.

Если причиной оказался тайм-аут остановки, исследуйте обработку SIGTERM основным процессом и время, нужное для завершения текущей задачи. Именно там исправление, а не в настройке памяти. Сценарий с ручным docker kill решается поиском команды или автоматизации, которая отправила сигнал.

Где продолжить практику

В платном курсе «Docker и Docker Compose: контейнеризация приложений» вы разберёте логи, код завершения и сигналы, настроите ограничения CPU и памяти, а затем упакуете HTTP-сервис с PostgreSQL и научитесь находить поломки этого окружения. Откройте урок «Логи и код завершения» и посмотрите программу курса: она продолжает эту диагностику на работающем Compose-проекте.

Соседние поломки разобраны в серии статей о Docker, в том числе повторная установка зависимостей из-за порядка COPY.