我如何等待一个码头集装箱启动并运行?

当在容器中运行服务时,我们假设是 mongodb 命令

docker run -d myimage

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

Docker 有一个命令 wait在这种情况下不起作用,因为容器不存在。 是码头的限制吗?

118037 次浏览

I've ended up with something like:

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

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

I'm testing like this whether the port is listening or not. In this case I have test running from inside container, but it's also possible from outside whether mongodb is ready or not.

$ docker run -p 37017:27017 -d myimage

And check whether the port 37017 is listening or not from host container.

Assuming that you know the host+port of your MongoDB server (either because you used a -link, or because you injected them with -e), you can just use curl to check if the MongoDB server is running and accepting connections.

The following snippet will try to connect every second, until it succeeeds:

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

If you don't want to expose the ports, as is the case if you plan to link the container and might be running multiple instances for testing, then I found this was a good way to do it in one line :) This example is based on waiting for ElasticSearch to be ready:

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

This requires wget to be available, which is standard on Ubuntu. It will retry 5 times, 3 seconds between tries, even if the connection is refused, and also does not download anything.

If the containerized service you started doesn't necessarily respond well to curl or wget requests (which is quite likely for many services) then you could use nc instead.

Here's a snippet from a host script which starts a Postgres container and waits for it to be available before continuing:

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

Edit - This example does not require that you EXPOSE the port you are testing, since it accesses the Docker-assigned 'private' IP address for the container. However this only works if the docker host daemon is listening on the loopback (127.x.x.x). If (for example) you are on a Mac and running the boot2docker VM, you will be unable to use this method since you cannot route to the 'private' IP addresses of the containers from your Mac shell.

Found this simple solution, been looking for something better but no luck...

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

or if you want to wait until the container is reporting as healthy (assuming you have a healthcheck)

until [ "`docker inspect -f \{\{.State.Health.Status}} CONTAINERNAME`"=="healthy" ]; do
sleep 0.1;
done;

Throwing my own solution out there:

I'm using docker networks so Mark's netcat trick didn't work for me (no access from the host network), and Erik's idea doesn't work for a postgres container (the container is marked as running even though postgres isn't yet available to connect to). So I'm just attempting to connect to postgres via an ephemeral container in a loop:

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

As commented in a similar issue for docker 1.12

HEALTHCHECK support is merged upstream as per docker/docker#23218 - this can be considered to determine when a container is healthy prior to starting the next in the order

This is available since docker 1.12rc3 (2016-07-14)

docker-compose is in the process of supporting a functionality to wait for specific conditions.

It uses libcompose (so I don't have to rebuild the docker interaction) and adds a bunch of config commands for this. Check it out here: https://github.com/dansteen/controlled-compose

You can use it in Dockerfile like this:

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

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

I had to tackle this recetly and came up with an idea. When doing research for this task I got here, so I thought I'd share my solution with future visitors of this post.

Docker-compose-based solution

If you are using docker-compose you can check out my docker synchronization POC. I combined some of the ideas in other questions (thanks for that - upvoted).

The basic idea is that every container in the composite exposes a diagnostic service. Calling this service checks if the required set of ports is open in the container and returns the overall status of the container (WARMUP/RUNNING as per the POC). Each container also has an utility to check upon startup if the dependant services are up and running. Only then the container starts up.

In the example docker-compose environment there are two services server1 and server2 and the client service which waits for both servers to start then sends a request to both of them and exits.

Excerpt from the 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

Waiting for multiple containers:

trap 'kill $(jobs -p)' EXIT


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

Diagnostic srervice basic implementation (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";

Wiring up the diagnostic service to a port:

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

You can use wait-for-it, "a pure bash script that will wait on the availability of a host and TCP port. It is useful for synchronizing the spin-up of interdependent services, such as linked docker containers. Since it is a pure bash script, it does not have any external dependencies".

However, you should try to design your services to avoid these kind of interdependencies between services. Can your service try to reconnect to the database? Can you let your container just die if it can't connect to the database and let a container orchestrator (e.g. Docker Swarm) do it for you?

Docker-compose solution

After docker-compose I dont know name of docker container, so I use

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

and checking true like here https://stackoverflow.com/a/33520390/7438079

For mongoDB docker instance we did this and works like a charm:

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

In order to verify if a PostgreSQL or MySQL (currently) Docker container is up and running (specially for migration tools like Flyway), you can use the wait-for binary: https://github.com/arcanjoaq/wait-for.

Here is what I ended up with which is similar to a previous answer just a little more concise,

until [[ $(docker logs $db_container_name) == *"waiting for connections on port 27017"* ]]
do
echo "waiting on mongo to boot..."
sleep 1
done

If you want to wait for an opened port, you can use this simple script:

until </dev/tcp/localhost/32022; do sleep 1; done

For wait until port 32022 be able to connect.