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

我发现了一篇他检查另一个主机是否在线的博客文章。 我还发现了这个docker命令:

等待 用法: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"]

2015年11月更新:

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

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

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

2016年3月更新

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

2016年6月更新

健康检查似乎将在1.12.0版本集成到docker中

2017年1月更新

我找到了一个docker-compose解决方案,见: Docker在启动Y之前撰写等待容器X


在本土,这是不可能的。另请参阅此特性请求。

到目前为止,您需要在容器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端口检查:

等待它 dockerize

对于更复杂的等待:

goss -解释博客


有一个叫做“docker-wait”的实用程序可以用于等待。


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

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


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

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"

为了增加等待时间,你可以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"

最近他们添加了depends_on特性。

编辑:

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

从文档中可以看出:

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服务本身可能仍然在容器中进行初始化。


您还可以通过设置一个端点来解决这个问题,该端点使用netcat(使用docker-wait脚本)等待服务启动。我喜欢这种方法,因为在docker-compose中仍然有一个干净的命令部分。Yml,你不需要添加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 

容器开始订购使用

depends_on:

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

entrypoint: ./wait-for-it.sh db:5432

这篇文章将帮助你 https://docs.docker.com/compose/startup-order/


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

我在一个例子中做过 您至少需要安装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 =>:

Docker-compose start_dependencies

Rabbitmq服务将以daemon模式启动,start_dependencies将完成工作。


重启:失败 这招对我有用吗

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


下面是主容器在开始响应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变量来提交一个服务主机列表(带端口),应该像这样“等待”:WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017。

假设你有下面的docker-compose。yml文件(从repo 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


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

在docker-compose 3.4版中,healthcheck添加了一条start_period指令。这意味着我们可以做到以下几点:

docker-compose.yml:

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

status.sh:

#!/bin/sh

exit 0

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

注意:重要的是你有版本:“3.4”。如果。4不在那里,docker-compose会抱怨。


在Docker撰写文件的版本3中,您可以使用RESTART。

例如:

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而不是links,因为后者在版本3中已弃用。

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

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

当你在生产环境中使用Compose时,最好使用restart策略:

指定像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

I currently also have that requirement of waiting for some services to be up and running before others start. Also read the suggestions here and on some other places. But most of them require that the docker-compose.yml some how has to be changed a bit. So I started working on a solution which I consider to be an orchestration layer around docker-compose itself and I finally came up with a shell script which I called docker-compose-profile. It can wait for tcp connection to a certain container even if the service does not expose any port to the host directy. The trick I am using is to start another docker container inside the stack and from there I can (usually) connect to every service (as long no other network configuration is applied). There is also waiting method to watch out for a certain log message. Services can be grouped together to be started in a single step before another step will be triggered to start. You can also exclude some services without listing all other services to start (like a collection of available services minus some excluded services). This kind of configuration can be bundled to a profile. There is a yaml configuration file called dcp.yml which (for now) has to be placed aside your docker-compose.yml file.

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

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

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

有个小问题。我目前的版本不支持特殊的等待条件,如ony 你实际上需要。这样就没有测试给兔子发信息了。

我已经在考虑进一步的等待方法,以在主机上或作为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命令的意图。我不会死在山上的,这对我很有用。