Как запустить несколько сервисов в одном контейнере Docker
Опубликовано: 2022-06-30Docker — это технология упаковки компонентов вашего стека в виде изолированных контейнеров. Обычной практикой является запуск каждого из ваших процессов в своем собственном контейнере, создавая четкое разделение между компонентами. Это повышает модульность и позволяет получить доступ к преимуществам масштабируемости контейнеризации.
Все еще могут быть ситуации, когда вы хотите запустить несколько служб в одном контейнере. Хотя это не является естественным в экосистеме Docker, мы покажем несколько различных подходов, которые вы можете использовать для создания контейнеров с более чем одним долгоживущим процессом.
Определение проблемы
Контейнеры Docker запускают один процесс переднего плана. Это определяется инструкциями ENTRYPOINT
и CMD
образа. ENTRYPOINT
задается в Dockerfile
образа, в то время как CMD
можно переопределить при создании контейнеров. Контейнеры автоматически останавливаются, когда их процесс переднего плана завершается.
Вы можете запускать другие процессы из CMD
, но контейнер будет работать только до тех пор, пока активен исходный процесс переднего плана. Поддержание работоспособности контейнера в течение всего жизненного цикла двух независимых служб невозможно напрямую с помощью механизма ENTRYPOINT/CMD
.
Объединение нескольких процессов в одну точку входа
Скрипты-обертки — самое простое решение проблемы. Вы можете написать скрипт, который запускает все ваши процессы и ждет их завершения. Установка сценария в качестве Docker ENTRYPOINT
запустит его как процесс переднего плана контейнера, поддерживая работу контейнера до тех пор, пока не завершится один из обернутых сценариев.
#!/бин/баш /opt/первый процесс & /opt/второй процесс & подожди -н выйти $?
Этот сценарий запускает двоичные файлы /opt/first-process
и /opt/second-process
внутри контейнера. Использование &
позволяет продолжить сценарий, не дожидаясь завершения каждого процесса. wait
используется для приостановки сценария до тех пор, пока один из процессов не завершится. Затем сценарий завершается с кодом состояния, выданным готовым сценарием.
Эта модель приводит к тому, что контейнер выполняет как first-process
так и second-process
пока один из них не завершится. В этот момент контейнер остановится, хотя другой процесс все еще может быть запущен.
Чтобы использовать этот скрипт, измените ENTRYPOINT
и CMD
вашего образа Docker, чтобы сделать его процессом переднего плана контейнера:
ТОЧКА ВХОДА ["/bin/sh"] CMD ["./путь/к/script.sh"]
Опция контейнера --init
Одной из проблем управления контейнерными процессами является эффективная очистка по мере их выхода. Docker запускает ваш CMD
как процесс с идентификатором 1, что делает его ответственным за обработку сигналов и устранение зомби. Если ваш сценарий не имеет этих возможностей, вы можете остаться с осиротевшими дочерними процессами, сохраняющимися внутри вашего контейнера.
Команда docker run
имеет флаг --init
, который изменяет точку входа для использования tini
в качестве PID 1. Это минимальная реализация процесса инициализации, которая запускает ваш CMD
, обрабатывает пересылку сигналов и постоянно пожинает зомби.
Стоит использовать --init
, если вы ожидаете запуска множества процессов и не хотите вручную обрабатывать очистку. Tini — это облегченный вариант инициализации, предназначенный для контейнеров. Это намного меньше, чем полноценные альтернативы, такие как systemd
и upstart
.
Использование специального диспетчера процессов
Ручное написание сценариев быстро становится неоптимальным, когда вам нужно управлять большим количеством процессов. Принятие менеджера процессов — это еще один способ запустить несколько служб внутри ваших контейнеров Docker. Менеджер процессов становится вашей ENTRYPOINT
и несет ответственность за запуск, поддержку и очистку ваших рабочих процессов.
Существует несколько вариантов реализации этого подхода. supervisord
— популярный выбор, который легко настраивается с помощью файла /etc/supervisor/conf.d/supervisord.conf
:
[программа:apache2] команда=/usr/sbin/apache2 -DFOREGROUND [программа:mysqld] команда =/usr/sbin/mysqld_safe
Этот файл конфигурации настраивает supervisord
для запуска Apache и MySQL. Чтобы использовать его в контейнере Docker, добавьте в образ все необходимые пакеты, а затем скопируйте файл конфигурации supervisord
в нужное место. Установите supervisord
в качестве CMD
образа, чтобы он запускался автоматически при запуске контейнеров.
ОТ убунту: последняя RUN apt-get install -y apache2 супервизор mysql-сервера КОПИРОВАТЬ supervisord.conf /etc/supervisor/conf.d/supervisord.conf ТОЧКА ВХОДА ["/bin/sh"] CMD ["/usr/bin/supervisord"]
Поскольку supervisord
работает постоянно, невозможно остановить контейнер, когда один из ваших контролируемых процессов завершается. Альтернативный вариант — s6-overlay
, у которого есть такая возможность. Он использует декларативную модель обслуживания, в которой вы размещаете сценарии обслуживания непосредственно в /etc/services.d
:
# Добавьте s6-оверлей к вашему изображению ДОБАВИТЬ https://github.com/just-containers/s6-overlay/releases/download/v3.1.0.0/s6-overlay-noarch.tar.xz/tmp ВЫПОЛНИТЬ tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz RUN printf "#!/bin/shn/usr/sbin/apache2 -DFOREGROUND" > /etc/services.d/first-service/run ВЫПОЛНИТЬ chmod +x /etc/services.d/first-service/run # Используйте s6-overlay в качестве точки входа вашего изображения ТОЧКА ВХОДА ["/init"]
Вы можете добавить исполняемый скрипт finish
в свои служебные каталоги для обработки остановки контейнера с помощью docker stop
. s6-overlay
автоматически запускает эти сценарии, когда его процесс получает сигнал TERM
из-за команды stop
.
Сценарии Finish получают в качестве первого аргумента код выхода своей службы. Код устанавливается равным 256, когда служба отключается из-за неперехваченного сигнала. Сценарий должен записать окончательный код выхода в /run/s6-linux-init-container-results/exitcode
; s6-overlay читает этот файл и завершает работу со значением внутри, в результате чего этот код используется в качестве кода остановки вашего контейнера.
#!/бин/ш echo "$1" > /run/s6-linux-init-container-results/exitcode
Когда следует запускать несколько процессов в контейнере?
Этот метод лучше всего использовать с тесно связанными процессами, которые нельзя разделить для запуска в качестве независимых контейнеров. У вас может быть программа, основанная на фоновой вспомогательной утилите, или монолитное приложение, самостоятельно управляющее отдельными процессами. Методы, показанные выше, могут помочь вам контейнеризировать эти типы программного обеспечения.
По возможности следует избегать запуска нескольких процессов в контейнере. Приверженность одному процессу переднего плана максимизирует изоляцию, предотвращает взаимодействие компонентов друг с другом и улучшает ваши возможности по отладке и тестированию определенных частей. Вы можете масштабировать компоненты по отдельности с помощью оркестраторов контейнеров, что дает вам возможность запускать больше экземпляров ваших самых ресурсоемких процессов.
Вывод
Контейнеры обычно имеют один процесс переднего плана и работают до тех пор, пока он жив. Эта модель согласуется с лучшими практиками контейнеризации и позволяет извлечь максимальную выгоду из этой технологии.
В некоторых ситуациях вам может понадобиться несколько процессов для запуска в контейнере. Поскольку все образы в конечном итоге имеют единую точку входа, вы должны написать сценарий-оболочку или добавить диспетчер процессов, который берет на себя ответственность за запуск ваших целевых двоичных файлов.
Менеджеры процессов дают вам все, что вам нужно, но раздувают ваши образы дополнительными пакетами и настройками. Скрипты-оболочки проще, но, возможно, их нужно будет использовать в паре с флагом Docker --init
, чтобы предотвратить распространение зомби-процессов.