Wie kann ich warten, bis ein Docker-Container betriebsbereit ist?

83

Wenn Sie einen Dienst in einem Container ausführen, sagen wir mongodb, den Befehl

docker run -d myimage

wird sofort beendet und gibt die Container-ID zurück. In meinem CI-Skript führe ich einen Client aus, um die Mongodb-Verbindung direkt nach dem Ausführen des Mongo-Containers zu testen. Das Problem ist: Der Client kann keine Verbindung herstellen, da der Dienst noch nicht aktiv ist. Abgesehen davon sleep 10, dass ich in meinem Skript ein großes Element hinzufüge , sehe ich keine Möglichkeit, darauf zu warten, dass ein Container betriebsbereit ist.

Docker hat einen Befehl, waitder in diesem Fall nicht funktioniert, da der Container nicht vorhanden ist. Ist es eine Einschränkung von Docker?

Gravis
quelle

Antworten:

49

Wie in einer ähnlichen Ausgabe für Docker 1.12 kommentiert

HEALTHCHECKDie Unterstützung wird gemäß Docker / Docker # 23218 vorgelagert zusammengeführt. Dies kann berücksichtigt werden, um festzustellen, wann ein Container fehlerfrei ist, bevor der nächste in der Reihenfolge gestartet wird

Dies ist seit Docker 1.12rc3 (14.07.2016) verfügbar.

docker-composeist dabei , mit einer Funktionalität von Stütz für spezifische Bedingungen zu warten.

Es verwendet libcompose(damit ich die Docker-Interaktion nicht neu erstellen muss) und fügt hierfür eine Reihe von Konfigurationsbefehlen hinzu. Überprüfen Sie es hier: https://github.com/dansteen/controlled-compose

Sie können es in Dockerfile wie folgt verwenden:

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

Offizielle Dokumente: https://docs.docker.com/engine/reference/builder/#/healthcheck

VonC
quelle
Ich hatte erwartet, dass dies am höchsten gewählt wird. Aber dann entdeckte ich, dass es vor kurzem beantwortet wurde.
Shiplu Mokaddim
52

Fand diese einfache Lösung, suchte nach etwas Besserem, aber kein Glück ...

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

oder wenn Sie warten möchten, bis der Container als fehlerfrei gemeldet wird (vorausgesetzt, Sie haben einen Healthcheck)

until [ "`/usr/bin/docker inspect -f {{.State.Health.Status}} CONTAINERNAME`"=="healthy" ]; do
    sleep 0.1;
done;
Superheld
quelle
4
ein Liner mit while-Schleife stattdessenwhile [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done
Mouath
Nur eine Anmerkung, Docker ist in / usr / local / bin / docker auf osx. Könnte es sich lohnen, $ (welcher Docker) hinzuzufügen, um das Skript plattformübergreifend zu gestalten?
con--
@ sicher, das wäre eine Verbesserung, obwohl ich die Dokumentation von "plattformübergreifenden Skripten" als externes Anliegen für den Umfang der Frage betrachte. Trotzdem, wenn Sie eine Bearbeitung vornehmen möchten, machen Sie es :)
Superheld
So hat es bei mir funktioniert: #! / bin / bash bis /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $ do sleep 0.1; getan; echo "mysql is on"
M.Hefny
@ M.Hefny Das ist das gleiche Beispiel, das in der Antwort ausgedrückt wird.
Superheld
32

Wenn Sie die Ports nicht verfügbar machen möchten, wie dies der Fall ist, wenn Sie den Container verknüpfen möchten und möglicherweise mehrere Instanzen zum Testen ausführen, war dies eine gute Möglichkeit, dies in einer Zeile zu tun :) Dieses Beispiel ist basierend auf dem Warten auf die Bereitschaft von ElasticSearch:

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

Dies setzt voraus, dass wget verfügbar ist, was unter Ubuntu Standard ist. Es wird 5 Mal, 3 Sekunden zwischen den Versuchen, wiederholt, auch wenn die Verbindung abgelehnt wird, und es wird auch nichts heruntergeladen.

David Lemphers
quelle
Ich denke, Sie möchten --waitretry=3anstelle von--wait=3
jpbochi
für die neugierige, wget man page --wait=seconds Wait the specified number of seconds between the retrievals. und --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.
ziellos
25

Wenn der von Ihnen gestartete Containerdienst nicht unbedingt gut auf Curl- oder Wget-Anforderungen reagiert (was für viele Dienste sehr wahrscheinlich ist), können Sie ihn ncstattdessen verwenden.

Hier ist ein Ausschnitt aus einem Host-Skript, das einen Postgres-Container startet und darauf wartet, dass er verfügbar ist, bevor Sie fortfahren:

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

Bearbeiten - In diesem Beispiel muss der zu testende Port nicht verfügbar gemacht werden, da auf die vom Docker zugewiesene "private" IP-Adresse für den Container zugegriffen wird. Dies funktioniert jedoch nur, wenn der Docker-Host-Daemon den Loopback (127.xxx) abhört. Wenn Sie (zum Beispiel) auf einem Mac arbeiten und die boot2docker-VM ausführen, können Sie diese Methode nicht verwenden, da Sie von Ihrer Mac-Shell aus nicht zu den "privaten" IP-Adressen der Container weiterleiten können.

Mark Henwood
quelle
Ich glaube, dass sich die --formatOption seit dieser Antwort geändert hat. Was jetzt funktioniert, ist docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...](siehe das Beispiel unter docs.docker.com/engine/reference/commandline/inspect ).
Kurt Peek
16

Angenommen, Sie kennen den Host + -Port Ihres MongoDB-Servers (entweder weil Sie einen verwendet -linkhaben oder weil Sie ihn injiziert haben -e), können Sie nur curlüberprüfen, ob der MongoDB-Server ausgeführt wird und Verbindungen akzeptiert.

Das folgende Snippet versucht jede Sekunde, eine Verbindung herzustellen, bis es erfolgreich ist:

#!/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
quelle
1
Aber Sie müssen den Port auf dem Host binden :(
Gravis
Ich habe ähnliche Probleme. Der Versuch, die Überwachung mithilfe von PID-Dateien einzurichten und nicht in der Lage zu sein, ein granulares Ereignis beim Start / Stopp von Docker auszulösen, ohne vars manuell zu injizieren, ist ein Problem. Das bedeutet, dass ich nicht einfach so einfach generische Wrapper schreiben kann.
Alex Lynham
Sie können IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)die IP-Adresse Ihres MySQL-Containers abrufen (wobei "MySQL" der Name oder die Container-ID ist) und die URL durch Folgendes ersetzen : http://$IP:3306. funktioniert bei mir!
Danyel
12

Ich habe so etwas wie:

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

Ich werfe meine eigene Lösung da draußen:

Ich verwende Docker-Netzwerke, daher hat Marks Netcat-Trick bei mir nicht funktioniert (kein Zugriff vom Host-Netzwerk), und Eriks Idee funktioniert bei einem Postgres-Container nicht (der Container wird als ausgeführt markiert, obwohl Postgres noch nicht vorhanden ist verfügbar, um eine Verbindung herzustellen). Ich versuche also nur, über einen kurzlebigen Container in einer Schleife eine Verbindung zu Postgres herzustellen:

#!/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
quelle
Das machen wir heute. Seien Sie vorsichtig mit dem Postgres-Image, da der Server einmal gestartet wird, bevor Sie neu starten ...
Gravis
Dies funktioniert gut mit ein paar geringfügigen Änderungen. 1. postgresHost wird nicht aufgelöst, also benutze ich docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgresstattdessen. 2. psqlbraucht ein Passwort, pg_isreadypasst besser. 3. Mit pg_isreadygibt es auch keine Notwendigkeit in -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

Ich teste so, ob der Port lauscht oder nicht. In diesem Fall habe ich einen Testlauf innerhalb des Containers, aber es ist auch von außen möglich, ob Mongodb bereit ist oder nicht.

$ docker run -p 37017:27017 -d myimage

Überprüfen Sie, ob der Port 37017 vom Host-Container abhört oder nicht.

Banyan
quelle
2

Ich musste das frühzeitig angehen und kam auf eine Idee. Als ich für diese Aufgabe recherchierte, kam ich hierher und dachte, ich würde meine Lösung mit zukünftigen Besuchern dieses Beitrags teilen.

Docker-Compose-basierte Lösung

Wenn Sie Docker-Compose verwenden, können Sie meinen Docker-Synchronisations-POC überprüfen . Ich habe einige der Ideen in anderen Fragen kombiniert (danke dafür - positiv bewertet).

Die Grundidee ist, dass jeder Container im Verbund einen Diagnosedienst bereitstellt. Durch Aufrufen dieses Dienstes wird überprüft, ob der erforderliche Satz von Ports im Container geöffnet ist, und der Gesamtstatus des Containers zurückgegeben (WARMUP / RUNNING gemäß POC). Jeder Container verfügt außerdem über ein Dienstprogramm, mit dem beim Start überprüft werden kann, ob die abhängigen Dienste ausgeführt werden. Erst dann startet der Container.

In der Beispiel-Docker-Compose-Umgebung gibt es zwei Dienste, Server1 und Server2, und der Client- Dienst, der auf den Start beider Server wartet und dann eine Anforderung an beide sendet und beendet.

Auszug aus dem 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

Warten auf mehrere Container:

trap 'kill $(jobs -p)' EXIT

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

Grundlegende Implementierung von Diagnostic srervice ( 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";

Verdrahtung des Diagnosedienstes mit einem Port:

nc -v -lk -p 7070 -e /assets/checkports.sh
jannis
quelle
Interessanter Ansatz. +1
VonC
1

Sie können wait-for-it verwenden , ein reines Bash-Skript, das auf die Verfügbarkeit eines Hosts und eines TCP-Ports wartet. Es ist nützlich, um das Hochfahren von voneinander abhängigen Diensten wie verknüpften Docker-Containern zu synchronisieren reines Bash-Skript, es hat keine externen Abhängigkeiten ".

Sie sollten jedoch versuchen, Ihre Dienste so zu gestalten, dass diese Art von Abhängigkeiten zwischen Diensten vermieden werden. Kann Ihr Dienst versuchen, die Verbindung zur Datenbank wiederherzustellen? Können Sie Ihren Container einfach sterben lassen, wenn er keine Verbindung zur Datenbank herstellen kann, und einen Container-Orchestrator (z. B. Docker Swarm) dies für Sie tun lassen?

Guido
quelle
1

Um zu überprüfen, ob ein PostgreSQL- oder MySQL- Docker-Container (derzeit) aktiv ist (speziell für Migrationstools wie Flyway), können Sie die Warte- Binärdatei verwenden: https://github.com/ArcanjoQueiroz/wait-for .

Alexandre Arcanjo de Queiroz
quelle
0

Docker-Compose-Lösung

Nach dem Docker-Compose kenne ich den Namen des Docker-Containers nicht, also benutze ich

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

und überprüfen Sie truewie hier https://stackoverflow.com/a/33520390/7438079

DarkAiR
quelle
0

Für die mongoDB Docker-Instanz haben wir dies getan und arbeiten wie ein Zauber:

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