Docker-контейнер постоянно перезапускается: как диагностировать restart loop

Restart loop — состояние, при котором контейнер запускается, завершается с ошибкой и сразу стартует снова, повторяя цикл бесконечно. В выводе docker ps такой контейнер отображается со статусом Restarting или быстро переключается между Up и Exited. Проблема не в самом Docker, а в том, что процесс внутри контейнера не может работать стабильно.

Первый шаг: определить exit-код и статус​


Команда docker ps показывает текущее состояние, но для диагностики нужен последний exit-код:

Bash:
docker ps -a --filter "name=mycontainer" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

Более детальную информацию даёт docker inspect:

Bash:
docker inspect --format='{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}' mycontainer

Exit-код сразу сужает круг поиска:

КодТипичная причина
0Процесс завершился штатно — контейнер не рассчитан на длительную работу
1Ошибка приложения (исключение, неверная конфигурация)
2Ошибка shell-скрипта или misuse команды
126Файл найден, но не является исполняемым
127Команда или бинарник не найдены
137SIGKILL — чаще всего OOM-kill
139SIGSEGV — segmentation fault
143SIGTERM — graceful shutdown по таймауту

Чтение логов контейнера​


Логи — основной источник информации о причине падения. Docker перехватывает STDOUT и STDERR процесса и сохраняет их через logging driver.

Bash:
## Последние 100 строк
docker logs --tail 100 mycontainer

## С временными метками
docker logs --timestamps mycontainer

## Непрерывное чтение (аналог tail -f)
docker logs --follow mycontainer

## Логи за последний час
docker logs --since 1h mycontainer

Если контейнер перезапускается каждые несколько секунд, --follow покажет вывод каждого цикла. Обратите внимание на повторяющиеся сообщения об ошибках: они указывают на конкретную причину.

При использовании нестандартного logging driver (например, json-file с ограничениями или syslog) убедитесь, что логи вообще пишутся. Проверить активный driver можно через docker inspect:

Bash:
docker inspect --format='{{.HostConfig.LogConfig.Type}}' mycontainer

Частые причины restart loop​


Процесс завершается штатно (exit 0)​


Контейнер существует, пока работает его PID 1. Если основной процесс выполнил задачу и вышел, контейнер останавливается. При restart policy always или unless-stopped Docker немедленно перезапустит его — и цикл повторится.

Типичный случай: скрипт инициализации без foreground-процесса.

Код:
## Плохо: скрипт отработал и вышел
CMD ["/init.sh"]

## Хорошо: процесс остаётся на переднем плане
CMD ["nginx", "-g", "daemon off;"]

OOM-kill (exit 137)​


Ядро Linux убивает процесс, когда контейнер превышает лимит памяти. Проверить:

Bash:
docker inspect --format='{{.State.OOMKilled}}' mycontainer

Если вывод true, нужно либо увеличить лимит памяти (--memory), либо оптимизировать потребление внутри приложения. На хосте событие также фиксируется в dmesg:

Bash:
dmesg | grep -i "oom\|killed process"

Отсутствующий бинарник или библиотека (exit 127 / 126)​


Образ собран на одной базе, а запускается на другой, или multi-stage build не скопировал зависимости. Логи покажут not found или permission denied.

Проверка:

Bash:
docker run --rm --entrypoint /bin/sh myimage -c "which myapp && ldd /usr/bin/myapp"

Ошибка конфигурации приложения​


Приложение стартует, читает конфиг, обнаруживает неверный параметр и выходит с кодом 1. Частые примеры:

  • Неверный формат переменных окружения
  • Недоступная база данных или брокер сообщений на момент старта
  • Ошибка в YAML/TOML-конфиге

Зависимость от внешнего сервиса​


Контейнер пытается подключиться к БД, Redis или другому сервису, который ещё не готов. Без retry-логики процесс падает. Решение — healthcheck и depends_on с условием service_healthy (в Docker Compose), либо retry-логика в самом приложении.

Как работают restart policy​


Docker предоставляет restart policy через флаг --restart при создании контейнера. Доступные значения:

ПолитикаПоведение
noНе перезапускать (по умолчанию)
on-failure[:N]Перезапускать только при ненулевом exit-коде, опционально до N попыток
alwaysПерезапускать при любом завершении, включая ручную остановку после рестарта демона
unless-stoppedКак always, но не перезапускать, если контейнер был остановлен вручную

Важные нюансы из документации Docker:

  • Правило 10 секунд. Restart policy вступает в силу только после того, как контейнер проработал минимум 10 секунд. Если процесс падает сразу, Docker не входит в бесконечный цикл перезапусков — контейнер остаётся в статусе Exited.
  • Ручная остановка. Если вы выполнили docker stop, restart policy игнорируется до перезапуска демона Docker или явного docker start.

Изменить политику для уже созданного контейнера:

Bash:
docker update --restart unless-stopped mycontainer

Отключить перезапуск, чтобы остановить loop и провести диагностику:

Bash:
docker update --restart no mycontainer
docker stop mycontainer

Диагностика через docker events​


Поток событий Docker в реальном времени показывает, что происходит с контейнером:

Bash:
docker events --filter container=mycontainer

Вывод фиксирует события die, restart, start, kill с временными метками. Если между die и start проходит менее секунды — это подтверждение restart loop.

Проверка resource limits​


Если контейнер упирается в лимиты CPU или памяти, он может работать нестабильно. Посмотреть текущие ограничения:

Bash:
docker inspect --format='Memory: {{.HostConfig.Memory}}, CPUs: {{.HostConfig.NanoCpus}}' mycontainer

Статистику потребления в реальном времени:

Bash:
docker stats mycontainer --no-stream

Если память близка к лимиту, а OOMKilled равен true — проблема подтверждена.

Временная остановка loop для отладки​


Чтобы исследовать файловую систему упавшего контейнера без его перезапуска:

Bash:
## Остановить и убрать restart policy
docker update --restart no mycontainer
docker stop mycontainer

## Создать новый контейнер из того же образа с shell
docker run --rm -it myimage /bin/sh

## Или примонтировать файловую систему остановленного контейнера
docker export mycontainer > /tmp/container_fs.tar

Для более глубокой отладки можно переопределить entrypoint:

Bash:
docker run --rm -it --entrypoint /bin/sh myimage

Это позволяет вручную запустить проблемную команду и увидеть ошибку в интерактивном режиме.

Типичные ошибки при исправлении​


Установка --restart always без понимания причины. Это маскирует проблему: контейнер продолжает падать, но Docker упорно его перезапускает, расходуя ресурсы.

Игнорирование healthcheck. Без healthcheck Docker считает контейнер «работающим», даже если приложение внутри уже не отвечает. Добавьте HEALTHCHECK в Dockerfile или healthcheck в Compose-файл.

Смешивание restart policy и внешнего process manager. Документация Docker прямо не рекомендует комбинировать restart policy с systemd-юнитами или supervisor для одного и того же контейнера — это создаёт конфликты.

Отсутствие логирования. Если logging driver не настроен или логи ротируются слишком агрессивно, в момент падения контейнера вы не увидите причину.

Чек-лист диагностики​


  1. docker ps -a — зафиксировать статус и количество рестартов.
  2. docker inspect — получить exit-код, флаг OOMKilled, текст ошибки.
  3. docker logs --tail 200 — прочитать вывод приложения перед падением.
  4. docker events --filter container=... — убедиться, что цикл действительно происходит.
  5. docker stats — проверить потребление памяти и CPU относительно лимитов.
  6. При необходимости — отключить restart policy, запустить контейнер с shell и воспроизвести ошибку вручную.
  7. После исправления — вернуть подходящую restart policy (unless-stopped для продакшена, on-failure:3 для задач с ограниченным числом попыток).

Источники​


 
Назад
Верх Низ