Docker Compose 在启动 Y 容器前等待 X 容器完成

618

我正在使用RabbitMQ和来自这里的简单Python示例,以及Docker Compose。我的问题是,我需要等待RabbitMQ完全启动。从我迄今为止搜索的内容来看,我不知道如何在容器x(在我的情况下是worker)等待y(RabbitMQ)启动。

我找到了这篇博客文章,在其中他检查另一个主机是否在线。我还找到了这个Docker命令

wait

用法:docker wait CONTAINER [CONTAINER...]

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

也许等待容器停止不是我想要的,但如果是,是否可以在docker-compose.yml中使用该命令?到目前为止,我的解决方案是等待几秒钟并检查端口,但这是达成此目的的方法吗?如果我不等待,就会出现错误。

docker-compose.yml

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

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

Python初学示例(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:

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

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

2015年11月更新:

一个Shell脚本或者在你的程序中等待可能是一种可行的解决方案。但经过查看这个问题后,我正在寻找docker/docker-compose自身的命令或功能。

他们提到了一种实现健康检查的解决方案,这可能是最好的选择。一个开放的TCP连接并不意味着你的服务已经准备好或者可以保持准备状态。除此之外我还需要在我的Dockerfile中更改入口点。

因此,我希望有一个带有docker-compose内置命令的答案,如果他们完成了这个问题,那就太好了。

2016年3月更新:

有一个提议提供一种内置的方法来确定容器是否“存活”。因此,在不久的将来,docker-compose可能会利用它。

2016年6月更新:

看起来健康检查将被集成到docker 1.12.0版本中。

2017年1月更新:

我找到了一个docker-compose的解决方案,参见: Docker Compose等待容器X启动后再启动Y


3
在docker-compose 2.3中,使用“healthchecks”已被弃用,以鼓励构建具备容错能力的分布式系统。详情请参考:https://docs.docker.com/compose/startup-order/。 - Kmaid
我已经多次遇到这个问题。你可以克服它,但是docker-compose会阻碍你的每一个步骤。如果你想要设置测试拆卸容器控制权,最好使用类似conducto的东西。 - MatrixManAtYrService
@Kmaid 更新: healthchecksdepends_on.condition 的支持实际上在v3中已经回归。因此,您确实可以使用它-请参见发布说明(重要的部分是它说v2和v3规范已合并)+这里是最新规范 - aradalvand
20个回答

578
终于找到了一个使用docker-compose方法的解决方案。自从docker-compose文件格式2.1以来,你可以定义healthchecks
我在一个示例项目中实现了这个功能,你需要安装至少docker 1.12.0+版本。 我还需要扩展rabbitmq-management Dockerfile,因为官方镜像上没有安装curl。
现在我测试一下rabbitmq容器的管理页面是否可用。如果curl以退出码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

第三版不再支持depends_on的条件形式。 所以我从depends_on改为了restart on-failure。现在我的应用容器会重新启动2-3次,直到它正常工作,但这仍然是docker-compose的一个特性,而不是覆盖entrypoint。

docker-compose(第三版):

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

47
"depends on"在3版本中已被移除。 - nha
73
@nha 看起来 depends_oncondition 形式被移除了,但是 depends_on 本身在 v3 中仍然存在。 - akivajgordon
29
如果depends_oncondition被移除,健康检查如何仍然用于控制启动顺序? - Franz
14
遗憾的是,在运行测试时,“重启:失败时重新启动”不是一个选项 :C - TooroSan
184
很难相信这种痛苦仍然存在。 - npr
显示剩余19条评论

115

他们最近添加了depends_on特性。

编辑:

从版本2.1+到版本3,您可以与healthcheck一起使用depends_on来实现这一点:

文档中说明:

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 容器可能已经启动了。但是容器内的postgres服务本身可能仍在初始化。


13
dnephin写道:depends_on只是排序作用。如果要实际延迟另一个容器的启动,需要有一种方式来检测进程何时完成初始化。 - svenhornberg
3
depends_on 不会等待容器处于 ready 状态(在您的情况下可能意味着什么)。它只会等待容器进入 'running' 状态。 - tortuga
3
我没有找到这样的引用语句,相反我发现版本3确实支持depends_on(备注中指出如果在swarm模式下部署则不支持)。请参见https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on。我已经在本地使用docker-compose版本3.7进行了测试,并且它支持带有条件形式的`depends_on`。 - Benissimo
@Benissimo 你意识到我的评论是从2017年的吗?在Docker时间里已经过去了几十年。 - akauppi
1
@akauppi 当然,我认识到自那时以来很多事情可能已经改变了。尽管这个问题/答案很旧,但它仍然在搜索结果中高居榜首,用于了解如何在Docker中管理启动顺序。其他开发者可能会像我一样偶然发现这些评论,并可能会发现更新内容有用。我想depends_on的条件形式在某个时候被移除了,后来又恢复了。 - Benissimo
1
非常感谢您,特别是对文档的解释和参考。在您的帮助下,我解决了我的问题。 - kokserek

88
Natively that is not possible, yet. See also this feature request.

So far you need to do that in your containers CMD to wait until all required services are there.

In the Dockerfiles CMD you could refer to your own start script that wraps starting up your container service. Before you start it, you wait for a depending one like:

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端口检查:

对于更复杂的等待:


你能解释一下你所说的CMD是什么吗?这是否意味着我的程序必须像我使用端口检查一样执行它?或者你是指来自例如Linux的特定CMD吗? - svenhornberg
谢谢您的解释,我点赞了您的回答。但我认为即将推出的功能请求可能是我问题的正确答案,所以我目前还没有回答它。 - svenhornberg

65

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

如果工作进程 container 在rabbitMQ没有准备好时停止,它将被重启直到准备就绪。


5
我喜欢这个方案解决这个问题,但它不适用于当它运行的子进程失败时不退出的容器。例如,一个Tomcat容器会继续运行,即使它运行的Java servlet无法连接到数据库服务器。当然,Docker容器使得像Tomcat这样的servlet容器基本上是不必要的。 - Derek Mahar
@DerekMahar,如果您有一个仅提供REST调用的基于Java的Web应用程序,那么您会使用Jetty/Tomcat之外的什么? - JoeG
2
@JoeG,我的意思是Tomcat这个Servlet容器可以托管许多应用程序,而不是嵌入式Tomcat。Docker使前者大多数情况下变得不必要,同时使后者在微服务方面更受欢迎,例如。 - Derek Mahar

28
如果您希望在另一个服务已成功完成(例如迁移、数据填充等)后才启动服务,则 docker-compose 1.29 版本 提供了内建功能 service_completed_successfully
depends_on:
  <service-name>:
    condition: service_completed_successfully

根据规范

service_completed_successfully - 表示在启动依赖服务之前,需要确保该依赖项成功完成


1
这真的很好!您会推荐使用它而不是使用healthcheck吗?这两者之间有什么区别? - William Le
1
@SeanWilliam 不,healthcheck 的目的不同,它应该确定容器是否健康。可以使用 depends_onservice_completed_successfully 条件进行设置,例如创建服务的数据库、填充数据、运行迁移、运行依赖项安装等。与将所有设置放在单个容器中相比,您可以将整个设置分离到小型专用容器中,然后配置实际服务依赖于具有 service_completed_successfully 的设置容器。 - zooblin
@zooblin 当我使用它时,依赖服务完成后它只是为我暂停了,依赖服务就没有发生,有什么帮助吗? - Amon

25
你也可以将其添加到命令选项中,例如:
command: bash -c "sleep 5; start.sh"

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"

要增加等待时间,您可以进行一些黑客技巧:

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"

有效且易于使用:这是一个好答案。 - Symon

19

restart: on-failure对我起到了作用..请看下面

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

14

7
@svenhornberg在评论中提供的链接中,没有关于wait-for-it.sh特性的解释。 - quit

11

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

你可以在docker compose文件中使用环境变量来提交一组服务主机(带端口),然后像这样“等待”它们:WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017

假设您有以下docker-compose.yml文件(从存储库README复制/粘贴):

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 README):

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

有关可能使用方式的其他详细信息,请参见 README


我正在寻找类似的答案。通常我使用https://hub.docker.com/r/dadarek/wait-for-dependencies,因为它在底层使用netcat。你提供的是基于Rust的。对于你提供的这个,我不能评论其质量。但对于我来说,没有额外的层是明显的优点。 - Filip Malczak
8
出于安全考虑,我强烈建议不要这样做。您正在从超链接运行任意可执行文件。更好的解决方案是使用一个静态脚本,在构建镜像时将其复制到镜像中,然后执行相同的操作。 - Paul K
1
@PaulK 当然,从超链接运行任何东西都不安全是可以理解的,但上面只是演示如何使 https://github.com/ufoscout/docker-compose-wait 库工作 :) 你使用该库的方式并不改变你可以利用某些库的答案。安全是一个复杂的话题,如果我们深入探讨,无论如何都应该检查该库内部正在做什么,即使我们将其复制 :) 因此最好在您的评论中更具体地说明:“我强烈反对从超链接使用该库”。希望您同意,感谢提示! - Evereq

7
您也可以通过设置一个终端点来解决这个问题,该终端点使用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

现在,这已经在官方的docker文档中有所记录。

PS:如果您的docker实例中没有可用的netcat,则应安装它。要这样做,请将以下内容添加到您的Docker文件中:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接