Схема по материалу: Как я разбираю Docker restart loop, не маскируя его авто‑перезапуском
Схема основана на материале «Как я разбираю Docker restart loop, не маскируя его авто‑перезапуском» и его сигналах. Временная линия связывает завершение процесса, решение restart policy, события Docker Engine и наблюдение сервиса в Zabbix.

Контекст и первичная фиксация цикла

В одном из внедрений я увидел типичную внешнюю картину: сервис то появлялся доступным, то снова исчезал, а Docker регулярно запускал его основной процесс. Я не стал считать авто‑перезапуск восстановлением. Сначала зафиксировал временное окно, имя сервиса, имя контейнера, образ и момент первого замеченного сбоя. Затем сопоставил это с доступностью зависимых компонентов и состоянием хоста. Диагностические команды использовал только на чтение: docker ps --no-trunc для состава и docker inspect для полной конфигурации и состояния. В рабочую запись не переносил значения переменных или меток, если они могли содержать секрет. Такая исходная карточка нужна, потому что после пересоздания меняется container ID, журнал может начать новую историю, а наблюдатель видит уже другой экземпляр. Сам факт нескольких запусков ещё не объясняет причину. Я отдельно записываю, завершался ли процесс сам, посылал ли ему сигнал Engine, менялся ли образ и не совпало ли начало цикла с нехваткой диска, сетевым отказом или недоступностью внешней системы. Только после этой фиксации формулирую несколько проверяемых ветвей, не объявляя ни одну доказанной заранее.

Что действительно говорят State и счётчики

В выводе docker inspect я рассматриваю объект State как снимок, который имеет смысл лишь вместе со временем. Поля Status, Running, Restarting, StartedAt и FinishedAt дают последовательность, RestartCount показывает число повторных запусков текущего контейнера, а ExitCode описывает результат последнего завершения. Нулевой код не всегда означает исправность сервиса: приложение могло штатно закончить работу, хотя контейнер задуман как постоянно работающий. Ненулевой код тоже требует расшифровки по документации приложения и способу запуска. OOMKilled является сильным признаком вмешательства механизма нехватки памяти для контейнера, но я проверяю рядом события ядра и метрики хоста: похожий симптом способен возникнуть при общем дефиците памяти или завершении процесса извне. Значение Error и текст последней ошибки запуска помогают отделить падение приложения от невозможности создать процесс. Я сохраняю релевантные поля структурированно, например через docker inspect --format, но не строю вывод по одному текущему Status: короткий цикл легко попасть в фазу running. Критерий подтверждения — повторяемая связь времени завершения, кода, признака OOM и соответствующей записи приложения либо Engine. Если связь не повторяется, ветвь остаётся предположением.

Restart policy задаёт реакцию, а не лечение

Далее я читаю HostConfig.RestartPolicy и проверяю, кто фактически управляет жизненным циклом. Политика no не запускает контейнер снова автоматически; on-failure реагирует на неуспешное завершение и может иметь ограничение попыток; always и unless-stopped возвращают контейнер после остановки процесса, но различаются поведением после ручной остановки и перезапуска демона. У политики есть важная практическая семантика: она начинает действовать после успешного старта контейнера, а ручная остановка подавляет немедленное возобновление до следующего подходящего события управления. Я сверяю это с документацией текущей версии Engine и не обещаю одинакового поведения внешнего оркестратора. Если цикл уже идёт, увеличение числа попыток или переход на always лишь чаще воспроизводит дефект, нагружает зависимости и стирает ранние сообщения из ограниченного журнала. Поэтому я сначала сохраняю доказательства и определяю владельца запуска: Docker, Compose, systemd либо другая автоматика могут конкурировать. Любое временное изменение политики допускаю лишь после согласованной безопасной паузы, с записанным исходным значением и точным обратным действием. В обычной диагностике достаточно чтения inspect; менять policy ради наблюдения не требуется.

Журналы, timestamps и смысл healthcheck

Журнал я читаю ограниченным временным окном командой docker logs --timestamps --since с подходящей точкой начала. Метки времени позволяют соединить последнюю строку приложения с FinishedAt и событием restart, не принимая старую ошибку за текущую. Перед сохранением фрагмента редактирую токены, пароли, строки подключения, персональные данные и иные секреты; маскирование выполняю в рабочей выписке, а исходный журнал не изменяю. Если logging driver не поддерживает docker logs, проверяю его тип через inspect и использую разрешённый read-only источник, не меняя драйвер в ходе инцидента. Отдельно разбираю Health.Status и массив недавних результатов healthcheck. Healthcheck отвечает только на заданную проверку: он может заметить отказ внутреннего endpoint, но плохая команда проверки способна дать ложную тревогу. Обратная ситуация также важна: живой PID 1 и статус running доказывают существование процесса, а не готовность принимать запросы, читать хранилище или обращаться к зависимости. При этом сам Docker не перезапускает обычный контейнер только из-за unhealthy без дополнительной автоматики. Я ищу, кто реагирует на этот статус, и связываю каждое решение о рестарте с конкретным событием, а не с предположением.

Цепочка image, config, env и secret

После состояния я восстанавливаю входы процесса. Сначала фиксирую неизменяемый digest образа, команду, entrypoint и параметры запуска; один тег без digest недостаточен, потому что он мог указывать на другую сборку. Затем сравниваю ожидаемые конфигурационные файлы и фактически подключённые mounts, права чтения и путь внутри контейнера. Переменные окружения рассматриваю по именам и наличию, но не печатаю их полный набор: там часто находятся учётные данные. Для secret проверяю способ доставки, существование ссылки, владельца и доступность для процесса, не раскрывая содержимое. Эта цепочка важна: исправный image может падать из-за устаревшего config, пустой env, недоступного secret или несовместимого формата после обновления. И наоборот, совпадение времени с выпуском образа ещё не доказывает дефект сборки. В изолированной проверке меняю только одно звено и сравниваю результат с прежним экземпляром. Гипотезу об образе исключаю, если тот же digest с теми же безопасно сопоставленными входами устойчиво работает в эквивалентных условиях; гипотезу о конфигурации — если контрольная версия не меняет поведение. Не копирую секреты в тикет, Zabbix item, командную строку или диагностический архив.

Cgroup и альтернативные причины на хосте

Ресурсную ветвь я начинаю с лимитов контейнера, а не с общего впечатления о мощности сервера. Через inspect читаю ограничения памяти, CPU, PIDs и связанные параметры cgroup, после чего сопоставляю их с графиками потребления до каждого завершения. OOMKilled вместе с приближением к лимиту поддерживает гипотезу о памяти, однако нужно различить лимит конкретной группы, давление на всём хосте и внутреннюю утечку приложения. CPU throttling способен растянуть инициализацию до таймаута внешнего наблюдателя, но сам по себе не объясняет любой ExitCode. Исчерпание PIDs мешает создавать дочерние процессы и тоже оставляет характерные ошибки. Параллельно проверяю альтернативы хоста: свободные блоки и inode, ошибки файловой системы, задержку ввода-вывода, разрешение DNS, маршрут и установление соединения с зависимостью. Полный диск способен сорвать запись состояния или старт базы, а сетевой разрыв — заставить приложение завершиться по собственной логике. Ветвь исключаю только по наблюдениям в том же временном окне. Нормальная память сейчас не опровергает краткий пик перед рестартом; доступный endpoint после события не доказывает, что сеть была исправна в момент запуска.

Безопасный сбор Engine API и обнаружение в Zabbix

Для временной линии я использую docker events с фильтрами по типу container и нужным сервисным признакам, ограничивая начало и конец периода. Поток Engine показывает start, die, restart, health_status и другие события, но не заменяет журнал приложения. Если сбор автоматизирован, обращение к Docker Engine API выполняет выделенный компонент с минимально необходимым read-only сценарием, ограниченным набором endpoint и без передачи сокета произвольным пользовательским проверкам: доступ к docker.sock фактически даёт широкие полномочия на хосте. Полученные метаданные проходят редактирование секретных labels и env до сохранения. В Zabbix я строю low-level discovery так, чтобы обнаруженный объект сопоставлялся с устойчивыми service, name и image, причём image сохраняю с digest, когда он доступен. Изменчивый ID остаётся техническим атрибутом экземпляра, но не основным ключом истории. Правила фильтрации не должны случайно объединять два одноимённых сервиса разных контуров, поэтому добавляю стабильный контекст хоста или проекта. Метрики RestartCount, состояния и health полезны вместе с зависимыми item и событиями Engine. Триггер проверяю на собственной истории конкретного сервиса; универсального числа рестартов, подходящего всем приложениям, у меня нет.

Изолированная проверка, пауза, откат и регламент

Подтверждать причину я предпочитаю на изолированном экземпляре с тем же digest и обезличенной конфигурацией, без боевого трафика и записи в общие хранилища. До проверки определяю владельца сервиса, окно, критерий остановки и способ вернуть исходное состояние. Если требуется прервать быстрый цикл, сначала вывожу экземпляр из маршрутизации и убеждаюсь, что нагрузку принимает резерв; затем сохраняю inspect, ограниченный журнал и события, записываю исходную policy и только после этого выполняю согласованную паузу средствами владельца жизненного цикла. Возврат идёт в обратном порядке: восстанавливаю ровно изменённый параметр или прежний digest, запускаю через тот же управляющий контур, проверяю health, запрос приложения и отсутствие нового цикла, затем возвращаю трафик. Я не публикую универсальную команду остановки, потому что для Compose, systemd и оркестратора безопасный порядок различается. В итоговом регламенте остаются устойчивая идентификация сервиса, перечень read-only данных, правила редактирования секретов, альтернативные причины и критерии их исключения. Инцидент закрываю только когда выбранная гипотеза воспроизводится контролируемо, минимальная правка устраняет повторение, а откат проверен и наблюдение Zabbix сохраняет непрерывную историю.

Сигналы, которые сопоставляю

  • Согласованная временная последовательность State, RestartCount, ExitCode, OOMKilled, записей журнала и событий Docker Engine.
  • Повторяемое завершение одного устойчиво идентифицированного сервиса, а не появление нового изменчивого container ID.
  • Различие между работающим основным процессом, результатом healthcheck и фактической готовностью приложения обслуживать запросы.
  • Сопоставление лимитов cgroup с метриками памяти и CPU контейнера, свободным местом, вводом-выводом и сетевой доступностью хоста.

Отвергнутые альтернативы

  • Сразу назначить более настойчивую restart policyПолитика определяет реакцию Docker на завершение, но не исправляет ошибку процесса, конфигурации или внешней зависимости и способна ускорить цикл.
  • Считать большой RestartCount доказательством одной причиныСчётчик подтверждает повторные запуски данного контейнера, однако ExitCode, OOMKilled, журнал и события нужны для различения ветвей.
  • Ориентироваться только на статус runningПроцесс может быть жив, пока приложение не готово, зависло или не проходит настроенный healthcheck.
  • Привязать объект Zabbix к container IDПри пересоздании ID меняется, поэтому история распадается и новый экземпляр ошибочно выглядит другим сервисом.
  • Одновременно заменить образ, секреты и лимитыНесколько изменений уничтожают проверяемую связь между гипотезой и результатом и усложняют безопасный откат.

Конфигурация и сбор

Чтение docker ps, docker inspect, ограниченного окна docker logs и docker events; в Zabbix — просмотр обнаружения, элементов, истории и событий. Запись в Docker Engine API, раскрытие env/secret и передача docker.sock пользовательским скриптам не допускаются.

  • Зафиксировать service, name, image digest, container ID и временное окно без экспорта значений env и secret.
  • Снять State, RestartCount, ExitCode, OOMKilled, RestartPolicy, health и ресурсные лимиты через read-only inspect.
  • Сопоставить журнал с timestamps и отфильтрованные события Engine, отредактировав секреты в выписке.
  • Проверить обнаружение Zabbix по устойчивому ключу service/name/image и сохранить метрики хоста за то же окно.

Контролируемая проверка и свидетельство

В изолированном экземпляре без боевого трафика воспроизводится один запуск с тем же image digest и безопасно подготовленными входами. За одну итерацию меняется единственный фактор; сравниваются State, ExitCode, OOMKilled, health, журнал, события Engine и ресурсы. Проверка прекращается при обращении к боевому хранилищу, появлении неотредактированного секрета или влиянии на соседние сервисы.

Фрагмент рабочего регламента

  • Сопоставить docker inspect State и RestartCount с временными метками начала и завершения.
  • Прочитать ограниченный docker logs --timestamps и поток docker events за тот же период, редактируя секреты в выписке.
  • Проверить image digest, mounts, наличие требуемых env/secret и лимиты cgroup без чтения значений секретов.
  • Сверить в Zabbix устойчивый ключ service/name/image, историю рестартов, health и метрики диска, памяти и сети хоста.

Откат

Перед контролируемой проверкой записываются исходные image digest, конфигурация, ресурсные лимиты, restart policy и способ маршрутизации. Для паузы экземпляр сначала безопасно выводится из трафика при наличии подтверждённого резерва. Откат восстанавливает одно изменённое звено через штатного владельца запуска, затем проверяются health, прикладной запрос, зависимости и непрерывность истории Zabbix до возврата трафика.

Ограничения вывода

RestartCount, ExitCode, OOMKilled, health и события Engine не являются самостоятельным доказательством первопричины. Поведение зависит от версии Docker, logging driver, владельца жизненного цикла, приложения, cgroup, ядра и внешних зависимостей. Нельзя задать общий безопасный порог рестартов или считать running эквивалентом готовности сервиса.

Источники

  1. Docker Docs: Start containers automatically
  2. Docker Docs: docker container inspect
  3. Docker Docs: docker container logs
  4. Docker Docs: docker system events
  5. Zabbix documentation: Docker monitoring and integration
  6. Zabbix documentation: Low-level discovery