Docker Compose дождаться контейнера X перед запуском Y
Я использую rabbitmq и простой пример Python вместе с docker-compose. Моя проблема в том, что мне нужно дождаться полного запуска rabbitmq. Из того, что я искал до сих пор, я не знаю, как ждать с контейнером x (в моем случае рабочий), пока не будет запущен y (rabbitmq).
Я нашел этот пост, где он проверяет, есть ли другой хост в сети. Я также нашел эту команду Docker:
Подождите
Использование: докер подождите КОНТЕЙНЕР [КОНТЕЙНЕР...]
Блокируйте, пока контейнер не остановится, затем напечатайте его код выхода.
Ожидание остановки контейнера, возможно, не то, что я ищу, но если это так, возможно ли использовать эту команду внутри docker-compose.yml? Мое решение пока состоит в том, чтобы подождать несколько секунд и проверить порт, но так ли это можно сделать? Если я не жду, я получаю ошибку.
докер-compose.yml
worker:
build: myapp/.
volumes:
- myapp/.:/usr/src/app:ro
links:
- rabbitmq
rabbitmq:
image: rabbitmq:3-management
Пример приветствия Python (rabbit.py):
import pika
import time
import socket
pingcounter = 0
isreachable = False
while isreachable is False and pingcounter < 5:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('rabbitmq', 5672))
isreachable = True
except socket.error as e:
time.sleep(2)
pingcounter += 1
s.close()
if isreachable:
connection = pika.BlockingConnection(pika.ConnectionParameters(
host="rabbitmq"))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='',
routing_key='hello',
body='Hello World!')
print (" [x] Sent 'Hello World!'")
connection.close()
Dockerfile для работника:
FROM python:2-onbuild
RUN ["pip", "install", "pika"]
CMD ["python","rabbit.py"]
Обновление ноябрь 2015:
Сценарий оболочки или ожидание внутри вашей программы, возможно, является возможным решением. Но после просмотра этой проблемы я ищу команду или функцию docker / docker-compose.
Они упоминают решение для проверки работоспособности, которое может быть лучшим вариантом. Открытое TCP-соединение не означает, что ваш сервис готов или может оставаться готовым. В дополнение к этому мне нужно изменить мою точку входа в моем файле Docker.
Поэтому я надеюсь получить ответ с командами docker-compose на борту, что, будем надеяться, произойдет, если они закончат эту проблему.
Обновление март 2016
Существует предложение по предоставлению встроенного способа определения, является ли контейнер "живым". Так что docker-compose может использовать его в ближайшем будущем.
Обновление июнь 2016
Похоже, что проверка работоспособности будет интегрирована в докер в версии 1.12.0
Обновление январь 2017
Я нашел решение docker-compose, см.: Docker Compose ждет контейнера X перед запуском Y
21 ответ
Наконец-то нашел решение с помощью метода docker-compose. Начиная с формата файла docker-compose 2.1 вы можете определять проверки работоспособности.
Я сделал это в примере проекта, вам нужно установить хотя бы докер 1.12.0+. Мне также нужно было расширить Dockerfile с управлением rabbitmq, потому что curl не установлен на официальном образе.
Теперь я проверяю, доступна ли страница управления rabbitmq-контейнером. Если curl заканчивается с кодом выхода 0, приложение контейнера (python pika) будет запущено и опубликует сообщение в очередь приветствия. Его сейчас работает (выходной).
docker-compose (версия 2.1):
version: '2.1'
services:
app:
build: app/.
depends_on:
rabbit:
condition: service_healthy
links:
- rabbit
rabbit:
build: rabbitmq/.
ports:
- "15672:15672"
- "5672:5672"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:15672"]
interval: 30s
timeout: 10s
retries: 5
выход:
rabbit_1 | =INFO REPORT==== 25-Jan-2017::14:44:21 ===
rabbit_1 | closing AMQP connection <0.718.0> (172.18.0.3:36590 -> 172.18.0.2:5672)
app_1 | [x] Sent 'Hello World!'
healthcheckcompose_app_1 exited with code 0
Dockerfile (rabbitmq + curl):
FROM rabbitmq:3-management
RUN apt-get update
RUN apt-get install -y curl
EXPOSE 4369 5671 5672 25672 15671 15672
Версия 3 больше не поддерживает форму условия depen_on. Таким образом, я перешел от зависит от перезапуска при сбое. Теперь мой контейнер приложения будет перезапускаться 2-3 раза, пока он не заработает, но это все еще функция создания docker без перезаписи точки входа.
docker-compose (версия 3):
version: "3"
services:
rabbitmq: # login guest:guest
image: rabbitmq:management
ports:
- "4369:4369"
- "5671:5671"
- "5672:5672"
- "25672:25672"
- "15671:15671"
- "15672:15672"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:15672"]
interval: 30s
timeout: 10s
retries: 5
app:
build: ./app/
environment:
- HOSTNAMERABBIT=rabbitmq
restart: on-failure
depends_on:
- rabbitmq
links:
- rabbitmq
Совсем недавно они добавили depends_on
особенность
Редактировать:
Начиная с версии 2.1+ вы можете использовать depends_on
в сочетании с healthcheck
для достижения этой цели:
version: '2.1'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
redis:
image: redis
db:
image: redis
healthcheck:
test: "exit 0"
До версии 2.1
Вы все еще можете использовать depends_on
, но это влияет только на порядок, в котором запускаются сервисы, но не в том случае, если они готовы до запуска зависимого сервиса.
Кажется, требуется хотя бы версия 1.6.0.
Использование будет выглядеть примерно так:
version: '2'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
Из документов:
Экспресс-зависимость между сервисами, которая имеет два эффекта:
- docker-compose up запустит сервисы в порядке зависимости. В следующем примере db и redis будут запущены перед web.
- docker-compose up SERVICE автоматически включает в себя зависимости SERVICE. В следующем примере docker-compose up web также создаст и запустит db и redis.
Примечание: насколько я понимаю, хотя это и устанавливает порядок загрузки контейнеров. Это не гарантирует, что сервис внутри контейнера действительно загружен.
Например, у вас может быть контейнер postgres. Но сам сервис postgres все еще может инициализироваться внутри контейнера.
Собственно это пока невозможно. Смотрите также этот запрос функции.
Пока что вам нужно сделать это в ваших контейнерах CMD
ждать, пока все необходимые услуги будут там.
в Dockerfile
s CMD
Вы могли бы обратиться к своему собственному стартовому скрипту, который оборачивает запуск вашего контейнерного сервиса. Перед тем, как начать, вы ждете такой зависимости, как:
Dockerfile
FROM python:2-onbuild
RUN ["pip", "install", "pika"]
ADD start.sh /start.sh
CMD ["/start.sh"]
start.sh
#!/bin/bash
while ! nc -z rabbitmq 5672; do sleep 3; done
python rabbit.py
Вероятно, вам нужно установить Netcat в вашем Dockerfile
также. Я не знаю, что предустановлено на образе питона.
Существует несколько инструментов, которые предоставляют простую в использовании логику ожидания для простых проверок порта tcp:
Для более сложных ожиданий:
С помощью restart: unless-stopped
или же restart: always
может решить эту проблему.
Если рабочий container
останавливается, когда rabbitMQ не готов, он будет перезапущен, пока не будет.
Если вы хотите запустить службу только тогда, когда другая служба успешно завершена (например, миграция, заполнение данных и т. Д.),
docker-compose
версия 1.29 поставляется со встроенными функциями для этого -.
depends_on:
<service-name>:
condition: service_completed_successfully
Согласно спецификации :
service_completed_successfully
- указывает, что зависимость должна работать до успешного завершения перед запуском зависимой службы.
Вы также можете просто добавить его в опцию команды, например.
command: bash -c "sleep 5; start.sh"
https://github.com/docker/compose/issues/374
для ожидания порта вы также можете использовать что-то вроде этого
command: bash -c "while ! curl -s rabbitmq:5672 > /dev/null; do echo waiting for xxx; sleep 3; done; start.sh"
чтобы увеличить время ожидания, вы можете взломать немного больше:
command: bash -c "for i in {1..100} ; do if ! curl -s rabbitmq:5672 > /dev/null ; then echo waiting on rabbitmq for $i seconds; sleep $i; fi; done; start.sh"
restart: on-failure
сделал трюк для меня.. см. ниже
---
version: '2.1'
services:
consumer:
image: golang:alpine
volumes:
- ./:/go/src/srv-consumer
working_dir: /go/src/srv-consumer
environment:
AMQP_DSN: "amqp://guest:guest@rabbitmq:5672"
command: go run cmd/main.go
links:
- rabbitmq
restart: on-failure
rabbitmq:
image: rabbitmq:3.7-management-alpine
ports:
- "15672:15672"
- "5672:5672"
Для заказа контейнера начните использовать
depends_on:
Для ожидания предыдущего запуска контейнера используйте скрипт
entrypoint: ./wait-for-it.sh db:5432
Эта статья поможет вам https://docs.docker.com/compose/startup-order/
Пробовал много разных способов, но понравилась простота этого: https://github.com/ufoscout/docker-compose-wait
Идея в том, что вы можете использовать ENV-переменные в файле docker compose для отправки списка хостов служб (с портами), которые следует "ожидать" следующим образом: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017
,
Допустим, у вас есть следующий файл docker-compose.yml (копия / история из репозитория README):
version: "3"
services:
mongo:
image: mongo:3.4
hostname: mongo
ports:
- "27017:27017"
postgres:
image: "postgres:9.4"
hostname: postgres
ports:
- "5432:5432"
mysql:
image: "mysql:5.7"
hostname: mysql
ports:
- "3306:3306"
mySuperApp:
image: "mySuperApp:latest"
hostname: mySuperApp
environment:
WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017
Затем, чтобы службы могли ждать, вам нужно добавить следующие две строки в ваши Dockerfiles (в Dockerfile служб, которые должны ожидать запуска других служб):
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait
Полный пример такого примера Dockerfile (снова из репозитория проекта README):
FROM alpine
## Add your application to the docker image
ADD MySuperApp.sh /MySuperApp.sh
## Add the wait script to the image
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait
## Launch the wait tool and then your application
CMD /wait && /MySuperApp.sh
Для получения дополнительной информации о возможном использовании см. README.
Вы также можете решить эту проблему, установив конечную точку, которая ожидает запуска службы, используя netcat (используя сценарий docker-wait). Мне нравится этот подход, так как у вас все еще есть чистый command
раздел в вашем docker-compose.yml
и вам не нужно добавлять специфичный для докера код в ваше приложение:
version: '2'
services:
db:
image: postgres
django:
build: .
command: python manage.py runserver 0.0.0.0:8000
entrypoint: ./docker-entrypoint.sh db 5432
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
Тогда ваш docker-entrypoint.sh
:
#!/bin/sh
postgres_host=$1
postgres_port=$2
shift 2
cmd="$@"
# wait for the postgres docker to be running
while ! nc $postgres_host $postgres_port; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done
>&2 echo "Postgres is up - executing command"
# run the command
exec $cmd
В настоящее время это задокументировано в официальной документации докера.
PS: вы должны установить netcat
в вашем экземпляре докера, если это не доступно. Для этого добавьте это в свой Docker
файл:
RUN apt-get update && apt-get install netcat-openbsd -y
Попробовав несколько подходов, IMO самым простым и элегантным вариантом является использованиеjwilder/dockerize
служебное изображение (упомянутое @Henrik Sachse, но он не показал конкретный пример) с его-wait
флаг. Вот простой пример, когда мне нужно, чтобы RabbitMQ был готов перед запуском моего приложения:
version: "3.8"
services:
# Start RabbitMQ.
rabbit:
image: rabbitmq
# Wait for RabbitMQ to be joinable.
check-rabbit-started:
image: jwilder/dockerize:0.6.1
depends_on:
- rabbit
command: 'dockerize -wait=tcp://rabbit:5672'
# Only start myapp once RabbitMQ is joinable.
myapp:
image: myapp:latest
depends_on:
- check-rabbit-started
Основываясь на этом сообщении в блоге https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html
Я настроил свой docker-compose.yml
как показано ниже:
version: "3.1"
services:
rabbitmq:
image: rabbitmq:3.7.2-management-alpine
restart: always
environment:
RABBITMQ_HIPE_COMPILE: 1
RABBITMQ_MANAGEMENT: 1
RABBITMQ_VM_MEMORY_HIGH_WATERMARK: 0.2
RABBITMQ_DEFAULT_USER: "rabbitmq"
RABBITMQ_DEFAULT_PASS: "rabbitmq"
ports:
- "15672:15672"
- "5672:5672"
volumes:
- data:/var/lib/rabbitmq:rw
start_dependencies:
image: alpine:latest
links:
- rabbitmq
command: >
/bin/sh -c "
echo Waiting for rabbitmq service start...;
while ! nc -z rabbitmq 5672;
do
sleep 1;
done;
echo Connected!;
"
volumes:
data: {}
Тогда я делаю для запуска =>:
docker-compose up start_dependencies
rabbitmq
сервис запустится в режиме демона, start_dependencies
закончу работу.
Существует готовая утилита под названием " docker-wait", которую можно использовать для ожидания.
В версии 3 файла Docker Compose вы можете использовать RESTART.
Например:
docker-compose.yml
worker:
build: myapp/.
volumes:
- myapp/.:/usr/src/app:ro
restart: on-failure
depends_on:
- rabbitmq
rabbitmq:
image: rabbitmq:3-management
Обратите внимание, что я использовал зависимые_on вместо ссылок, поскольку последний устарел в версии 3.
Несмотря на то, что это работает, это может быть не идеальное решение, поскольку вы перезапускаете контейнер докера при каждом сбое.
Также обратите внимание на RESTART_POLICY. это позволяет вам точно настроить политику перезапуска.
Когда вы используете Compose в рабочей среде, лучше всего использовать политику перезапуска:
Указание политики перезапуска, например перезапуска: всегда, чтобы избежать простоев
Не рекомендуется для серьезных развертываний, но по сути это команда "подождите x секунд".
С docker-compose
версия 3.4
а start_period
инструкция была добавлена в healthcheck
. Это означает, что мы можем сделать следующее:
docker-compose.yml
:
version: "3.4"
services:
# your server docker container
zmq_server:
build:
context: ./server_router_router
dockerfile: Dockerfile
# container that has to wait
zmq_client:
build:
context: ./client_dealer/
dockerfile: Dockerfile
depends_on:
- zmq_server
healthcheck:
test: "sh status.sh"
start_period: 5s
status.sh
:
#!/bin/sh
exit 0
Здесь происходит то, что healthcheck
вызывается через 5 секунд. Это вызываетstatus.sh
скрипт, который всегда возвращает "Нет проблем". Мы только что сделалиzmq_client
контейнер подождите 5 секунд перед запуском!
Примечание. Важно, чтобы у вас version: "3.4"
. Если.4
нет, docker-compose жалуется.
В настоящее время у меня также есть это требование ждать, пока некоторые службы будут запущены, прежде чем запустятся другие. Также прочтите предложения здесь и в некоторых других местах. Но большинство из них требует, чтобыdocker-compose.yml
кое-как нужно немного изменить. Итак, я начал работать над решением, которое я считаю слоем оркестровки вокруг самого docker-compose, и, наконец, придумал сценарий оболочки, который я назвалdocker-compose-profile
. Он может ждать tcp-соединения с определенным контейнером, даже если служба не предоставляет какой-либо порт напрямую хосту. Уловка, которую я использую, заключается в том, чтобы запустить другой контейнер докеров внутри стека, и оттуда я могу (обычно) подключиться к каждой службе (если не применяется другая конфигурация сети). Существует также метод ожидания, чтобы следить за определенным сообщением журнала. Службы могут быть сгруппированы вместе для запуска за один шаг, прежде чем будет запущен другой шаг. Вы также можете исключить некоторые службы, не перечисляя все остальные службы для запуска (например, набор доступных служб за вычетом некоторых исключенных служб). Такая конфигурация может быть связана с профилем. Существует файл конфигурации yaml с именемdcp.yml
который (на данный момент) нужно поместить в файл docker-compose.yml.
На ваш вопрос это будет выглядеть так:
command:
aliases:
upd:
command: "up -d"
description: |
Create and start container. Detach afterword.
profiles:
default:
description: |
Wait for rabbitmq before starting worker.
command: upd
steps:
- label: only-rabbitmq
only: [ rabbitmq ]
wait:
- 5@tcp://rabbitmq:5432
- label: all-others
Теперь вы можете запустить свой стек, вызвав
dcp -p default upd
или даже просто
dcp
поскольку есть только профиль по умолчанию для запуска up -d
на.
Есть небольшая проблема. Моя текущая версия (пока) не поддерживает специальные условия ожидания, такие как те, которые вам действительно нужны. Так что нет теста, чтобы отправить сообщение кролику.
Я уже думал о дальнейшем способе ожидания для выполнения определенной команды на хосте или в качестве контейнера докеров. Тогда мы могли бы расширить этот инструмент чем-то вроде
...
wait:
- service: rabbitmq
method: container
timeout: 5
image: python-test-rabbit
...
имея образ докера, называемый python-test-rabbit
это делает ваш чек.
Преимущество в этом состоит в том, что больше нет необходимости приносить ожидающую часть вашему работнику. Он будет изолирован и останется внутри слоя оркестровки.
Может быть, кому-то это пригодится. Любые предложения очень приветствуются.
Вы можете найти этот инструмент на https://gitlab.com/michapoe/docker-compose-profile
Одним из альтернативных решений является использование решения для оркестровки контейнеров, такого как Kubernetes. В Kubernetes есть поддержка контейнеров init, которые выполняются до завершения, прежде чем другие контейнеры могут запускаться Вы можете найти пример здесь с контейнером SQL Server 2017 Linux, где контейнер API использует контейнер инициализации для инициализации базы данных
https://www.handsonarchitect.com/2018/08/understand-kubernetes-object-init.html
Я не мог использовать этот подход, я на самом деле не могу использовать том в docker-compose, в обоих случаях я не работал:
s-flyway_1_d7a78571d25c | env: невозможно выполнить 'bash': нет такого файла или каталога docker_s-flyway_1_d7a78571d25c с кодом 127
Что мне здесь не хватает? Я немного растерялся об этом прямо сейчас.....
Ссылка на проект: https://github.com/jbrasileiro/introducing-example/blob/master/introducing-example-docker-flyway/src/docker/docker-compose.yml
Dockerfile
FROM dhoer/flyway:alpine
COPY ./sql/*.sql /flyway/sql
VOLUME ./wait-for-it.sh:/usr/local/bin/wait-for-it.sh
ADD ./wait-for-it.sh /
RUN chmod 777 /wait-for-it.sh
версия докера: '3'
version: '3'
services:
hello-world:
image: ubuntu
volumes:
- ./wait-for-it.sh:/usr/local/bin/wait-for-it.sh
command:
- wait-for-it.sh
- github.com:80
- --
- echo
- "hello world"
версия докера: '2'
version: '2'
services:
s-postgres:
image: postgres:9.4-alpine
hostname: host-postgres
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
networks:
- network-docker
s-flyway:
image: dhoer/flyway:alpine
build: flyway/
entrypoint: /wait-for-it.sh s-postgres 5432 --timeout=0 --strict
command: -url=jdbc:postgresql://s-postgres:5432/postgres -baselineOnMigrate=true -schemas=public -user=postgres -password=postgres migrate
networks:
- network-docker
depends_on:
- s-postgres
networks:
network-docker:
driver: bridge
Вот пример, где main
контейнер ждет worker
когда он начинает отвечать на пинги:
version: '3'
services:
main:
image: bash
depends_on:
- worker
command: bash -c "sleep 2 && until ping -qc1 worker; do sleep 1; done &>/dev/null"
networks:
intra:
ipv4_address: 172.10.0.254
worker:
image: bash
hostname: test01
command: bash -c "ip route && sleep 10"
networks:
intra:
ipv4_address: 172.10.0.11
networks:
intra:
driver: bridge
ipam:
config:
- subnet: 172.10.0.0/24
Тем не менее, правильный способ заключается в использовании healthcheck
(>=2.1).
Я предполагаю, что разработчики докеров действительно хотят, чтобы мы ждали сервисов, используя код в наших собственных образах. Я все еще хочу настроить службы для ожидания в файлах . Вот один из способов, если вы хотите использовать сценарий точки входа.
Добавьте этот цикл в свой сценарий точки входа, используя выбранный вами инструмент ожидания, включенный в образ. Я использую https://github.com/vishnubob/wait-for-it/. Если вы не передаете ни одного сервиса, цикл ничего не делает.
for service in "$@"; do
echo "$0: wait for service $service"
if ! wait-for-it "$service"; then
echo "$0: failed on service $service"
exit 1
fi
done
Передайте необходимые службы с этой записью для контейнера вdocker-compose.yml
:
command: ["my-data-svc:5000"]
Это зависит от того, как команды docker передаются в качестве аргументов сценарию точки входа. Вероятно, вы можете привести убедительный аргумент, что я злоупотребляю намерениями команды docker. Я не собираюсь умирать на этом холме, это просто работает для меня.
У меня есть только 2 файла для создания файлов, и я запускаю один первый и второй позже. Мой сценарий выглядит так:
#!/bin/bash
#before i build my docker files
#when done i start my build docker-compose
docker-compose -f docker-compose.build.yaml up
#now i start other docker-compose which needs the image of the first
docker-compose -f docker-compose.prod.yml up