kai.kzLab

Restarting: почему Docker-контейнер постоянно перезапускается и «то работает, то нет»

Команда KAI Lab · · 8 мин чтения

Если контейнер «то работает, то нет», почти всегда он падает и перезапускается: политика restart: always или unless-stopped поднимает его заново, и снаружи это выглядит как сервис, который отвечает через раз. Причину ищут по коду выхода и событиям Docker: 137 — процесс убит сигналом SIGKILL, часто из-за лимита памяти, 143 — остановка по SIGTERM, 1 и другие небольшие коды — ошибка самого приложения.

Пользователи в такой ситуации жалуются на «нестабильную сеть» или «кэш браузера», а разработчики уверены, что ничего не меняли. Не спорьте ни с теми, ни с другими: сначала докажите или исключите перезапуски, а потом найдите, кто и почему завершает процесс.

Как понять, что контейнер перезапускается

Три признака, каждый проверяется одной командой.

Контейнер создан давно, а работает секунды. Колонка CREATED показывает возраст контейнера, STATUS — сколько длится текущий запуск:

$ docker ps --filter name=billing
CONTAINER ID   IMAGE                                    COMMAND          CREATED       STATUS          PORTS                      NAMES
8d41c0e2a7b3   registry.example.kz/shop/billing:2.4.1   "/app/billing"   3 hours ago   Up 38 seconds   127.0.0.1:8090->8090/tcp   billing

Если попасть в паузу между запусками, STATUS будет вида Restarting (1) 4 seconds ago, где в скобках код последнего завершения.

Растёт счётчик перезапусков. Docker хранит его в поле RestartCount:

$ docker inspect -f '{{ .RestartCount }} {{ .State.StartedAt }}' billing
17 2026-09-25T09:12:44.183920551Z

В событиях повторяются die и start. docker events показывает историю, а в событии die есть код выхода и время работы процесса в секундах:

$ docker events --since 1h --filter container=billing --filter event=die --filter event=start
2026-09-25T14:10:02.339457811+05:00 container die 8d41...a7b3 (execDuration=58, exitCode=1, image=registry.example.kz/shop/billing:2.4.1, name=billing)
2026-09-25T14:10:04.412908353+05:00 container start 8d41...a7b3 (image=registry.example.kz/shop/billing:2.4.1, name=billing)

Одинаковые фильтры по одному ключу объединяются через «или», разные — через «и». Учтите, что docker events отдаёт только последние 256 событий, поэтому для долгих разборов события стоит собирать в систему логов.

Кто поднимает контейнер: restart policy

Политику задают флагом --restart у docker run или ключом restart в Compose. Значения из документации Docker:

ПолитикаКогда Docker перезапускает контейнер
noникогда, это значение по умолчанию
on-failure[:max-retries]только при ненулевом коде выхода; можно ограничить число попыток
alwaysвсегда, когда контейнер остановился; остановленный вручную поднимется при перезапуске демона
unless-stoppedкак always, но остановленный вручную контейнер не поднимается и после перезапуска демона

Между попытками Docker выжидает паузу, которая удваивается с 100 миллисекунд до потолка в 1 минуту. Если контейнер после запуска проработал хотя бы 10 секунд, пауза сбрасывается на 100 миллисекунд. Поэтому сервис, который падает через минуту работы, перезапускается почти мгновенно и в docker ps почти всегда выглядит как Up.

В этом главная ловушка restart: always: он прячет падения. Сервис формально жив, но каждые пару минут недоступен на время перезапуска, а перед ним nginx в эти секунды отдаёт 502. Мониторинг, который проверяет только «контейнер запущен», ничего не заметит.

Политику работающего контейнера можно поменять без пересоздания: docker update --restart unless-stopped billing.

Причина по коду выхода

Код последнего завершения и признак OOM — в docker inspect:

$ docker inspect -f '{{ .State.ExitCode }} {{ .State.OOMKilled }}' billing
1 false

Когда процесс завершается по сигналу N, код выхода равен 128 + N. SIGKILL — это 9, SIGTERM — 15, номера есть в man 7 signal.

КодЧто значитГде искать дальше
0процесс завершился сам, без ошибкиконтейнер с always просто перезапускается; основной процесс не должен уходить в фон
1 и другие небольшиеошибка приложенияdocker logs за время перед падением
137128 + 9, SIGKILLOOMKilled: true и событие oom — память; иначе docker kill или добивание после таймаута остановки
139128 + 11, SIGSEGVпадение в самом приложении или его библиотеках
143128 + 15, SIGTERMконтейнер кто-то остановил: человек, скрипт, Compose, выключение хоста

Пошаговая диагностика

Шаг 1. Логи всех запусков

docker logs показывает вывод контейнера за все его запуски, пока сам контейнер не удалили. Смотрите последние строки перед каждым падением:

$ docker logs --since 30m --timestamps billing | tail -20

Если перед рестартом приложение пишет ошибку — у вас ответ. Если лог обрывается на полуслове, процесс, скорее всего, убили SIGKILL: этот сигнал нельзя перехватить, и приложение не успевает ничего записать.

Шаг 2. Память

$ docker stats --no-stream billing
$ docker events --since 1h --filter container=billing --filter event=oom
$ sudo dmesg -T | grep -i "memory cgroup out of memory"

docker stats покажет потребление против лимита, событие oom и строка ядра Memory cgroup out of memory подтвердят, что процесс убит за превышение лимита памяти. Лимит в байтах видно в docker inspect -f '{{ .HostConfig.Memory }}' billing, ноль значит «без лимита». Как подобрать лимит и почему нельзя просто его убрать, мы разобрали в статье OOMKilled и exit code 137.

Шаг 3. Кто останавливает контейнер

Если код 143 или 137 без OOM, контейнер останавливали снаружи. В событиях это видно по kill:

$ docker events --since 1h --filter container=billing --filter event=kill
2026-09-25T13:20:11.108391402+05:00 container kill 8d41...a7b3 (image=registry.example.kz/shop/billing:2.4.1, name=billing, signal=15)
2026-09-25T13:20:21.117540776+05:00 container kill 8d41...a7b3 (image=registry.example.kz/shop/billing:2.4.1, name=billing, signal=9)

Пара signal=15, через 10 секунд signal=9 — это docker stop, который не дождался завершения: 10 секунд — таймаут по умолчанию для Linux-контейнеров. Ищите, кто его вызывает: скрипт деплоя, cron, инструмент автообновления образов, docker compose up, пересоздающий контейнер. Журнал самого демона — journalctl -u docker --since "1 hour ago". Если процесс не укладывается в 10 секунд, научите его обрабатывать SIGTERM или увеличьте таймаут через --stop-timeout или stop_grace_period в Compose.

Шаг 4. Healthcheck

Restart policy срабатывает, когда контейнер завершается. Статус unhealthy из HEALTHCHECK — это только статус и событие health_status: документация Docker не описывает перезапуск по нему, в отличие от liveness probe в Kubernetes. Поэтому контейнер может неделями висеть в Up 5 days (unhealthy). Подробности проверки:

$ docker inspect -f '{{json .State.Health}}' billing

В выводе есть статус, число неудач подряд (FailingStreak) и последние результаты проверки с их выводом.

Шаг 5. Что изменилось

Сверьте время первого перезапуска с последними изменениями: новая версия образа, правка переменных окружения, выросшая нагрузка, обновление базы данных, заполненный диск. Фраза «ничего не меняли» — повод проверить журнал изменений, а не закрыть вопрос.

Причины перезапусков: таблица

ПричинаКак узнатьКак исправить
Процессу не хватает памятиexitCode=137, OOMKilled: true, событие oom, строка в dmesgзамерить потребление, поднять лимит, искать утечку
Ошибка приложения на стартеexitCode=1, ошибка в docker logsисправить конфигурацию или откатить версию
Недоступна зависимостьв логах connection refused или таймаутповторные подключения в приложении; в Compose — depends_on с condition: service_healthy
Контейнер останавливают снаружиkill с signal=15, затем die с exitCode=143найти скрипт или инструмент, который это делает
Не успевает остановитьсяsignal=15, через 10 секунд signal=9, exitCode=137обработка SIGTERM, --stop-timeout
Процесс завершается самexitCode=0держать процесс на переднем плане; разовую задачу запускать без restart
Кончилось местоno space left on device в логахосвободить место в Docker

Как не узнавать о перезапусках от пользователей

  • Алерт на рост числа перезапусков, а не только на «контейнер не запущен». Данные есть в RestartCount и в событиях die.
  • Для сервисов, которые должны работать всегда, — unless-stopped или always вместе с этим алертом. Для задач, которые должны завершаться, — on-failure с ограничением попыток, например on-failure:5.
  • Лимит памяти по замерам, а не наугад.
  • Приложение, которое переживает временную недоступность базы, а не падает при первой ошибке соединения.

Если те же контейнеры переедут в Kubernetes, симптомы сохранятся, но называться будут иначе: под уйдёт в CrashLoopBackOff. Для сервисов на systemd без контейнеров похожий разбор — в статье systemd-сервис не запускается.

Частые вопросы

Как посмотреть логи предыдущего запуска контейнера? docker logs уже показывает вывод всех запусков одного и того же контейнера, от старых к новым. Добавьте --timestamps и --since, чтобы найти строки перед конкретным падением. Если контейнер пересоздали, например через docker compose up, старый лог удалён вместе со старым контейнером.

Почему docker ps пишет Up, если сервис падает? STATUS показывает только текущий запуск. Если контейнер проработал больше 10 секунд и упал, Docker поднимает его почти сразу, и почти всё время вы видите Up с небольшим числом секунд. Сравните его с колонкой CREATED и проверьте RestartCount.

Что выбрать: always или unless-stopped? Разница в поведении после ручной остановки. Контейнер с always, остановленный вручную, снова поднимется при перезапуске демона Docker, например после перезагрузки сервера. С unless-stopped он останется остановленным, пока вы не запустите его сами.

Как ограничить число перезапусков? Политикой on-failure с числом попыток: docker run --restart=on-failure:10 .... После 10 неудачных завершений подряд Docker перестанет перезапускать контейнер. Для always и unless-stopped ограничения нет. Как работают политики перезапуска и чем 137 отличается от 143, часто спрашивают на собеседовании по Docker.

Потренироваться на практике

В симуляторе DevOps KAI Lab первый рабочий день начинается именно с такой жалобы: сервис у пользователей то открывается, то нет, а разработчики говорят, что ничего не меняли. Консоль в браузере отдаёт реальные выводы docker, и что происходит, нужно выяснить самому. Это первые задания первого месяца, они бесплатны, как и тест навыков DevOps.

Источники

Читать дальше