﻿# Почему docker stop не завершает worker: PID 1, сигналы и zombie

Диагностика: Почему docker stop не завершает worker: PID 1, сигналы и zombie. Признаки, последовательность проверки, рабочий пример и критерии восстановления.

Автор: Команда Retrivium
Обновлено: 2026-11-01
Канонический URL: https://retrivium.ai/ru/journal/container-pid1-signals-zombies/
Language: ru

Когда docker stop ждёт до принудительного завершения, исследуйте дерево процессов и PID 1. Shell-wrapper может не передавать сигнал worker, а основной процесс — не обрабатывать TERM. Дополнительная проблема возникает, если никто не собирает завершившиеся дочерние процессы.

## Восстановите команду запуска

Сохраните image ENTRYPOINT, CMD и фактически запущенные аргументы. Установите, какая программа стала PID 1 внутри контейнера и кто является родителем worker. Интерактивный shell и production entrypoint могут создавать разные деревья. Для shell-script, запускающего единственный основной процесс, exec меняет модель: программа заменяет shell вместо запуска отдельным потомком. Однако корректность зависит от самого приложения и его обработки сигналов, а не только от формы Dockerfile.


[Официальный источник 1](https://docs.docker.com/reference/cli/docker/container/run/).

## Разделите доставку сигнала и cleanup

Docker направляет stop signal основному процессу. PID 1 имеет особое поведение обработки сигналов Linux; приложение должно реализовать нужное завершение. Передача TERM дочернему worker и reaping завершившихся детей — отдельные обязанности. --init добавляет небольшой init-процесс для соответствующих функций, но не придумывает за приложение протокол сохранения данных. Наличие init не подтверждает автоматически, что каждый worker получил сигнал и освободил свой ресурс.


[Официальный источник 2](https://docs.docker.com/engine/containers/multi-service_container/).

## Проверьте условный wrapper

В учебном сценарии script запускает worker в фоне и остаётся ждать. После stop лог содержит только завершение shell, а worker не записывает свой финальный маркер. Сравните это с вариантом корректного exec или явного trap/forwarding, подобранным под реальное дерево. Затем создайте отдельный короткоживущий дочерний процесс и проверьте, исчезает ли он из zombie состояния. Пример не требует запускать реальную модель или менять CUDA настройки.

| Данные опыта | Проверяемое свойство |
| --- | --- |
| PID/PPID дерево | Определён получатель stop signal |
| Маркер TERM worker | Подтверждена обработка сигнала |
| Время завершения | Видно ожидание до принудительного stop |
| Завершившиеся дети | Проверена обязанность reaping |

## Исправьте минимальный участок

Для одного основного приложения избегайте лишнего слоя shell, если он не нужен. Для нескольких процессов явно определите supervision и порядок остановки. Сохраните ограниченное время ожидания, достаточное для предусмотренного cleanup. Увеличение stop timeout маскирует отсутствующий forwarding, если worker вообще не получает сигнал. Проверьте права на запись финального состояния: корректно доставленный TERM не помогает, когда завершающий обработчик сам блокируется или пытается писать в недоступный путь.

## Проверьте несколько завершений

Повторите запуск и stop в состоянии простоя, во время работы и после отказа дочернего процесса. Сохраните ожидаемые exit codes и финальные маркеры без пользовательских данных. Итоговый артефакт содержит дерево, способ доставки сигнала, ответственного за reaping и предельное время завершения. Эти сведения понадобятся и при Kubernetes deployment, где grace period ограничивает весь процесс остановки. Прекращение существования объекта контейнера не должно быть единственным доказательством выполненного cleanup внутри приложения.

Связанные разборы: [gpu containers vs vms](/ru/journal/gpu-containers-vs-vms/); [vllm docker first launch](/ru/journal/vllm-docker-first-launch/).
