Docker Compose attend le conteneur X avant de commencer Y


326

J'utilise rabbitmq et un simple échantillon de python d' ici avec docker-compose. Mon problème est que je dois attendre que rabbitmq démarre complètement. D'après ce que j'ai cherché jusqu'à présent, je ne sais pas comment attendre avec le conteneur x (dans mon assistant social) jusqu'à ce que y (rabbitmq) soit démarré.

J'ai trouvé ce blog où il vérifie si l'autre hôte est en ligne. J'ai également trouvé cette commande docker :

attendre

Utilisation: docker wait CONTAINER [CONTAINER ...]

Bloquer jusqu'à l'arrêt d'un conteneur, puis imprimer son code de sortie.

Attendre l'arrêt d'un conteneur n'est peut-être pas ce que je recherche, mais si c'est le cas, est-il possible d'utiliser cette commande dans le docker-compose.yml? Jusqu'à présent, ma solution est d'attendre quelques secondes et de vérifier le port, mais est-ce le moyen d'y parvenir?. Si je n'attends pas, j'obtiens une erreur.

docker-compose.yml

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

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

échantillon de 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()

Dockerfile pour le travailleur:

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

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

Mise à jour de novembre 2015 :

Un script shell ou une attente dans votre programme est peut-être une solution possible. Mais après avoir vu ce problème, je recherche une commande ou une fonctionnalité de docker / docker-compose.

Ils mentionnent une solution pour mettre en place un bilan de santé, qui peut être la meilleure option. Une connexion TCP ouverte ne signifie pas que votre service est prêt ou peut rester prêt. En plus de cela, je dois changer mon point d'entrée dans mon dockerfile.

J'espère donc une réponse avec les commandes embarquées de docker-compose, ce qui sera le cas, espérons-le, si elles finissent ce problème.

Mise à jour mars 2016

Il est proposé de fournir une méthode intégrée pour déterminer si un conteneur est "vivant". Ainsi, docker-compose peut peut-être l'utiliser dans un avenir proche.

Mise à jour juin 2016

Il semble que le bilan de santé sera intégré dans docker dans la version 1.12.0

Mise à jour janvier 2017

J'ai trouvé une solution docker-compose voir: Docker Compose attendre le conteneur X avant de commencer Y


2
L'utilisation des contrôles de santé dans a été déconseillée dans docker-compose 2.3 pour encourager la tolérance aux pannes des systèmes distribués. Voir: docs.docker.com/compose/startup-order
Kmaid

Réponses:


284

Enfin trouvé une solution avec une méthode docker-compose. Depuis le format de fichier docker-compose 2.1, vous pouvez définir des contrôles de santé .

Je l'ai fait dans un exemple de projet dont vous avez besoin d'installer au moins docker 1.12.0+. J'ai également dû étendre le fichier Dockerfile de rabbitmq-management , car curl n'est pas installé sur l'image officielle.

Maintenant, je teste si la page de gestion du rabbitmq-container est disponible. Si curl se termine avec exitcode 0, l'application conteneur (python pika) sera démarrée et publiera un message dans la file d'attente hello. Son fonctionne maintenant (sortie).

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

production:

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

La version 3 ne prend plus en charge la forme condition de depend_on . J'ai donc déménagé de depend_on pour redémarrer en cas d'échec. Maintenant, mon conteneur d'application va redémarrer 2-3 fois jusqu'à ce qu'il fonctionne, mais c'est toujours une fonctionnalité de composition de docker sans écraser le point d'entrée.

docker-compose (version 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 pingutilise ICMP et ne prend donc pas en charge les ports TCP. Peut-être ncpour tester un port TCP. Il vaut probablement mieux utiliser psql -h localhost -p 5432et interroger quelque chose.
Matt

36
"dépend de" a été supprimé dans la version 3 docs.docker.com/compose/compose-file/#dependson
nha

49
@nha Il semble que la conditionforme de depends_onsoit supprimée, mais depends_onelle-même est toujours présente dans la v3
akivajgordon

14
Comment les contrôles de santé peuvent-ils encore être utilisés pour contrôler l'ordre de démarrage si depends_onavec conditiona été supprimé?
Franz

43
Difficile de croire encore une telle douleur
npr

71

Nativement, ce n'est pas encore possible. Voir aussi cette demande de fonctionnalité .

Jusqu'à présent, vous devez le faire dans vos conteneurs CMDpour attendre que tous les services requis soient là.

Dans le Dockerfiles, CMDvous pouvez vous référer à votre propre script de démarrage qui termine le démarrage de votre service de conteneur. Avant de le démarrer, vous attendez un dépendant comme:

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

Vous devrez probablement également installer netcat sur votre ordinateur Dockerfile. Je ne sais pas ce qui est pré-installé sur l'image python.

Il existe quelques outils qui fournissent une logique d'attente facile à utiliser, pour des vérifications de port TCP simples:

Pour les attentes plus complexes:


Pourriez-vous expliquer ce que vous entendez par CMD? Est-ce que cela signifie que mon programme doit le faire, comme je l'ai fait avec une vérification de port? Ou voulez-vous dire un CMD spécifique de Linux par exemple pour cela?
svenhornberg

merci d'avoir expliqué, j'ai voté pour votre réponse, mais je pense que la demande de fonctionnalité à venir serait la bonne réponse à ma question, je la laisse donc sans réponse jusqu'à présent.
svenhornberg

44

L'utilisation restart: unless-stoppedou restart: alwayspeut résoudre ce problème.

Si le travailleur containers'arrête lorsque rabbitMQ n'est pas prêt, il sera redémarré jusqu'à ce qu'il le soit.


3
J'aime cette solution pour ce cas, mais elle ne fonctionne pas pour les conteneurs qui ne se terminent pas lorsque l'un des sous-processus qu'il exécute échoue. Par exemple, un conteneur Tomcat continuerait de s'exécuter même si un servlet Java qu'il exécutait échouait à se connecter à un serveur de base de données. Certes, les conteneurs Docker rendent les conteneurs de servlets comme Tomcat presque inutiles.
Derek Mahar

@DerekMahar, si vous avez une application Web Java qui ne sert que les appels REST, qu'utilisez-vous à la place de Jetty / Tomcat?
JoeG

2
@JoeG, je voulais dire Tomcat, le conteneur de servlet qui peut héberger de nombreuses applications, pas Tomcat intégré. Docker rend le premier pratiquement inutile, tout en rendant le second plus populaire pour les microservices, par exemple.
Derek Mahar

35

Tout récemment, ils ont ajouté la depends_onfonctionnalité .

Éditer:

À partir de la version 2.1+ de compose, vous pouvez utiliser depends_onconjointement avec healthcheckpour y parvenir:

De la documentation :

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"

Avant la version 2.1

Vous pouvez toujours l'utiliser depends_on, mais cela n'affecte que l' ordre dans lequel les services sont démarrés - pas s'ils sont prêts avant le démarrage du service dépendant.

Il semble nécessiter au moins la version 1.6.0.

L'utilisation ressemblerait à quelque chose comme ceci:

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

De la documentation:

Dépendance express entre services, ce qui a deux effets:

  • docker-compose up démarrera les services dans l'ordre des dépendances. Dans l'exemple suivant, db et redis seront démarrés avant Web.
  • docker-compose up SERVICE inclura automatiquement les dépendances de SERVICE. Dans l'exemple suivant, docker-compose up web créera et démarrera également db et redis.

Remarque: Si je comprends bien, bien que cela définisse l'ordre dans lequel les conteneurs sont chargés. Il ne garantit pas que le service à l'intérieur du conteneur a effectivement été chargé.

Par exemple, votre conteneur postgres peut être actif. Mais le service postgres lui-même peut encore être en cours d'initialisation dans le conteneur.


10
dnephin a écrit: depend_on ne fait que commander. Pour retarder réellement le démarrage d'un autre conteneur, il faudrait trouver un moyen de détecter le moment où un processus a fini de s'initialiser.
svenhornberg

15
"La version 3 ne prend plus en charge la forme condition de depends_on." docs.docker.com/compose/compose-file/#dependson
akauppi

depends_onn'attend pas que le conteneur soit en readyétat (quoi que cela puisse signifier dans votre cas). Il attend uniquement que le conteneur soit en état de «fonctionnement».
htyagi

19

vous pouvez également l'ajouter à l'option de commande, par exemple.

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

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

d'attendre sur un port, vous pouvez également utiliser quelque chose comme ça

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

pour augmenter le temps d'attente, vous pouvez pirater un peu plus:

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 a fait l'affaire pour moi..voir ci-dessous

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

Pour la commande de début de conteneur, utilisez

depends_on:

Pour attendre le script de démarrage du conteneur précédent

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

Cet article vous aidera https://docs.docker.com/compose/startup-order/


5
@svenhornberg dans le commentaire, vous liez, il n'y a aucune explication sur la fonction wait-for-it.sh.
quittez

7

Vous pouvez également résoudre ce problème en définissant un point de terminaison qui attend que le service soit opérationnel à l'aide de netcat (à l'aide du script docker-wait ). J'aime cette approche car vous avez toujours une commandsection propre dans votre docker-compose.ymlet vous n'avez pas besoin d'ajouter du code spécifique de docker à votre application:

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

Alors votre 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

Ceci est aujourd'hui documenté dans la documentation officielle des dockers .

PS: Vous devez installer netcatdans votre instance de docker si ce n'est pas disponible. Pour ce faire, ajoutez ceci à votre Dockerfichier:

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

4

Il existe un utilitaire prêt à l'emploi appelé " docker-wait " qui peut être utilisé pour attendre.


1
Merci, mais ce n'est qu'un script shell, c'est comme une réponse h3nrik ou une attente à l'intérieur de python. Ce n'est pas une fonctionnalité de docker-compose. Puissiez- vous jeter un œil sur github.com/docker/compose/issues/374, ils prévoient de mettre en œuvre un bilan de santé qui serait le meilleur moyen. Une connexion TCP ouverte ne signifie pas que votre service est prêt ou peut rester prêt. En plus de cela, je dois changer mon point d'entrée dans mon dockerfile.
svenhornberg

3

J'ai essayé de nombreuses façons différentes, mais j'ai aimé la simplicité de ceci: https://github.com/ufoscout/docker-compose-wait

L'idée que vous pouvez utiliser ENV vars dans le fichier Compose docker soumettre une liste des services hôtes (avec les ports) qui devrait être « attendu » comme ceci: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017.

Supposons donc que vous ayez le fichier docker-compose.yml suivant (copier / coller à partir du fichier 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

Ensuite, pour que les services attendent, vous devez ajouter les deux lignes suivantes à vos Dockerfiles (dans Dockerfile des services qui devraient attendre le démarrage d'autres services):

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

L'exemple complet d'un tel exemple Dockerfile (encore une fois à partir du référentiel de projet 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

Pour plus de détails sur l'utilisation possible, consultez le fichier README


Je cherchais une réponse similaire. J'ai généralement travaillé avec hub.docker.com/r/dadarek/wait-for-dependencies car il utilise netcat en dessous. Celui que vous avez fourni est basé sur Rust. Je ne peux pas commenter la qualité de la vôtre, mais pour moi, aucune couche supplémentaire n'est définitivement un pro.
Filip Malczak du

1
Je le déconseille fortement pour des raisons de sécurité. Vous exécutez un exécutable arbitraire à partir d'un lien hypertexte. Une meilleure solution serait de faire la même chose avec un script statique copié dans l'image avec COPY
Paul K

@PaulK bien sûr, il est compréhensible que l'exécution de quoi que ce soit à partir d'un lien hypertexte n'est pas sécurisée, mais c'est juste une démonstration ci-dessus sur la façon de faire https://github.com/ufoscout/docker-compose-waitfonctionner la bibliothèque :) La façon dont vous utilisez cette bibliothèque ne change pas une réponse que vous pouvez utiliser une bibliothèque. La sécurité est un sujet complexe et si nous allons loin, nous devrions de toute façon vérifier ce que fait cette bibliothèque à l'intérieur, même si nous le copions :) Donc, il vaut mieux être plus précis dans votre commentaire comme: "Je déconseille fortement l'utilisation de cette bibliothèque à partir d'un lien hypertexte ". J'espère que vous êtes d'accord, merci pour un indice!
Evereq

2

en se basant sur cet article de blog https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

J'ai configuré mon docker-compose.ymlcomme indiqué ci-dessous:

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

Ensuite, je fais pour courir =>:

docker-compose up start_dependencies

rabbitmqle service démarrera en mode démon, start_dependenciesterminera le travail.


lol, faire une requête via "curl", "-f", "http://localhost:15672"pour laquelle vous devez installer le managementplugin et utiliser healthcheck qui est déjà obsolète - sa meilleure réponse. Exemple de travail simple avec vérification via ncson - downvote. ha, ok ...
Igor Komar

la réponse n'utilise pas de fonctionnalité native de docker, ce n'est pas pertinent si vous utilisez curl, nc ou d'autres outils. tandis que! nc est le même que celui déjà publié dans d'autres réponses.
svenhornberg


1
@IgorKomar, merci mec, tu m'as sauvé la journée! : 3 J'ai utilisé presque le même mécanisme pour vérifier que le serveur mysql est prêt avant le démarrage de l'application réelle. ;) Je passe une commande similaire à celle dudocker-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

Dans la version 3 d'un fichier Docker Compose, vous pouvez utiliser RESTART .

Par exemple:

docker-compose.yml

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

Notez que j'ai utilisé depend_on au lieu de liens puisque ce dernier est déconseillé dans la version 3.

Même si cela fonctionne, ce n'est peut-être pas la solution idéale puisque vous redémarrez le conteneur Docker à chaque échec.

Jetez également un œil à RESTART_POLICY . il vous permet d'affiner la politique de redémarrage.

Lorsque vous utilisez Compose en production , il est en fait recommandé d'utiliser la stratégie de redémarrage:

Spécification d'une politique de redémarrage comme redémarrage: toujours pour éviter les temps d'arrêt


0

L'une des solutions alternatives consiste à utiliser une solution d'orchestration de conteneurs comme Kubernetes. Kubernetes prend en charge les conteneurs d'initialisation qui s'exécutent jusqu'à la fin avant que d'autres conteneurs puissent démarrer. Vous pouvez trouver un exemple ici avec le conteneur SQL Server 2017 Linux où le conteneur API utilise le conteneur init pour initialiser une base de données

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


0

Voici l'exemple où le mainconteneur attend workerlorsqu'il commence à répondre aux 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

Cependant, la bonne façon est d'utiliser healthcheck(> = 2.1).


0

Non recommandé pour les déploiements sérieux, mais voici essentiellement une commande "wait x seconds".

Avec la docker-composeversion, 3.4une start_periodinstruction a été ajoutéehealthcheck . Cela signifie que nous pouvons faire ce qui suit:

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

Ce qui se passe ici, c'est que le healthcheckest invoqué après 5 secondes. Cela appelle le status.shscript, qui renvoie toujours "Pas de problème". Nous venons de faire zmq_clientattendre le conteneur 5 secondes avant de commencer!

Remarque: il est important que vous l'ayez version: "3.4". Si le .4n'est pas là, docker-compose se plaint.


1
En tant que solution naïve de "wait 5s", celle-ci est assez ingénieuse. Je voterais positivement, mais je ne le ferai pas car cela ne fonctionne pas vraiment avec des configurations de type prod et je crains que quelqu'un ne regarde le nombre de votes à la place en lisant attentivement. Pourtant, je voulais dire "mec, c'est intelligent";)
Filip Malczak

PS. Pour des solutions plus compliquées, voir la réponse d'Evereq
Filip Malczak

C'est pas ce start_periodfait. Cette configuration signifie qu'il existe une période de grâce où les vérifications de l'état de santé échouées ne comptent pas comme des tentatives. S'il réussit tôt, il est considéré comme sain. Après la période de démarrage, un échec comptera comme une nouvelle tentative. Voir docs.docker.com/engine/reference/builder/#healthcheck
Capi Etheriel

-4

J'ai juste 2 fichiers de composition et j'en démarre un premier et un deuxième plus tard. Mon script ressemble à ça:

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

Ce n'est pas considéré comme une bonne pratique. Vous ne pouvez pas alors fournir la solution composée de plusieurs conatiners à partir d'un seul fichier de composition.
juergi
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.