Як я можу дочекатися запуску контейнера докера?


84

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

docker run -d myimage

миттєво вийде і поверне ідентифікатор контейнера. У моєму сценарії CI я запускаю клієнт для тестування підключення mongodb відразу після запуску контейнера mongo. Проблема полягає в тому, що клієнт не може підключитися, оскільки служба ще не встановлена. Окрім додавання великого sleep 10в мій сценарій, я не бачу жодної можливості чекати, поки контейнер запрацює.

У Docker є команда, waitяка в цьому випадку не працює, оскільки контейнер не існує. Це обмеження докера?

Відповіді:


50

Як зазначається в аналогічному випуску для docker 1.12

HEALTHCHECKпідтримка об’єднана вище за течією згідно з docker / docker # 23218 - це можна врахувати, щоб визначити, чи справді контейнер справний до початку наступного в порядку

Це доступно з docker 1.12rc3 (14.07.2016)

docker-composeперебуває в процесі підтримки функціоналу для очікування конкретних умов.

Він використовує libcompose(тому мені не потрібно відновлювати взаємодію докера) і додає купу команд конфігурації для цього. Перевірте це тут: https://github.com/dansteen/controled-compose

Ви можете використовувати його в Dockerfile так:

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

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


Я очікував, що це буде найвищим голосом. Але потім я виявив, що на нього відповіли зовсім недавно.
Шиплу Мокаддім

53

Знайшов це просте рішення, шукав щось краще, але не везе ...

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

@ впевнений, це було б поліпшенням, хоча я вважаю документацію "міжплатформних сценаріїв" зовнішнім питанням до обсягу питання.
супергерой

ось як це працювало зі мною: #! / bin / bash до /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $ do sleep 0.1; зроблено; ехо "mysql увімкнено"
М.Хефні

@ M.Hefny Це той самий приклад, який наведено у відповіді.
супергерой

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

для допитливої, чудової сторінки користувача --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-адреси контейнера. Однак це працює лише в тому випадку, якщо демон хосту докера прослуховує петлю (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 (або тому, що ви використовували a -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"

1
Але вам потрібно прив'язати порт на хості :(
Гравіс,

У мене подібні проблеми. Спроба встановити monit за допомогою pidfiles і неможливість ініціювати детальну подію при запуску / зупинці Docker без ручного введення vars - це біль, це означає, що я не можу просто так просто писати загальні обгортки.
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

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, оскільки сервер запускається один раз, перед перезапуском ...
Гравіс

Це працює добре, з невеликими змінами. 1. postgresхост не вирішується, тому я використовую 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

Якщо ви використовуєте docker-compose, ви можете перевірити мій POC для синхронізації докера . Я поєднав деякі ідеї в інших питаннях (дякую за це - проголосував).

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

У прикладі середовища складання докерів є дві служби 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

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

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

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

Цікавий підхід. +1
VonC,

1

Ви можете використовувати wait-for-it , " скрипт чистого bash, який буде чекати на наявність хосту та порту TCP. Це корисно для синхронізації розподілу взаємозалежних служб, таких як пов'язані контейнери докера. Оскільки це чистий скрипт bash, він не має жодних зовнішніх залежностей ".

Однак слід спробувати розробити свої послуги, щоб уникнути такого роду взаємозалежностей між службами. Чи може ваша служба спробувати підключитися до бази даних? Чи можете ви дозволити своєму контейнеру просто померти, якщо він не може підключитися до бази даних, і нехай контейнер-оркестратор (наприклад, Docker Swarm) зробить це за вас?



0

Докер-композитне рішення

Після складання докера я не знаю назву контейнера докера, тому використовую

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

та перевірка, trueяк тут https://stackoverflow.com/a/33520390/7438079


0

Для екземпляра docker 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.