pampas docker compose 启动顺序

docker compose 启动顺序怎么了?

dockerdocker compose 就不在此详细介绍了, 小伙伴可自行 google. 后期有精力也可以开一个话题简单介绍一下, 目前在准备的跟 docker 相关的都有 [docker ufs 简析], 还没写完, 后续会放出.

使用过 docker compose 的同学应该都了解, docker-compose.yml 的配置文件里, 对于 service 的依赖有一个描述, 就是 depends_on.

该配置声明了 docker service 的依赖顺序, 会在启动时将顺序调整. 如 galaxy 举例, 其 docker-compose 简化后如下:

version: 3  
services:  
  galaxy-front:
    depends_on:
    - "galaxy-web"
    - "galaxy-mysql"
  galaxy-web:
    depends_on:
    - "galaxy-mysql"
    - "galaxy-zookeeper"
  galaxy-mysql:
  galaxy-zookeeper:
  galaxy-user:
    depends_on:
    - "galaxy-mysql"
    - "galaxy-zookeeper"

docker compose 在启动时, 会将服务进行依赖排序, 将没有被依赖的调整至最前, 被依赖的后置, 以此方式调整后, 上述启动顺序则为:

1. [galaxy-mysql, galaxy-zookeeper]  
2. [galaxy-user, galaxy-web]  
3. [galaxy-front]  

看起来十分完美, 无懈可击. docker compose 在启动时确实是按照这个顺序启动的, 然而...

docker compose 启动顺序潜在的问题

然而... docker compose 在启动时, 之确保当前顺序的服务启动, 并不会在其 ready 的状态再去启动后续服务.

因为 docker 没有很好的 health check 机制去验证服务是否健康, 只能通过主进程是否运行来判断服务是否启动.

这问题就大了, 虽然第一时间启动了 mysqlzookeeper, 但是 mysql 启动是需要时间的, 然而有一些服务会在启动时去拉取数据库的数据, 但是mysql 进程虽起, 并没有启动完毕, 并没有提供服务. 这就导致了该服务启动失败, 从而导致整个 docker compose 启动出现了问题.

官方文档也明确说明了:

depends_on will not wait for db and redis to be “ready” before starting web - only until they have been started.

这可怎么办?

docker 官方提供的方案

翻了翻文档, 就在 depends_on 的部分找到了一条说明:

If you need to wait for a service to be ready, see Controlling startup order for more on this problem and strategies for solving it.

官方例子中, 有一个 wait-for-itshell 例子, 基本原理实际上就是: 轮询对应IP:PORT, 通了之后执行命令.

哎呦? 不错哦.

但是, 试了试发现通过 /dev/tcp/$ip/$port 的形式创建 socketalpine 中无法实现, 可能是没有这部分功能, 这就尴尬了...

峰回路转, 发现 NC

本来准备深追这个问题, 想看看为什么 alpine 不支持这种形式的时候, 发现了另外一个命令, nc:

BusyBox v1.25.1 (2016-10-26 16:15:20 GMT) multi-call binary.

Usage: nc [OPTIONS] HOST PORT  - connect  
nc [OPTIONS] -l -p PORT [HOST] [PORT]  - listen

    -e PROG Run PROG after connect (must be last)
    -l  Listen mode, for inbound connects
    -lk With -e, provides persistent server
    -p PORT Local port
    -s ADDR Local address
    -w SEC  Timeout for connects and final net reads
    -i SEC  Delay interval for lines sent
    -n  Don't do DNS resolution
    -u  UDP mode
    -v  Verbose
    -o FILE Hex dump traffic
    -z  Zero-I/O mode (scanning)

nc 就可以完成上述检测端口的能力, 而且在 alpine 中直接就有, 不需要额外增加任何工具, 使用方式也很简单:

nc -z $ip $port  
result=$?  

这样就只需要判断 result 是不是 0, 就可以知道目标 IP:PORT 是否可以联通, 从而用来检测服务是否已经 ready.

下来只是需要一个 wait-for-itnc 版. 这就简单多了, 而且我写的更简单...

#!/bin/sh
echo -n "wait $1:$2..."  
for k in $( seq 1 180 )  
do  
  nc -z $1 $2
  if [ $? -ne 0 ]; then
    echo -n .
    sleep 1
  else 
    echo ok
    break
  fi
done  
$3

命令接受 3 个参数, 分别是 IP, PORT要执行的命令, 180秒 后如果还不能联通, 就死马当做活马医, 启动试试...

这样调用起来也很简单, 只要满足我的需求即可, 如上述 galaxy 的例子, 就可以在 galaxy-usergalaxy-web 上, 增加等待 galaxy-mysql 的命令即可. 例如:

ADD https://raw.githubusercontent.com/mxsl-gr/wait-for-docker/master/wait.sh /wait.sh

CMD  sh /wait.sh galaxy-mysql 3306 "java -jar /opt/app.jar"  

另外, galaxy-front 等待 galaxy-web 即可, 不在复述.

pampas? wait for what?

pampas 在启动项目的时候, 也会有类似问题, 但是服务之间的依赖异常复杂, 如果要一个一个都完全按照顺序起, 怪麻烦的. 如果依赖复杂, 都是多依赖, 那就更复杂了...

所以 pampas 用了一种比较取巧的方式, 做了 3 层启动模式. 哪 3 层?

  1. middleware 层
  2. migrate 层
  3. pampas module 层

具体怎么回事呢?

  1. 先启动无依赖的 middleware
  2. migrate 等待 middleware 启动之后启动, 完成 migration 动作后, 开放出一个端口
  3. 其他所有的 pampas module 等待 migrate 的端口, 启动所有 pampas module

为什么要这么做呢?

原因很多啦, 最大的原因肯定不是 ...

真正最大的原因, 其实是这已经能够满足大多数场景, 因为服务之间一般不会直接依赖, 都是依赖于中间件, 只要中间件启动之后, 再去启动服务, 基本就没什么问题了.

另外一个比较大的问题就是, 需要去解决服务多依赖的问题, 这个问题比较难处理...

写在最后

其实 docker 已经有 healthcheck 的配置, 后期应该也可以基于这个去确认一个服务是否健康, 当然也就可以去确认服务是否 ready, 就不需要去搞这么多蛋疼的东西了.

传说 docker swarm 已经支持了, 具体我暂时没有时间去研究, 只能先挖个坑, 后面再补上了...

耿荣

Read more posts by this author.

中国浙江省杭州市

Subscribe to The Terminus Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!