ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

PID 1, сигналы и restart policy: корректная остановка контейнера

Автор: · Обновлено

Когда контейнер получает команду остановки, Docker обращается к его главному процессу. Приложение должно успеть завершить запросы и освободить ресурсы; если главный процесс…

Когда контейнер получает команду остановки, Docker обращается к его главному процессу. Приложение должно успеть завершить запросы и освободить ресурсы; если главный процесс является лишним shell, сигнал может пройти не так, как ожидает автор Dockerfile. Здесь мы увидим доставку SIGTERM и отделим остановку от перезапуска.

Главный процесс задаёт время жизни

Контейнер работает, пока работает его основной процесс. Запуск сервера в фоне с немедленным выходом shell может сразу завершить контейнер, даже если команду запуска «успели выполнить». JSON-форма CMD ["python", "app.py"] не добавляет промежуточный shell. Если нужен shell-entrypoint, завершайте его exec основной программы, чтобы она заняла его место.

PID 1 имеет особое поведение сигналов и обязан корректно работать с дочерними процессами. Флаг --init добавляет небольшой init, который помогает пересылать сигналы и собирать завершившихся детей; он не добавляет обработчик graceful shutdown внутрь самого приложения. Назначение init.

Наблюдаемый обработчик

Сохраните worker.py:

import signal
import time

received = None

def request_stop(signum, frame):
    global received
    received = signum

signal.signal(signal.SIGTERM, request_stop)
print("ready", flush=True)
while received is None:
    time.sleep(0.05)
print(f"received signal {received}", flush=True)
print("flushed and stopped", flush=True)

Обработчик только присваивает простой флаг: печать и завершение выполняются в обычном потоке управления. Захват lock или threading.Event.set внутри signal handler может приводить к deadlock; ограничения Python signal нужно учитывать отдельно.

В Bash из той же папки:

docker run -d --name yk-signal-demo --mount "type=bind,src=$(pwd)/worker.py,dst=/worker.py,readonly" python:3.13-alpine python -u /worker.py
docker logs yk-signal-demo
docker stop --timeout 10 yk-signal-demo
docker logs yk-signal-demo
docker inspect --format '{{.State.ExitCode}}' yk-signal-demo

После остановки ожидайте сообщения о SIGTERM (на Linux номер 15), завершающую строку и код 0. Дождитесь ready до stop: это отделяет проверку обработчика от гонки с инициализацией. docker stop сначала отправляет настроенный stop signal, обычно TERM, а после таймаута может применить KILL. Справка stop.

Выбрать политику восстановления

no не перезапускает процесс автоматически. on-failure реагирует на ненулевой exit code, always и unless-stopped отличаются поведением после ручной остановки и перезапуска daemon. Это политика процесса, не проверка успешности HTTP-запроса. Прочитайте условия применения restart policy перед использованием в сервисе.

В Compose для долгоживущего сервиса можно задать restart: unless-stopped и stop_grace_period: 20s. Значение времени должно соответствовать тому, сколько реально занимает завершение работы. Бесконечный рестарт из-за неверной конфигурации не исправляет конфигурацию; сначала посмотрите первые сообщения запуска и число повторов.

Почему unhealthy не равен exited

Healthcheck может проваливаться при работающем процессе. Обычный Docker не перезапускает контейнер только из-за метки unhealthy. Поэтому приложение с зависшим обработчиком может требовать действий внешнего supervisor или оператора; его healthcheck должен помогать диагностике, а не создавать ложную гарантию самовосстановления.

Для очереди задач graceful shutdown обычно означает прекратить брать новые задания, закончить или вернуть текущие и только затем выйти. Для HTTP — перестать принимать новые соединения, дождаться текущих в пределах бюджета и закрыть ресурсы. Это проектное поведение приложения: один только stop_grace_period не реализует его.

Задание

Измените worker так, чтобы перед выходом он делал контролируемую задержку длиннее выбранного timeout. Сравните логи и exit code, затем верните достаточный бюджет и повторите. Не используйте настоящую базу для опыта с KILL. После проверки удалите yk-signal-demo. Объясните, какая часть результата обеспечена Docker, а какая написана вами в Python: это и есть граница между инфраструктурой и корректной остановкой приложения.

Источники