В контейнере копятся осиротевшие процессы: зачем нужен init

Сервис периодически запускает дочернюю утилиту обработки файлов. После многих запусков в контейнере видны завершившиеся процессы, а их число постепенно растёт. Сам HTTP-сервер отвечает, поэтому restart policy ничего не меняет. Нужно разобраться, кто должен забирать статусы дочерних процессов и какую роль играет основной процесс контейнера.

Обычный родитель должен вызывать wait для своих детей. Если родитель завершается раньше дочернего процесса, тот может стать осиротевшим и перейти под ответственность init-процесса своего пространства PID. Когда приложение работает главным процессом контейнера, оно не всегда готово выполнять эту дополнительную работу. Docker может добавить небольшой init, который займёт PID 1 и возьмёт на себя обработку осиротевших потомков.

Включить init в конфигурации

При docker run используют флаг --init, а для Compose есть отдельное поле. В фрагменте ниже app собирается из вашего Dockerfile; остальные настройки приложения остаются в том же сервисе.

services:
  app:
    build: .
    init: true

Изменение применяется при пересоздании контейнера. После этого основным процессом становится init, а приложение — его потомком. Такой init также помогает передавать сигналы, но не заменяет корректное завершение самого приложения. Если worker игнорирует SIGTERM или теряет подтверждения заданий, появление нового PID 1 не исправит его прикладной протокол.

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

Проверить дерево, а не только число процессов

Посмотрите PID, PPID и состояние процессов доступным в образе инструментом ps. Для каждого zombie найдите родителя. До исправления сохраните небольшую сводку, чтобы после пересоздания повторить тот же сценарий. Простое уменьшение числа процессов сразу после рестарта ничего не доказывает: история старого контейнера в любом случае исчезла.

Воспроизведите серию запусков дочерней утилиты и дождитесь их завершения. Число завершившихся процессов не должно расти с каждой итерацией. Затем остановите контейнер штатно и проверьте, что приложение завершает активную работу в отведённый срок. Эти проверки отвечают на два разных вопроса: уборка потомков и доставка остановки.

Не добавляйте полноценный systemd в образ ради одного такого поведения. Маленький init покрывает конкретную процессную задачу, а управление сервисами остаётся у Docker и Compose. В описании образа полезно указать, порождает ли приложение процессы, кто ожидает их статусы и как оно завершает незаконченные операции. Тогда проблема не вернётся после смены способа запуска.

Продолжить практику

Эту настройку проще понимать в составе приложения, у которого есть сеть, данные и понятная процедура обновления. На курсе «Docker и Docker Compose: контейнеризация приложений» вы упакуете HTTP-сервис с PostgreSQL, разберёте конфигурацию, хранение данных и диагностику сбоев. В программе есть локальные лабораторные, работа с registry и версиями образов; итоговый проект включает передачу окружения другому разработчику и проверку восстановления базы.

Посмотреть программу курса

Источник: официальная документация.

Другие задачи по теме: Процессы, сеть, тома и сборка контейнера.

Продолжить на практике

  • Docker и Docker Compose: контейнеризация приложений ↗

    Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.