Docker Compose espera el contenedor X antes de comenzar Y

326

Estoy usando rabbitmq y una simple muestra de python de aquí junto con docker-compose. Mi problema es que necesito esperar a que rabbitmq se inicie por completo. Por lo que busqué hasta ahora, no sé cómo esperar con el contenedor x (en mi caso trabajador) hasta que se inicie y (rabbitmq).

Encontré esta publicación de blog donde comprueba si el otro host está en línea. También encontré este comando docker :

Espere

Uso: docker wait CONTENEDOR [CONTENEDOR ...]

Bloquee hasta que un contenedor se detenga, luego imprima su código de salida.

Esperar a que se detenga un contenedor quizás no sea lo que estoy buscando, pero si lo es, ¿es posible usar ese comando dentro de docker-compose.yml? Mi solución hasta ahora es esperar unos segundos y verificar el puerto, pero ¿es esta la forma de lograr esto? Si no espero me sale un error.

docker-compose.yml

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

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

Python hola muestra (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 para trabajador:

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

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

Actualización de noviembre de 2015 :

Un script de shell o esperar dentro de su programa es quizás una posible solución. Pero después de ver este problema , estoy buscando un comando o función de docker / docker-compose.

Mencionan una solución para implementar un control de salud, que puede ser la mejor opción. Una conexión tcp abierta no significa que su servicio esté listo o pueda permanecer listo. Además de eso, necesito cambiar mi punto de entrada en mi dockerfile.

Así que espero una respuesta con los comandos a bordo de Docker-compose, que con suerte será el caso si terminan este problema.

Actualización de marzo de 2016

Existe una propuesta para proporcionar una forma integrada de determinar si un contenedor está "vivo". Por lo tanto, Docker-compose puede usarlo en un futuro próximo.

Actualización de junio de 2016

Parece que el chequeo de salud se integrará en Docker en la versión 1.12.0

Actualización enero 2017

Encontré una solución docker-compose ver: Docker Compose espera el contenedor X antes de comenzar Y

Svenhornberg
fuente
2
El uso de controles de salud ha quedado en desuso en docker-compose 2.3 para alentar a los sistemas distribuidos a tolerar fallas. Ver: docs.docker.com/compose/startup-order
Kmaid

Respuestas:

284

Finalmente encontré una solución con un método docker-compose. Desde el formato de archivo docker-compose 2.1 puede definir comprobaciones de estado .

Lo hice en un proyecto de ejemplo que necesita instalar al menos Docker 1.12.0+. También necesitaba ampliar el Dockerfile rabbitmq-management , porque curl no está instalado en la imagen oficial.

Ahora pruebo si la página de administración del rabbitmq-container está disponible. Si el rizo termina con el código de salida 0, la aplicación de contenedor (python pika) se iniciará y publicará un mensaje en la cola de saludo. Ahora está funcionando (salida).

docker-compose (versión 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

salida:

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 versión 3 ya no admite la forma de condición de dependen_on . Así que me mudé de dependen_on para reiniciar en caso de falla. Ahora mi contenedor de aplicaciones se reiniciará 2-3 veces hasta que funcione, pero sigue siendo una función de compilación acoplable sin sobrescribir el punto de entrada.

docker-compose (versión 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
fuente
66
@svenhornberg pingusa ICMP, por lo que no admite puertos TCP. Tal vez ncpara probar un puerto TCP. Probablemente mejor usar psql -h localhost -p 5432y consultar algo.
Matt
36
"depende de" se ha eliminado en la versión 3 docs.docker.com/compose/compose-file/#dependson
nha
48
@nha Parece que se eliminó la conditionforma de depends_on, pero depends_ontodavía está alrededor en v3
akivajgordon
14
¿Cómo puede HealthChecks todavía ser utilizado para el control de orden de inicio si depends_onla conditionha quitado?
Franz
43
Es difícil creer que esto sea un dolor todavía
npr
71

Nativamente eso no es posible, todavía. Consulte también esta solicitud de función .

Hasta ahora, debe hacer eso en sus contenedores CMDpara esperar hasta que todos los servicios requeridos estén allí.

En los Dockerfilemensajes de CMDcorreo electrónico, puede consultar su propio script de inicio que envuelve el inicio de su servicio de contenedor. Antes de comenzar, espera a uno dependiente como:

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

Probablemente también necesites instalar netcat en tu Dockerfile. No sé qué está preinstalado en la imagen de Python.

Existen algunas herramientas que proporcionan una lógica de espera fácil de usar, para verificaciones simples de puertos tcp:

Para esperas más complejas:

0x7d7b
fuente
¿Podría explicar qué quiere decir con CMD? ¿Esto significa que mi programa tiene que hacerlo, como lo hice con una verificación de puerto? ¿O te refieres a un CMD específico de, por ejemplo, Linux para esto?
svenhornberg
gracias por explicarme, he votado a favor su respuesta, pero creo que la próxima solicitud de función sería la respuesta correcta a mi pregunta, por lo que hasta ahora no la he respondido.
svenhornberg 01 de
44

Usando restart: unless-stoppedo restart: alwayspuede resolver este problema.

Si el trabajador se containerdetiene cuando rabbitMQ no está listo, se reiniciará hasta que lo esté.

Toilal
fuente
3
Me gusta esta solución para este caso, pero no funciona para contenedores que no salen cuando falla uno de los subprocesos que ejecuta. Por ejemplo, un contenedor Tomcat continuaría ejecutándose incluso si un servlet Java que ejecutó no se conectara a un servidor de base de datos. Por supuesto, los contenedores Docker hacen innecesarios los contenedores de servlets como Tomcat.
Derek Mahar
@DerekMahar, si tiene una aplicación web basada en Java que solo atiende llamadas REST, ¿qué usa en lugar de Jetty / Tomcat?
JoeG
2
@JoeG, me refería a Tomcat, el contenedor de servlets que puede alojar muchas aplicaciones, no Tomcat incrustado. Docker hace que la primera sea en su mayoría innecesaria, mientras que la segunda es más popular para los microservicios, por ejemplo.
Derek Mahar
35

Recientemente han agregado la depends_onfunción .

Editar:

A partir de compose la versión 2.1+, puede usarlo depends_onen conjunto con healthcheckpara lograr esto:

De los documentos :

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"

Antes de la versión 2.1

Todavía puede usar depends_on, pero solo afecta el orden en que se inician los servicios, no si están listos antes de que se inicie el servicio dependiente.

Parece requerir al menos la versión 1.6.0.

El uso se vería así:

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

De los documentos:

Expresar dependencia entre servicios, que tiene dos efectos:

  • docker-compose up iniciará los servicios en orden de dependencia. En el siguiente ejemplo, db y redis se iniciarán antes que la web.
  • El servicio docker-compose up incluirá automáticamente las dependencias de SERVICE. En el siguiente ejemplo, docker-compose up web también creará e iniciará db y redis.

Nota: Según tengo entendido, aunque esto establece el orden en que se cargan los contenedores. No garantiza que el servicio dentro del contenedor se haya cargado realmente.

Por ejemplo, el contenedor de postgres podría estar activo. Pero el servicio postgres en sí mismo aún podría estar inicializándose dentro del contenedor.

tostadas38coza
fuente
10
dnephin escribió: depende_on solo está ordenando. Para retrasar el inicio de otro contenedor, debería haber alguna forma de detectar cuándo un proceso ha terminado de inicializarse.
svenhornberg
15
"La versión 3 ya no admite la forma de condición de depends_on". docs.docker.com/compose/compose-file/#dependson
akauppi
depends_onno espera hasta que el contenedor esté en readyestado (lo que sea que eso signifique en su caso). Solo espera hasta que el contenedor esté en estado 'en ejecución'.
htyagi
19

También puede agregarlo a la opción de comando, por ejemplo.

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

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

esperar en un puerto también puedes usar algo como esto

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

para aumentar el tiempo de espera puedes hackear un poco más:

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

restart: on-failure hizo el truco para mí ... ver abajo

---
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"
Edwin Ikechukwu Okonkwo
fuente
12

Para el contenedor comenzar a ordenar el uso

depends_on:

Para esperar el inicio del contenedor anterior, use el script

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

Este artículo lo ayudará a https://docs.docker.com/compose/startup-order/

dejar
fuente
55
@svenhornberg en el comentario, enlace, no hay explicación sobre la función wait-for-it.sh.
Salga del
7

También puede resolver esto estableciendo un punto final que espere a que el servicio esté activo utilizando netcat (utilizando el script docker-wait ). Me gusta este enfoque, ya que todavía tiene una commandsección limpia en su docker-compose.ymly no necesita agregar código específico de Docker a su aplicación:

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

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

Esto se documenta en la actualidad el funcionario documentación ventana acoplable .

PD: debe instalarlo netcaten su instancia de docker si no está disponible. Para hacerlo, agregue esto a su Dockerarchivo:

RUN apt-get update && apt-get install netcat-openbsd -y 
maerteijn
fuente
4

Hay una utilidad lista para usar llamada " docker-wait " que se puede usar para esperar.

Adrian Mitev
fuente
1
Gracias, pero es solo un script de shell, por lo que es como una respuesta h3nrik o esperando dentro de Python. No es una característica de docker-compose en sí. Puede echar un vistazo en github.com/docker/compose/issues/374 y planean implementar un chequeo de salud que sería la mejor manera. Una conexión tcp abierta no significa que su servicio esté listo o pueda permanecer listo. Además de eso, necesito cambiar mi punto de entrada en mi dockerfile.
svenhornberg
3

Intenté muchas maneras diferentes, pero me gustó la simplicidad de esto: https://github.com/ufoscout/docker-compose-wait

La idea de que se puede utilizar ENV vars en el archivo de cargador de muelle de redacción para presentar una lista de hosts de servicios (con puertos) que debe ser "esperado" de esta manera: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017.

Entonces, supongamos que tiene el siguiente archivo docker-compose.yml (copy / past de 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

A continuación, para que los servicios esperen, debe agregar las siguientes dos líneas a sus Dockerfiles (en Dockerfile de los servicios que deben esperar a que otros servicios comiencen):

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

El ejemplo completo de tal Dockerfile de muestra (nuevamente desde el proyecto 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

Para otros detalles sobre el posible uso, consulte README

Evereq
fuente
Estaba buscando una respuesta similar. Por lo general, he trabajado con hub.docker.com/r/dadarek/wait-for-dependencies, ya que utiliza netcat debajo. El que ha proporcionado está basado en óxido. No puedo comentar sobre la calidad de los suyos, pero para mí ninguna capa adicional es un profesional definitivo.
Filip Malczak
1
Recomiendo encarecidamente contra esto por razones de seguridad. Estás ejecutando un ejecutable arbitrario desde un hipervínculo. Una mejor solución sería hacer lo mismo con un script estático que se copia en la imagen con COPY
Paul K
@PaulK, por supuesto, es comprensible que ejecutar cualquier cosa desde un hipervínculo no sea seguro, pero es solo una demostración sobre cómo hacer que la https://github.com/ufoscout/docker-compose-waitbiblioteca funcione :) La forma en que usas esa biblioteca no cambia una respuesta de que puedes utilizar algo de lib. La seguridad es un tema complejo y, si llegamos lejos, deberíamos comprobar qué hace esa biblioteca en el interior de todos modos, incluso si la COPIAMOS :) Por lo tanto, es mejor ser más específico en su comentario como: "Recomiendo encarecidamente el uso de esa biblioteca desde hipervínculo ". Espero que estés de acuerdo, gracias por una pista!
Evereq
2

basándose en esta publicación de blog https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

Configuré mi docker-compose.ymlcomo se muestra a continuación:

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

Entonces lo hago para ejecutar =>:

docker-compose up start_dependencies

rabbitmqEl servicio comenzará en modo demonio, start_dependenciesfinalizará el trabajo.

Igor Komar
fuente
jajaja, hacer una consulta a través "curl", "-f", "http://localhost:15672"de la cual necesitas instalar el managementcomplemento y usar un chequeo de salud que ya no se utiliza - es la mejor respuesta. Ejemplo de trabajo simple con cheque a través de ncsu - downvote. ja, ok ...
Igor Komar
la respuesta no usa una función de acoplador nativa, es irrelevante si usa curl, nc u otras herramientas. ¡mientras! nc es lo mismo que ya publicado en otras respuestas.
svenhornberg
1
@IgorKomar, gracias hombre, me salvaste el día! : 3 Usé casi la misma mecánica para verificar que el servidor mysql esté listo antes de que se inicie la aplicación real. ;) Estoy pasando un comando similar aldocker-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

En la versión 3 de un archivo Docker Compose, puede usar RESTART .

Por ejemplo:

docker-compose.yml

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

Tenga en cuenta que utilicé depende_en lugar de enlaces, ya que este último está en desuso en la versión 3.

Aunque funciona, puede que no sea la solución ideal, ya que reinicia el contenedor acoplable en cada falla.

Eche un vistazo a RESTART_POLICY también. le permite ajustar la política de reinicio.

Cuando usa Compose en producción , en realidad es una buena práctica usar la política de reinicio:

Especificar una política de reinicio como reiniciar: siempre para evitar el tiempo de inactividad

Mathieu Gemard
fuente
0

Una de las soluciones alternativas es utilizar una solución de organización de contenedores como Kubernetes. Kubernetes tiene soporte para contenedores init que se ejecutan hasta su finalización antes de que otros contenedores puedan comenzar. Puede encontrar un ejemplo aquí con el contenedor Linux de SQL Server 2017 donde el contenedor API usa el contenedor init para inicializar una base de datos

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

Nilesh Gule
fuente
0

Aquí está el ejemplo donde el maincontenedor espera workercuando comienza a responder 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

Sin embargo, la forma correcta es usar healthcheck(> = 2.1).

kenorb
fuente
0

No se recomienda para implementaciones serias, pero aquí es esencialmente un comando "esperar x segundos".

Con la docker-composeversión se ha agregado3.4 una start_periodinstrucciónhealthcheck . Esto significa que podemos hacer lo siguiente:

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

Lo que sucede aquí es que healthcheckse invoca después de 5 segundos. Esto llama al status.shscript, que siempre devuelve "No hay problema". ¡Acabamos de hacer que el zmq_clientcontenedor espere 5 segundos antes de comenzar!

Nota: es importante que tengas version: "3.4". Si .4no está allí, Docker-compose se queja.

NumesSanguis
fuente
1
Como una ingenua solución de "espera 5s", esta es bastante ingeniosa. Votaría, pero no lo haré porque esto realmente no funciona con configuraciones similares a las de los productos y me temo que alguien miraría el número de votos en lugar de leer con cuidado. Aún así, quería decir "hombre, eso es inteligente";)
Filip Malczak
PD. Para soluciones más complicadas, vea la respuesta del Evereq
Filip Malczak
Eso no es lo que start_periodhace. Esa configuración significa que hay un período de gracia en el que las comprobaciones de estado fallidas no cuentan como reintentos. Si tiene éxito temprano, se considera saludable. Después del período de inicio, una falla contará como un reintento. Ver docs.docker.com/engine/reference/builder/#healthcheck
Capi Etheriel
-4

Solo tengo 2 archivos de composición y comienzo uno primero y el segundo más tarde. Mi guión se ve así:

#!/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
Benjamin Grašič
fuente
Esto no se considera una buena práctica. Entonces no puede entregar la solución que consiste en múltiples contenedores de un archivo de composición.
juergi