Come posso aspettare che un container Docker sia attivo e funzionante?


85

Quando si esegue un servizio all'interno di un contenitore, diciamo mongodb, il comando

docker run -d myimage

uscirà immediatamente e restituirà l'id del contenitore. Nel mio script CI, eseguo un client per testare la connessione mongodb, subito dopo aver eseguito il contenitore mongo. Il problema è: il client non può connettersi perché il servizio non è ancora attivo. Oltre ad aggiungere un grande sleep 10nel mio script, non vedo alcuna opzione per aspettare che un container sia attivo e funzionante.

Docker ha un comando waitche non funziona in quel caso, perché il contenitore non esiste. È una limitazione di docker?


Risposte:


51

Come commentato in un problema simile per la finestra mobile 1.12

HEALTHCHECKil supporto è unito a monte come da docker / docker # 23218 - questo può essere considerato per determinare quando un container è integro prima di avviare il successivo nell'ordine

Questo è disponibile dalla finestra mobile 1.12rc3 (14-07-2016)

docker-composeè in procinto di supportare una funzionalità in attesa di condizioni specifiche.

Usa libcompose(quindi non devo ricostruire l'interazione docker) e aggiunge un gruppo di comandi di configurazione per questo. Dai un'occhiata qui: https://github.com/dansteen/controlled-compose

Puoi usarlo in Dockerfile in questo modo:

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

Documenti ufficiali: https://docs.docker.com/engine/reference/builder/#/healthcheck


Mi aspettavo che questo fosse il voto più alto. Ma poi ho scoperto che aveva ricevuto risposta molto di recente.
Shiplu Mokaddim

55

Ho trovato questa semplice soluzione, stavo cercando qualcosa di meglio ma senza fortuna ...

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

o se vuoi aspettare fino a quando il contenitore non viene segnalato come integro (supponendo che tu abbia un controllo di integrità)

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

4
una linea usando invece il ciclo whilewhile [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done
Mouath

Solo una nota, docker si trova in / usr / local / bin / docker su osx. Potrebbe valere la pena aggiungere $ (quale finestra mobile) per rendere lo script multipiattaforma?
con--

@ con-- certo, questo sarebbe un miglioramento, anche se considero la documentazione di "script multipiattaforma" una preoccupazione esterna all'ambito della domanda. Nondimeno, se ti piace fare una modifica, fallo :)
supereroe

1
questo è come ha funzionato con me: #! / bin / bash until /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $ do sleep 0.1; fatto; echo "mysql è
attivo

@ M.Hefny Questo è lo stesso esempio espresso nella risposta.
supereroe

32

Se non vuoi esporre le porte, come nel caso in cui prevedi di collegare il contenitore e potresti eseguire più istanze per il test, ho scoperto che questo era un buon modo per farlo in una riga :) Questo esempio è basato sull'attesa che ElasticSearch sia pronto:

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

Ciò richiede che wget sia disponibile, che è standard su Ubuntu. Riproverà 5 volte, 3 secondi tra i tentativi, anche se la connessione viene rifiutata e inoltre non scarica nulla.


Penso che tu voglia usare al --waitretry=3posto di--wait=3
jpbochi

per la curiosa pagina man di wget --wait=seconds Wait the specified number of seconds between the retrievals. e --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.
senza meta

25

Se il servizio containerizzato che hai avviato non risponde necessariamente bene alle richieste di curl o wget (cosa abbastanza probabile per molti servizi), puoi ncinvece usarlo .

Ecco uno snippet da uno script host che avvia un contenitore Postgres e attende che sia disponibile prima di continuare:

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

Modifica : questo esempio non richiede di ESPORRE la porta che si sta testando, poiché accede all'indirizzo IP "privato" assegnato da Docker per il contenitore. Tuttavia questo funziona solo se il demone dell'host docker è in ascolto sul loopback (127.xxx). Se (ad esempio) sei su un Mac e stai eseguendo la VM boot2docker, non sarai in grado di utilizzare questo metodo poiché non puoi instradare agli indirizzi IP "privati" dei contenitori dalla shell del tuo Mac.


Credo che l' --formatopzione sia cambiata dopo questa risposta; ciò che funziona ora è docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...](vedere l'esempio su docs.docker.com/engine/reference/commandline/inspect ).
Kurt Peek

16

Supponendo che tu conosca l'host + la porta del tuo server MongoDB (o perché hai usato a -link, o perché li hai iniettati con -e), puoi semplicemente usare curlper verificare se il server MongoDB è in esecuzione e accetta le connessioni.

Il seguente frammento proverà a connettersi ogni secondo, finché non avrà successo:

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

1
Ma è necessario associare la porta all'host :(
Gravis

Ho problemi simili. Cercare di configurare monit utilizzando pidfile e non essere in grado di attivare un evento granulare su Docker start / stop senza iniettare manualmente vars è un problema, significa che non posso semplicemente scrivere wrapper generici con la stessa facilità.
Alex Lynham

Si può andare con IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)per ottenere l'indirizzo IP del vostro contenitore mysql (dove "mysql" è il nome o il contenitore id) e sostituire l'url con: http://$IP:3306. per me va bene!
Danyel

12

Ho finito con qualcosa del tipo:

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

10

Lanciare la mia soluzione là fuori:

Sto usando le reti docker quindi il trucco netcat di Mark non ha funzionato per me (nessun accesso dalla rete host) e l'idea di Erik non funziona per un contenitore postgres (il contenitore è contrassegnato come in esecuzione anche se postgres non lo è ancora disponibile per la connessione). Quindi sto solo tentando di connettermi a postgres tramite un contenitore temporaneo in un ciclo:

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

È quello che stiamo facendo oggi. Fai attenzione all'immagine postgres, poiché il server si avvia una volta, prima di riavviare ...
Gravis

Funziona bene, con alcune piccole modifiche. 1. postgreshost non viene risolto, quindi uso docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgresinvece. 2. psqlnecessita di una password, pg_isreadysi adatta meglio. 3. Con pg_isreadynon c'è nemmeno bisogno di -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

Sto testando in questo modo se la porta è in ascolto o meno. In questo caso ho il test in esecuzione dall'interno del container, ma è anche possibile dall'esterno se mongodb è pronto o meno.

$ docker run -p 37017:27017 -d myimage

E controlla se la porta 37017 è in ascolto o meno dal contenitore host.


2

Ho dovuto affrontarlo di recente e mi è venuta un'idea. Durante la ricerca per questo compito sono arrivato qui, quindi ho pensato di condividere la mia soluzione con i futuri visitatori di questo post.

Soluzione basata su Docker-compose

Se stai usando docker-compose puoi controllare il mio POC di sincronizzazione docker . Ho combinato alcune delle idee in altre domande (grazie per questo - voto positivo).

L'idea di base è che ogni contenitore nel composito esponga un servizio diagnostico. La chiamata a questo servizio controlla se il set di porte richiesto è aperto nel container e restituisce lo stato generale del container (WARMUP / RUNNING secondo il POC). Ogni contenitore dispone inoltre di un'utilità per verificare all'avvio se i servizi dipendenti sono attivi e in esecuzione. Solo allora il contenitore si avvia.

Nell'ambiente docker-compose di esempio sono presenti due servizi server1 e server2 e il servizio client che attende l'avvio di entrambi i server, quindi invia una richiesta a entrambi ed esce.

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

In attesa di più contenitori:

trap 'kill $(jobs -p)' EXIT

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

Implementazione di base del servizio diagnostico ( 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";

Collegamento del servizio di diagnostica a una porta:

nc -v -lk -p 7070 -e /assets/checkports.sh

Approccio interessante. +1
VonC

1

È possibile utilizzare wait-for-it ", uno script bash puro che attenderà la disponibilità di una porta host e TCP. È utile per sincronizzare lo spin-up di servizi interdipendenti, come i contenitori docker collegati. Poiché è un puro script bash, non ha dipendenze esterne ".

Tuttavia, dovresti provare a progettare i tuoi servizi per evitare questo tipo di interdipendenze tra i servizi. Il tuo servizio può provare a riconnettersi al database? Puoi lasciare che il tuo container muoia se non riesce a connettersi al database e lasciare che un orchestratore di container (ad esempio Docker Swarm) lo faccia per te?




0

Per l'istanza docker di mongoDB abbiamo fatto questo e funziona come un fascino:

#!/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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.