Docker在启动Y之前撰写等待容器X

我使用rabbitmq和一个简单的python示例,来自这里 和docker-compose一起。我的问题是我需要等待rabbitmq完全启动。从我搜索到目前为止,我不知道如何等待容器x(在我的case worker中)直到y (rabbitmq)启动

我发现这个博客,他检查另一个主机是否在线。 我还发现了这个码头工人命令:

等待

用法:docker wait CONTAINER[容器…]

阻塞直到容器停止,然后打印它的退出代码。

等待一个容器停止可能不是我想要的,但如果 是否可以在docker-compose。yml中使用这个命令? 到目前为止,我的解决方案是等待几秒钟并检查端口,但这是实现这一点的方法吗?如果我不等待,就会得到一个错误

docker-compose.yml

worker:
build: myapp/.
volumes:
- myapp/.:/usr/src/app:ro


links:
- rabbitmq
rabbitmq:
image: rabbitmq:3-management

Python hello sample (rabbit.py):

import pika
import time


import socket


pingcounter = 0
isreachable = False
while isreachable is False and pingcounter < 5:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('rabbitmq', 5672))
isreachable = True
except socket.error as e:
time.sleep(2)
pingcounter += 1
s.close()


if isreachable:
connection = pika.BlockingConnection(pika.ConnectionParameters(
host="rabbitmq"))
channel = connection.channel()


channel.queue_declare(queue='hello')


channel.basic_publish(exchange='',
routing_key='hello',
body='Hello World!')
print (" [x] Sent 'Hello World!'")
connection.close()

Dockerfile for worker:

FROM python:2-onbuild
RUN ["pip", "install", "pika"]


CMD ["python","rabbit.py"]

# EYZ0:

在程序中使用shell脚本或等待可能是一种解决方案。但是在看到这个问题之后,我正在寻找docker/docker-compose本身的命令或功能。

他们提到了实现运行状况检查的解决方案,这可能是最佳选择。一个开放的tcp连接并不意味着你的服务已经准备好或可能保持准备状态。除此之外,我需要改变我的入口点在我的dockerfile。

因此,我希望通过docker-compose on board命令得到一个答案,如果他们完成了这个问题,就有希望出现这种情况。

2016年3月更新

有一个建议用于提供一个内置的方法来确定一个容器是否“活着”。因此,docker-compose在不久的将来可能会得到应用。

2016年6月更新

在1.12.0版本中,healthcheck似乎将集成放入docker中

2017年1月更新

我找到了一个docker-compose解决方案见: # EYZ0 < / p >

462241 次浏览

在本土,这是不可能的。看看这个功能要求

到目前为止,您需要在容器CMD中这样做,直到所有所需的服务都在那里。

Dockerfiles CMD中,您可以引用自己的启动脚本,该脚本封装了容器服务的启动。在你开始之前,你需要等待一个依赖的选项,比如:

Dockerfile

FROM python:2-onbuild
RUN ["pip", "install", "pika"]
ADD start.sh /start.sh
CMD ["/start.sh"]

start.sh

#!/bin/bash
while ! nc -z rabbitmq 5672; do sleep 3; done
python rabbit.py

可能你也需要在Dockerfile中安装netcat。我不知道python映像上预装了什么。

有一些工具提供了易于使用的等待逻辑,用于简单的tcp端口检查:

  • # EYZ0
  • < a href = " https://github.com/jwilder/dockerize " > dockerize < / >

对于更复杂的等待:

有一个名为“docker-wait”的现成实用程序可以用于等待。

使用restart: unless-stoppedrestart: always可以解决这个问题。

如果worker container在rabbitMQ还没有准备好时停止,它将被重新启动,直到rabbitMQ准备好。

你也可以把它添加到命令选项中。

command: bash -c "sleep 5; start.sh"

< a href = " https://github.com/docker/compose/issues/374 issuecomment - 156546513 " > https://github.com/docker/compose/issues/374 issuecomment - 156546513 < / >

要等待一个端口,您也可以使用类似这样的东西

command: bash -c "while ! curl -s rabbitmq:5672 > /dev/null; do echo waiting for xxx; sleep 3; done; start.sh"

为了增加等待时间,你可以hack更多一点:

command: bash -c "for i in {1..100} ; do if ! curl -s rabbitmq:5672 > /dev/null ; then echo waiting on rabbitmq for $i seconds; sleep $i; fi; done; start.sh"

最近他们又增加了# EYZ0特性

编辑:

从撰写版本2.1+到版本3,你可以使用depends_onhealthcheck来实现这一点:

# EYZ0:

version: '2.1'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
redis:
image: redis
db:
image: redis
healthcheck:
test: "exit 0"

2.1版本之前

您仍然可以使用depends_on,但是它只影响启动服务的订单—如果它们在依赖的服务启动之前就准备好了,则不会影响。

它似乎至少需要1.6.0版本。

用法应该是这样的:

version: '2'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres

从文档中可以看出:

表达服务之间的依赖关系,这有两个效果:

  • Docker-compose up将按依赖顺序启动服务。在下面的例子中,db和redis会在web之前启动。
  • docker-compose up SERVICE会自动包含SERVICE的依赖项。在下面的例子中,docker-compose up web也将创建并启动db和redis。

注意:据我所知,尽管这确实设置了容器装载的顺序。它不保证容器内的服务已经实际加载。

例如,postgres 容器可能是up的。但是postgres服务本身可能仍然在容器中进行初始化。

您还可以通过使用netcat(使用docker-wait脚本)设置一个等待服务启动的端点来解决这个问题。我喜欢这种方法,因为你仍然在你的docker-compose.yml中有一个干净的command部分,你不需要向你的应用程序添加特定于docker的代码:

version: '2'
services:
db:
image: postgres
django:
build: .
command: python manage.py runserver 0.0.0.0:8000
entrypoint: ./docker-entrypoint.sh db 5432
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db

然后你的docker-entrypoint.sh:

#!/bin/sh


postgres_host=$1
postgres_port=$2
shift 2
cmd="$@"


# wait for the postgres docker to be running
while ! nc $postgres_host $postgres_port; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done


>&2 echo "Postgres is up - executing command"


# run the command
exec $cmd

这在官方的码头工人的文档中有记录。

PS:你应该在docker实例中安装netcat,如果这个不可用的话。要做到这一点,将这个添加到您的Docker文件:

RUN apt-get update && apt-get install netcat-openbsd -y

容器开始订购使用

depends_on:

等待前一个容器启动使用脚本

entrypoint: ./wait-for-it.sh db:5432
这篇文章将帮助你 # EYZ0 < / p >

最后用docker-compose方法找到了解决方案。由于docker-compose文件格式为2.1,您可以定义healthcheck

我在示例项目中做到了这一点 您至少需要安装docker 1.12.0+。 我还需要扩展rabbitmq-management Dockerfile,因为curl没有安装在正式映像上

现在我测试rabbitmq-container的管理页面是否可用。如果curl以exitcode 0结束,容器应用程序(python pika)将启动并发布消息到hello队列。它现在工作(输出)。

Docker-compose(2.1版):

version: '2.1'


services:
app:
build: app/.
depends_on:
rabbit:
condition: service_healthy
links:
- rabbit


rabbit:
build: rabbitmq/.
ports:
- "15672:15672"
- "5672:5672"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:15672"]
interval: 30s
timeout: 10s
retries: 5

输出:

rabbit_1  | =INFO REPORT==== 25-Jan-2017::14:44:21 ===
rabbit_1  | closing AMQP connection <0.718.0> (172.18.0.3:36590 -> 172.18.0.2:5672)
app_1     |  [x] Sent 'Hello World!'
healthcheckcompose_app_1 exited with code 0

Dockerfile (rabbitmq + curl):

FROM rabbitmq:3-management
RUN apt-get update
RUN apt-get install -y curl
EXPOSE 4369 5671 5672 25672 15671 15672

版本3不再支持depends_on的条件形式。 所以我从depends_on移动到restart -failure。现在我的应用容器将重新启动2-3次,直到它工作,但它仍然是一个docker-compose特性,没有覆盖入口点

Docker-compose(版本3):

version: "3"


services:


rabbitmq: # login guest:guest
image: rabbitmq:management
ports:
- "4369:4369"
- "5671:5671"
- "5672:5672"
- "25672:25672"
- "15671:15671"
- "15672:15672"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:15672"]
interval: 30s
timeout: 10s
retries: 5


app:
build: ./app/
environment:
- HOSTNAMERABBIT=rabbitmq
restart: on-failure
depends_on:
- rabbitmq
links:
- rabbitmq

基于这篇博客文章https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

我配置我的docker-compose.yml如下所示:

version: "3.1"


services:
rabbitmq:
image: rabbitmq:3.7.2-management-alpine
restart: always
environment:
RABBITMQ_HIPE_COMPILE: 1
RABBITMQ_MANAGEMENT: 1
RABBITMQ_VM_MEMORY_HIGH_WATERMARK: 0.2
RABBITMQ_DEFAULT_USER: "rabbitmq"
RABBITMQ_DEFAULT_PASS: "rabbitmq"
ports:
- "15672:15672"
- "5672:5672"
volumes:
- data:/var/lib/rabbitmq:rw


start_dependencies:
image: alpine:latest
links:
- rabbitmq
command: >
/bin/sh -c "
echo Waiting for rabbitmq service start...;
while ! nc -z rabbitmq 5672;
do
sleep 1;
done;
echo Connected!;
"


volumes:
data: {}

然后执行for run =>:

# EYZ0

rabbitmq服务将以守护进程模式启动,start_dependencies将完成工作。

< p > # EYZ0 这招对我有用吗?见下面

---
version: '2.1'
services:
consumer:
image: golang:alpine
volumes:
- ./:/go/src/srv-consumer
working_dir: /go/src/srv-consumer
environment:
AMQP_DSN: "amqp://guest:guest@rabbitmq:5672"
command: go run cmd/main.go
links:
- rabbitmq
restart: on-failure


rabbitmq:
image: rabbitmq:3.7-management-alpine
ports:
- "15672:15672"
- "5672:5672"

另一种解决方案是使用像Kubernetes这样的容器编排解决方案。Kubernetes支持init容器,它在其他容器启动之前运行到完成。你可以在这里找到一个SQL Server 2017 Linux容器的例子,其中API容器使用init容器初始化数据库

https://www.handsonarchitect.com/2018/08/understand-kubernetes-object-init.html

下面是main容器在开始响应ping时等待worker的例子:

version: '3'
services:
main:
image: bash
depends_on:
- worker
command: bash -c "sleep 2 && until ping -qc1 worker; do sleep 1; done &>/dev/null"
networks:
intra:
ipv4_address: 172.10.0.254
worker:
image: bash
hostname: test01
command: bash -c "ip route && sleep 10"
networks:
intra:
ipv4_address: 172.10.0.11
networks:
intra:
driver: bridge
ipam:
config:
- subnet: 172.10.0.0/24

但是,正确的方法是使用healthcheck(>=2.1)。

尝试了许多不同的方法,但喜欢简单的https://github.com/ufoscout/docker-compose-wait

你可以在docker compose文件中使用ENV vars来提交一个服务主机列表(带端口),应该像这样“等待”:WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017

假设你有下面的docker-compose。yml文件(从repo 自述复制/过去):

version: "3"


services:


mongo:
image: mongo:3.4
hostname: mongo
ports:
- "27017:27017"


postgres:
image: "postgres:9.4"
hostname: postgres
ports:
- "5432:5432"


mysql:
image: "mysql:5.7"
hostname: mysql
ports:
- "3306:3306"


mySuperApp:
image: "mySuperApp:latest"
hostname: mySuperApp
environment:
WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017

接下来,为了让服务等待,你需要在你的Dockerfile中添加以下两行(在服务的Dockerfile中,它应该等待其他服务启动):

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

Dockerfile的完整示例(同样来自项目repo 自述):

FROM alpine


## Add your application to the docker image
ADD MySuperApp.sh /MySuperApp.sh


## Add the wait script to the image
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait


## Launch the wait tool and then your application
CMD /wait && /MySuperApp.sh

有关可能使用的其他详细信息,请参阅自述

不建议用于严重的部署,但这里实际上是一个“等待x秒”命令。

docker-compose版本3.4 #EYZ2指令已添加到healthcheck指令。这意味着我们可以做到以下几点:

# EYZ0:

version: "3.4"
services:
# your server docker container
zmq_server:
build:
context: ./server_router_router
dockerfile: Dockerfile


# container that has to wait
zmq_client:
build:
context: ./client_dealer/
dockerfile: Dockerfile
depends_on:
- zmq_server
healthcheck:
test: "sh status.sh"
start_period: 5s

# EYZ0:

#!/bin/sh


exit 0
这里发生的事情是,healthcheck在5秒后被调用。这将调用status.sh脚本,该脚本总是返回“No problem”。 我们只是让zmq_client容器在启动前等待5秒!< / p >

注意:使用version: "3.4"非常重要。如果.4不存在,docker-compose会报错。

在Docker撰写文件的版本3中,您可以使用重新启动

例如:

docker-compose.yml

worker:
build: myapp/.
volumes:
- myapp/.:/usr/src/app:ro
restart: on-failure
depends_on:
- rabbitmq
rabbitmq:
image: rabbitmq:3-management

注意,我使用了depends_on而不是链接,因为后者在版本3中已弃用。

尽管它可以工作,但它可能不是理想的解决方案,因为每次失败都要重新启动docker容器。

看看RESTART_POLICY。它允许您微调重启策略。

当你在生产中使用Compose,它实际上是最好的实践使用重启策略:

指定像restart: always这样的重启策略以避免停机

我只是有2个撰写文件和开始一个第一个和第二个稍后。我的脚本是这样的:

#!/bin/bash
#before i build my docker files
#when done i start my build docker-compose
docker-compose -f docker-compose.build.yaml up
#now i start other docker-compose which needs the image of the first
docker-compose -f docker-compose.prod.yml up

我目前还需要等待一些服务启动并在其他服务启动之前运行。也可以阅读这里和其他地方的建议。但是他们中的大多数要求docker-compose.yml在某种程度上必须被改变一点。 所以我开始研究一个解决方案,我认为它是围绕docker-compose本身的编排层,最后我提出了一个shell脚本,我称之为docker-compose-profile。 即使服务不直接向主机公开任何端口,它也可以等待到某个容器的tcp连接。我使用的技巧是在堆栈中启动另一个docker容器,从那里我(通常)可以连接到每个服务(只要没有应用其他网络配置)。 还有一种等待方法来监视特定的日志消息。 可以将服务分组在一起,以便在触发另一个步骤之前在单个步骤中启动。 您还可以排除一些服务,而不列出要启动的所有其他服务(如可用服务的集合减去一些被排除的服务)。 这种配置可以绑定到概要文件中。 有一个名为dcp.yml的yaml配置文件(目前)必须放在docker-compose旁边。yml文件。< / p >

对于你的问题,这看起来像:

command:
aliases:
upd:
command: "up -d"
description: |
Create and start container. Detach afterword.


profiles:
default:
description: |
Wait for rabbitmq before starting worker.
command: upd
steps:
- label: only-rabbitmq
only: [ rabbitmq ]
wait:
- 5@tcp://rabbitmq:5432
- label: all-others

现在可以通过调用来启动堆栈

dcp -p default upd

或者仅仅通过

dcp

因为只有一个默认配置文件可以运行up -d

有一个微小的问题。我目前的版本不支持特殊的等待条件,如ony 你实际上需要。所以没有测试发送消息给rabbit.

我已经在考虑进一步的等待方法来在主机上或作为docker容器运行某个命令。 然后我们可以扩展这个工具,比如

...
wait:
- service: rabbitmq
method: container
timeout: 5
image: python-test-rabbit
...

有一个名为python-test-rabbit的docker映像来进行检查。

这样做的好处是,没有必要可以把等待的部分带到工人。 它将是孤立的,并保持在业务流程层内

也许有人会觉得这个有用。欢迎提出任何建议。

你可以在https://gitlab.com/michapoe/docker-compose-profile找到这个工具

如果你只想启动服务,然后另一个服务成功完成(例如迁移,数据填充等),docker-compose版本1.29,带有为此构建功能 - service_completed_successfully

depends_on:
<service-name>:
condition: service_completed_successfully

根据规范:

service_completed_successfully -指定在启动依赖服务之前,期望依赖项成功运行完成

在尝试了几种方法之后,我认为最简单和最优雅的选择是使用jwilder/dockerize实用程序映像(@Henrik Sachse提到过,但他没有给出具体示例)及其-wait标志。这是一个简单的例子,在启动我的应用程序之前,我需要一个RabbitMQ准备好:

version: "3.8"
services:
# Start RabbitMQ.
rabbit:
image: rabbitmq


# Wait for RabbitMQ to be joinable.
check-rabbit-started:
image: jwilder/dockerize:0.6.1
depends_on:
- rabbit
command: 'dockerize -wait=tcp://rabbit:5672'
  

# Only start myapp once RabbitMQ is joinable.
myapp:
image: myapp:latest
depends_on:
- check-rabbit-started

我猜docker的人真的想让我们用自己映像中的代码来等待服务。我仍然想在docker-compose.yml中配置要等待的服务。如果您愿意使用入口点脚本,这里有一种方法。

使用图像中包含的wait-for-it工具将此循环添加到入口点脚本中。我使用https://github.com/vishnubob/wait-for-it/。如果不传递任何服务,循环将不执行任何操作。

for service in "$@"; do
echo "$0: wait for service $service"
if ! wait-for-it "$service"; then
echo "$0: failed on service $service"
exit 1
fi
done

docker-compose.yml中的容器传递所需的服务:

    command: ["my-data-svc:5000"]

这依赖于docker命令作为参数传递给入口点脚本的行为。您可能会认为我在这里滥用了docker命令的意图。我不会死在山上的,这对我很有用。