Docker Compose дождаться контейнера X перед запуском Y

326

Я использую RabbitMQ и простой образец питона из здесь вместе с Докером-композом. Моя проблема в том, что мне нужно дождаться полного запуска 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

Похоже, что Healthcheck будет интегрирован в Docker в версии 1.12.0

Обновление январь 2017

Я нашел решение docker-compose, см .: Docker Compose ждет контейнера X перед запуском Y

svenhornberg
источник
2
Использование проверок работоспособности в docker-compose 2.3 не рекомендуется, чтобы поощрять отказоустойчивость распределенных систем. Смотрите: docs.docker.com/compose/startup-order
Kmaid

Ответы:

284

Наконец нашел решение с помощью метода 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-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
svenhornberg
источник
6
@svenhornberg pingиспользует ICMP, поэтому не поддерживает TCP-порты. Может быть, ncчтобы проверить порт TCP. Вероятно, лучше использовать psql -h localhost -p 5432и запросить что-то.
Мэтт
36
"зависит от" был удален в версии 3 docs.docker.com/compose/compose-file/#dependson
nha
49
@nha Похоже, что conditionформа depends_onудалена, но depends_onсама по-прежнему присутствует в v3
akivajgordon
14
Как проверки работоспособности все еще могут использоваться для контроля порядка запуска, если depends_onс conditionбыл удален?
Франц
43
Трудно поверить , что это такая боль еще
NPR
71

Собственно это пока невозможно. Смотрите также этот запрос функции .

Пока что вам нужно сделать это в своих контейнерах, CMDчтобы дождаться появления всех необходимых сервисов.

В разделе Dockerfiles 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:

Для более сложных ожиданий:

0x7d7b
источник
Не могли бы вы объяснить, что вы подразумеваете под CMD? Значит ли это, что моя программа должна это делать, как я это сделал с проверкой порта? Или вы имеете в виду конкретный CMD, например, из Linux для этого?
Свенхорнберг
Спасибо, что объяснили, я приветствую ваш ответ. Но я думаю, что предстоящий запрос о функции будет правильным ответом на мой вопрос, поэтому я пока оставляю его без ответа.
Свенхорнберг
44

Использование restart: unless-stoppedили restart: alwaysможет решить эту проблему.

Если работник containerостанавливается, когда rabbitMQ не готов, он будет перезапущен, пока не будет.

Toilal
источник
3
Мне нравится это решение для этого случая, но оно не работает для контейнеров, которые не завершаются, когда один из подпроцессов, которые он выполняет, завершается неудачно. Например, контейнер Tomcat будет продолжать работать, даже если запущенный сервлет Java не сможет подключиться к серверу базы данных. Конечно, контейнеры Docker делают контейнеры сервлетов, такие как Tomcat, в основном ненужными.
Дерек Махар
@DerekMahar, если у вас есть веб-приложение на основе Java, которое обслуживает только вызовы REST, что вы используете вместо Jetty / Tomcat?
JoeG
2
@JoeG, я имел в виду Tomcat контейнер сервлетов, который может содержать множество приложений, а не встроенный Tomcat. Docker делает первое в основном ненужным, в то время как второе делает его более популярным, например, для микросервисов.
Дерек Махар
35

Совсем недавно они добавили 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 все еще может инициализироваться внутри контейнера.

toast38coza
источник
11
dnephin писал: зависит_ только заказ. Чтобы фактически отложить запуск другого контейнера, должен быть какой-то способ определить, когда процесс завершил инициализацию.
Свенхорнберг
15
«Версия 3 больше не поддерживает форму условия depends_ondocs.docker.com/compose/compose-file/#dependson
akauppi
depends_onне ждет, пока контейнер находится в readyсостоянии (что бы это ни значило в вашем случае). Он только ждет, пока контейнер не будет в рабочем состоянии.
Хтяги
19

Вы также можете просто добавить его в опцию команды, например.

command: bash -c "sleep 5; start.sh"

https://github.com/docker/compose/issues/374#issuecomment-156546513

для ожидания порта вы также можете использовать что-то вроде этого

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"
AmanicA
источник
13

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"
Эдвин Икечукву Оконкво
источник
12

Для заказа контейнера начните использовать

depends_on:

Для ожидания предыдущего запуска контейнера используйте скрипт

entrypoint: ./wait-for-it.sh db:5432

Эта статья поможет вам https://docs.docker.com/compose/startup-order/

уволиться
источник
5
@svenhornberg в комментарии, вы ссылаетесь, нет объяснения о функции wait-for-it.sh.
выход
7

Вы также можете решить эту проблему, установив конечную точку, которая ожидает запуска службы, с помощью 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 
maerteijn
источник
4

Есть готовая утилита под названием « docker-wait », которую можно использовать для ожидания.

Адриан Митев
источник
1
Спасибо, но это всего лишь сценарий оболочки, так что это похоже на ответ h3nrik или ожидание внутри python. Это не особенность docker-compose. Можете посмотреть на github.com/docker/compose/issues/374, что они планируют провести проверку работоспособности, которая была бы наилучшим способом. Открытое TCP-соединение не означает, что ваш сервис готов или может оставаться готовым. В дополнение к этому мне нужно изменить мою точку входа в моем файле Docker.
Свенхорнберг
3

Пробовал много разных способов, но понравилась простота этого: https://github.com/ufoscout/docker-compose-wait

Идея , что вы можете использовать ENV варов в файл докер письма , чтобы представить список хостов услуг (с портами) , которые должны быть «долгожданный» , как это: 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.

Evereq
источник
Я искал похожий ответ. Я обычно работал с hub.docker.com/r/dadarek/wait-for-dependencies, так как он использует netcat внизу. Тот, который вы предоставили, основан на Rust. Не могу прокомментировать качество вашего, но для меня никакие дополнительные слои не являются определенным профессионалом.
Филипп Мальчак
1
Я настоятельно рекомендую против этого по соображениям безопасности. Вы запускаете произвольный исполняемый файл по гиперссылке. Лучшим решением было бы сделать то же самое с помощью статического сценария, который копируется в изображение с помощью COPY
Пол К.
@PaulK, конечно, понятно, что запуск чего-либо из гиперссылок небезопасен, но это всего лишь демонстрация выше, как заставить https://github.com/ufoscout/docker-compose-waitбиблиотеку работать :) Способ, которым вы используете эту библиотеку, не меняет ответ, что вы можете использовать некоторую библиотеку. Безопасность - это сложная тема, и если мы пойдем далеко, мы все равно должны проверить, что эта библиотека делает внутри, даже если мы ее КОПИРУЕМ :) Поэтому лучше быть более конкретным в своем комментарии, например: «Я настоятельно рекомендую не использовать эту библиотеку. из гиперссылки ". Надеюсь, вы согласны, спасибо за подсказку!
Evereq
2

основываясь на этом сообщении в блоге 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завершит работу.

Игорь Комар
источник
LOL, сделать запрос, "curl", "-f", "http://localhost:15672"для которого вам нужно установить managementплагин и использовать Healthcheck, который уже устарел - его лучший ответ. Простой рабочий пример с проверкой через ncсвой - downvote. ха, хорошо ...
Игорь Комар
ответ не использует встроенную функцию докера, он не имеет значения, если вы используете curl, nc или другие инструменты. пока! nc такой же, как уже опубликовано в других ответах.
Свенхорнберг
1
@IgorKomar, спасибо, парень, ты спас мой день! : 3 Я использовал почти такую ​​же механику, чтобы проверить, готов ли сервер mysql до запуска реального приложения. ;) Я передаю команду, аналогичнуюdocker-compose run --name app-test --rm "app" bash -l -c 'echo Waiting for mysql service start... && while ! nc -z db-server 3306; do sleep 1; done && echo Connected! && /bin/bash /script/ci_tests.sh'
TooroSan
1

В версии 3 файла Docker Compose вы можете использовать RESTART .

Например:

докер-compose.yml

worker:
    build: myapp/.
    volumes:
    - myapp/.:/usr/src/app:ro
    restart: on-failure
    depends_on:
    - rabbitmq
rabbitmq:
    image: rabbitmq:3-management

Обратите внимание, что я использовал зависимость__ вместо ссылок, поскольку последняя версия устарела в версии 3.

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

Посмотрите также на RESTART_POLICY . это позволит вам точно настроить политику перезапуска.

Когда вы используете Compose в производственной среде , лучше всего использовать политику перезапуска:

Указание политики перезапуска, такой как перезапуск: всегда, чтобы избежать простоев

Матье Жемар
источник
0

Одним из альтернативных решений является использование решения для оркестровки контейнеров, такого как Kubernetes. В Kubernetes есть поддержка контейнеров init, которые выполняются до завершения, прежде чем другие контейнеры могут запускаться. Вы можете найти пример здесь с контейнером SQL Server 2017 Linux, где контейнер API использует контейнер инициализации для инициализации базы данных

https://www.handsonarchitect.com/2018/08/understand-kubernetes-object-init.html

Нилеш Гуле
источник
0

Вот пример, где mainконтейнер ожидает, workerкогда он начинает отвечать на запросы ping:

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).

kenorb
источник
0

Не рекомендуется для серьезных развертываний, но здесь, по сути, есть команда "подождите х секунд".

С docker-composeверсией инструкция была добавлена . Это означает, что мы можем сделать следующее:3.4start_periodhealthcheck

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 жалуется.

NumesSanguis
источник
1
Как наивное решение "подожди 5 секунд", оно довольно гениальное. Я бы проголосовал, но я не буду, потому что это на самом деле не работает с настройками, подобными прод, и я боюсь, что кто-то посмотрит на количество голосов, вместо того, чтобы внимательно читать. Тем не менее, я хотел сказать "человек, это умный";)
Филипп Мальчак
PS. Для более сложных решений, смотрите ответ
Evereq
Это не то, что start_periodделает. Эта конфигурация означает, что есть льготный период, когда неудачные проверки работоспособности не считаются повторными попытками. Если это удастся рано, это считается здоровым. После начального периода сбой будет считаться повторной попыткой. См. Docs.docker.com/engine/reference/builder/#healthcheck
Capi Etheriel
-4

У меня просто есть 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
Бенджамин Грасич
источник
Это не считается хорошей практикой. Тогда вы не сможете доставить решение, состоящее из нескольких конкатайнеров, из одного составного файла.
juergi