在使用 Docker Compose 编排容器时,就涉及到容器启动的先后顺序。

比如一个Laravel应用,需要用到 PHP/Nginx/Mysql/Redis, 那么肯定是需要先启动 Mysql/Redis,然后才是PHP,最后才是Nginx。 因为 MySQL/Redis 是基础服务,PHP中的代码会去连接,所以要先启动; 而Nginx作为请求转发,需要PHP容器(php-fpm)准备就绪了才能正确转发。

启动顺序要求,很容易通过depends_on指令来实现,标明当前容器需要依赖哪些容器,Docker compose 会判断,并决定启动顺序。

但是现实世界就是可能有意外,这里要知道,容器启动了,并不代表容器里的服务已经【就绪】了。 我们要使用的是容器里的服务,而不是容器本身。

举例来说,构建过镜像的人都知道,一个MySQL镜像,通常是将MySQL安装进了镜像里,镜像(容器)启动后,才会去启动MySQL,监听在对应端口等待连接。当我们依赖MySQL数据库时,就不止要确保MySQL容器已经启动,而要确保里面的MySQL数据库已经启动好,等待客户端的连接了!

这在大多数情况下,可能问题不大,一般无论容器还是里面的服务启动都挺快的。但我之前试着把工作上的Java容器化时,就碰到了。简化来说,我的代码中依赖了nacos,必须要等nacos启动后,才能运行Java程序。但是 nacos 启动有点慢!

但是怎么确保nacos服务已经启动好了,而不只是容器启动好了呢?

这类问题,也有解决思路:一般我们依赖的服务,通常都监听在某个端口上,等待连接,那么可以通过循环监测对应的端口(mysql:3306;redis:6379;nacos:8848),如果端口能够连通,则服务已经起来了,我们再启动自己的程序。

这样,我们可以写一个启动脚本 entrypoint.sh, 在里面首先循环去监测依赖的服务是否都启动好了,然后再运行需要的程序。

那如何去监测端口呢? 最简单的,就是用 nc 命令去做。

但是又有问题,因为构建镜像要精简,很可能镜像里面并不包含 nc 命令!

好在市面上有一些能做这个事情的脚本,不依赖其他命令,例如 docker compose 官方推荐的 wait-for-it 以及类似的脚本。

参考文档:
官方文档