一、前言
docker-compose可以方便组合多个 docker 容器服务, 但是, 当容器服务之间存在依赖关系时, docker-compose 并不能保证服务的启动顺序。docker-compose 中的 depends_on 配置是容器的启动顺序, 并不是容器中服务的启动顺序。本章将详细叙述如何解决 docker-compose 顺序启动微服务的问题。
二、解决方案
经过两天的努力,大致总结出了三种解决顺序启动微服务的方案:
足够的容错和重试机制,比如从配置中心获取配置文件,服务消费者可以不断的重试直到连上为止,这里就用到了docker-compose 中的
restart
配置,docker-compose.yml如下:
version: "3" services: # 指定服务名称 #服务注册与发现中心 simonEureka: image: simon/eureka-server:2.0.1-SNAPSHOT hostname: simonEureka ports: - "8100:8100" #配置中心 simonConfig: image: simon/config-server:2.0.1-SNAPSHOT hostname: simonConfig ports: - "8101:8101" depends_on: - simonEureka restart: always #路由网关 apigateway: image: simon/apigateway:2.0.1-SNAPSHOT ports: - "8102:8102" depends_on: - simonEureka - simonConfig restart: always #监控平台 admin: image: simon/admin:2.0.1-SNAPSHOT ports: - "8103:8103" depends_on: - simonEureka - simonConfig restart: always
docker-compose.yml进行拆分,分成两部分部署, 将要先启动的服务放在一个docker-compose中,后启动的服务放在第二个docker-compose中,启动两次,两者使用同一个网络,启动命令示例:
$ docker-compose -f docker-compose-commond.yml up
同步等待,使用shell脚本阻止当前服务启动,直到所需依赖的服务全部启动之后再启动当前服务。
下面我将详细的讲述第三种解决顺序启动问题的方案。部署的微服务清单如下:
服务名 | 端口 | 服务说明 | 依赖服务 | 启动优先级(优先级越高越先启动) |
---|---|---|---|---|
eureka-service | 8100 | 服务注册与发现 | --- | 1 |
config-server | 8101 | 配置中心 | eureka-server | 2 |
apigateway | 8102 | 网关服务 | eureka-server,config-server | 3 |
admin | 8103 | 监控服务 | eureka-server,config-server | 3 |
2.1 各微服务镜像构建配置
由于各微服务的镜像构建配置差不多,这里只列举配置中心的配置:
<!-- Docker maven plugin --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.0.0</version> <configuration> <imageName>simon/${project.artifactId}:${project.version}</imageName> <!--<dockerDirectory>src/main/docker</dockerDirectory>--> <forceTags>true</forceTags> <baseImage>java</baseImage> <!--安装镜像所需要的软件--> <runs> <!--同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,这样才能获取到最新的软件包--> <run>["apt-get","update"]</run> <!--安装netcat--> <run>["apt-get","-y","install","netcat"]</run> </runs> <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin>
runs
标签表示在构建镜像的时候,会顺序执行标签run
中的命令,因为后面顺序启动微服务需要镜像中包含netcat
,所以在构建镜像的时候要进行安装。
2.2 同步等待脚本和使用
下面一共提供两种脚本,但前提是镜像中必须如上一节安装netcat
2.2.1 检测服务是否启动的脚本wait-for.sh
#!/bin/shTIMEOUT=15 QUIET=0echoerr() { if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi}usage() { exitcode="$1" cat << USAGE >&2 Usage: $cmdname host:port [-t timeout] [-- command args] -q | --quiet Do not output any status messages -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit "$exitcode"}wait_for() { for i in `seq $TIMEOUT` ; do nc -z "$HOST" "$PORT" > /dev/null 2>&1 result=$? if [ $result -eq 0 ] ; then if [ $# -gt 0 ] ; then exec "$@" fi exit 0 fi sleep 1 done echo "Operation timed out" >&2 exit 1 }while [ $# -gt 0 ]do case "$1" in *:* ) HOST=$(printf "%s\n" "$1"| cut -d : -f 1) PORT=$(printf "%s\n" "$1"| cut -d : -f 2) shift 1 ;; -q | --quiet) QUIET=1 shift 1 ;; -t) TIMEOUT="$2" if [ "$TIMEOUT" = "" ]; then break; fi shift 2 ;; --timeout=*) TIMEOUT="${1#*=}" shift 1 ;; --) shift break ;; --help) usage 0 ;; *) echoerr "Unknown argument: $1" usage 1 ;; esacdoneif [ "$HOST" = "" -o "$PORT" = "" ]; then echoerr "Error: you need to provide a host and port to test." usage 2fiwait_for "$@"
查看使用示例输入一下命令:
./wait-for.sh --help
示例:
$ ./wait-for.sh www.baidu.com:80 -- echo "baidu is up"baidu is up
2.2.2 docker-compose.yml
#docker compose编排微服务脚本version: "3"services: # 指定服务名称 simonEureka: image: simon/eureka-server:2.0.1-SNAPSHOT hostname: simonEureka ports: - "8100:8100" simonConfig: image: simon/config-server:2.0.1-SNAPSHOT hostname: simonConfig ports: - "8101:8101" volumes: - "./wait-for.sh:/wait-for.sh" entrypoint: "sh /wait-for.sh ibaseEureka:8100 -- java -jar /config-server.jar"
关于配置中心和服务注册中心的配置有疑惑的可以查看Docker Compose编排微服务关于localhost问题的描述;
实际使用中, 也可以将
wait-for.sh
打包到发布的镜像之中, 不用通过 volumes 配置来加载wait-for.sh
脚本;entrypoint配置会覆盖maven docker插件
entrypoint
标签的配置而执行,这里就是控制服务启动顺序的关键配置。
2.2.3 检测服务是否启动的脚本entrypoint.sh
#!/bin/bash#set -x#******************************************************************************# @file : entrypoint.sh# @author : simon# @date : 2018-08-28 15:18:43## @brief : entry point for manage service start order# history : init#******************************************************************************: ${SLEEP_SECOND:=2}wait_for() { echo Waiting for $1 to listen on $2... while ! nc -z $1 $2; do echo waiting...; sleep $SLEEP_SECOND; done}declare DEPENDSdeclare CMDwhile getopts "d:c:" argdo case $arg in d) DEPENDS=$OPTARG ;; c) CMD=$OPTARG ;; ?) echo "unkonw argument" exit 1 ;; esacdonefor var in ${DEPENDS//,/}do host=${var%:*} port=${var#*:} wait_for $host $portdoneeval $CMD#避免执行完命令之后退出容器tail -f /dev/null
这个脚本有 2 个参数,:
-d: 需要等待的服务和端口,例如:
simonEureka:8080
,simonEureka:8080,simonConfig:8081
;-c: 等待的服务和端口启动之后, 自己的启动命令,例如:
java -jar eureka.jar
2.2.4 docker-compose.yml
#docker compose编排微服务脚本version: "3"services: # 指定服务名称 simonEureka: image: simon/eureka-server:2.0.1-SNAPSHOT hostname: simonEureka ports: - "8100:8100" simonConfig: image: simon/config-server:2.0.1-SNAPSHOT hostname: simonConfig ports: - "8101:8101" depends_on: - simonEureka volumes: - "./entrypoint.sh:/entrypoint.sh" environment: SLEEP_SECOND: 4 tty: true entrypoint: /entrypoint.sh -d simonEureka:8100 -c 'java -jar /config-server.jar'; apigateway: image: simon/apigateway:2.0.1-SNAPSHOT ports: - "8102:8102" depends_on: - simonEureka - simonConfig volumes: - "./entrypoint.sh:/entrypoint.sh" environment: SLEEP_SECOND: 4 tty: true entrypoint: /entrypoint.sh -d simonEureka:8100,simonConfig:8101 -c 'java -jar /apigateway.jar'; admin: image: simon/admin:2.0.1-SNAPSHOT ports: - "8103:8103" depends_on: - simonEureka - simonConfig volumes: - "./entrypoint.sh:/entrypoint.sh" environment: SLEEP_SECOND: 4 tty: true entrypoint: /entrypoint.sh -d simonEureka:8100,simonConfig:8101 -c 'java -jar /admin.jar';
关于配置中心和服务注册中心的配置有疑惑的可以查看Docker Compose编排微服务关于localhost问题的描述;
实际使用中, 也可以将
entrypoint.sh
打包到发布的镜像之中, 不用通过 volumes 配置来加载entrypoint.sh
脚本;entrypoint配置会覆盖maven docker插件
entrypoint
标签的配置而执行,这里就是控制服务启动顺序的关键配置。
启动服务
$ docker-compose up
[root@simon simon2.0]# docker-compose upStarting simon20_simonEureka_1 ... doneStarting simon20_simonConfig_1 ... doneStarting simon20_admin_1 ... doneStarting simon20_apigateway_1 ... doneAttaching to simon20_simonEureka_1, simon20_simonConfig_1, simon20_admin_1, simon20_apigateway_1 simonConfig_1 | Waiting for simonEureka to listen on 8100... simonConfig_1 | waiting... admin_1 | Waiting for simonEureka to listen on 8100... admin_1 | waiting... apigateway_1 | Waiting for simonEureka to listen on 8100... apigateway_1 | waiting... ......
其它服务都在等待simonEureka
服务启动,这样就实现了服务的顺序启动,最终所有服务全部启动,如下是注册服务信息:
服务注册信息
作者:AaronSimon
链接:https://www.jianshu.com/p/d2f39703bcb5