Поиск по сайту:

Как запустить несколько сервисов в одном контейнере Docker


Docker — это технология упаковки компонентов вашего стека в виде изолированных контейнеров. Обычной практикой является запуск каждого из ваших процессов в своем собственном контейнере, создавая четкое разделение между компонентами. Это повышает модульность и позволяет получить доступ к преимуществам масштабируемости контейнеризации.

Все еще могут быть ситуации, когда вы хотите запустить несколько служб в одном контейнере. Хотя это не происходит естественным образом в экосистеме Docker, мы покажем несколько различных подходов, которые вы можете использовать для создания контейнеров с более чем одним долгоживущим процессом.

Определение проблемы

Контейнеры Docker запускают один процесс переднего плана. Это определяется инструкциями изображения ENTRYPOINT и CMD. ENTRYPOINT задается в Dockerfile образа, а CMD может быть переопределен при создании контейнеров. Контейнеры автоматически останавливаются, когда их процесс переднего плана завершается.

Вы можете запускать другие процессы из CMD, но контейнер будет работать только до тех пор, пока активен исходный процесс переднего плана. Поддержание работоспособности контейнера в течение всего жизненного цикла двух независимых сервисов невозможно напрямую с помощью механизма ENTRYPOINT/CMD.

Объединение нескольких процессов в одну точку входа

Скрипты-обертки — самое простое решение проблемы. Вы можете написать скрипт, который запускает все ваши процессы и ждет их завершения. Установка сценария в качестве Docker ENTRYPOINT запустит его как процесс переднего плана контейнера, поддерживая работу контейнера до тех пор, пока не завершится один из обернутых сценариев.

#!/bin/bash

/opt/first-process &

/opt/second-process &

wait -n

exit $?

Этот сценарий запускает двоичные файлы /opt/first-process и /opt/second-process внутри контейнера. Использование & позволяет продолжить выполнение скрипта, не дожидаясь завершения каждого процесса. wait используется для приостановки скрипта до тех пор, пока один из процессов не завершится. Затем сценарий завершается с кодом состояния, выданным готовым сценарием.

Эта модель приводит к тому, что контейнер выполняет как first-process, так и second-process, пока один из них не завершится. В этот момент контейнер остановится, хотя другой процесс все еще может быть запущен.

Чтобы использовать этот скрипт, измените ENTRYPOINT и CMD вашего образа Docker, чтобы сделать его процессом переднего плана контейнера:

ENTRYPOINT ["/bin/sh"]
CMD ["./path/to/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:

[program:apache2]
command=/usr/sbin/apache2 -DFOREGROUND

[program:mysqld]
command=/usr/sbin/mysqld_safe

Этот файл конфигурации настраивает supervisord для запуска Apache и MySQL. Чтобы использовать его в контейнере Docker, добавьте в образ все необходимые пакеты, а затем скопируйте файл конфигурации supervisord в нужное место. Установите supervisord в качестве CMD образа, чтобы он запускался автоматически при запуске контейнеров.

FROM ubuntu:latest
RUN 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:

# Add s6-overlay to your image
ADD 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

# Use s6-overlay as your image's entrypoint
ENTRYPOINT ["/init"]

Вы можете добавить исполняемый скрипт finish в свои служебные каталоги для обработки остановки контейнера с помощью docker stop. s6-overlay автоматически запускает эти скрипты, когда его процесс получает сигнал TERM из-за команды stop.

Сценарии Finish получают в качестве первого аргумента код выхода своей службы. Код устанавливается равным 256, когда служба отключается из-за неперехваченного сигнала. Сценарий должен записать окончательный код выхода в /run/s6-linux-init-container-results/exitcode; s6-overlay читает этот файл и завершает работу со значением внутри, в результате чего этот код используется в качестве кода остановки вашего контейнера.

#!/bin/sh
echo "$1" > /run/s6-linux-init-container-results/exitcode

Когда следует запускать несколько процессов в контейнере?

Этот метод лучше всего использовать с тесно связанными процессами, которые нельзя разделить для запуска в качестве независимых контейнеров. У вас может быть программа, основанная на фоновой вспомогательной утилите, или монолитное приложение, самостоятельно управляющее отдельными процессами. Методы, показанные выше, могут помочь вам контейнеризировать эти типы программного обеспечения.

По возможности следует избегать запуска нескольких процессов в контейнере. Приверженность одному процессу переднего плана максимизирует изоляцию, предотвращает взаимодействие компонентов друг с другом и улучшает ваши возможности по отладке и тестированию определенных частей. Вы можете масштабировать компоненты по отдельности с помощью оркестраторов контейнеров, что дает вам возможность запускать больше экземпляров ваших самых ресурсоемких процессов.

Заключение

Контейнеры обычно имеют один процесс переднего плана и работают, пока он жив. Эта модель соответствует передовым методам контейнеризации и позволяет извлечь максимальную пользу из этой технологии.

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

Менеджеры процессов дают вам все, что вам нужно, но раздувают ваши образы дополнительными пакетами и настройками. Скрипты-оболочки проще, но, возможно, их нужно будет соединить с флагом Docker --init, чтобы предотвратить распространение зомби-процессов.