Как я могу дождаться запуска контейнера докеров?

85

При запуске службы внутри контейнера, скажем, mongodb, команда

docker run -d myimage

мгновенно выйдет и вернет идентификатор контейнера. В моем сценарии CI я запускаю клиент для проверки соединения mongodb сразу после запуска контейнера mongo. Проблема в том, что клиент не может подключиться, потому что служба еще не запущена. sleep 10Я не вижу возможности ждать, пока контейнер заработает, кроме добавления большого значения в свой скрипт.

В Docker есть команда, waitкоторая в этом случае не работает, потому что контейнера не существует. Это ограничение докера?

Gravis
источник

Ответы:

51

Как прокомментировано в аналогичной проблеме для докера 1.12

HEALTHCHECKподдержка объединена с восходящим потоком согласно docker / docker # 23218 - это можно рассматривать для определения, когда контейнер исправен до запуска следующего в порядке

Это доступно с docker 1.12rc3 (2016-07-14)

docker-composeнаходится в процессе поддержки функции ожидания определенных условий.

Он использует libcompose(так что мне не нужно перестраивать взаимодействие докеров) и добавляет для этого кучу команд конфигурации. Посмотрите здесь: https://github.com/dansteen/controlled-compose

Вы можете использовать его в Dockerfile следующим образом:

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

Официальные документы: https://docs.docker.com/engine/reference/builder/#/healthcheck

VonC
источник
Я ожидал, что за это проголосуют больше всех. Но потом я обнаружил, что ответ был дан совсем недавно.
Шиплу Мокаддим
55

Нашел это простое решение, искал что-то получше, но не повезло ...

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

или если вы хотите подождать, пока контейнер не сообщит о работоспособности (при условии, что у вас есть проверка работоспособности)

until [ "`/usr/bin/docker inspect -f {{.State.Health.Status}} CONTAINERNAME`"=="healthy" ]; do
    sleep 0.1;
done;
супергерой
источник
4
один лайнер вместо этого использует цикл whilewhile [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done
Mouath
Просто обратите внимание, докер находится в / usr / local / bin / docker на osx. Может быть, стоит добавить $ (какой докер), чтобы скрипт стал кроссплатформенным?
con--
@ уверен, что это было бы улучшением, хотя я рассматриваю документацию по «кроссплатформенным скриптам» как внешнюю заботу по отношению к области вопроса. Тем не менее, если вам нравится вносить правки,
superhero
1
вот как это сработало со мной: #! / bin / bash until /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $ do sleep 0.1; сделанный; echo "mysql is on"
M.Hefny
@ M.Hefny Это тот же пример, что и в ответе.
superhero
32

Если вы не хотите открывать порты, как в случае, если вы планируете связать контейнер и можете запускать несколько экземпляров для тестирования, я обнаружил, что это хороший способ сделать это в одной строке :) Этот пример на основе ожидания готовности ElasticSearch:

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

Для этого требуется, чтобы wget был доступен, что является стандартным для Ubuntu. Он будет повторять 5 раз, 3 секунды между попытками, даже если в соединении отказано, а также ничего не загружает.

Дэвид Лемферс
источник
Я думаю, вы хотите использовать --waitretry=3вместо--wait=3
jpbochi
для любопытных, man-страница wget --wait=seconds Wait the specified number of seconds between the retrievals. и --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.
бесцельно
25

Если запущенная вами контейнерная служба не обязательно хорошо реагирует на запросы curl или wget (что весьма вероятно для многих служб), вы можете использовать ncвместо этого.

Вот фрагмент сценария хоста, который запускает контейнер Postgres и ожидает его доступности, прежде чем продолжить:

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

Изменить - в этом примере не требуется, чтобы вы открывали порт, который вы тестируете, поскольку он получает доступ к назначенному Docker «частному» IP-адресу для контейнера. Однако это работает только в том случае, если демон хоста докеров прослушивает loopback (127.xxx). Если (например) вы работаете на Mac и запускаете виртуальную машину boot2docker, вы не сможете использовать этот метод, так как вы не можете выполнить маршрутизацию на «частные» IP-адреса контейнеров из оболочки Mac.

Марк Хенвуд
источник
Я считаю, что --formatпосле этого ответа вариант изменился; сейчас работает docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...](см. пример на docs.docker.com/engine/reference/commandline/inspect ).
Курт Пик
16

Предполагая, что вы знаете хост + порт вашего сервера MongoDB (либо потому, что вы использовали -link, либо потому, что вы их внедрили -e), вы можете просто использовать, curlчтобы проверить, работает ли сервер MongoDB и принимает ли соединения.

Следующий фрагмент будет пытаться подключиться каждую секунду, пока не добьется успеха:

#!/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
источник
1
Но вам нужно привязать порт к хосту :(
Gravis
У меня похожие проблемы. Попытка настроить monit с использованием pid-файлов и невозможность инициировать детализированное событие при запуске / остановке Docker без ручного ввода переменных - это боль, это означает, что я не могу просто так легко писать общие оболочки.
Alex Lynham
Вы можете пойти с , IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)чтобы получить IP - адрес вашего тузд контейнера (где «MySQL» это имя или идентификатор контейнера) и заменить URL с: http://$IP:3306. работает на меня!
Дэниел
12

У меня получилось что-то вроде:

#!/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
источник
10

Выбросил свое собственное решение:

Я использую докерные сети, так что трюк Марка с netcat у меня не сработал (нет доступа из сети хоста), а идея Эрика не работает для контейнера postgres (контейнер отмечен как работающий, хотя postgres еще не доступны для подключения). Итак, я просто пытаюсь подключиться к postgres через эфемерный контейнер в цикле:

#!/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...
Элл Нил
источник
Вот что мы делаем сегодня. Будьте осторожны с образом postgres, так как сервер запускается один раз перед перезапуском ...
Gravis
Это работает хорошо, с небольшими изменениями. 1. postgreshost не разрешается, поэтому я использую docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgresвместо него. 2. psqlнужен пароль, pg_isreadyлучше подходит. 3. При pg_isreadyэтом тоже нет необходимости -U myuser.
Алек Мев
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

Я вот так тестирую, слушает порт или нет. В этом случае у меня есть тест, запущенный изнутри контейнера, но это также возможно извне, независимо от того, готов mongodb или нет.

$ docker run -p 37017:27017 -d myimage

И проверьте, прослушивает ли порт 37017 хост-контейнер или нет.

баньян
источник
2

Мне пришлось решить эту проблему недавно, и в голову пришла идея. Изучая эту задачу, я попал сюда, поэтому решил поделиться своим решением с будущими посетителями этого поста.

Решение на основе Docker-compose

Если вы используете docker-compose, вы можете проверить мой POC синхронизации докеров . Я объединил некоторые идеи в других вопросах (спасибо за это - проголосовали за).

Основная идея состоит в том, что каждый контейнер в композите предоставляет диагностический сервис. Вызов этой службы проверяет, открыт ли требуемый набор портов в контейнере, и возвращает общий статус контейнера (WARMUP / RUNNING согласно POC). В каждом контейнере также есть утилита для проверки при запуске, запущены ли зависимые службы. Только после этого контейнер запускается.

В примере среды docker-compose есть две службы server1 и server2, и клиентская служба, которая ожидает запуска обоих серверов, отправляет запрос им обоим и завершает работу.

Выдержка из 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

Ожидание нескольких контейнеров:

trap 'kill $(jobs -p)' EXIT

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

Базовая реализация диагностического сервиса ( 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";

Подключение диагностической службы к порту:

nc -v -lk -p 7070 -e /assets/checkports.sh
Яннис
источник
Интересный подход. +1
VonC
1

Вы можете использовать wait-for-it " чистый сценарий bash, который будет ожидать доступности хоста и TCP-порта. Он полезен для синхронизации развертывания взаимозависимых служб, таких как связанные контейнеры докеров. Поскольку это чистый сценарий bash, он не имеет внешних зависимостей ».

Однако вы должны попытаться разработать свои сервисы так, чтобы избежать подобных взаимозависимостей между сервисами. Может ли ваш сервис попытаться переподключиться к базе данных? Можете ли вы позволить своему контейнеру просто умереть, если он не может подключиться к базе данных, и позволить оркестратору контейнеров (например, Docker Swarm) сделать это за вас?

Гвидо
источник
1

Чтобы проверить, запущен ли контейнер Docker PostgreSQL или MySQL (в настоящее время) (особенно для инструментов миграции, таких как Flyway), вы можете использовать двоичный файл ожидания : https://github.com/ArcanjoQueiroz/wait-for .

Александр Арканжу де Кейруш
источник
0

Docker-compose решение

После docker-compose я не знаю имя контейнера докеров, поэтому использую

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

и проверяя, trueкак здесь, https://stackoverflow.com/a/33520390/7438079

DarkAiR
источник
0

Для экземпляра докера mongoDB мы сделали это и работает как шарм:

#!/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
Алекс Арванитидис
источник