Jak mogę poczekać, aż kontener Docker zostanie uruchomiony?

84

Podczas uruchamiania usługi w kontenerze, powiedzmy mongodb, polecenie

docker run -d myimage

wyjdzie natychmiast i zwróci identyfikator kontenera. W moim skrypcie CI uruchamiam klienta w celu przetestowania połączenia mongodb zaraz po uruchomieniu kontenera mongo. Problem polega na tym, że klient nie może się połączyć, ponieważ usługa jeszcze nie działa. Oprócz dodania dużego sleep 10w moim skrypcie, nie widzę żadnej opcji czekania na uruchomienie kontenera.

Docker ma polecenie, waitktóre w tym przypadku nie działa, ponieważ kontener nie istnieje. Czy to ograniczenie dockera?

Gravis
źródło

Odpowiedzi:

50

Jak skomentowano w podobnym wydaniu dla dockera 1.12

HEALTHCHECKobsługa jest łączona w górę, jak na docker / docker # 23218 - można to wziąć pod uwagę, aby określić, kiedy kontener jest zdrowy przed rozpoczęciem następnego w kolejności

Jest to dostępne od wersji docker 1.12rc3 (2016-07-14)

docker-composejest w trakcie obsługi funkcji, aby czekać na określone warunki.

Używa libcompose(więc nie muszę przebudowywać interakcji dockera) i dodaje do tego kilka poleceń konfiguracyjnych. Sprawdź to tutaj: https://github.com/dansteen/controlled-compose

Możesz go użyć w Dockerfile w następujący sposób:

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

Oficjalne dokumenty: https://docs.docker.com/engine/reference/builder/#/healthcheck

VonC
źródło
Spodziewałem się, że zostanie to głosowane najwyżej. Ale potem odkryłem, że otrzymałem odpowiedź bardzo niedawno.
Shiplu Mokaddim,
53

Znalazłem to proste rozwiązanie, szukałem czegoś lepszego, ale bez powodzenia ...

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

lub jeśli chcesz poczekać, aż kontener zgłosi się jako zdrowy (zakładając, że masz kontrolę stanu)

until [ "`/usr/bin/docker inspect -f {{.State.Health.Status}} CONTAINERNAME`"=="healthy" ]; do
    sleep 0.1;
done;
superbohater
źródło
4
jedna wkładka zamiast pętli whilewhile [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done
Mouath,
Uwaga, docker znajduje się w / usr / local / bin / docker na osx. Może warto dodać $ (który docker), aby skrypt był międzyplatformowy?
con--
@ Oczywiście, byłoby to ulepszeniem, chociaż uważam dokumentację „skryptów międzyplatformowych” za zewnętrzną kwestię dotyczącą zakresu tego pytania. Niemniej jednak, jeśli lubisz edytować, zrób to :)
superbohater
tak to działało ze mną: #! / bin / bash aż /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $ do sleep 0.1; Gotowe; echo "mysql jest włączone"
M.Hefny
@ M.Hefny To ten sam przykład, który jest wyrażony w odpowiedzi.
superbohater
32

Jeśli nie chcesz ujawniać portów, jak ma to miejsce, jeśli planujesz połączyć kontener i możesz uruchamiać wiele instancji do testowania, stwierdziłem, że to dobry sposób na zrobienie tego w jednej linii :) Ten przykład to na podstawie oczekiwania na gotowość ElasticSearch:

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

Wymaga to dostępności wget, co jest standardem w Ubuntu. Spróbuje ponownie 5 razy, 3 sekundy między próbami, nawet jeśli połączenie zostanie odrzucone, a także nie pobierze niczego.

David Lemphers
źródło
Myślę, że chcesz użyć --waitretry=3zamiast--wait=3
jpbochi
dla ciekawych, wget strony podręcznika --wait=seconds Wait the specified number of seconds between the retrievals. i --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.
bez celu
25

Jeśli uruchomiona usługa kontenerowa niekoniecznie dobrze reaguje na żądania curl lub wget (co jest całkiem prawdopodobne w przypadku wielu usług), możesz użyć nczamiast tego.

Oto fragment skryptu hosta, który uruchamia kontener Postgres i czeka, aż będzie dostępny, zanim przejdziesz dalej:

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

Edycja - ten przykład nie wymaga WYKRYWANIA testowanego portu, ponieważ uzyskuje on dostęp do przypisanego przez platformę Docker „prywatnego” adresu IP dla kontenera. Jednak działa to tylko wtedy, gdy demon hosta Dockera nasłuchuje na sprzężeniu zwrotnym (127.xxx). Jeśli (na przykład) jesteś na komputerze Mac i używasz maszyny wirtualnej boot2docker, nie będziesz w stanie użyć tej metody, ponieważ nie możesz przekierować do „prywatnych” adresów IP kontenerów z powłoki Maca.

Mark Henwood
źródło
Uważam, że --formatopcja uległa zmianie od czasu tej odpowiedzi; teraz działa docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...](zobacz przykład na docs.docker.com/engine/reference/commandline/inspect ).
Kurt Peek
16

Zakładając, że znasz port + host swojego serwera MongoDB (albo dlatego, że użyłeś a -link, albo dlatego, że je wstrzyknąłeś -e), możesz po prostu użyć curldo sprawdzenia, czy serwer MongoDB działa i akceptuje połączenia.

Poniższy fragment będzie próbował łączyć się co sekundę, aż się powiedzie:

#!/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
źródło
1
Ale musisz powiązać port na hoście :(
Gravis,
Mam podobne problemy. Próba skonfigurowania monit za pomocą pidfiles i niemożność wyzwolenia szczegółowego zdarzenia przy uruchamianiu / zatrzymywaniu Dockera bez ręcznego wstrzykiwania zmiennych jest uciążliwa, oznacza to, że nie mogę tak łatwo pisać ogólnych opakowań.
Alex Lynham
Możesz iść z IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)aby uzyskać adres IP mysql pojemnika (gdzie „mysql” to nazwa lub pojemnik id) i zastąpić URL z: http://$IP:3306. pracuje dla mnie!
Danyel
12

Skończyło się na czymś takim:

#!/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
źródło
10

Rzucam własne rozwiązanie:

Używam sieci dockerowych, więc sztuczka z netcatem Marka nie zadziałała (brak dostępu z sieci hosta), a pomysł Erika nie działa dla kontenera postgres (kontener jest oznaczony jako uruchomiony, mimo że postgres jeszcze nie jest dostępne do połączenia). Po prostu próbuję połączyć się z postgresem za pośrednictwem kontenera efemerycznego w pętli:

#!/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
źródło
To właśnie robimy dzisiaj. Uważaj na obraz postgres, ponieważ serwer uruchamia się raz, przed ponownym uruchomieniem ...
Gravis
Działa to dobrze, z kilkoma drobnymi modyfikacjami. 1. postgreshost nie zostaje rozwiązany, więc używam docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgreszamiast tego. 2. psqlpotrzebuje hasła, pg_isreadylepiej pasuje. 3. pg_isreadyNie ma też potrzeby w -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

Testuję w ten sposób, czy port nasłuchuje, czy nie. W tym przypadku mam test uruchomiony z wnętrza kontenera, ale jest też możliwe z zewnątrz, czy mongodb jest gotowy czy nie.

$ docker run -p 37017:27017 -d myimage

I sprawdź, czy port 37017 nasłuchuje z kontenera hosta.

banyan
źródło
2

Musiałem sobie z tym poradzić i wpadłem na pewien pomysł. Robiąc badania związane z tym zadaniem, dostałem tutaj, więc pomyślałem, że podzielę się moim rozwiązaniem z przyszłymi odwiedzającymi ten post.

Rozwiązanie oparte na Docker Compose

Jeśli używasz docker-compose, możesz sprawdzić mój POC synchronizacji docker . Część pomysłów połączyłem w innych pytaniach (dzięki za to - zaopracowano).

Podstawową ideą jest to, że każdy kontener w kompozycie udostępnia usługę diagnostyczną. Wywołanie tej usługi sprawdza, czy wymagany zestaw portów jest otwarty w kontenerze i zwraca ogólny stan kontenera (WARMUP / RUNNING zgodnie z POC). Każdy kontener ma również narzędzie do sprawdzania po uruchomieniu, czy usługi zależne działają. Dopiero wtedy kontener się uruchamia.

W przykładowym środowisku docker-compose są dwie usługi serwer1 i serwer2 oraz usługa klienta , która czeka na uruchomienie obu serwerów, a następnie wysyła żądanie do obu z nich i kończy pracę.

Wyciąg z 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

Czekam na wiele kontenerów:

trap 'kill $(jobs -p)' EXIT

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

Podstawowa implementacja usługi diagnostycznej ( 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";

Podłączanie usługi diagnostycznej do portu:

nc -v -lk -p 7070 -e /assets/checkports.sh
jannis
źródło
Ciekawe podejście. +1
VonC,
1

Można użyć „ czekaj na to ”, czystego skryptu bash, który będzie czekał na dostępność hosta i portu TCP. Jest to przydatne do synchronizowania działania współzależnych usług, takich jak połączone kontenery docker. czysty skrypt bash, nie ma żadnych zewnętrznych zależności ”.

Należy jednak spróbować zaprojektować swoje usługi, aby uniknąć tego rodzaju współzależności między usługami. Czy Twoja usługa może spróbować ponownie połączyć się z bazą danych? Czy możesz pozwolić swojemu kontenerowi umrzeć, jeśli nie może połączyć się z bazą danych i pozwolić, aby koordynator kontenerów (np. Docker Swarm) zrobił to za Ciebie?

Guido
źródło
1

Aby sprawdzić, czy kontener Docker PostgreSQL lub MySQL (obecnie) działa i działa (szczególnie w przypadku narzędzi do migracji, takich jak Flyway), możesz użyć pliku binarnego czekającego na : https://github.com/ArcanjoQueiroz/wait-for .

Alexandre Arcanjo de Queiroz
źródło
0

Rozwiązanie Docker-Compose

Po docker-compose nie znam nazwy kontenera docker, więc używam

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

i sprawdzając truejak tutaj https://stackoverflow.com/a/33520390/7438079

DarkAiR
źródło
0

W przypadku instancji dokera mongoDB zrobiliśmy to i działa jak marzenie:

#!/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
źródło