如何等待Docker容器启动并运行?


84

在容器内运行服务时,假设使用mongodb命令

docker run -d myimage

将立即退出,并返回容器ID。在我的CI脚本中,我在运行mongo容器之后立即运行客户端以测试mongodb连接。问题是:由于服务尚未启动,客户端无法连接。除了sleep 10在脚本中添加大字体外,我没有看到等待容器启动并运行的任何选项。

Docker的命令wait在这种情况下不起作用,因为该容器不存在。是docker的限制吗?

Answers:


50

如在docker 1.12的类似问题中所述

HEALTHCHECK支持已根据docker / docker#23218在上游合并-可考虑在启动该订单中的下一个容器之前确定容器何时健康

docker 1.12rc3(2016-07-14)起可用

docker-compose正在支持等待特定条件的功能。

它使用libcompose(因此我不必重建docker交互)并为此添加了一堆配置命令。在这里查看:https : //github.com/dansteen/control-compose

您可以像这样在Dockerfile中使用它:

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

官方文档:https : //docs.docker.com/engine/reference/builder/#/healthcheck


我期望这是最高的投票。但是后来我发现它最近才得到答复。
Shiplu Mokaddim '18

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
一只班轮使用while循环while [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done
Mouath

请注意,docker位于osx上的/ usr / local / bin / docker中。可能值得添加$(哪个docker)来使脚本跨平台吗?
con--

@ con--当然,这将是一个改进,尽管我认为“跨平台脚本”的文档是问题范围的外部关注。不管怎样,如果您想进行编辑,请继续:)
超级英雄

这就是我的工作方式:#!/ bin / bash直到 /usr/bin/docker inspect -f {{.State.Running}} local_mysql== true $睡觉0.1;完成 回声“ mysql on”
M.Hefny

@ 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

对于好奇的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地址。但是,这仅在docker host守护进程在环回(127.xxx)上侦听时才有效。如果(例如)您在Mac上并且正在运行boot2docker VM,则将无法使用此方法,因为您无法从Mac shell路由到容器的“私有” IP地址。


我相信--format自此回复以来,选项已更改;现在有效的是docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...](请参阅docs.docker.com/engine/reference/commandline/inspect上的示例)。
Kurt Peek '02

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"

1
但是您需要在主机上绑定端口:(
Gravis 2014年

我也有类似的问题。尝试使用pidfiles设置monit,并且在不手动注入vars的情况下无法在Docker启动/停止上触发粒度事件是一件很痛苦的事情,这意味着我不能只是简单地编写通用包装器。
Alex Lynham 2014年

您可以继续IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)获取mysql容器的IP地址(其中“ mysql”是名称或容器ID),然后将URL替换为:http://$IP:3306。为我工作!
Danyel 2014年

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

把我自己的解决方案扔在那里:

我使用的是docker网络,所以Mark的netcat技巧对我不起作用(无法从主机网络访问),Erik的想法对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.postgres主机无法解析,因此我docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgres改用了。2.psql需要密码,pg_isready比较合适。3.有了pg_isready,也不需要-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

我正在像这样测试端口是否在监听。在这种情况下,我可以从容器内部运行测试,但是也可以从外部运行mongodb是否准备就绪。

$ docker run -p 37017:27017 -d myimage

并检查端口37017是否正在从主机容器侦听。


2

我不得不悄悄解决这个问题,并提出了一个想法。在为此任务做研究时,我来到了这里,所以我想与这篇文章的未来访问者分享我的解决方案。

基于Docker-compose的解决方案

如果您使用的是docker-compose,则可以查看我的Docker同步POC。我在其他问题中结合了一些想法(对此表示感谢-赞成)。

基本思想是,组合中的每个容器都将公开诊断服务。调用此服务将检查容器中所需的端口组是否已打开,并返回容器的整体状态(根据POC进行“ WARMUP / RUNNING”)。每个容器还具有一个实用程序,用于在启动时检查相关服务是否已启动并正在运行。只有这样,容器才能启动。

在示例docker-compose环境中,有两个服务server1server2客户端服务等待两个服务器启动,然后向它们发送请求并退出。

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端口的可用性。它对于同步相互依赖的服务(例如链接的docker容器)的启动很有用。纯bash脚本,它没有任何“外部依赖项”。

但是,您应该尝试设计服务,以避免服务之间的这种相互依赖性。您的服务可以尝试重新连接到数据库吗?如果容器无法连接到数据库,是否可以让它死掉,并让容器协调器(例如Docker Swarm)为您完成呢?




0

对于mongoDB docker实例,我们做到了这一点,并且像一个符咒一样工作:

#!/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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.