¿Cómo puedo esperar a que un contenedor Docker esté en funcionamiento?

84

Al ejecutar un servicio dentro de un contenedor, digamos mongodb, el comando

docker run -d myimage

saldrá instantáneamente y devolverá la identificación del contenedor. En mi script de CI, ejecuto un cliente para probar la conexión mongodb, justo después de ejecutar el contenedor mongo. El problema es: el cliente no puede conectarse porque el servicio aún no está activo. Aparte de agregar un gran sleep 10en mi script, no veo ninguna opción para esperar a que un contenedor esté en funcionamiento.

Docker tiene un comando waitque no funciona en ese caso, porque el contenedor no existe. ¿Es una limitación de Docker?

Gravis
fuente

Respuestas:

50

Como se comentó en un problema similar para Docker 1.12

HEALTHCHECKel soporte se fusiona en sentido ascendente según la ventana acoplable / ventana acoplable # 23218 ; esto se puede considerar para determinar cuándo un contenedor está en buen estado antes de comenzar el siguiente en el orden

Esto está disponible desde docker 1.12rc3 (2016-07-14)

docker-composeestá en proceso de soportar una funcionalidad para esperar condiciones específicas.

Utiliza libcompose(por lo que no tengo que reconstruir la interacción de la ventana acoplable) y agrega un montón de comandos de configuración para esto. Compruébalo aquí: https://github.com/dansteen/controlled-compose

Puedes usarlo en Dockerfile así:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

Documentos oficiales: https://docs.docker.com/engine/reference/builder/#/healthcheck

VonC
fuente
Esperaba que esto fuera el más votado. Pero luego descubrí que fue respondida muy recientemente.
Shiplu Mokaddim
53

Encontré esta solución simple, he estado buscando algo mejor pero sin suerte ...

until [ "`/usr/bin/docker inspect -f {{.State.Running}} CONTAINERNAME`"=="true" ]; do
    sleep 0.1;
done;

o si desea esperar hasta que el contenedor se informe como saludable (suponiendo que tenga una verificación de estado)

until [ "`/usr/bin/docker inspect -f {{.State.Health.Status}} CONTAINERNAME`"=="healthy" ]; do
    sleep 0.1;
done;
superhéroe
fuente
4
un trazador de líneas usando el bucle while en su lugarwhile [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done
Mouath
Solo una nota, Docker está en / usr / local / bin / docker en osx. ¿Podría valer la pena agregar $ (qué ventana acoplable) para hacer que el script sea multiplataforma?
con--
@ seguro, eso sería una mejora, aunque considero la documentación de "scripts multiplataforma" como una preocupación externa al alcance de la pregunta. Sin embargo, si te gusta hacer una edición, hazlo :)
superhéroe
así es como funcionó conmigo: #! / bin / bash hasta /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $ do sleep 0.1; hecho; echo "mysql está
activado
@ M.Hefny Ese es el mismo ejemplo que se expresa en la respuesta.
superhéroe
32

Si no desea exponer los puertos, como es el caso si planea vincular el contenedor y podría estar ejecutando varias instancias para realizar pruebas, descubrí que esta era una buena manera de hacerlo en una línea :) Este ejemplo es basado en esperar a que ElasticSearch esté listo:

docker inspect --format '{{ .NetworkSettings.IPAddress }}:9200' elasticsearch | xargs wget --retry-connrefused --tries=5 -q --wait=3 --spider

Esto requiere que wget esté disponible, que es estándar en Ubuntu. Reintentará 5 veces, 3 segundos entre intentos, incluso si se rechaza la conexión, y tampoco descarga nada.

David Lemphers
fuente
Creo que quieres usar en --waitretry=3lugar de--wait=3
jpbochi
para la página de manual de wget curiosa --wait=seconds Wait the specified number of seconds between the retrievals. y --waitretry=seconds If you don't want Wget to wait between every retrieval, but only between retries of failed downloads, you can use this option. Wget will use linear backoff, waiting 1 second after the first failure on a given file, then waiting 2 seconds after the second failure on that file, up to the maximum number of seconds you specify.
sin rumbo
25

Si el servicio en contenedor que inició no necesariamente responde bien a las solicitudes curl o wget (lo cual es muy probable para muchos servicios), entonces puede usarlo nc.

Aquí hay un fragmento de un script de host que inicia un contenedor de Postgres y espera a que esté disponible antes de continuar:

POSTGRES_CONTAINER=`docker run -d --name postgres postgres:9.3`
# Wait for the postgres port to be available
until nc -z $(sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $POSTGRES_CONTAINER) 5432
do
    echo "waiting for postgres container..."
    sleep 0.5
done

Editar : este ejemplo no requiere que EXPONGA el puerto que está probando, ya que accede a la dirección IP 'privada' asignada por Docker para el contenedor. Sin embargo, esto solo funciona si el daemon del host de la ventana acoplable está escuchando en el loopback (127.xxx). Si (por ejemplo) está en una Mac y está ejecutando la máquina virtual boot2docker, no podrá utilizar este método ya que no puede enrutar a las direcciones IP 'privadas' de los contenedores desde su shell de Mac.

Mark Henwood
fuente
Creo que la --formatopción ha cambiado desde esta respuesta; lo que funciona ahora es docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...](ver el ejemplo en docs.docker.com/engine/reference/commandline/inspect ).
Kurt Peek
16

Suponiendo que conoce el puerto host + de su servidor MongoDB (ya sea porque usó un -linko porque los inyectó -e), puede usarlo curlpara verificar si el servidor MongoDB se está ejecutando y acepta conexiones.

El siguiente fragmento intentará conectarse cada segundo, hasta que tenga éxito:

#!/bin/sh
while ! curl http://$DB_PORT_27017_TCP_ADDR:$DB_PORT_27017_TCP_PORT/
do
  echo "$(date) - still trying"
  sleep 1
done
echo "$(date) - connected successfully"
jpetazzo
fuente
1
Pero debe vincular el puerto en el host :(
Gravis
Tengo problemas similares. Tratar de configurar monit usando pidfiles y no poder activar un evento granular en Docker start / stop sin inyectar vars manualmente es un dolor, significa que no puedo escribir envoltorios genéricos tan fácilmente.
Alex Lynham
Usted puede ir con IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)para obtener la dirección IP de su contenedor de MySQL (donde "mysql" es el nombre o contenedor id) y vuelva a colocar la url con: http://$IP:3306. ¡funciona para mi!
Danyel
12

Terminé con algo como:

#!/bin/bash

attempt=0
while [ $attempt -le 59 ]; do
    attempt=$(( $attempt + 1 ))
    echo "Waiting for server to be up (attempt: $attempt)..."
    result=$(docker logs mongo)
    if grep -q 'waiting for connections on port 27017' <<< $result ; then
      echo "Mongodb is up!"
      break
    fi
    sleep 2
done
Gravis
fuente
10

Lanzando mi propia solución por ahí:

Estoy usando redes docker, por lo que el truco de netcat de Mark no funcionó para mí (no hay acceso desde la red de host), y la idea de Erik no funciona para un contenedor de postgres (el contenedor está marcado como en ejecución aunque postgres aún no disponible para conectarse). Así que solo intento conectarme a postgres a través de un contenedor efímero en un bucle:

#!/bin/bash

docker network create my-network
docker run -d \
    --name postgres \
    --net my-network \
    -e POSTGRES_USER=myuser \
    postgres

# wait for the database to come up
until docker run --rm --net my-network postgres psql -h postgres -U myuser; do
    echo "Waiting for postgres container..."
    sleep 0.5
done

# do stuff with the database...
Ell Neal
fuente
Eso es lo que estamos haciendo hoy. Tenga cuidado con la imagen de postgres, ya que el servidor se inicia una vez, antes de reiniciar ...
Gravis
Esto funciona bien, con algunas modificaciones menores. 1. el postgreshost no se resuelve, así que utilizo docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgresen su lugar. 2. psqlnecesita una contraseña, pg_isreadyse adapta mejor. 3. Con pg_isreadytampoco hay necesidad de entrar -U myuser.
Alec Mev
2

test/test_runner

#!/usr/bin/env ruby

$stdout.sync = true

def wait_ready(port)
  until (`netstat -ant | grep #{port}`; $?.success?) do
    sleep 1
    print '.'
  end
end

print 'Running supervisord'
system '/usr/bin/supervisord'

wait_ready(3000)

puts "It's ready :)"

$ docker run -v /tmp/mnt:/mnt myimage ruby mnt/test/test_runner

Estoy probando así si el puerto está escuchando o no. En este caso, tengo una prueba que se ejecuta desde el interior del contenedor, pero también es posible desde el exterior si mongodb está listo o no.

$ docker run -p 37017:27017 -d myimage

Y verifique si el puerto 37017 está escuchando o no desde el contenedor del host.

secretario
fuente
2

Tuve que abordar esto recientemente y se me ocurrió una idea. Al hacer una investigación para esta tarea, llegué aquí, así que pensé en compartir mi solución con los futuros visitantes de esta publicación.

Solución basada en Docker compose

Si está utilizando docker-compose, puede consultar mi POC de sincronización de docker . Combiné algunas de las ideas en otras preguntas (gracias por eso, voté a favor).

La idea básica es que cada contenedor del compuesto expone un servicio de diagnóstico. Llamar a este servicio verifica si el conjunto de puertos requerido está abierto en el contenedor y devuelve el estado general del contenedor (WARMUP / RUNNING según el POC). Cada contenedor también tiene una utilidad para verificar al inicio si los servicios dependientes están en funcionamiento. Solo entonces se pone en marcha el contenedor.

En el entorno docker-compose de ejemplo, hay dos servicios server1 y server2 y el servicio de cliente que espera que ambos servidores se inicien y luego envía una solicitud a ambos y sale.

Extracto del POC

wait_for_server.sh

#!/bin/bash

server_host=$1
sleep_seconds=5

while true; do
    echo -n "Checking $server_host status... "

    output=$(echo "" | nc $server_host 7070)

    if [ "$output" == "RUNNING" ]
    then
        echo "$server_host is running and ready to process requests."
        break
    fi

    echo "$server_host is warming up. Trying again in $sleep_seconds seconds..."
    sleep $sleep_seconds
done

Esperando varios contenedores:

trap 'kill $(jobs -p)' EXIT

for server in $DEPENDS_ON
do
    /assets/wait_for_server.sh $server &
    wait $!
done

Implementación básica del servicio de diagnóstico ( checkports.sh ):

#!/bin/bash

for port in $SERVER_PORT; do
    nc -z localhost $port;

    rc=$?

    if [[ $rc != 0 ]]; then
        echo "WARMUP";
        exit;
    fi
done

echo "RUNNING";

Cableado del servicio de diagnóstico a un puerto:

nc -v -lk -p 7070 -e /assets/checkports.sh
Jannis
fuente
Enfoque interesante. +1
VonC
1

Puede usar wait-for-it ", un script bash puro que esperará la disponibilidad de un host y un puerto TCP. Es útil para sincronizar la puesta en marcha de servicios interdependientes, como contenedores acoplados de Docker. Dado que es un script bash puro, no tiene dependencias externas ".

Sin embargo, debe intentar diseñar sus servicios para evitar este tipo de interdependencias entre servicios. ¿Puede su servicio intentar volver a conectarse a la base de datos? ¿Puede dejar que su contenedor muera si no puede conectarse a la base de datos y dejar que un orquestador de contenedores (por ejemplo, Docker Swarm) lo haga por usted?

Guido
fuente
1

Para verificar si un contenedor Docker de PostgreSQL o MySQL (actualmente) está en funcionamiento (especialmente para herramientas de migración como Flyway), puede usar el binario de espera : https://github.com/ArcanjoQueiroz/wait-for .

Alexandre Arcanjo de Queiroz
fuente
0

Solución Docker-compose

Después de docker-compose, no sé el nombre del contenedor de la ventana acoplable, así que uso

docker inspect -f {{.State.Running}} $(docker-compose ps -q <CONTAINER_NAME>)

y comprobando truecomo aquí https://stackoverflow.com/a/33520390/7438079

DarkAiR
fuente
0

Para la instancia de la ventana acoplable mongoDB, hicimos esto y funciona como un encanto:

#!/usr/bin/env bash

until docker exec -i ${MONGO_IMAGE_NAME} mongo -u ${MONGO_INITDB_ROOT_USERNAME} -p ${MONGO_INITDB_ROOT_PASSWORD}<<EOF
exit
EOF
do
    echo "Waiting for Mongo to start..."
    sleep 0.5
done
Alex Arvanitidis
fuente