关于Docker|关于Docker Compose的启动顺序的讨论 - springboot实战电商项目mall4j

springboot实战电商项目mall4j (https://gitee.com/gz-yami/mall4j)
java商城系统源码
以下讨论基于Docker Compose的V2版本:
现在容器编排使用广泛,大家都在用docker-compose进行网站部署,在部署的过程中,会使用到Mysql,Redis,JAVA后端程序,PHP后端程序之类的,会涉及到一个程序的启动先后问题,一般的解决方案都是在Compose文件中,添加depends_on参数,例如:

version: "2.4" services: xxl-job-mysql: container_name: xxl-job-mysql environment: - TZ=Asia/Shanghai - MYSQL_ROOT_PASSWORD=123456 image: mysql:5.7 networks: xxl-job-network: ipv4_address: 192.168.189.2 ports: - 3306:3306 restart: always volumes: - ./mysql/data:/var/lib/mysql - ./mysql/initdb:/docker-entrypoint-initdb.dxxl-job-admin: container_name: xxl-job-admin environment: - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \ --spring.datasource.username=root \ --spring.datasource.password=123456 \ --xxl.job.accessToken= image: xuxueli/xxl-job-admin:2.3.0 networks: xxl-job-network: ipv4_address: 192.168.189.3 ports: - 8080:8080 restart: always # 依赖于数据库,先启动数据库再启动JAVA程序 depends_on: - xxl-job-mysqlnetworks: xxl-job-network: driver: bridge ipam: config: - subnet: 192.168.189.0/24 name: xxl-job-network

以上编排文件好像看起来没什么问题,但实际执行的时候,在数据库启动后,因为depends_on参数,会在启动数据库后马上启动JAVA程序。不管Mysql是否已经启动完成。这时候就可能出现数据库还没启动好,JAVA程序就已经启动完成,并尝试去连接数据库,造成连接失败,JAVA程序报错。所以说,depends_on并没有真正的等待上一个程序启动完成。这个在docker官方文档也有说明:https://docs.docker.com/compo...
里面有一句"However, for startup Compose does not wait until a container is “ready” (whatever that means for your particular application) - only until it’s running. There’s a good reason for this."
关于Docker|关于Docker Compose的启动顺序的讨论 - springboot实战电商项目mall4j
文章图片

所以,官方推荐了一些TCP检测的小工具,用于检测程序端口是否畅通,一旦畅通代表程序已经启动完成。
下面,我们来使用wait-for-it小工具:https://github.com/vishnubob/... 来检测数据库是否已经启动完成
xxl-job-admin的java程序的dockerfile内容如下:
FROM openjdk:8-jre-slim MAINTAINER xuxueliENV PARAMS=""ENV TZ=PRC RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezoneADD target/xxl-job-admin-*.jar /app.jarENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]

xxl-job-admin: container_name: xxl-job-admin environment: - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \ --spring.datasource.username=root \ --spring.datasource.password=123456 \ --xxl.job.accessToken= image: xuxueli/xxl-job-admin:2.3.0 networks: xxl-job-network: ipv4_address: 192.168.189.3 ports: - 8080:8080 restart: always # 依赖于数据库,先启动数据库再启动JAVA程序 depends_on: - xxl-job-mysql volumes: # 把wait-for-it脚本挂载到容器内根目录, # 因为查看xxl-job-admin的dockerfile得知app.jar也在根目录,所以放在同一个目录下 - ./wait-for-it.sh:/wait-for-it.sh # 使用command命令,先执行wait-for-it等待数据库启动完成,然后执行java -jar app.jar command: ["/wait-for-it.sh", "xxl-job-mysql:3306", "--", "sh", "-c", "java -jar $JAVA_OPTS /app.jar $PARAMS"]

完整代码如下:
version: "2.4" services: xxl-job-mysql: container_name: xxl-job-mysql environment: - TZ=Asia/Shanghai - MYSQL_ROOT_PASSWORD=123456 image: mysql:5.7 networks: xxl-job-network: ipv4_address: 192.168.189.2 ports: - 3306:3306 restart: always volumes: - ./mysql/data:/var/lib/mysql - ./mysql/initdb:/docker-entrypoint-initdb.dxxl-job-admin: container_name: xxl-job-admin environment: - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \ --spring.datasource.username=root \ --spring.datasource.password=123456 \ --xxl.job.accessToken= image: xuxueli/xxl-job-admin:2.3.0 networks: xxl-job-network: ipv4_address: 192.168.189.3 ports: - 8080:8080 restart: always # 依赖于数据库,先启动数据库再启动JAVA程序 depends_on: - xxl-job-mysql volumes: # 把wait-for-it脚本挂载到容器内根目录, # 因为查看xxl-job-admin的dockerfile得知app.jar也在根目录,所以放在同一个目录下 - ./wait-for-it.sh:/wait-for-it.sh # 使用command命令,先执行wait-for-it等待数据库启动完成,然后执行java -jar app.jar command: ["/wait-for-it.sh", "xxl-job-mysql:3306", "--", "sh", "-c", "java -jar $JAVA_OPTS /app.jar $PARAMS"]networks: xxl-job-network: driver: bridge ipam: config: - subnet: 192.168.189.0/24 name: xxl-job-network

除了官方推荐的TCP检测的小工具以外,官方还提供了一个方法,healthcheck,健康检测,文档连接:https://docs.docker.com/engin...
关于Docker|关于Docker Compose的启动顺序的讨论 - springboot实战电商项目mall4j
文章图片

根据文档意思,我们添加healthcheck参数,代码如下:
xxl-job-admin:
# 增加healthcheck参数:执行'mysqladmin ping -h localhost'命令 # 执行时间设定为10秒,超时时常设定为20秒,重试次数为10次,如果能ping通,说明数据库启动完成 healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] interval: 10s timeout: 20s retries: 10

xxl-job-mysql:
depends_on: xxl-job-mysql: # java容器在mysql容器状态为健康前不会启动 condition: service_healthy

完整代码如下:
version: "2.4" services: xxl-job-mysql: container_name: xxl-job-mysql environment: - TZ=Asia/Shanghai - MYSQL_ROOT_PASSWORD=123456 image: mysql:5.7 networks: xxl-job-network: ipv4_address: 192.168.189.2 ports: - 3306:3306 restart: always volumes: - ./mysql/data:/var/lib/mysql - ./mysql/initdb:/docker-entrypoint-initdb.d # 增加healthcheck参数:执行'mysqladmin ping -h localhost'命令 # 执行时间设定为10秒,超时时常设定为20秒,重试次数为10次,如果能ping通,说明数据库启动完成 healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] interval: 10s timeout: 20s retries: 10xxl-job-admin: container_name: xxl-job-admin environment: - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \ --spring.datasource.username=root \ --spring.datasource.password=123456 \ --xxl.job.accessToken= image: xuxueli/xxl-job-admin:2.3.0 networks: xxl-job-network: ipv4_address: 192.168.189.3 ports: - 8080:8080 restart: always depends_on: xxl-job-mysql: # java容器在mysql容器状态为健康前不会启动,需要注意的是:ondition: service_healthy,在V3版本的Compose中已废弃 condition: service_healthynetworks: xxl-job-network: driver: bridge ipam: config: - subnet: 192.168.189.0/24 name: xxl-job-network

那么,到此为止,问题是否已经解决了呢?然而,只是解决了一部分,因为在Mysql的启动脚本中,有一段命令,启动时会检测/docker-entrypoint-initdb.d文件夹中是否存在初始化sql脚本,如果存在,就会在第一次启动时执行sql,对数据库进行初始化,链接如下:https://github.com/docker-lib... 如果初始化脚本很大,就会出现一种情况,mysql的3306端口已经可以ping通,但sql还在执行中,app容器就以为他已经启动完成,从而进行数据库连接,所以,我们需要针对检测方式进行改进一下:将mysqladmin ping -h localhost替换成/usr/bin/mysql --user=root --password=123456 --execute "SHOW DATABASE; "
代码如下:
xxl-job-mysql: container_name: xxl-job-mysql environment: - TZ=Asia/Shanghai - MYSQL_ROOT_PASSWORD=123456 image: mysql:5.7 networks: xxl-job-network: ipv4_address: 192.168.189.2 ports: - 3306:3306 restart: always volumes: - ./mysql/data:/var/lib/mysql - ./mysql/initdb:/docker-entrypoint-initdb.d # 增加healthcheck参数:执行'/usr/bin/mysql --user=root --password=123456 --execute "SHOW DATABASES; "'命令 # 执行时间设定为10秒,超时时常设定为20秒,重试次数为10次,如果数据库能登陆并且可以查询时,说明数据库启动完成 healthcheck: # 只有真正能执行查询命令,才算启动完成 test: '/usr/bin/mysql --user=root --password=123456 --execute "SHOW DATABASES; "' interval: 10s timeout: 20s retries: 10

总结:
1,只存在depends_on时一定是不可靠的
2,当初始化脚本比较大时,能ping通不一定代表数据库初始化完成
3, 当数据库能登陆并且可以查询时,才算真正的启动完成
【关于Docker|关于Docker Compose的启动顺序的讨论 - springboot实战电商项目mall4j】springboot实战电商项目mall4j (https://gitee.com/gz-yami/mall4j)
java商城系统源码

    推荐阅读