Kompilacja dokowania zaczekaj na kontener X przed uruchomieniem Y


325

Korzystam z rabbitmq i prostej próbki pytona stąd wraz z docker-compose. Mój problem polega na tym, że muszę poczekać, aż Rabbitmq się w pełni uruchomi. Z tego, co szukałem do tej pory, nie wiem, jak czekać z kontenerem x (w moim przypadku robotnikiem), aż zostanie uruchomiony y (rabbitmq).

Znalazłem ten post na blogu, na którym sprawdza, czy drugi host jest online. Znalazłem również to polecenie dokera :

czekać

Zastosowanie: docker czekać CONTAINER [CONTAINER ...]

Blokuj, aż kontener się zatrzyma, a następnie wydrukuj kod wyjścia.

Oczekiwanie na zatrzymanie kontenera może nie jest tym, czego szukam, ale jeśli tak, to czy można użyć tego polecenia w oknie docker-compose.yml? Jak dotąd moim rozwiązaniem jest poczekanie kilku sekund i sprawdzenie portu, ale czy to jest sposób na osiągnięcie tego ?. Jeśli nie czekam, pojawia się błąd.

docker-compose.yml

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

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

przykładowa python hello (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()

Plik Docker dla pracownika:

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

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

Aktualizacja lis 2015 :

Możliwym rozwiązaniem może być skrypt powłoki lub oczekiwanie w twoim programie. Ale po zobaczeniu tego problemu szukam polecenia lub funkcji docker / docker-compose.

Wspominają o rozwiązaniu służącym do kontroli stanu zdrowia, które może być najlepszym rozwiązaniem. Otwarte połączenie TCP nie oznacza, że ​​usługa jest gotowa lub może pozostać gotowa. Ponadto muszę zmienić punkt wejścia w pliku dokera.

Mam więc nadzieję na odpowiedź z komendami docker-komponuj na pokładzie, co, mam nadzieję, będzie miało miejsce, jeśli zakończą ten problem.

Aktualizacja z marca 2016 r

Jest to propozycja dla zapewnienia wbudowany sposobem określenia, czy dany pojemnik jest „żywy”. Więc docker-compose może z niego skorzystać w najbliższej przyszłości.

Aktualizacja z czerwca 2016 r

Wygląda na to, że kontrola zdrowia zostanie zintegrowana z dokerem w wersji 1.12.0

Aktualizacja ze stycznia 2017 r

Znalazłem rozwiązanie komponowania dokera patrz: Docker Compose poczekaj na kontener X przed uruchomieniem Y


2
Używanie kontroli poprawności w przestarzałej wersji 2.3 jest przestarzałe, aby zachęcić do uczynienia systemów rozproszonych odpornymi na uszkodzenia. Zobacz: docs.docker.com/compose/startup-order
Kmaid

Odpowiedzi:


284

Wreszcie znalazłem rozwiązanie z metodą dokowania-komponowania. Ponieważ format pliku dokowania-komponuj 2.1, możesz zdefiniować kontrole kondycji .

Zrobiłem to w przykładowym projekcie, w którym musisz zainstalować przynajmniej dokera 1.12.0+. Musiałem także rozszerzyć plik Dockerfile do zarządzania królikami , ponieważ curl nie jest zainstalowany na oficjalnym obrazie.

Teraz sprawdzam, czy strona zarządzania kontenera rabbitmq jest dostępna. Jeśli curl zakończy się kodem wyjścia 0, aplikacja kontenera (python pika) zostanie uruchomiona i opublikuje komunikat w kolejce hello. Teraz działa (wyjście).

docker-compose (wersja 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

wynik:

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

Wersja 3 nie obsługuje już formy warunku depend_on . Więc przeniosłem się z depend_on, aby zrestartować po awarii. Teraz mój kontener aplikacji uruchomi się ponownie 2-3 razy, aż będzie działał, ale nadal jest funkcją komponowania dokera bez nadpisywania punktu wejścia.

docker-compose (wersja 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 pingużywa ICMP, więc nie obsługuje portów TCP. Może ncprzetestować port TCP. Prawdopodobnie lepiej użyć psql -h localhost -p 5432i zapytać o coś.
Matt

36
„zależy od” zostało usunięte w wersji 3 docs.docker.com/compose/compose-file/#dependson
nha

48
@nha Wygląda na to, że conditionforma depends_onzostała usunięta, ale depends_onsama jest nadal dostępna w v3
akivajgordon

14
Jak można healthchecks być nadal używane do celu kontrolki startowego Jeśli depends_onze conditionzostał usunięty?
Franz

42
Trudno w to uwierzyć - taki ból wciąż
npr

71

Natywnie nie jest to jeszcze możliwe. Zobacz także to żądanie funkcji .

Do tej pory musisz to zrobić w swoich kontenerach, CMDaby poczekać, aż pojawią się wszystkie wymagane usługi.

W Dockerfiles CMDmożesz odwołać się do własnego skryptu startowego, który otacza uruchamianie usługi kontenera. Zanim go uruchomisz, zaczekaj na zależną, taką jak:

Plik Docker

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

Prawdopodobnie musisz również zainstalować netcat Dockerfile. Nie wiem, co jest wstępnie zainstalowane na obrazie Pythona.

Istnieje kilka narzędzi, które zapewniają łatwą w użyciu logikę oczekiwania na proste kontrole portów TCP:

Na bardziej złożone oczekiwania:


Czy możesz wyjaśnić, co masz na myśli przez CMD? Czy to oznacza, że ​​mój program musi to zrobić, tak jak zrobiłem to z kontrolą portu? Czy masz na myśli konkretną CMD z np. Linux?
svenhornberg

dziękuję za wyjaśnienie, głosuję za odpowiedzią, ale myślę, że nadchodzące żądanie funkcji byłoby właściwą odpowiedzią na moje pytanie, więc pozostawiam do tej pory bez odpowiedzi.
svenhornberg

44

Za pomocą restart: unless-stoppedlub restart: alwaysmoże rozwiązać ten problem.

Jeśli pracownik containerzatrzyma się, gdy rabbitMQ nie będzie gotowy, zostanie zrestartowany, aż będzie.


3
Podoba mi się to rozwiązanie w tym przypadku, ale nie działa w przypadku kontenerów, które nie wychodzą, gdy jeden z uruchomionych podprocesów zawiedzie. Na przykład kontener Tomcat nadal działałby, nawet jeśli uruchamiany przez niego serwlet Java nie mógłby się połączyć z serwerem bazy danych. To prawda, że ​​kontenery Docker sprawiają, że kontenery serwletów takie jak Tomcat są w większości niepotrzebne.
Derek Mahar

@DerekMahar, jeśli masz aplikację internetową opartą na Javie, która obsługuje tylko wywołania REST, czego używasz zamiast Jetty / Tomcat?
JoeG

2
@JoeG, miałem na myśli Tomcat, kontener serwletów, który może obsługiwać wiele aplikacji, a nie osadzony Tomcat. Docker sprawia, że ​​ten pierwszy jest w większości niepotrzebny, a ten drugi jest bardziej popularny na przykład w przypadku mikrousług.
Derek Mahar

35

Całkiem niedawno dodali tę depends_onfunkcję .

Edytować:

Począwszy od komponowania wersji 2.1+, możesz tego użyć depends_onw połączeniu z healthcheck:

Z dokumentów :

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"

Przed wersją 2.1

Nadal możesz używać depends_on, ale wpływa to tylko na kolejność uruchamiania usług - nie jeśli są one gotowe przed uruchomieniem usługi zależnej.

Wydaje się, że wymaga co najmniej wersji 1.6.0.

Użycie wyglądałoby mniej więcej tak:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres 

Z dokumentów:

Wyraźna zależność między usługami, która ma dwa efekty:

  • dokowanie-komponowanie uruchomi usługi w kolejności zależności. W poniższym przykładzie db i redis zostaną uruchomione przed uruchomieniem sieci.
  • dokowanie-skomponuj SERVICE automatycznie uwzględni zależności SERVICE. W poniższym przykładzie web-docker-compose up również utworzy i uruchomi db i redis.

Uwaga: jak rozumiem, chociaż ustawia to kolejność ładowania kontenerów. Nie gwarantuje to, że usługa wewnątrz kontenera rzeczywiście się załadowała.

Na przykład postgres pojemnik może być włączony. Ale sama usługa postgres może nadal inicjować się w kontenerze.


10
dnephin napisał: depend_on zamawia tylko. Aby faktycznie opóźnić uruchomienie innego kontenera, musiałby istnieć sposób na wykrycie, kiedy proces się zakończy inicjalizujący.
svenhornberg

15
„Wersja 3 nie obsługuje już formy warunku depends_on”. docs.docker.com/compose/compose-file/#dependson
akauppi

depends_onnie czeka, aż kontener będzie w readystanie (cokolwiek to może znaczyć w twoim przypadku). Czeka tylko, aż kontener będzie w stanie „uruchomionym”.
htyagi

19

możesz także dodać go do opcji polecenia np.

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

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

aby poczekać na porcie, możesz również użyć czegoś takiego

command: bash -c "while ! curl -s rabbitmq:5672 > /dev/null; do echo waiting for xxx; sleep 3; done; start.sh"

Aby wydłużyć czas oczekiwania, możesz zhakować trochę więcej:

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 zrobił dla mnie lewę ... patrz poniżej

---
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"


7

Możesz również rozwiązać ten problem, ustawiając punkt końcowy, który czeka na uruchomienie usługi, używając netcat (używając skryptu docker-wait ). Podoba mi się to podejście, ponieważ nadal masz czystą commandsekcję docker-compose.ymli nie musisz dodawać do aplikacji kodu specyficznego dla dokera:

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

Następnie twój 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

Jest to obecnie udokumentowane w oficjalnej dokumentacji dokera .

PS: Należy zainstalować netcatw instancji dokera, jeśli nie jest to dostępne. Aby to zrobić, dodaj to do Dockerpliku:

RUN apt-get update && apt-get install netcat-openbsd -y 

4

Istnieje gotowe do użycia narzędzie o nazwie „ docker-wait ”, którego można używać do oczekiwania.


1
Dziękuję, ale jest to tylko skrypt powłoki, więc jest jak odpowiedź h3nrik lub oczekiwanie w pythonie. To nie jest cecha samego dokowania-komponowania. Czy możesz zajrzeć na github.com/docker/compose/issues/374 , planują zaimplementować sprawdzanie kondycji , które byłoby najlepszym sposobem. Otwarte połączenie TCP nie oznacza, że ​​usługa jest gotowa lub może pozostać gotowa. Ponadto muszę zmienić punkt wejścia w pliku dokera.
svenhornberg

3

Próbowałem na wiele różnych sposobów, ale podobała się jej prostota: https://github.com/ufoscout/docker-compose-wait

Pomysł, że można użyć ENV vars w pliku doker redagowania przedłożyć wykaz usług gospodarze (z otworami), który powinien być „oczekiwany” tak: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017.

Powiedzmy, że masz następujący plik docker-compose.yml (copy / past from repo 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

Następnie, aby usługi mogły czekać, należy dodać następujące dwa wiersze do plików Docker (do pliku Docker usług, które powinny oczekiwać na uruchomienie innych usług):

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

Kompletny przykład takiego przykładowego pliku Docker (ponownie z repozytorium projektu 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

Aby uzyskać szczegółowe informacje na temat możliwego użycia, zobacz README


Szukałem podobnej odpowiedzi. Zwykle pracowałem z hub.docker.com/r/dadarek/wait-for-dependencies, ponieważ używa netcat pod spodem. Ten, który podałeś, jest oparty na rdzy. Nie mogę komentować twojej jakości, ale dla mnie żadne dodatkowe warstwy nie są zdecydowanym profesjonalistą.
Filip Malczak

1
Zdecydowanie odradzam to ze względów bezpieczeństwa. Używasz dowolnego pliku wykonywalnego z hiperłącza. Lepszym rozwiązaniem byłoby zrobienie tego samego ze statycznym skryptem skopiowanym do obrazu za pomocą COPY
Paul K

@PaulK oczywiście zrozumiałe jest, że uruchamianie czegokolwiek z hiperłącza nie jest bezpieczne, ale to tylko demonstracja powyżej, jak uruchomić https://github.com/ufoscout/docker-compose-waitbibliotekę :) Sposób, w jaki korzystasz z tej biblioteki, nie zmienia odpowiedzi, z której możesz skorzystać. Bezpieczeństwo jest złożonym tematem i jeśli posuniemy się daleko, powinniśmy sprawdzić, co ta biblioteka robi w środku, nawet jeśli KOPIUJEMY ją :) Więc lepiej sprecyzuj swój komentarz w następujący sposób: „Zdecydowanie odradzam korzystanie z tej biblioteki z hiperłącza ”. Mam nadzieję, że się zgadzasz, dzięki za podpowiedź!
Evereq

2

na podstawie tego postu na blogu https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

Skonfigurowałem mój, docker-compose.ymljak pokazano poniżej:

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: {}

Następnie robię dla run =>:

docker-compose up start_dependencies

rabbitmqusługa uruchomi się w trybie demona, start_dependencieszakończy pracę.


lol, tworzenie zapytania, za pomocą "curl", "-f", "http://localhost:15672"którego należy zainstalować managementwtyczkę, i sprawdzanie poprawności, które już jest przestarzałe - to najlepsza odpowiedź. Prosty przykład działania z czekiem za pomocą nc- downvote. ha, ok ...
Igor Komar

odpowiedź nie korzysta z natywnej funkcji dokera, nie ma znaczenia, jeśli używasz curl, nc lub innych narzędzi. podczas! nc jest taki sam, jak już opublikowany w innych odpowiedziach.
svenhornberg


1
@IgorKomar, dzięki stary, uratowałeś mi dzień! : 3 Użyłem prawie tej samej mechaniki, aby sprawdzić, czy serwer mysql jest gotowy przed uruchomieniem aplikacji. ;) 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'
Podaję

1

W wersji 3 pliku Docker Compose możesz użyć RESTART .

Na przykład:

docker-compose.yml

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

Zauważ, że użyłem depend_on zamiast linków, ponieważ ten ostatni jest przestarzały w wersji 3.

Nawet jeśli działa, może nie być idealnym rozwiązaniem, ponieważ restartujesz kontener dokera przy każdej awarii.

Mają wygląd RESTART_POLICY również. pozwala dostroić zasady restartu.

Podczas korzystania z funkcji Komponuj w produkcji najlepszą praktyką jest użycie zasady ponownego uruchamiania:

Określanie zasad ponownego uruchamiania, takich jak restart: zawsze w celu uniknięcia przestojów



0

Oto przykład, w którym mainkontener czeka na workerrozpoczęcie odpowiedzi na 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

Jednak właściwym sposobem jest użycie healthcheck(> = 2.1).


0

Niezalecane w przypadku poważnych wdrożeń, ale tutaj jest zasadniczo polecenie „poczekaj x sekund”.

W docker-composewersji 3.4dodano start_periodinstrukcjęhealthcheck . Oznacza to, że możemy wykonać następujące czynności:

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

To, co się tutaj dzieje, polega na tym, że healthcheckwywoływane jest po 5 sekundach. To wywołuje status.shskrypt, który zawsze zwraca „Bez problemu”. Właśnie zmusiliśmy zmq_clientkontener do oczekiwania 5 sekund przed rozpoczęciem!

Uwaga: ważne, że masz version: "3.4". Jeśli tego .4nie ma, dokerowanie-komponowanie narzeka.


1
Jako naiwne rozwiązanie „poczekaj 5 sekund”, jest dość pomysłowe. Chciałbym głosować, ale nie zrobię tego, ponieważ tak naprawdę to nie działa z konfiguracjami podobnymi do prowokacji i obawiam się, że ktoś spojrzałby na liczbę głosów, zamiast czytać uważnie. Chciałem jednak powiedzieć „człowiek, to sprytne”;)
Filip Malczak

PS. Bardziej skomplikowane rozwiązania można znaleźć w odpowiedzi Evereqa
Filip Malczak

To nie to, co start_periodrobi. Ta konfiguracja oznacza okres karencji, w którym nieudane kontrole stanu nie liczą się jako próby. Jeśli to się powiedzie wcześnie, jest uważane za zdrowe. Po okresie początkowym awaria będzie liczona jako ponowna próba. Zobacz docs.docker.com/engine/reference/builder/#healthcheck
Capi Etheriel,

-4

Mam tylko 2 pliki do komponowania i zaczynam pierwszy, a drugi później. Mój skrypt wygląda tak:

#!/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

Nie jest to uważane za dobrą praktykę. W takim przypadku nie można dostarczyć rozwiązania składającego się z wielu koniugatorów z jednego pliku tworzenia.
juergi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.