Код выхода 137 у Docker-шлюза API: как проверить OOM и причины перезапуска
Разделите код 137, OOMKilled, тайм-аут остановки и нехватку памяти хоста. Свяжите свидетельства с ID контейнера и временем запуска, проверьте лимиты и не принимайте новый запуск за старый сбой.
Поток ответа API-шлюза оборвался, а контейнер показывает Exited (137). Не спешите удваивать память. Код 137 часто связан с завершением через SIGKILL, но к нему могут привести нехватка памяти, принудительная остановка администратором и истечение времени штатной остановки. Нужно выяснить, какой контейнер, в каком запуске и почему завершился.
Материал относится к Linux-контейнерам Docker Engine и Compose. В Docker Desktop учитывайте также ресурсы Linux-виртуальной машины; для Windows-контейнеров и других оркестраторов действуют свои правила. Примеры проверены только локально и автономно: мы не вызывали настоящий OOM, не управляли производственными контейнерами и не обращались к платным моделям.
1. Что доказывают 137, OOMKilled и график памяти
Docker связывает статус выхода 137 с SIGKILL(9) и перечисляет несколько возможных причин, включая ручное завершение.[1] Команда docker stop обычно сначала отправляет сигнал завершения. Если процесс не остановился за отведённое время, отправляется SIGKILL.[2] Поэтому 137 сразу после развёртывания и 137 при высокой нагрузке требуют разных подтверждений.
| Наблюдение | Какой вывод допустим | Чего оно ещё не доказывает |
|---|---|---|
| Только код выхода 137 | Нужно проверить принудительное завершение | Кто отправил сигнал и был ли OOM |
OOMKilled: true для того же запуска | Docker зафиксировал убийство процесса внутри контейнера из-за нехватки памяти | Что это основной процесс, утечка или обязательная необходимость расширения |
Совпадающие по времени события oom и die | Можно восстановить порядок событий контейнера | Все первопричины без дополнительных показателей |
| Низкое потребление после перезапуска | Текущее потребление нового запуска невелико | Что предыдущий запуск не достигал лимита |
OOMKilled: false или отсутствие событий | В прочитанных данных нет соответствующего подтверждения | Отсутствие OOM в другом запуске или в потерянной истории |
Engine API определяет OOMKilled как признак того, что после последнего запуска контейнера один из его процессов был убит из-за нехватки памяти.[3] После гибели дочернего процесса контейнер может продолжать работать. Даже true без временной линии не доказывает, что именно OOM был единственной причиной данного завершения. Ошибка выделения памяти в куче самого приложения тоже не обязана давать Docker-флаг OOM или код 137.
2. Сначала сохраните сведения о том же контейнере и запуске
До пересоздания или повторных перезапусков убедитесь, что Docker CLI подключён к нужной среде. Следующие команды только читают данные. Замените gateway-container проверенным именем или ID. Получив полный ID первой командой, предпочтительно используйте его дальше: имя могло перейти к новому контейнеру.[4]
docker inspect --type container --format 'id={{.Id}} created={{.Created}} restarts={{.RestartCount}}' gateway-container
docker inspect --type container --format '{{json .State}}' gateway-container
docker inspect --type container --format 'memory={{.HostConfig.Memory}} reservation={{.HostConfig.MemoryReservation}} memorySwap={{.HostConfig.MemorySwap}}' gateway-container
Сохраните время сбора, StartedAt, FinishedAt, Status, ExitCode и OOMKilled. Несколько чтений не образуют атомарный снимок: при частых перезапусках поля могут относиться к разным запускам. Сверьте идентификатор и время старта ещё раз. В State могут быть сообщения об ошибках и вывод проверок здоровья. Перед передачей удалите секреты; не заменяйте выборку полным inspect, содержащим переменные окружения.
Затем запросите конкретный исторический интервал вокруг сбоя. Время ниже вымышленное: замените его своим интервалом, причём его конец должен быть в прошлом. Также замените FULL_CONTAINER_ID. Смещение +08:00 явно задаёт пекинское время, исключая неоднозначность часового пояса клиента.[5]
docker events --since '2026-11-04T16:20:00+08:00' --until '2026-11-04T16:30:00+08:00' --filter 'container=FULL_CONTAINER_ID' --format '{{json .}}'
Сопоставьте oom, kill, die, stop, start и их время с журналом развёртываний, обслуживанием и ошибками приложения. Сохраняйте обезличенную выдержку ограниченного интервала. Docker возвращает только последние 256 исторических событий: --since не превращает его в постоянный журнал аудита. Отсутствие события не доказывает, что его не было.[5] Для длительной истории нужен заранее настроенный сбор событий и мониторинг хоста.
Если старый контейнер удалён или поля прежнего запуска обновились при последующем старте, отметьте недостаточность исторических данных. Нормальное состояние нового контейнера с тем же именем нельзя подставлять вместо свидетельств старого сбоя.
3. Разделите лимит контейнера, давление на хост и ошибки приложения
HostConfig.Memory задаёт жёсткий лимит памяти контейнера в байтах. Значение 0 означает, что этот лимит не установлен, а не запрет на использование памяти. Оно также не гарантирует отсутствия ограничений хоста или вышестоящей группы ресурсов.[3] При общей нехватке памяти на Linux-хосте ядро может убивать процессы; пострадать способен не только шлюз.[6]
Для уже восстановленного работающего экземпляра можно получить текущую статистику:
docker stats --no-stream gateway-container
Это не исторический максимум. В Linux Docker CLI вычитает из показателя использования памяти часть кеша, тогда как API предоставляет общий объём и отдельные поля кеша. Нельзя напрямую сравнивать разные метрики и заключать, что запаса было достаточно. Для остановленного контейнера команда также не восстановит потребление перед сбоем.[7]
Выбирайте следующий шаг по наблюдениям:
- Давление вблизи лимита и OOM в том же запуске: проверьте буферизацию больших запросов, параллелизм, очередь и освобождение ресурсов, затем оцените достаточность лимита.
- Несколько сервисов отказали одновременно: проверьте доступную память хоста или виртуальной машины Docker Desktop, записи OOM ядра и другие процессы. Увеличения квоты шлюза может быть недостаточно.
- Сбой совпал с остановкой или развёртыванием: проверьте корректное завершение, обработку активных запросов и время ожидания остановки. Увеличивайте его обоснованно и с конечным пределом.
- Есть ошибка выделения памяти в куче приложения, но нет свидетельств OOM контейнера: проверьте лимит кучи среды выполнения и журнал приложения. Лимит контейнера и размер кучи — разные настройки.
Снижение потребления после завершения запросов и постоянный рост базового уровня после простоя — разные признаки. Один пик не доказывает утечку. Методика оценки нагрузки описана в материале о ресурсах и параллелизме шлюза.
4. Перед изменением лимита определите проблемный уровень
Ниже приведён фрагмент для существующего сервиса Compose, а не полный файл развёртывания или рекомендация по размеру сервера. Сохраните версию образа, тома, сеть, авторизацию и остальные настройки. Замените gateway настоящим именем сервиса.[8]
services:
gateway:
mem_limit: 1g
mem_reservation: 512m
mem_limit ограничивает память контейнера, а mem_reservation задаёт мягкое ограничение. Это не означает, что система выделила ему эксклюзивные 512 MiB, и не гарантирует, что потребление останется ниже этой величины. Мягкий лимит должен быть ниже жёсткого.[6] Если одновременно заданы параметры памяти в deploy.resources, значения должны быть согласованы с атрибутами сервиса.[8] Планируйте память совместно для шлюза, базы, прокси и ОС: повышение лимита одного контейнера может перенести проблему на хост.
Положительное значение memory-swap / memswap_limit обозначает сумму памяти и swap, а не дополнительный swap. Эффект зависит от лимита памяти и поддержки на хосте. Не задавайте неограниченные значения и не отключайте OOM killer только ради исчезновения ошибки.[6] Этот фрагмент не меняет swap; фактическую настройку всё равно нужно проверить.
Сохраните старую конфигурацию и проверьте решение в изолированной среде, затем примените изменение целевого сервиса по существующей процедуре развёртывания. Правка файла сама по себе не доказывает изменение работающего контейнера: повторно прочитайте HostConfig.Memory и связанные поля. Если требуется пересоздание, заранее защитите постоянные данные, обработайте активные запросы и подготовьте откат. Не совмещайте это с обновлением образа или удалением томов. Готовность после старта проверяйте по руководству о здоровье и готовности шлюза.
5. Автономный пример: не перепутайте новый запуск со старым сбоем
Функция Node.js ниже проверяет уже собранный снимок. incident.id и incident.startedAt должны происходить из исходной записи сбойного запуска, а не повторно копироваться из текущего снимка. Если они неизвестны, требуется проверка, а не догадка. Функция не подключается к Docker, не собирает доказательства и не принимает решение о перезапуске или расширении ресурсов.
function reviewExit(snapshot, incident) {
const object = v => v !== null && typeof v === 'object' && !Array.isArray(v)
const text = v => typeof v === 'string' && v.trim().length > 0
if (!object(snapshot) || !object(incident) || !object(snapshot.State)) {
return 'invalid-input'
}
const state = snapshot.State
if (![snapshot.Id, incident.id, state.StartedAt, incident.startedAt].every(text)) {
return 'missing-run-identity'
}
if (snapshot.Id !== incident.id || state.StartedAt !== incident.startedAt) {
return 'different-run'
}
if (!['created', 'running', 'paused', 'restarting', 'removing', 'exited', 'dead']
.includes(state.Status)) return 'invalid-status'
if (!['exited', 'dead'].includes(state.Status)) return 'not-an-exit-snapshot'
if (!Number.isInteger(state.ExitCode) || state.ExitCode < 0 || state.ExitCode > 255 ||
typeof state.OOMKilled !== 'boolean') return 'invalid-exit-fields'
if (state.OOMKilled) return 'oom-evidence-review-timeline'
if (state.ExitCode === 137) return 'exit-137-cause-unconfirmed'
return 'other-exit-review-logs'
}
const incident = { id: 'demo-container', startedAt: '2026-11-04T08:00:00Z' }
const snapshot = {
Id: 'demo-container',
State: {
StartedAt: incident.startedAt, Status: 'exited', ExitCode: 137, OOMKilled: false
}
}
console.log(reviewExit(snapshot, incident))
Результат примера — exit-137-cause-unconfirmed: код 137 не превращается автоматически в диагноз OOM. Новый ID или другое время запуска дают different-run. Снимок работающего контейнера даёт not-an-exit-snapshot: его поле кода выхода нельзя считать причиной текущего завершения. oom-evidence-review-timeline означает лишь наличие OOM-свидетельства и необходимость сверить временную линию из раздела 2.
Локальная проверка охватывает эти случаи, пустые значения, массивы, отсутствие идентификаторов, строковый код выхода, числа вне диапазона и не булев флаг OOM. Строки идентификации сравниваются точно с исходными значениями. Это не валидатор формата времени и не проверка подлинности свидетельств. Мониторинг гибели дочернего процесса в работающем контейнере нужен отдельно: функция предназначена для разбора завершений. Docker-команды и фрагмент Compose сверены с официальными документами; в среде проверки нет Docker CLI, настоящие контейнерные испытания не проводились.
6. Приёмка восстановления шире, чем отсутствие перезапусков
Сначала уменьшите или приостановите новую нагрузку, чтобы автоматические повторы не увеличивали очередь. Разрыв соединения со шлюзом не доказывает остановку генерации у провайдера или отсутствие списания. Сверьте результаты запросов из аварийного интервала по идентификаторам, а не повторяйте все задания сразу. Для финансовой сверки используйте руководство по детализации запросов и повторам.
В изолированной среде используйте локальный имитатор провайдера без чувствительных данных. Зафиксируйте размер запроса, длительность ответа и максимальный параллелизм; меняйте по одному фактору. Заранее ограничьте число запросов, длительность и порог памяти для остановки. Не ставьте целью доведение системы до OOM. Проверьте завершение обычных и потоковых ответов, освобождение ресурсов после отмены, сокращение очереди, отсутствие новых соответствующих OOM-свидетельств и согласованность базы с учётными записями.
Сохраните идентификаторы контейнеров до и после изменения, фактические лимиты, сопоставимые графики памяти, условия нагрузки, ошибки и непроверенные аспекты. Отсутствие перезапусков за короткий период означает лишь, что сбой не воспроизвёлся в этом окне. При недостатке старых данных оставьте причину неподтверждённой: разовое восстановление после увеличения памяти не является доказанным диагнозом.
7. Официальные источники и дата проверки
Источники проверены 2026-11-04. Параметры и доступность полей зависят от установленных версий Docker, Compose и ядра.
- Docker container ls: фильтрация по сигналу завершения: код 137 и разные причины принудительного завершения.
- Docker container stop: сигнал завершения и SIGKILL после тайм-аута.
- Официальная схема Docker Engine API v1.47:
ContainerState.OOMKilled, время запуска и завершения, поля памяти. Это определение фиксированной версии, а не заявление о самой новой версии. - Docker inspect: чтение объектов по типу и шаблону.
- Docker events: типы событий, фильтры времени, часовые пояса и ограничение истории.
- Docker resource constraints: жёсткие и мягкие ограничения памяти, OOM хоста и границы swap.
- Docker stats: текущая статистика, вычитание кеша Linux и ограничения для остановленных контейнеров.
- Compose Specification: согласованность
mem_limit,mem_reservationи параметров ресурсов развёртывания.