Docker组成 如何等待直到容器准备好

引言


关于运行容器和编写docker-compose.yml的文章很多。 但是对于我来说很长一段时间,问题仍然不清楚,如果某个容器在另一个容器准备好处理其请求或完成某些工作之前不应该启动,那么如何正确进行。

在我们开始积极使用docker-compose而不是启动单个docker之后,这个问题就变得很重要。

这是为了什么


实际上,让容器B中的应用程序依赖于容器A中服务的可用性。并且在启动时,容器B中的应用程序不会接收此服务。 应该怎么办?

有两种选择:

  • 首先是死(最好带有错误代码)
  • 第二个是等待,如果容器B中的应用程序未响应所分配的超时,则无论如何都会死亡

容器B死亡后, docker-compose (当然取决于配置)将重新启动它,容器B中的应用程序将再次尝试访问容器A中的服务。

这将一直持续到容器A中的服务准备好响应请求,或者直到我们注意到容器不断超载。
实际上,这是多容器体系结构的常规方法。

但是,尤其是,我们面临着这样的情况:容器A启动并为容器B准备数据。容器B中的应用程序无法检查数据是否已准备就绪,而是立即开始使用它们。 因此,我们必须自己接收和处理有关数据准备就绪的信号。

我认为您仍然可以给出一些用例。 但最重要的是,您需要确切了解为什么要这样做。 否则,最好使用标准的docker-compose工具。

一点思想


如果您仔细阅读了文档,则所有内容都写在那里。 即每个
该部门是独立的,必须注意所有服务
他将与之一起工作。

因此,问题不是启动或不启动容器,而是
在容器内,仅检查所有必要服务的准备情况
然后将控制权转移到容器应用程序。

如何实施


为了解决这个问题, docker-compose描述对我有很大帮助, 部分
以及关于正确使用entrypointcmd的文章

所以我们需要得到:

  • 我们在容器A中包装了一个附录A
  • 它启动并开始在端口8000上响应确定
  • 还有应用程序B,我们从容器B开始,但是它应该不早于应用程序A开始响应端口8000上的请求而开始工作

官方文档提供了两种解决此问题的方法。

首先是在容器中编写您自己的入口点 ,该入口点将执行所有检查,然后启动正在运行的应用程序。

第二种是使用已经写入的批处理文件wait-for-it.sh
我们尝试了两种方式。

编写自己的入口点


什么是入口点

这只是您在Dockerfile中的ENTRYPOINT字段中创建容器时指定的可执行文件。 如前所述,该文件执行检查,然后启动容器的主应用程序。

所以我们得到了:

创建一个Entrypoint文件夹。

它具有两个子文件夹-container_Acontainer_B 。 我们将在其中创建容器。

对于容器A,让我们在python上使用一个简单的http服务器。 启动后,他开始响应端口8000上的获取请求。

为了使实验更加明确,我们在启动服务器之前设置了15秒的延迟。

结果是容器A的以下docker文件

FROM python:3 EXPOSE 8000 CMD sleep 15 && python3 -m http.server --cgi 

对于容器B,为容器B创建以下docker文件

 FROM ubuntu:18.04 RUN apt-get update RUN apt-get install -y curl COPY ./entrypoint.sh /usr/bin/entrypoint.sh ENTRYPOINT [ "entrypoint.sh" ] CMD ["echo", "!!!!!!!! Container_A is available now !!!!!!!!"] 

并将我们的entrypoint.sh可执行文件放在同一文件夹中。 我们会像这样

 #!/bin/bash set -e host="conteiner_a" port="8000" cmd="$@" >&2 echo "!!!!!!!! Check conteiner_a for available !!!!!!!!" until curl http://"$host":"$port"; do >&2 echo "Conteiner_A is unavailable - sleeping" sleep 1 done >&2 echo "Conteiner_A is up - executing command" exec $cmd 

容器B中发生了什么:

  • 启动时,它将启动ENTRYPOINT ,即 启动entrypoint.sh
  • entrypoint.sh使用curl启动容器A的轮询端口8000.直到接收到200响应为止(即在这种情况下curl将以零结果结束并且循环将结束)
  • 收到200时,循环结束,控制权传递到$ cmd变量中指定的命令。 它指示了我们在CMD字段的docker文件中指示的内容,即 回声“ !!! Container_A现在可用!!!!!!!!” 上面的文章中介绍了为什么会这样
  • 我们打印- !!! Container_A现在可用!!! 并得出结论。

我们将从docker-compose开始一切。

docker-compose.yml在这里,我们有这个:

 version: '3' networks: waiting_for_conteiner: services: conteiner_a: build: ./conteiner_A container_name: conteiner_a image: conteiner_a restart: unless-stopped networks: - waiting_for_conteiner ports: - 8000:8000 conteiner_b: build: ./conteiner_B container_name: conteiner_b image: waiting_for_conteiner.entrypoint.conteiner_b restart: "no" networks: - waiting_for_conteiner 

此处,在conteiner_a中,无需指定端口:8000:8000 。 这样做是为了能够从外部验证其中运行的http服务器的操作。

同样,容器B在关闭后也不会重新启动。

我们推出:

 docker-compose up —-build 

我们看到有15秒的时间,关于容器A不可用的消息,然后

 conteiner_b | Conteiner_A is unavailable - sleeping conteiner_b | % Total % Received % Xferd Average Speed Time Time Time Current conteiner_b | Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> conteiner_b | <html> conteiner_b | <head> conteiner_b | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> conteiner_b | <title>Directory listing for /</title> conteiner_b | </head> conteiner_b | <body> conteiner_b | <h1>Directory listing for /</h1> conteiner_b | <hr> conteiner_b | <ul> conteiner_b | <li><a href=".dockerenv">.dockerenv</a></li> conteiner_b | <li><a href="bin/">bin/</a></li> conteiner_b | <li><a href="boot/">boot/</a></li> conteiner_b | <li><a href="dev/">dev/</a></li> conteiner_b | <li><a href="etc/">etc/</a></li> conteiner_b | <li><a href="home/">home/</a></li> conteiner_b | <li><a href="lib/">lib/</a></li> conteiner_b | <li><a href="lib64/">lib64/</a></li> conteiner_b | <li><a href="media/">media/</a></li> conteiner_b | <li><a href="mnt/">mnt/</a></li> conteiner_b | <li><a href="opt/">opt/</a></li> conteiner_b | <li><a href="proc/">proc/</a></li> conteiner_b | <li><a href="root/">root/</a></li> conteiner_b | <li><a href="run/">run/</a></li> conteiner_b | <li><a href="sbin/">sbin/</a></li> conteiner_b | <li><a href="srv/">srv/</a></li> conteiner_b | <li><a href="sys/">sys/</a></li> conteiner_b | <li><a href="tmp/">tmp/</a></li> conteiner_b | <li><a href="usr/">usr/</a></li> conteiner_b | <li><a href="var/">var/</a></li> conteiner_b | </ul> conteiner_b | <hr> conteiner_b | </body> conteiner_b | </html> 100 987 100 987 0 0 98700 0 --:--:-- --:--:-- --:--:-- 107k conteiner_b | Conteiner_A is up - executing command conteiner_b | !!!!!!!! Container_A is available now !!!!!!!! 

我们得到您的要求的答案,打印 现在可以使用Container_A !!!!!!!! 并得出结论。

使用wait-for-it.sh


值得一提的是,按照文档中的说明,此路径对我们不起作用。
即,众所周知,如果在Dockerfile 写入ENTRYPOINTCMD ,则在容器启动时,将执行来自ENTRYPOINT的命令,并将CMD的内容作为参数传递给它。

还众所周知,可以在docker -compose.yml中重新定义Dockerfile中指定的ENTRYPOINTCMD

wait-it-sh.sh启动格式如下:

 wait-for-it.sh __ -- ___ 

然后,如文章中所述,我们可以在docker -compose.yml中定义一个新的ENTRYPOINT ,并将从Dockerfile中替换CMD

因此,我们得到:

容器A的Docker文件保持不变:

 FROM python:3 EXPOSE 8000 CMD sleep 15 && python3 -m http.server --cgi 

容器B的Docker文件

 FROM ubuntu:18.04 COPY ./wait-for-it.sh /usr/bin/wait-for-it.sh CMD ["echo", "!!!!!!!! Container_A is available now !!!!!!!!"] 

Docker-compose.yml看起来像这样:

 version: '3' networks: waiting_for_conteiner: services: conteiner_a: build: ./conteiner_A container_name: conteiner_a image: conteiner_a restart: unless-stopped networks: - waiting_for_conteiner ports: - 8000:8000 conteiner_b: build: ./conteiner_B container_name: conteiner_b image: waiting_for_conteiner.wait_for_it.conteiner_b restart: "no" networks: - waiting_for_conteiner entrypoint: ["wait-for-it.sh", "-s" , "-t", "20", "conteiner_a:8000", "--"] 

我们运行wait-it-it命令,告诉它等待20秒,直到容器A生效,并指定另一个参数“-” ,该参数应将wait-for-it参数与完成后将启动的程序分开。

我们尝试!
不幸的是,我们什么也没得到。

如果检查使用哪些参数运行wait-for-it,则将看到仅将在入口点中指定的内容传递给它 ,而未附加容器中的CMD

工作选项


然后,只有一种选择。 我们在DockerfileCMD中指定的内容 ,我们必须转移到docker-compose.yml中命令

然后,保持容器B 的Dockerfile不变,并且docker-compose.yml将如下所示:

 version: '3' networks: waiting_for_conteiner: services: conteiner_a: build: ./conteiner_A container_name: conteiner_a image: conteiner_a restart: unless-stopped networks: - waiting_for_conteiner ports: - 8000:8000 conteiner_b: build: ./conteiner_B container_name: conteiner_b image: waiting_for_conteiner.wait_for_it.conteiner_b restart: "no" networks: - waiting_for_conteiner entrypoint: ["wait-for-it.sh", "-s" ,"-t", "20", "conteiner_a:8000", "--"] command: ["echo", "!!!!!!!! Container_A is available now !!!!!!!!"] 

在此版本中,它可以工作。

总之,必须说,我们认为正确的方法是第一。 它用途最广泛,可以让您以任何可能的方式进行准备情况检查。 等待它只是一个有用的实用程序,您可以单独使用它,也可以将其嵌入到entrypoint.sh中

Source: https://habr.com/ru/post/zh-CN454552/


All Articles