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 ждать, пока все необходимые услуги будут там.

в 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:

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

С помощью 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
Другие вопросы по тегам