Жизненный цикл контейнера и политики перезапуска

Рейтинг: 65.7% · 17 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Жизненный цикл контейнера и политики перезапуска

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска (вы здесь)
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Боль: контейнер падает - и сервис лежит до утра

Представь типичную картину. Ночью прилетел всплеск трафика, приложение в контейнере словило OOM и умерло. Дежурный спит, мониторинг шлёт письмо в пустоту, а docker контейнер просто стоит в состоянии exited. Утром выясняется, что поднять его надо было одной командой - но никто этого не сделал, потому что это надо было сделать руками. Вторая классика - перезагрузили хост ради обновления ядра, а после ребута половина сервисов не встала, потому что про автозапуск никто не подумал.

Весь этот класс проблем закрывается пониманием двух вещей: как именно устроен docker жизненный цикл контейнера (через какие состояния он проходит и что переводит его из одного в другое) и как работает docker restart policy - политика, которая говорит демону, что делать, когда процесс внутри умер. Это не магия и не настройки "на удачу". Это конечный автомат с понятными правилами, и если ты их знаешь, ты строишь самовосстанавливающиеся сервисы, а не дежуришь у телефона. Разберём механику до винтика: состояния, сигналы, коды выхода, политики, связь с systemd и сбор мусора.

Изображение

Состояния контейнера: конечный автомат под капотом

Контейнер - это не одно "запущен/не запущен", а полноценный конечный автомат. Демон dockerd хранит состояние в своей БД, а реально процессом управляет containerd через runtime runc. Полный набор состояний такой:
  • created - контейнер создан командой create, для него выделены неймспейсы, cgroups, файловая система собрана из слоёв, но главный процесс (PID 1 внутри) ещё не запущен. Это как накрытый стол, за который никто не сел.
  • running - главный процесс работает. Это основное рабочее состояние.
  • paused - все процессы внутри заморожены через cgroup freezer (механизм ядра, который замораживает задачи на уровне планировщика). Процессы не получают процессорное время, но память не освобождается, сокеты держатся. Это пауза, а не остановка.
  • restarting - демон сейчас перезапускает контейнер согласно restart policy. Состояние обычно мелькает быстро, но если контейнер падает в крэш-петле, ты увидишь именно его.
  • exited - главный процесс завершился (сам, по сигналу или с ошибкой). Контейнер сохранён, у него есть код выхода, его можно стартануть заново.
  • dead - аварийное состояние. Демон не смог корректно удалить или остановить контейнер (часто из-за залипшего mount, проблем с драйвером хранилища или подвисшего runc). Из dead его нельзя стартовать, только удалить, и то иногда с боем.
Посмотреть текущее состояние можно так:

Код: Выделить всё

docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} pid={{.State.Pid}}' web
Типичный вывод:

Код: Выделить всё

exited exit=137 oom=true pid=0
Здесь сразу читается история: контейнер мёртв (exited), код выхода 137, и причина - OOMKilled=true, то есть ядро убило его за превышение лимита памяти. Pid=0 потому, что живого процесса больше нет. Поле OOMKilled - первое, куда смотришь, когда сервис загадочно перезапускается: если там true, дело не в баге приложения, а в лимите памяти.

Команды управления и что происходит на самом деле

Теперь пройдём по командам, которые двигают контейнер между состояниями, и разберём, что реально делает каждая.

create - готовит контейнер (состояние created), но не запускает. start - запускает главный процесс. Связка create + start - это и есть привычный docker run, просто run делает оба шага сразу и обычно ещё и тянет образ.

Самое интересное - docker stop. Это не "прибить", а "вежливо попросить уйти". Механика двухфазная:
  • Демон шлёт главному процессу SIGTERM (сигнал номер 15). Это приглашение завершиться красиво: дописать буферы, закрыть соединения, сбросить данные на диск.
  • Дальше демон ждёт. По умолчанию - 10 секунд. Управляется флагом --time (или -t) у docker stop, либо настройкой --stop-timeout на самом контейнере.
  • Если за это время процесс не вышел, прилетает SIGKILL (сигнал 9). Этот сигнал нельзя перехватить или проигнорировать - ядро убивает процесс мгновенно, без шанса на cleanup.

Код: Выделить всё

docker stop -t 30 web
Здесь мы даём приложению 30 секунд на graceful shutdown перед тем, как его прибьют. Для баз данных и очередей это критично: SIGKILL посреди записи - это потенциально битые данные.

Сигнал, который шлётся первым, можно поменять. В Dockerfile это инструкция STOPSIGNAL, на запуске - флаг --stop-signal. Например, nginx по своей логике хочет SIGQUIT для graceful, а не SIGTERM:

Код: Выделить всё

docker run --stop-signal=SIGQUIT --stop-timeout=25 nginx
Остальные команды короче:
  • docker kill - сразу шлёт сигнал (по умолчанию SIGKILL), без фазы ожидания. docker kill -s SIGHUP позволяет послать произвольный сигнал, чем часто пользуются для reload конфигов на лету.
  • docker restart - это stop, затем start. Внимание: docker restart выполняет ту же двухфазную остановку с тем же таймаутом, так что docker restart -t 30 тоже ждёт graceful. Команда docker restart удобна, чтобы перечитать конфиг или сбросить подвисшее состояние, но это полный цикл остановки и запуска, а не "мягкая перезагрузка процесса".
  • docker pause / docker unpause - заморозить и разморозить через cgroup freezer. Полезно, чтобы временно отдать CPU другому процессу или снять консистентный снапшот, не убивая контейнер.
  • docker rm - удалить контейнер. Работает только для не-running (или с флагом --force, который сначала прибьёт). Удаляет метаданные и слой записи, но не именованные тома.
Важный нюанс про PID 1. Внутри контейнера твой процесс часто становится PID 1, а у PID 1 в Linux особая судьба: ядро не применяет к нему дефолтные обработчики сигналов. Если приложение не повесило явный обработчик на SIGTERM, сигнал по умолчанию игнорируется - и docker stop честно ждёт 10 секунд, после чего прибивает SIGKILL. Отсюда вечная жалоба "почему мой контейнер останавливается ровно 10 секунд". Лекарство - либо корректно обрабатывать SIGTERM в коде, либо запускать с init-процессом (флаг --init подкладывает tini как PID 1, который правильно проксирует сигналы и собирает зомби-процессы).

Коды выхода: язык, на котором контейнер говорит, как он умер

Когда главный процесс завершается, остаётся exit code. Это не просто число - это диагноз. Базовое правило Unix: код 0 - успех, всё остальное - ошибка. Но есть важная конвенция: смерть по сигналу даёт код 128 + номер сигнала.
  • 0 - процесс завершился штатно. Для долгоживущего сервиса это часто подозрительно: почему демон вдруг решил, что закончил работу?
  • 1 - generic-ошибка приложения. Чаще всего исключение или ошибка в коде.
  • 125 - ошибка самого демона docker (например, неверный флаг в команде run). Контейнер даже не стартанул.
  • 126 - команда найдена, но не исполняемая (нет бита +x на entrypoint).
  • 127 - команда не найдена (опечатка в пути, нет бинарника в образе).
  • 137 - 128+9, убит SIGKILL. Если при этом OOMKilled=true - ядро прибило за память. Если false - это твой docker stop, у которого истёк таймаут.
  • 143 - 128+15, корректно завершился по SIGTERM. Нормальный результат docker stop, если приложение умеет gracefully останавливаться.
Связка кода и причины - твой главный диагностический инструмент. 143 - всё хорошо, остановили штатно. 137 с OOM - поднимай лимит памяти или чини утечку. 137 без OOM - приложение не реагирует на SIGTERM, чини обработчик сигналов. 1 - лезь в логи приложения. 127 - проверяй entrypoint и PATH.

Docker restart policy: автоматическое восстановление и автозапуск после ребута

Теперь главное про устойчивость. Политика перезапуска - это правило, которое демон применяет, когда главный процесс контейнера завершился. Задаётся флагом --restart у docker run или полем restart в compose. Четыре варианта:
  • no - дефолт. Демон ничего не делает, контейнер просто уходит в exited. Подходит для разовых задач и батчей.
  • on-failure - перезапуск только при ненулевом коде выхода. Можно ограничить число попыток: --restart on-failure:5 даст максимум пять попыток, после чего демон сдаётся. Идеально для задач, которые должны доработать, но не крутиться вечно.
  • always - перезапускать всегда, независимо от кода выхода. Даже если ты остановил контейнер руками через docker stop, после рестарта демона (или ребута хоста) always снова его поднимет. Это и плюс, и грабли.
  • unless-stopped - как always, но с одним отличием: если ты остановил контейнер вручную через docker stop, после перезагрузки демона или хоста он останется лежать. Демон запоминает, что ты явно его остановил, и уважает это решение.
Разница между always и unless-stopped тонкая, но в проде решающая. Сценарий: ты вывел сервис на обслуживание командой docker stop и ушёл. Ночью хост перезагрузился по плану. С политикой always сервис поднимется сам, посреди твоего обслуживания - сюрприз. С unless-stopped он останется выключенным, потому что демон помнит твой явный stop. Для большинства продакшен-сервисов unless-stopped - правильный баланс: автовосстановление при падениях есть, но твоё ручное решение остановить уважается.

Как это связано с ребутом хоста. Сами по себе политики работают, только пока жив демон dockerd. Чтобы контейнеры встали после перезагрузки сервера, нужны два условия одновременно:
  • Демон docker должен стартовать при загрузке: systemctl enable docker (а лучше enable --now). Без этого после ребута никакой docker вообще не запустится, и никакая политика не сработает.
  • У контейнера должна стоять политика always или unless-stopped. Политика no и on-failure автозапуск после ребута не дают.
Логика восстановления такая: при старте dockerd поднимает контейнеры, которые на момент остановки демона были в состоянии running и имеют подходящую политику. Контейнеры, которые ты явно остановил под unless-stopped, пропускаются. Поэтому связка "systemctl enable docker + restart: unless-stopped" - это и есть штатный способ переживать перезагрузки без отдельных юнитов systemd.

Когда же писать собственный systemd-юнит на контейнер? Когда нужен жёсткий порядок старта (сначала база, потом приложение), зависимости между сервисами через After=/Requires=, или интеграция с остальной инфраструктурой хоста. Тогда контейнер запускают через docker run внутри ExecStart юнита (политику docker при этом ставят в no, чтобы рулил один хозяин - systemd, а не оба сразу).

В compose это выглядит чисто:

Код: Выделить всё

services:
  api:
    image: registry.company.ru/api:1.4.2
    restart: unless-stopped
    stop_grace_period: 30s
    stop_signal: SIGTERM
Поле stop_grace_period - это тот самый таймаут до SIGKILL, аналог -t у docker stop.

Docker events и наблюдение за жизненным циклом

Чтобы видеть переходы состояний в реальном времени, есть docker events - поток событий от демона. Это бесценно при отладке крэш-петель: ты видишь точную последовательность die -> restart -> start и таймстампы между ними.

Код: Выделить всё

docker events --filter event=die --filter event=restart --since 10m
Пример потока для падающего контейнера:

Код: Выделить всё

2026-06-15T11:02:14 container die 9f3a... (exitCode=1, image=api:1.4.2, name=api)
2026-06-15T11:02:14 container restart 9f3a... (name=api)
2026-06-15T11:02:15 container start 9f3a... (name=api)
2026-06-15T11:02:16 container die 9f3a... (exitCode=1, name=api)
Здесь сразу видно крэш-петлю: die с кодом 1, рестарт, старт, и через секунду опять die. Поле exitCode=1 говорит, что это ошибка приложения, а не OOM. Демон применяет экспоненциальную задержку между попытками рестарта (начинает с долей секунды и увеличивает), чтобы не молотить впустую, поэтому интервалы между событиями будут расти.

Сбор мусора: prune и грабли с залипшими контейнерами

Остановленные контейнеры не исчезают сами - они копятся, занимая место под слои записи и метаданные. Со временем docker ps -a превращается в свалку. Чистят это командой prune:

Код: Выделить всё

docker container prune --filter "until=24h"
Эта команда удалит все остановленные контейнеры старше суток. Фильтр until спасает от удаления свежих контейнеров, которые ты, возможно, ещё хочешь изучить. Для тотальной уборки есть docker system prune (контейнеры + сети + образы + кэш сборки), но с ним осторожнее - флаг --volumes снесёт и неиспользуемые тома, а это уже данные.

Грабли с dead. Самая неприятная ситуация - контейнер залип в состоянии dead и не удаляется ни prune, ни обычным rm. Обычно причина в том, что у контейнера остался незакрытый mount (overlayfs не размонтировался) или подвис runc-процесс. Алгоритм лечения: сначала docker rm --force. Если не помогло - смотри docker logs самого демона (journalctl -u docker) на предмет ошибок размонтирования, проверяй mount | grep docker на залипшие точки монтирования в /var/lib/docker/overlay2, при необходимости размонтируй их вручную. В крайнем случае помогает рестарт самого демона systemctl restart docker - он переинициализирует состояние и часто отпускает залипший контейнер. К dead почти всегда ведёт что-то на уровне хоста (диск, драйвер хранилища, ядро), а не само приложение.

Антипаттерны и грабли из практики
  • restart: always на отладочном контейнере. Сервис падает на старте, always тут же его поднимает, он опять падает - и ты получаешь бесконечный цикл, который ещё и греет CPU. На время отладки ставь no и смотри логи спокойно.
  • Игнор SIGTERM в приложении. Каждый docker stop превращается в 10 секунд ожидания и грубый SIGKILL. Данные могут не сброситься на диск. Всегда вешай обработчик SIGTERM на graceful shutdown.
  • Тяжёлая работа в shutdown без запаса по таймауту. Если корректное завершение требует 40 секунд, а stop_grace_period стоит дефолтные 10 - SIGKILL прилетит посреди процесса. Подгоняй таймаут под реальное время остановки.
  • Расчёт, что политика спасёт после ребута без enable docker. Классика: restart: always стоит, а демон не включён в автозагрузку. После перезагрузки ничего не встаёт. Проверь systemctl is-enabled docker.
  • Накопление exited-контейнеров. Без регулярного prune диск под /var/lib/docker забивается мёртвыми контейнерами и осиротевшими слоями.
Мини-лаба: пройди жизненный цикл руками

Повтори всё сам, чтобы увидеть переходы своими глазами:

Код: Выделить всё

# 1. Контейнер, который держит SIGTERM и сам не уходит
docker run -d --name life --restart unless-stopped alpine sh -c 'trap "" TERM; sleep 600'

# 2. Посмотри состояние и политику
docker inspect -f '{{.State.Status}} policy={{.HostConfig.RestartPolicy.Name}}' life

# 3. Останови с коротким таймаутом и засеки время - увидишь паузу и SIGKILL
docker stop -t 5 life

# 4. Проверь код выхода: ждём 137 (убит SIGKILL по таймауту, без OOM)
docker inspect -f 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' life

# 5. В соседнем терминале запусти поток событий, потом стартуй и убей контейнер
docker events --since 5m --filter container=life

# 6. Уборка
docker rm life
Обрати внимание на шаге 3: контейнер игнорирует SIGTERM (мы поставили trap "" TERM), поэтому docker stop честно ждёт 5 секунд и добивает SIGKILL - на выходе код 137. Убери trap, повесь нормальный обработчик - и получишь мгновенную остановку с кодом 143. Это и есть разница между приложением, которое уважает docker stop, и тем, которое нет.

Контрольные вопросы
  • Какую последовательность сигналов шлёт docker stop и сколько ждёт по умолчанию между ними? Что произойдёт, если PID 1 игнорирует первый сигнал?
  • В чём практическая разница между restart policy always и unless-stopped при перезагрузке хоста, если контейнер до этого был остановлен вручную?
  • Контейнер в состоянии exited с кодом 137 и OOMKilled=false - что это значит и где искать причину? А если OOMKilled=true?
  • Какие два условия должны выполниться одновременно, чтобы контейнер сам поднялся после ребута сервера?
Итог

Docker жизненный цикл контейнера - это конечный автомат: created, running, paused, restarting, exited, dead, а команды create/start/stop/restart/pause/kill/rm двигают его между состояниями. docker stop - вежливая двухфазная остановка SIGTERM -> ожидание -> SIGKILL, и коды выхода (137, 143, OOMKilled) точно говорят, как умер процесс. Docker restart policy (no, on-failure, always, unless-stopped) автоматизирует восстановление, а в связке с systemctl enable docker - и автозапуск после ребута. Знай этот автомат - и твои сервисы чинят себя сами, а ты спишь по ночам.
👍5 ❤️1 🔥 😄 🤔3
Аватара пользователя
gaulin
Сообщения: 1
Зарегистрирован: 26 май 2026, 23:12

Re: Жизненный цикл контейнера и политики перезапуска

Сообщение gaulin »

Вот про 137 без OOM теперь дошло - у меня контейнер ровно 10 сек стопался и я грешил на докер, а это мой го-сервис SIGTERM не ловил. Добавил graceful shutdown, стало мгновенно с кодом 143. Спасибо за trap в лабе, наглядно
👍1 ❤️1 🔥 😄 🤔
Аватара пользователя
vault_ops
Сообщения: 1
Зарегистрирован: 23 май 2026, 04:26

Re: Жизненный цикл контейнера и политики перезапуска

Сообщение vault_ops »

А правильно понимаю что если запускать контейнер через свой systemd юнит то policy надо ставить no, иначе docker и systemd будут драться кто его перезапускает? Где то ловил из за этого двойные старты
👍1 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Образы изнутри: слои, OverlayFS и storage driver
Следующая глава →
Отладка контейнеров: exec, attach, nsenter, debug

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: как установить docker на ubuntu и linuxdocker run как запустить контейнер с флагамиdocker контейнер падает сразу после запуска как найти причинуdocker network как связать контейнеры между собой по имени

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость