docker-compose下的java运用启动递次两部曲之二:实战
2019-11-18杂谈搜奇网29°c
A+ A-上篇回忆
- 本文是《docker-compose下的java运用启动递次两部曲》的终篇,在上一篇《docker-compose下的java运用启动递次两部曲之一:题目剖析》中,我们以SpringCloud环境下的注册中间和营业效劳为例,展现了docker-compose.yml中depends_on参数的不足:即只能掌握容器建立递次,但我们想要的是eureka效劳停当以后再启动营业效劳,而且docker官方也以为depends_on参数是达不到这个请求的,以下图所示:
针对上述题目,docker给出的解决方法是运用wait-for-it.sh脚原本解决题目,地点:https://docs.docker.com/compose/startup-order/ ,以下图:
什么是wait-for-it.sh
- wait-for-it.sh剧本用来接见指定的地点和端口,假如收不到相应就守候一段时候再去重试,直到收到相应后,再去做前面指定好的敕令,如上图红框所示./wait-for-it.sh db:5432 -- python app.py的意义是:比及db:5432这个长途接见能够相应的时刻,就去实行python app.py敕令
wait-for-it.sh文件的链接:
https://raw.githubusercontent.com/zq2599/blog_demos/master/wait-for-it-demo/docker/wait-for-it.sh环境信息
本次实战的环境以下:
- 操作体系:CentOS Linux release 7.7.1908
- docker:1.13.1
- docker-compose:1.24.1
- spring cloud:Finchley.RELEASE
- maven:3.6.0
- jib:1.7.0
实战简介
上一篇的例子中,我们用到了eureka和service两个容器,eureka是注册中间,service是一般营业运用,service容器向eureka容器注册时,eureka还没有初始化完成,因此service注册失利,在稍后的自动重试时由于eureka进入ready状况,因此service注册胜利。
本日我们来革新上一篇的例子,让service用上docker官方引荐的wait-for-it.sh剧本,守候eureka效劳停当再启动java历程,确保service能够一次性注册eureka胜利;
为了抵达上述目的,统共须要做以下几步:
- 简朴引见eureka和service容器的镜像是怎样制造的;
- 制造基本镜像,包括wait-for-it.sh剧本;
- 运用新的基本镜像构建service镜像;
- 革新docker-compose.yml;
- 启动容器,考证递次掌握是不是胜利;
- wait-for-it.sh计划的缺点;
接下来进入实战环节;
源码下载
假如您不想编码,也能够在GitHub上猎取文中一切源码和剧本,地点和链接信息以下表所示:
| 称号 | 链接 | 备注|
| :-------- | :----| :----|
| 项目主页| https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
| git堆栈地点(https)| https://github.com/zq2599/blog_demos.git | 该项目源码的堆栈地点,https协定 |
| git堆栈地点(ssh)| git@github.com:zq2599/blog_demos.git | 该项目源码的堆栈地点,ssh协定 |
这个git项目中有多个文件夹,本章的运用在wait-for-it-demo文件夹下,以下图红框所示:
源码的构造以下图所示:
接下来最先编码了;
简朴引见eureka和service容器
上一篇和本篇,我们都在用eureka和service这两个容器做试验,如今就来看看他们是怎样做出来的:
- eureka是个maven工程,和SpringCloud环境中的eureka效劳一样,唯一差别的是它的pom.xml中运用了jib插件,用来将工程构建成docker镜像:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bolingcavalry</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka</name>
<description>eureka</description>
<parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>wait-for-it-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--运用jib插件-->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.7.0</version>
<configuration>
<!--from节点用来设置镜像的基本镜像,相当于Docerkfile中的FROM关键字-->
<from>
<!--运用openjdk官方镜像,tag是8-jdk-stretch,示意镜像的操作体系是debian9,装好了jdk8-->
<image>openjdk:8-jdk-stretch</image>
</from>
<to>
<!--镜像称号和tag,运用了mvn内置变量${project.version},示意当前工程的version-->
<image>bolingcavalry/${project.artifactId}:${project.version}</image>
</to>
<!--容器相干的属性-->
<container>
<!--jvm内存参数-->
<jvmFlags>
<jvmFlag>-Xms1g</jvmFlag>
<jvmFlag>-Xmx1g</jvmFlag>
</jvmFlags>
<!--要暴露的端口-->
<ports>
<port>8080</port>
</ports>
<useCurrentTimestamp>true</useCurrentTimestamp>
</container>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>dockerBuild</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
上述pom.xml中多了个jib插件,如许在实行mvn compile的时刻,插件就会用构建效果制造好docker镜像并放入当地堆栈;
- service是个一般的SpringCloud运用,除了在pom.xml中也用到了jib插件来构建镜像,它的设置文件中,接见eureka的地点要写成eureka容器的称号:
spring:
application:
name: service
eureka:
client:
serviceUrl:
defaultZone: http://eureka:8080/eureka/
- 关于如何将java运用制造成docker镜像,假如您想相识更多请参考以下两篇文章:
《Docker与Jib(maven插件版)实战》
《Jib运用小结(Maven插件版)》
制造基本镜像
从上面的pom.xml可见,我们将Java运用制造成docker镜像时,运用的基本镜像是openjdk:8-jdk-stretch,如许做出的运用镜像是不含wait-for-it.sh剧本的,天然就没法完成启动递次掌握了,因此我们要做一个带有wait-for-it.sh的基本镜像给营业镜像用:
- 把wait-for-it.sh文件准备好,下载地点:https://raw.githubusercontent.com/zq2599/blog_demos/master/wait-for-it-demo/docker/wait-for-it.sh
- 在wait-for-it.sh文件地点目次新建Dockerfile文件,内容以下:
FROM openjdk:8-jdk-stretch
ADD wait-for-it.sh /wait-for-it.sh
RUN sh -c 'chmod 777 /wait-for-it.sh'
注重:我这里用的是openjdk:8-jdk-stretch,您能够依据本身的实际须要挑选差别的openjdk版本,能够参考:《openjdk镜像的tag申明》
- 实行敕令docker build -t bolingcavalry/jkd8-wait-for-it:0.0.2 .就可以构建出名为bolingcavalry/jkd8-wait-for-it:0.0.2的镜像了,请您依据本身的状况设置镜像称号和tag,注重敕令的末端有个小数点,不要漏了;
- 假如您有hub.docker.com账号,建请运用docker push敕令将新建的镜像推送到镜像堆栈上去,或许推送到私有堆栈,由于背面运用jib插件构建镜像是,jib插件要去堆栈猎取基本镜像的元数据信息,取不到会致使构建失利;
运用新的基本镜像构建service镜像
我们的目的是让service效劳守候eureka效劳停当,所以应当革新service效劳,让它用docker官方引荐的wait-for-it.sh计划来完成守候:
- 修正service工程的pom.xml,有关jib插件的设置改成以下内容:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.7.0</version>
<configuration>
<!--from节点用来设置镜像的基本镜像,相当于Docerkfile中的FROM关键字-->
<from>
<!--运用克己的基本镜像,内里有wait-for-it.sh剧本-->
<image>bolingcavalry/jkd8-wait-for-it:0.0.2</image>
</from>
<to>
<!--镜像称号和tag,运用了mvn内置变量${project.version},示意当前工程的version-->
<image>bolingcavalry/${project.artifactId}:${project.version}</image>
</to>
<!--容器相干的属性-->
<container>
<!--entrypoint的值即是INHERIT示意jib插件不构建启动敕令了,此时要运用者本身掌握,能够在启动时输入,或许写在基本镜像中-->
<entrypoint>INHERIT</entrypoint>
<!--要暴露的端口-->
<ports>
<port>8080</port>
</ports>
<useCurrentTimestamp>true</useCurrentTimestamp>
</container>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>dockerBuild</goal>
</goals>
</execution>
</executions>
</plugin>
上述设置有几点须要注重:
a. 基本镜像改成方才构建好的bolingcavalry/jkd8-wait-for-it:0.0.2
b. 增添entrypoint节点,内容是INHERIT,根据官方的说法,entrypoint的值即是INHERIT示意jib插件不构建启动敕令了,此时要运用者本身掌握,能够在启动时输入,或许写在基本镜像中,如许我们在docker-compose.yml顶用command参数来设置service容器的启动敕令,就可以够把wait-for-it.sh剧本用上了
c. 去掉jvmFlags节点,根据官方文档的说法,entrypoint节点的值即是INHERIT时,jvmFlags和mainClass参数会被疏忽,以下图,地点是:https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin
至此,service工程革新终了,接下来修正docker-compose.yml,让service容器能用上wait-for-it.sh
### 革新docker-compose.yml
- 完全的docker-compose.yml内容以下所示:
version: '3'
services:
eureka:
image: bolingcavalry/eureka:0.0.1-SNAPSHOT
container_name: eureka
restart: unless-stopped
service:
image: bolingcavalry/service:0.0.1-SNAPSHOT
container_name: service
restart: unless-stopped
command: sh -c './wait-for-it.sh eureka:8080 -t 0 -- java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.ServiceApplication'
depends_on:
- eureka
- 注重command参数的内容,以下,service容器建立后,会一向守候eureka:8080的相应,直到该地点有相应后,才会实行敕令java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.ServiceApplication:
sh -c './wait-for-it.sh eureka:8080 -t 0 -- java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.ServiceApplication'
- 关于敕令java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.ServiceApplication,您能够以为太长了不好写,这里有个小窍门,就是在不运用entrypoint节点的时刻,用jib插件制造的镜像本身是带有启动敕令的,容器运转的时刻,您能够经由过程docker ps --no-trunc敕令看到该容器的完全启动敕令,复制过来直接用就好了;
一切的革新事情都完成了,能够最先考证了;
启动容器,考证递次掌握是不是胜利
- 在docker-compose.yml文件地点目次实行敕令docker-compose up,会建立两个容器,而且日记信息会直接打印在掌握台,我们来剖析这些日记信息,考证递次掌握是不是胜利;
- 以下图,可见service容器中并没有启动java历程,而是在守候eureka:8080的相应:
- 继承看日记,可见eureka效劳停当的时刻,service容器的wait-for-it.sh剧本收到了相应,因而马上启动service运用的历程:
继承看日记,以下图,service在eureka上注册胜利:
综上所述,运用docker官方引荐的wait-for-it.sh来掌握java运用的启动递次是可行的,能够根据营业本身的需求来量身定做适宜的启动递次;wait-for-it.sh计划的缺点
运用docker官方引荐的wait-for-it.sh来掌握容器启动递次,虽然已满足了我们的需求,但照旧留不是圆满计划,留下的缺点照样请您先晓得吧,或许这个缺点会对您的体系发生严峻的负面影响:
- 再开启一个SSH衔接,登录到实战的linux电脑上,实行敕令docker exec eureka ps -ef,将eureka容器内的历程打印出来,以下所示,java历程的PID即是1:
[root@maven ~]# docker exec eureka ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 2 07:04 ? 00:00:48 java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.EurekaApplication
root 56 0 0 07:25 ? 00:00:00 /bin/bash
root 63 0 0 07:31 ? 00:00:00 ps -ef
- 再来看看service的历程状况,实行敕令docker exec service ps -ef,将service容器内的历程打印出来,以下所示,PID即是1的历程不是java,而是启动时的shell敕令:
[root@maven ~]# docker exec service ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 07:04 ? 00:00:00 sh -c ./wait-for-it.sh eureka:8080 -t 0 -- java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.ServiceApplication
root 7 1 1 07:04 ? 00:00:32 java -Xms1g -Xmx1g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.waitforitdemo.ServiceApplication
root 107 0 0 07:33 ? 00:00:00 ps -ef
- 通常状况下,在实行敕令docker stop xxx住手容器时,只要PID=1的历程才会收到"SIGTERM"信号量,所以在运用docker stop住手容器时,eureka容器中的java历程收到了"SIGTERM"能够马上住手,然则service容器中的java历程收不到"SIGTERM",因此只能比及默许的10秒超时时候抵达的时刻,被"SIGKILL"信号量杀死,不只守候时候长,而且文雅停机的功用也用不上了;
- 您能够离别输入docker stop eureka和docker stop service来感受一下,前者马上完成,后者要守候10秒。
- 我的shell妙技过于平凡,现在还找不到好的解决方法让service容器中的java历程获得1号历程ID,个人以为自定义entrypoint.sh脚原本挪用wait-for-it.sh而且处置惩罚"SIGTERM"说不定可行,假如您有好的方法请留言示知,在此感激涕零;
- 现在看来,掌握容器启动递次最好的解决计划并不是wait-for-it.sh,而是营业本身完成容错,比方service注册eureka失利后会自动重试,然则这对营业的请求就略高了,尤其是在庞杂的分布式环境中越发难以完成;
docker官方引荐运用wait-for-it.sh剧本的文章地点是:https://docs.docker.com/compose/startup-order/ ,文章末端显现了顶和踩的数目,以下图,顶的数目是145,踩的数目抵达了563,一份官方文档竟然这么不受待见,也算是开了眼界,不知道和我前面提到的1号PID题目有无关联:
至此,java运用的容器递次掌握实战就完成了,愿望您在对本身的运用做容器化的时刻,此文能给您供应一些参考。迎接关注民众号:程序员欣宸