Docker Складіть зачекайте контейнер X перед запуском Y


325

Я використовую RabbitMQ і простий зразок пітона з тут разом з Докером-композит. Моя проблема полягає в тому, що мені потрібно дочекатися завершення роботи rabbitmq. З того, що я шукав до цих пір, я не знаю, як чекати з контейнером x (у моєму випадку працівник), поки не почнеться y (rabbitmq).

Я знайшов цей блог, де він перевіряє, чи є інший хост в Інтернеті. Я також знайшов цю команду docker :

чекати

Використання: докер чекати CONTAINER [CONTAINER ...]

Блокуйте, поки контейнер не зупиниться, а потім надрукуйте його вихідний код.

Очікування зупинки контейнера - це, можливо, не те, що я шукаю, але якщо він є, чи можливо використовувати цю команду всередині docker-compose.yml? Моє рішення поки що - почекати кілька секунд і перевірити порт, але чи це спосіб досягти цього? Якщо я не чекаю, я отримую помилку.

docker-compose.yml

worker:
    build: myapp/.
    volumes:
    - myapp/.:/usr/src/app:ro

    links:
    - rabbitmq
rabbitmq:
    image: rabbitmq:3-management

зразок пітона привіт (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()

Докерфайл для працівника:

FROM python:2-onbuild
RUN ["pip", "install", "pika"]

CMD ["python","rabbit.py"]

Оновити листопад 2015 року :

Сценарій оболонки або очікування всередині вашої програми - можливо, можливе рішення. Але побачивши цей випуск, я шукаю команду чи функцію docker / docker-compose.

Вони згадують рішення щодо здійснення медичної перевірки, яке може бути найкращим варіантом. Відкрите з'єднання tcp не означає, що ваша послуга готова або може залишатися готовою. На додаток до цього мені потрібно змінити вхідну точку в моєму докерфілі.

Тож я сподіваюся на відповідь командами докер-композиції на борту команд, що, сподіваємось, випадок, якщо вони закінчать цю проблему.

Оновлення березня 2016 року

Є пропозиція щодо вбудованого способу визначення того, чи є контейнер "живим". Тож докер-композит може, можливо, скористатися ним найближчим часом.

Оновлення червня 2016 року

Здається, що перевірка стану здоров'я буде інтегрована у докер у версії 1.12.0

Оновлення січня 2017 року

Я знайшов рішення для створення докерів, див.: Docker Compose зачекайте контейнера X перед запуском Y


2
Використання перевірок здоров'я в програмі 2.3 докер-композиції застаріло, щоб заохочувати робити розподільні системи до відмов. Дивіться: docs.docker.com/compose/startup-order
Kmaid

Відповіді:


284

Нарешті знайшов рішення методом докер-композиту. Оскільки докер-композитний формат файлу 2.1, Ви можете визначити перевірки здоров'я .

Я це зробив у прикладі проекту, який потрібно встановити принаймні докер 1.12.0+. Мені також потрібно було розширити Dockerfile для управління rabbitmq , оскільки curl не встановлений на офіційному зображенні.

Тепер я перевіряю, чи доступна сторінка управління контейнером rabbitmq. Якщо згортання завершиться кодом виходу 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

Докерфайл (rabbitmq + curl):

FROM rabbitmq:3-management
RUN apt-get update
RUN apt-get install -y curl 
EXPOSE 4369 5671 5672 25672 15671 15672

Версія 3 більше не підтримує форму умови залежності_on . Тож я перейшов від залежності_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

6
@svenhornberg pingвикористовує ICMP, тому не підтримує порти TCP. Можливо, ncпротестувати порт TCP. Напевно, краще psql -h localhost -p 5432щось використовувати і запитати.
Мет

36
«Залежить від" було вилучено у версії 3 docs.docker.com/compose/compose-file/#dependson
Nha

48
@nha Схоже, conditionформа depends_onвидалена, але depends_onвона все ще існує в v3
akivajgordon

14
Як ще можна перевірити стан здоров'я, щоб контролювати порядок запуску, якщо depends_onз conditionнього було знято?
Франц

42
Важко в це повірити такому болю
напр.

71

По-новому, це ще неможливо. Дивіться також цей запит на функцію .

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

У Dockerfiles CMDви можете звернутися до власного сценарію запуску, який завершує запуск служби контейнерів. Перш ніж почати його, ви чекаєте на такий, як:

Докерфайл

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. Я не знаю, що попередньо встановлено на зображенні python.

Є кілька інструментів, які забезпечують просту логіку очікування для простих перевірок портів tcp:

Для більш складних очікувань:


Чи можете ви пояснити, що ви маєте на увазі під CMD? Чи означає це, що моя програма повинна це робити, як я це робив за допомогою перевірки порту? Або ви маєте на увазі конкретну CMD, наприклад, для цього Linux?
svenhornberg

дякую за пояснення, я підтримую вашу відповідь. Але я думаю, що наступний запит на функцію стане правильною відповіддю на моє запитання, тому я поки що залишаю його без відповіді.
svenhornberg

44

Використання restart: unless-stoppedабо restart: alwaysможе вирішити цю проблему.

Якщо працівник containerзупиняється, коли кроликMQ не готовий, він буде перезапущений, поки не буде.


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 буде запускати послуги в порядку залежності. У наступному прикладі db та redis будуть запущені перед веб.
  • docker-compose up SERVICE автоматично включатиме залежності SERVICE. У наступному прикладі докер-композиція веб також створить і запустить db та redis.

Примітка. Як я розумію, хоча це встановлює порядок завантаження контейнерів. Це не гарантує, що служба всередині контейнера фактично завантажена.

Наприклад, контейнер postgres може бути налаштований. Але сама служба postgres все ще може ініціалізуватися в контейнері.


10
dnephin писав: залежно_он лише замовляє. Щоб насправді затримати запуск іншого контейнера, потрібно було б визначити, коли процес закінчується ініціалізацією.
svenhornberg

15
"Версія 3 більше не підтримує форму умови depends_on." docs.docker.com/compose/compose-file/#dependson
akauppi

depends_onне чекає, поки контейнер перебуває у readyстані (що б це не означало у вашому випадку). Він лише чекає, поки контейнер перебуває у стані "запуску".
htyagi

19

ви також можете просто додати його до параметра команди, наприклад.

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

https://github.com/docker/compose/isissue/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"

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 

4

Існує готова до використання утиліта під назвою " docker-wait ", яку можна використовувати для очікування.


1
Дякую, але це лише скрипт оболонки, тому це як відповідь h3nrik або очікування всередині python. Це не особливість докер-композиції. Зверніть увагу на сайт github.com/docker/compose/isissue/374, який планують здійснити перевірку здоров'я, яка була б найкращим способом. Відкрите з'єднання tcp не означає, що ваша послуга готова або може залишатися готовою. На додаток до цього мені потрібно змінити вхідну точку в моєму докерфілі.
svenhornberg

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 (знову ж таки з проекту repo 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


Я шукав подібну відповідь. Я зазвичай працював з hub.docker.com/r/dadarek/wait-for-dependitions, оскільки він використовує netcat під ним. Той, який ви надали, базується на іржі. Не можу коментувати вашу якість, але для мене жодні додаткові шари не є певним профі.
Філіп Мальчак

1
Я настійно не рекомендую проти цього з міркувань безпеки. Ви працюєте з довільним виконуваним файлом із гіперпосилання. Кращим рішенням було б те ж саме зробити зі статичним сценарієм, скопійованим у зображення за допомогою COPY
Paul K

@PaulK Звичайно, зрозуміло, що запускати що-небудь із гіперпосилання не є безпечним, але це лише демонстрація вище, як зробити так, щоб https://github.com/ufoscout/docker-compose-waitбібліотека працювала :) Спосіб, яким ви користуєтеся цією бібліотекою, не змінює відповідь, що ви можете використовувати якусь лібу. Безпека - це складна тема, і якщо ми підемо далеко, ми повинні перевірити, що ця бібліотека все-таки робить всередині, навіть якщо ми її КОПУЄМО :) Тому краще бути більш конкретним у вашому коментарі, наприклад: "Я настійно рекомендую не використовувати цю бібліотеку від гіперпосилання ". Сподіваюся, ви згодні, дякую за підказку!
Evereq

2

ґрунтуючись на цій публікації в блозі https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependitions.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плагін, та використовувати перевірку здоров'я, яка вже застаріла - найкраща відповідь. Простий робочий приклад з перевіркою через ncйого - downvote. га, добре ...
Ігор Комар

у відповіді не використовується функція нативної докера, її не має значення, якщо ви використовуєте curl, nc або інші інструменти. поки! nc - це те саме, що вже розміщено в інших відповідях.
svenhornberg


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 .

Наприклад:

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 у виробництві , насправді найкращою практикою є використання політики перезавантаження:

Визначення політики перезапуску, як перезавантаження: завжди, щоб уникнути простоїв


0

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

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


0

Ось приклад, коли mainконтейнер чекає, workerколи він починає відповідати за pings:

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


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немає, докер-композитор скаржиться.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.