Запуск нескольких процессов в одном контейнере Docker
TL;DR
Запуск нескольких долгоживущих процессов в одном контейнере возможен, но обычно не рекомендуется. Подходы: скрипт-обёртка, флаг –init (tini), менеджеры процессов (supervisord, s6-overlay). Выберите метод по сложности приложения, требованиям к сигналам и обслуживанию. Используйте чек-листы и тесты при переходе от монолита к микросервисам.

Быстрые ссылки
Идентификация проблемы
Обёртка нескольких процессов в одном ENTRYPOINT
Параметр –init для контейнера
Использование специализированного менеджера процессов
Когда следует запускать несколько процессов в контейнере?
Заключение
Docker — это технология упаковки компонентов стека в изолированные контейнеры. Обычно каждый процесс запускают в своём контейнере, чтобы получить модульность и возможность масштабирования. Однако бывают ситуации, когда удобно или необходимо запускать два и более сервиса в одном контейнере. В этой заметке подробно рассмотрены варианты реализации, фиксы для сигнальной обработки и практические рекомендации по тестированию, безопасности и миграции.
Идентификация проблемы
Контейнеры Docker по умолчанию предназначены для запуска одного процесса в фореграунде. Этот процесс определяется инструкциями образа ENTRYPOINT и CMD. ENTRYPOINT задаётся в образе через
Dockerfileа CMD можно переопределить при создании контейнера. Контейнер автоматически останавливается, когда завершает работу его процесс в фореграунде.
Вы можете запустить дополнительные процессы из CMD, но контейнер будет оставаться жив только пока главный фореграунд-процесс активен. Поддерживать контейнер работающим в течение жизненного цикла двух независимых служб напрямую нельзя только с помощью механизма
ENTRYPOINT/CMDВажно: если ваш контейнер должен корректно обрабатывать сигналы (SIGTERM, SIGINT) и предотвращать появление зомби-процессов, обычный скрипт-обёртка может этого не обеспечивать: PID 1 внутри контейнера получает специальные обязанности по реапу дочерних процессов и форвардингу сигналов.
Обёртка нескольких процессов в одном ENTRYPOINT
Самый простой метод — написать скрипт, который стартует все необходимые процессы и ожидает их завершения. Установив этот скрипт в качестве ENTRYPOINT, вы делаете его фореграунд-процессом контейнера, и он будет держать контейнер живым, пока не завершится.
#!/bin/bash/opt/first-process &
/opt/second-process &
wait -n
exit $?
Этот пример запускает два бинарника /opt/first-process и /opt/second-process. Символ & позволяет запускать процессы в фоне, не блокируя выполнение скрипта. Команда wait -n приостанавливает исполнение до завершения одного из дочерних процессов; затем скрипт завершится с кодом этого процесса.
Плюсы обёртки:
- Простая реализация, минимум зависимостей.
- Хорошо подходит для пары вспомогательных процессов.
Минусы:
- PID 1 должен правильно обрабатывать сигналы и реапить детей; простая sh/bash-обёртка этого может не делать.
- При падении одного процесса контейнер остановится, даже если другой процесс ещё работает — иногда это желаемое поведение, но не всегда.
Рекомендация: сочетайте обёртку с --init (описано ниже) или добавьте ручную обработку сигналов и реапа в скрипт.
Параметр –init для контейнера
Одна из распространённых проблем — корректная уборка дочерних процессов (reaping zombies) и форвардинг сигналов. Docker запускает ваш CMD как процесс с PID 1, и если этот процесс не реализует логику init, зомби-процессы могут накапливаться.
Команда docker run поддерживает флаг --init, который изменяет точку входа контейнера, чтобы использовать tini в качестве PID 1. Tini — минимальная реализация init-процесса, которая запускает ваш CMD, пробрасывает сигналы и периодически очищает зомби-процессы.
Плюсы --init:
- Лёгкий, проверенный механизм для реапа и форвардинга сигналов.
- Не требует внесения изменений в код приложения.
Минусы:
- Не решает задачи оркестрации процессов внутри контейнера (запуск/рестарт/зависимости).
- Не предоставляет декларативной конфигурации сервисов.
Используйте --init, если ожидается много дочерних процессов и вы не хотите писать код для управления PID 1 вручную.
Использование специализированного менеджера процессов
Когда процессов много и их нужно надёжно администрировать (рестарт при падении, логирование, зависимости), ручные скрипты теряют смысл. В таких случаях проще поставить менеджер процессов и сделать его ENTRYPOINT.
Популярные варианты:
- supervisord — простой, конфигурируется через
/etc/supervisor/conf.d/*.conf. - s6-overlay — современный, лёгкий, с декларативной моделью сервисов и управляющими скриптами
run/finish.
Пример конфигурации для supervisord:
[program:apache2]command=/usr/sbin/apache2 -DFOREGROUND
[program:mysqld]
command=/usr/sbin/mysqld_safe
Фрагмент Dockerfile с установкой:
FROM ubuntu:latestRUN apt-get install -y apache2 mysql-server supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
ENTRYPOINT [“/bin/sh”]
CMD [“/usr/bin/supervisord”]
supervisord работает непрерывно и контролирует процессы. Обратите внимание: некоторые менеджеры не останавливают контейнер при падении одного из сервисов — поведение зависит от конфигурации.
s6-overlay предлагает другой подход: каждая служба — это каталог в /etc/services.d, содержащий исполняемый скрипт run и опционально finish. Пример добавления s6-overlay в образ:
# Add s6-overlay to your imageADD https://github.com/just-containers/s6-overlay/releases/download/v3.1.0.0/s6-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
RUN printf “#!/bin/shn/usr/sbin/apache2 -DFOREGROUND” > /etc/services.d/first-service/run
RUN chmod +x /etc/services.d/first-service/run
ENTRYPOINT [“/init”]
s6-overlay автоматически запускает finish-скрипты при получении сигнала TERM и может использовать код выхода сервиса для завершения контейнера. Пример простого finish-скрипта:
#!/bin/shecho “$1” > /run/s6-linux-init-container-results/exitcode
Такой сценарий позволяет сервисам сообщать о коде завершения, и s6-overlay корректно передаёт его наружу.
Плюсы менеджеров:
- Надёжное управление процессами: рестарт, логирование, зависимости.
- Декларативность и стандартизация поведения.
Минусы:
- Увеличение размера образа за счёт дополнительных пакетов и конфигурации.
- Дополнительная сложность при отладке — нужно знать, как менеджер обрабатывает сигналы и коды завершения.
Практические шаблоны и примеры
- Простая обёртка с обработкой сигналов (рекомендуемая улучшенная версия):
#!/bin/bash
set -e
# Функция для форвардинга сигналов
_term() {
echo "Получен TERM сигнал, пересылаем дочерним процессам"
kill -TERM "${pids[@]}" 2>/dev/null
}
trap _term SIGTERM SIGINT
/opt/first-process &
pid1=$!
/opt/second-process &
pid2=$!
pids=( $pid1 $pid2 )
wait -n ${pids[@]}
# дождёмся полного завершения дочерних процессов
wait
exit $?Этот скрипт демонстрирует форвардинг сигналов и хранение PID дочерних процессов в массиве для корректной отправки сигналов.
- Использование
--initс обёрткой:
- Команда для запуска: docker run –init my-image
- Позволяет не писать сложный код для реапа зомби.
- s6-overlay: используйте, если хотите декларативную модель и корректную интеграцию с
docker stop.
Когда следует запускать несколько процессов в контейнере?
Запуск нескольких процессов оправдан, если:
- Процессы очень плотно связаны и неразделимы (например, главный бинарник + helper daemon, без которого приложение не работает).
- Миграция на отдельные контейнеры невозможна по причинам архитектуры или ограничений окружения.
- Вы контейнеризуете старое монолитное приложение, где процессы управляются локально.
Стоит избегать многопроцессных контейнеров в следующих случаях:
- Если вы хотите масштабировать компоненты независимо.
- Если вы хотите упростить отладку и мониторинг каждого компонента.
- Если безопасность и минимальные привилегии для каждого компонента критичны.
Ментальные модели и эвристики
- Модель “один процесс = одна ответственность”: если можно выделить процесс в отдельный сервис — делайте это.
- Эргономика сигналов: PID 1 обязан правильно реапить дочерние процессы и форвардить сигналы — если ваш код этого не делает, используйте –init или менеджер.
- “Fail fast”: если падение одного процесса должно привести к остановке всей единицы работы — допустим запуск нескольких процессов в одном контейнере.
Когда этот подход терпит неудачу (контрпримеры)
- Высоконагруженные микросервисы, которые нужно масштабировать независимо — многопроцессный контейнер мешает горизонтальному масштабированию.
- Сценарии, где процессы требуют разных уровней привилегий или изоляции по безопасности.
- Среды с жёсткими требованиями к мониторингу и SLO: отдельные контейнеры проще подключить к метрикам и алертам.
Чек-листы по ролям
Разработчик:
- Может ли компонент работать отдельно?
- Есть ли необходимость в inter-process коммуникации по localhost?
- Имеет ли основной процесс логику реапа?
Операции (Ops):
- Менеджер процессов документирован и контролирует restart policy.
- Тестирован shutdown через docker stop — контейнер завершает работу корректно.
- Логи корректно собираются внешним агрегатором.
Безопасность:
- Привилегии процессов минимальны.
- Сегментация конфигураций и секретов по процессам реализована.
- Нет ненужных пакетов в образе.
Критерии приёмки
- Контейнер корректно реагирует на SIGTERM и завершает все дочерние процессы в разумный тайм-аут.
- При падении одного из ключевых процессов поведение соответствует ожиданиям (либо рестарт, либо остановка контейнера).
- Нет накопления зомби-процессов при длительной работе.
- Образ минимален и не содержит ненужного ПО.
Тест-кейсы и приёмочные проверки
- Запуск контейнера и проверка, что оба процесса запущены.
- Инициация падения одного процесса: проверка кода завершения контейнера или рестарта по политике менеджера.
- Проверка поведения при SIGTERM: все процессы останавливаются корректно.
- Нагрузочный тест: сервисы обслуживают трафик в ожидаемых пределах.
Безопасность и минимизация размера образа
- Выбирайте лёгкие менеджеры (tini, s6), избегайте systemd в контейнерах.
- Старайтесь минимизировать набор пакетов: каждый дополнительный пакет увеличивает поверхность атаки.
- Ограничивайте привилегии контейнера (user, capabilities) и используйте read-only файловые системы, где возможно.
Важно: если приложение требует привилегий для одного процесса и не требует для другого, лучше разнести процессы по контейнерам, чтобы минимизировать риск.
План миграции и дорожная карта
- Инвентаризация: какие процессы и зависимости есть в текущем образе.
- Решение о границах: какие компоненты можно и нужно выделить.
- Выбор подхода для оставшихся много-процессных образов: обёртка + –init, supervisord или s6.
- Создание тестов (см. раздел тест-кейсы).
- Постепенное разнесение: сначала вынесите критичные компоненты в отдельные контейнеры.
- Мониторинг и SLO: проверьте, что метрики и алерты корректно работают после изменений.
Сравнение менеджеров (краткая матрица)
- supervisord: прост, знаком многим, но весит больше и имеет собственный синтаксис конфигурации.
- s6-overlay: современный, небольшой, хорошая интеграция с docker stop и finish-скриптами.
- tini/–init: минимален, решает проблему PID 1, но не менеджер процессов.
Шаблон SOP для создания многопроцессного образа
- Определите список процессов и их зависимости.
- Выберите стратегию: обёртка, supervisord или s6.
- Добавьте обработку сигналов и проверку зомби-процессов.
- Протестируйте shutdown и restart-паттерны.
- Настройте логирование и мониторинг.
- Минимизируйте образ: удалите лишние утилиты и кеши.
- Документируйте поведение и коды завершения контейнера.
Краткий словарь терминов
- PID 1: процесс в контейнере, который имеет специальную роль реапа дочерних процессов и обработки сигналов.
- reaping: процесс очистки завершённых дочерних процессов, чтобы не оставалось зомби.
- finish-скрипт: скрипт в s6, выполняемый при остановке сервиса для установки кода завершения.
Примерный decision flow (Mermaid)
flowchart TD
A[Нужно ли запускать более одного процесса?] -->|Да| B{Процессы тесно связаны?}
A -->|Нет| Z[Используйте отдельные контейнеры]
B -->|Нет| Z
B -->|Да| C{Требуется рестарт/сложное управление?}
C -->|Да| D[Используйте менеджер процессов 's6/supervisord']
C -->|Нет| E{Нужна обработка зомби/сигналов?}
E -->|Да| F[Используйте --init + обёртка]
E -->|Нет| G[Простая обёртка в ENTRYPOINT]Заключение
Контейнеры по умолчанию ориентированы на один фореграунд-процесс, но запуск нескольких процессов внутри контейнера — жизнеспособная опция для узкоспециализированных сценариев. Простая обёртка подойдёт для пары тесно связанных процессов; --init решает проблемы с PID 1; менеджеры процессов (supervisord, s6) дают богатую функциональность при управлении сервисами, но добавляют сложность и вес образа.
Итоговая рекомендация: придерживайтесь одного процесса на контейнер, когда это возможно. Если вынуждены запускать несколько — выберите подходящую стратегию на основе требований по сигналам, рестартам, безопасности и масштабированию, и обязательно добавьте тесты и документацию.
Ключевые выводы
- По умолчанию контейнеры запускают один фореграунд-процесс.
- Обёртки, –init и менеджеры процессов — основные подходы для многопроцессных контейнеров.
- Тестируйте обработку сигналов, отсутствие зомби и поведение при падении сервисов.
- Разделяйте компоненты на контейнеры по возможности, чтобы упростить масштабирование и безопасность.
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента