Как сохранить работоспособность контейнера Docker после запуска служб?

156

Я видел кучу учебников, которые, кажется, делают то же самое, что и я, но по какой-то причине мои контейнеры Docker закрываются. По сути, я настраиваю веб-сервер и несколько демонов внутри контейнера Docker. Я делаю последние части этого через скрипт bash, run-all.shкоторый я запускаю через CMD в моем Dockerfile. run-all.shвыглядит так:

service supervisor start
service nginx start

И я запускаю его внутри моего Dockerfile следующим образом:

CMD ["sh", "/root/credentialize_and_run.sh"]

Я вижу, что все службы запускаются правильно, когда я запускаю вещи вручную (то есть, получаю образ с помощью -i -t / bin / bash), и все выглядит так, как будто он работает правильно, когда я запускаю образ, но он выходит один раз это заканчивает запуск моих процессов. Я бы хотел, чтобы процессы выполнялись бесконечно, и, насколько я понимаю, контейнер должен продолжать работать, чтобы это произошло. Тем не менее, когда я бегу docker ps -a, я вижу:

➜  docker_test  docker ps -a
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS                      PORTS               NAMES
c7706edc4189        some_name/some_repo:blah   "sh /root/run-all.sh   8 minutes ago       Exited (0) 8 minutes ago                        grave_jones

Что дает? Почему это выходит? Я знаю, что мог бы просто положить цикл while в конец моего bash-скрипта, чтобы он продолжался, но как правильно не допустить его выхода?

Eli
источник
1
Вы выставляете порты сервисов снаружи (опция -p для запуска Docker)? (конечно, это не помешает им выйти)
ribamar
1
Я использовал ENTRYPOINT в своем Dockerfile, и после запуска сценария, определенного в ENTRYPOINT (мой сценарий инициализации), он обнаружился в журналах, но мой контейнер, казалось, выходил. Поэтому вместо ENTRYPOINT я использовал команду RUN для запуска сценария, а контейнер все еще работает в фоновом режиме.
ypahalajani

Ответы:

51

Это не совсем то, как вы должны проектировать свои контейнеры Docker.

При разработке контейнера Docker вы должны строить его так, чтобы выполнялся только один процесс (т. Е. У вас должен быть один контейнер для Nginx и один для супервизора или приложения, в котором он работает); Кроме того, этот процесс должен выполняться на переднем плане.

Контейнер будет «выходить» при выходе самого процесса (в вашем случае этот процесс является вашим bash-скриптом).


Однако, если вам действительно нужно (или вы хотите) запустить несколько служб в вашем контейнере Docker, рассмотрите возможность начать с «Базового образа Docker» , который использует runitв качестве процесса псевдо-инициализации ( runitбудет оставаться в сети, пока запущены Nginx и Supervisor), который останется на переднем плане, в то время как другие ваши процессы делают свое дело.

У них есть существенные документы, поэтому вы должны быть в состоянии достичь того, что вы пытаетесь сделать достаточно легко.

Томас Орозко
источник
1
Можете ли вы объяснить, почему у меня должен быть запущен только один сервис? Я мог бы добавить nginx к супервизору при необходимости, но не уверен, почему это необходимо.
Илай
3
@Eli Короткий ответ: так работает Docker. Docker запускает только один процесс (и его дочерние элементы) для каждого контейнера. Рекомендуется, чтобы этот процесс был реальным процессом приложения (так что, если он завершится, Docker знает), но вы действительно можете использовать супервизор в качестве этого процесса. Обратите внимание, что вам нужно настроить супервизор для работы на переднем плане (т.е. не для демонизации), что делается с помощью --nodaemonопции.
Томас Орозко,
1
@Eli Этот пост в блоге Docker показывает, что запуск нескольких процессов (и, вообще говоря, просмотр контейнера как «маленького VPS») является неоптимальным. В вашем случае ветка комментариев, вероятно, будет более актуальной, чем фактическое сообщение в блоге.
Томас Орозко,
1
Базовый образ Docker - ужасное решение для многих корпоративных проблем, потому что немногие серьезные компании используют Ubuntu, предпочитая вместо этого дерево RHEL / Centos.
инженер-программист
9
«Немного серьезных компаний» кажется неоправданным. Казалось бы, выбор ОС полностью зависит от варианта использования. В любой конкретной компании существует множество различных сред, включая внутреннее использование разработчиков, внутреннее использование сотрудников, поддержку продаж, постановку, POC и, наконец, производство (и даже это неопределенный термин). Я не верю, что ФП упомянул их вариант использования, так что (извините, что я придирчивый), но комментарии такого рода, по-видимому, распространяют высокопрофессиональную информацию без каких-либо аргументов относительно того, почему.
Джон Каррелл
157

Если вы используете Dockerfile, попробуйте:

ENTRYPOINT ["tail", "-f", "/dev/null"]

(Очевидно, это только для целей разработки, вам не нужно сохранять контейнер живым, если он не запускает процесс, например. Nginx ...)

Мягкие пули
источник
5
Я использовал, CMD["sleep", "1d"]но ваше решение кажется лучше
Джордж Плигоропулос
@ GeorgiosPligoropoulos это застрянет в этой строке; может быть, работает в фоновом режиме будет работать
Prashanth Sams
5
Можно также использовать CMD["sleep", "infinity"].
Ромен
6
или «кошка», но люди могут сказать, что это жестокое обращение с животными. xD
lawphotog
Вы можете закончить свой сценарий точки входа, exec tail -f /dev/nullно использование tailв качестве точки входа неверный ответ.
Торстен Бронджер
86

Я просто была такая же проблема , и я обнаружил, что если вы используете свой контейнер с -tи -dфлагом, он продолжает работать.

docker run -td <image>

Вот что делают флаги (согласно docker run --help):

-d, --detach=false         Run container in background and print container ID
-t, --tty=false            Allocate a pseudo-TTY

Самый важный из них - -tфлаг. -dпросто позволяет запустить контейнер в фоновом режиме.

arne.z
источник
3
Я не могу воспроизвести это. Не могли бы вы привести пример? Что-нибудь конкретное (например, CMD) в Dockerfile нам нужно, чтобы это работало?
Матеус Сантана
2
Это не сработало для меня. Я использовал команду, docker logs <image>чтобы убедиться, что это ошибка, которая приводит к выходу из моего док-контейнера. Статус выхода 0и последний вывод - подтверждение того, что мой lighttpdсервер работает:[ ok ] Starting web server: lighttpd.
ob1
Я давно не работал с Докером. Таким образом, возможно, что интерфейс командной строки изменился и эта команда больше не работает.
arne.z
4
Я могу подтвердить, что это действительно работает с последней версией докера. Если вы хотите позже присоединиться к этому сеансу, использование -dit также будет работать.
Джон Гамильтон
1
@ Длинный скрипт не примет tty, add exec bashили, exec shесли bash не установлен, до конца start.sh. Тогда вы можете использовать флаг -t
123
43

Причина, по которой он завершается, заключается в том, что сценарий оболочки запускается сначала как PID 1, а когда он завершен, PID 1 исчезает, а докер работает только тогда, когда PID 1 работает.

Вы можете использовать supervisor, чтобы делать все, если запускать с флагом "-n", ему запрещено не демонизироваться, поэтому он останется первым процессом:

CMD ["/usr/bin/supervisord", "-n"]

И ваш supervisord.conf:

[supervisord]
nodaemon=true

[program:startup]
priority=1
command=/root/credentialize_and_run.sh
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
autorestart=false
startsecs=0

[program:nginx]
priority=10
command=nginx -g "daemon off;"
stdout_logfile=/var/log/supervisor/nginx.log
stderr_logfile=/var/log/supervisor/nginx.log
autorestart=true

Тогда вы можете иметь столько других процессов, сколько захотите, и супервизор будет обрабатывать их перезапуск в случае необходимости.

Таким образом, вы можете использовать supervisord в тех случаях, когда вам могут понадобиться nginx и php5-fpm, и разбирать их не имеет особого смысла.

phazei
источник
Где в документации указано, что PID 1 завершает работу Docker-контейнера?
8
@ 8oh8 По сути, так работают пространства имен процессов; это не столько специфично для Docker, сколько «вещь, лежащая в основе всех контейнеров». От man7.org/linux/man-pages/man7/pid_namespaces.7.html :If the "init" process of a PID namespace terminates, the kernel terminates all of the processes in the namespace via a SIGKILL signal. This behavior reflects the fact that the "init" process is essential for the correct operation of a PID namespace.
dannysauer
40

вы можете запустить обычный catбез каких-либо аргументов, как упомянуто bro @ Sa'ad, чтобы просто сохранить контейнер работающим [на самом деле ничего не делая, кроме ожидания пользовательского ввода] (плагин Jenkins 'Docker делает то же самое)

Серж Великанов
источник
в дополнение к моему ответу: но вы понимаете, что docker-compose (не daemonized) используется для того, чтобы показать вам рабочий процесс вашего контейнера, так что может быть удобно привязывать файлы журнала запущенных вами сервисов. ура
Серж Великанов
1
или cat. Докер плагин Дженкин делает это.
Sa'ad
12

Убедитесь, что вы добавляете daemon off;к себе nginx.conf или запускаете его CMD ["nginx", "-g", "daemon off;"]согласно официальному образу nginx

Затем используйте следующую команду для запуска супервизора как службы и nginx как процесса переднего плана, который будет препятствовать выходу контейнера

service supervisor start && nginx

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

Так что вам нужно понять компромиссы и принять соответствующее решение.

ITech
источник
7

Мотивация:

Нет ничего плохого в запуске нескольких процессов внутри контейнера Docker . Если вам нравится использовать докер в качестве легковесной виртуальной машины - пусть будет так. Другие любят делить свои приложения на микро сервисы. Я думаю: ЛАМПА стек в одном контейнере? Просто прекрасно.

Ответ:

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

И это еще одна просьба к руководителю. Потому что базовый образ phusion предоставляет супервизор помимо некоторых других вещей, таких как cron и locale setup. Вещи, которые вы хотели бы настроить при работе с такой легкой виртуальной машиной. Для чего это стоит также предоставляет ssh-соединения в контейнер.

Сам образ phusion просто запустится и продолжит работу, если вы выполните следующую базовую инструкцию запуска Docker:

moin@stretchDEV:~$ docker run -d phusion/baseimage
521e8a12f6ff844fb142d0e2587ed33cdc82b70aa64cce07ed6c0226d857b367
moin@stretchDEV:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS
521e8a12f6ff        phusion/baseimage   "/sbin/my_init"     12 seconds ago      Up 11 seconds

Или просто:

Если базовый образ не для вас ... Для быстрой работы CMD я бы предположил что-то вроде этого для bash:

CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"

Или это для busybox:

CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"

Это хорошо, потому что он сразу выйдет на docker stop. Просто sleepили catзаймет несколько секунд, прежде чем контейнер выйдет.

itsafire
источник
Я настроил базовый образ centos7 для загрузки PostgreSQL 11. Вы начинаете это с вызова / usr / pgsql-11 / bin / pg_ctl, но pg_ctl завершается после запуска сервера. Ваше предложение использовать ловушку сработало; это последняя строка моего скрипта pgstartwait.sh
Alchemistmatt
6

Захват PID процесса ngnix в переменной (например, $ NGNIX_PID) и в конце файла точки входа выполните

wait $NGNIX_PID 

Таким образом, ваш контейнер должен работать, пока ngnix не будет активен, когда ngnix останавливается, контейнер также останавливается

user2825611
источник