在《极速体验docker容器健康》一文已体验了docker容器健康检查功能,今天就来给java应用的容器加入健康检查,使应用的状态随时都可以被监控和查看。
实战环境信息
- 操作系统:macOS Catalina 10.15
- Docker:19.03.2
java应用简介
今天实战的java应用,是用来模拟生产环境应用的,特点如下:
- 普通springboot应用,对外提供http服务,路径:<font color="blue">/hello</font>
- springboot应用运行在docker容器,在容器的<font color="blue">/app/depend/</font>目录下有名为<font color="blue">abc.txt</font>的文件;
- 上述abc.txt文件存在时,springboot应用的hello接口正常,若abc.txt不存在,springboot应用就不对外提供服务,相当于不健康状态(以此来模拟应用出现异常);
源码下载
如果您不想写代码,上述springboot应用的源码可在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协议 | </br>
这个git项目中有多个文件夹,本章的应用在<font color="blue">springboot-app-docker-health-check</font>文件夹下,如下图红框所示: 
步骤简介
应用接入容器健康检查的步骤如下:
- 将java应用制作成docker镜像时需要基础镜像,因此先准备好基础镜像,将容器健康检查的参数都配置在基础镜像中,包括提供容器健康信息的接口路径,这里定为<font color="blue">/getstate</font>;
- 改造java应用,提供<font color="blue">/getstate</font>接口服务,根据业务的实际情况决定当前应用是否健康,健康时返回码为200,不健康时返回码为403;
- 编译构建应用并且生成docker镜像;
- 验证;
制作基础镜像
-
创建名为<font color="blue">Dockerfile</font>的文件,内容如下:
Docker file from bolingcavalry # VERSION 0.0.1
Author: bolingcavalry
#基础镜像 FROM openjdk:8-jdk-stretch
#作者 MAINTAINER BolingCavalry zq2599@gmail.com
#健康检查参数设置,每5秒检查一次,接口超时时间2秒,连续10次返回1就判定该容器不健康 HEALTHCHECK --interval=5s --timeout=2s --retries=10
CMD curl --silent --fail localhost:8080/getstate || exit 1
由上述可见Dockerfile的内容非常简单,选定自身的基础镜像为<font color="blue">openjdk:8-jdk-stretch</font>,再配置好健康检查参数: | 参数名 | 作用 | |--|--| | health-cmd | 指定命令在容器内执行,用于检查容器健康状态 | | health-interval | 每次健康检查的间隔时间,默认30秒 | | health-retries | 假设该值为3,表示若连续三次检测的返回结果都是不健康,就判定该容器不健康,默认值为3 | | health-timeout | 超时时间,默认30秒 | 2. 在Dockerfile文件所在目录执行命令<font color="blue">docker build -t bolingcavalry/jdk8-healthcheck:0.0.1 .</font>(最后那个点号不要漏掉),控制台输出如下,提示镜像构建成功:
1(base) zhaoqindeMacBook-Pro:springboot-app-docker-health-check zhaoqin$ docker build -t bolingcavalry/jdk8-healthcheck:0.0.1 . 2Sending build context to Docker daemon 217.6kB 3Step 1/3 : FROM openjdk:8-jdk-stretch 48-jdk-stretch: Pulling from library/openjdk 59a0b0ce99936: Already exists 6db3b6004c61a: Already exists 7f8f075920295: Already exists 86ef14aff1139: Already exists 9962785d3b7f9: Already exists 10631589572f9b: Already exists 11c55a0c6f4c7b: Already exists 12Digest: sha256:8bce852e5ccd41b17bf9704c0047f962f891bdde3e401678a52d14a628defa49 13Status: Downloaded newer image for openjdk:8-jdk-stretch 14 ---> 57c2c2d2643d 15Step 2/3 : MAINTAINER BolingCavalry <zq2599@gmail.com> 16 ---> Running in 270f78efa617 17Removing intermediate container 270f78efa617 18 ---> 01b5df83611d 19Step 3/3 : HEALTHCHECK --interval=5s --timeout=2s --retries=10 CMD curl --silent --fail localhost:8080/getstate || exit 1 20 ---> Running in 7cdd08b9ca22 21Removing intermediate container 7cdd08b9ca22 22 ---> 9dd7ffb22df4 23Successfully built 9dd7ffb22df4 24Successfully tagged bolingcavalry/jdk8-healthcheck:0.0.1
3. 此时宿主机上已经有了名为<font color="blue">bolingcavalry/jdk8-healthcheck:0.0.1</font>的镜像,该镜像带有容器健康检查的参数配置,以此作为基础镜像来构建的其他镜像都集成了健康检查的特性; 4. 如果您已经在hub.docker.com上注册过,就可以用<font color="blue">docker login</font>命令登录,然后执行以下命令将本地镜像推送到hub.docker.com给更多人使用:
docker push bolingcavalry/jdk8-healthcheck:0.0.1
改造Java应用
本次实战的目标是让Java应用支持docker的容器健康检查功能,接下来一起创建这个Java应用:
-
这是个基于maven构建的springboot工程,pom.xml内容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.bolingcavalry</groupId> <artifactId>springboot-app-docker-health-check</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-app-docker-health-check</name> <description>Demo project for Spring Boot</description>
</project>1<properties> 2 <java.version>1.8</java.version> 3</properties> 4 5<dependencies> 6 <dependency> 7 <groupId>org.springframework.boot</groupId> 8 <artifactId>spring-boot-starter-web</artifactId> 9 </dependency> 10 <dependency> 11 <groupId>org.projectlombok</groupId> 12 <artifactId>lombok</artifactId> 13 <optional>true</optional> 14 </dependency> 15 <dependency> 16 <groupId>commons-io</groupId> 17 <artifactId>commons-io</artifactId> 18 <version>2.5</version> 19 </dependency> 20 <dependency> 21 <groupId>org.springframework.boot</groupId> 22 <artifactId>spring-boot-starter-test</artifactId> 23 <scope>test</scope> 24 <exclusions> 25 <exclusion> 26 <groupId>org.junit.vintage</groupId> 27 <artifactId>junit-vintage-engine</artifactId> 28 </exclusion> 29 </exclusions> 30 </dependency> 31</dependencies> 32 33<build> 34 <plugins> 35 <plugin> 36 <groupId>org.springframework.boot</groupId> 37 <artifactId>spring-boot-maven-plugin</artifactId> 38 </plugin> 39 40 41 <!--使用jib插件--> 42 <plugin> 43 <groupId>com.google.cloud.tools</groupId> 44 <artifactId>jib-maven-plugin</artifactId> 45 <version>1.7.0</version> 46 <configuration> 47 <!--from节点用来设置镜像的基础镜像,相当于Docerkfile中的FROM关键字--> 48 <from> 49 <!--基础镜像是bolingcavalry/jdk8-healthcheck:0.0.1,该镜像已配置了健康检查参数--> 50 <image>bolingcavalry/jdk8-healthcheck:0.0.1</image> 51 </from> 52 <to> 53 <!--镜像名称和tag,使用了mvn内置变量${project.version},表示当前工程的version--> 54 <image>bolingcavalry/${project.artifactId}:${project.version}</image> 55 </to> 56 <!--容器相关的属性--> 57 <container> 58 <!--jvm内存参数--> 59 <jvmFlags> 60 <jvmFlag>-Xms1g</jvmFlag> 61 <jvmFlag>-Xmx1g</jvmFlag> 62 </jvmFlags> 63 <!--要暴露的端口--> 64 <ports> 65 <port>8080</port> 66 </ports> 67 <!--使用该参数将镜像的创建时间与系统时间对其--> 68 <useCurrentTimestamp>true</useCurrentTimestamp> 69 </container> 70 </configuration> 71 </plugin> 72 </plugins> 73</build>
上述pom.xml有以下几处需要注意: a. 使用jib插件来将当前工程构建成docker镜像; b. 基础镜像是前面构建的<font color="blue">bolingcavalry/jdk8-healthcheck:0.0.1</font>,以此为基础镜像的镜像都带有健康检查功能;
-
主要功能类是<font color="blue">SpringbootAppDockerHealthCheckApplication.java</font>:
package com.bolingcavalry.springbootappdockerhealthcheck;
import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;
import java.io.*; import java.util.List;
@SpringBootApplication @RestController @Slf4j public class SpringbootAppDockerHealthCheckApplication {
1public static void main(String[] args) { 2 SpringApplication.run(SpringbootAppDockerHealthCheckApplication.class, args); 3} 4 5/** 6 * 读取本地文本文件的内容并返回 7 * @return 8 */ 9private String getLocalFileContent() { 10 String content = null; 11 12 try{ 13 InputStream is = new FileInputStream("/app/depend/abc.txt"); 14 List<String> lines = IOUtils.readLines(is, "UTF-8"); 15 16 if(null!=lines && lines.size()>0){ 17 content = lines.get(0); 18 } 19 } catch (FileNotFoundException e) { 20 log.error("local file not found", e); 21 } catch (IOException e) { 22 log.error("io exception", e); 23 } 24 25 return content; 26} 27 28/** 29 * 对外提供的http服务,读取本地的txt文件将内容返回, 30 * 如果读取不到内容返回码为403 31 * @return 32 */ 33@RequestMapping(value = "/hello", method = RequestMethod.GET) 34public ResponseEntity<String> hello(){ 35 String localFileContent = getLocalFileContent(); 36 37 if(StringUtils.isEmpty(localFileContent)) { 38 log.error("hello service error"); 39 return ResponseEntity.status(403).build(); 40 } else { 41 log.info("hello service success"); 42 return ResponseEntity.status(200).body(localFileContent); 43 } 44} 45 46/** 47 * 该http服务返回当前应用是否正常, 48 * 如果能从本地txt文件成功读取内容,当前应用就算正常,返回码为200, 49 * 如果无法从本地txt文件成功读取内容,当前应用就算异常,返回码为403 50 * @return 51 */ 52@RequestMapping(value = "/getstate", method = RequestMethod.GET) 53public ResponseEntity<String> getstate(){ 54 String localFileContent = getLocalFileContent(); 55 56 if(StringUtils.isEmpty(localFileContent)) { 57 log.error("service is unhealthy"); 58 return ResponseEntity.status(403).build(); 59 } else { 60 log.info("service is healthy"); 61 return ResponseEntity.status(200).build(); 62 } 63}}
上述代码有以下几处需要注意: a. <font color="blue">hello</font>方法是此应用对外提供的服务,如果本地文件abc.txt存在且内容不为空,hello方法的返回码就是200,否则返回码为403,表示当前服务出现异常; b. <font color="blue">getstate</font>方法是新增的服务,该接口会被docke-daemon调用,如果返回码是200,就表示容器健康,如果返回码是403,表示容器不健康; 3. 在pom.xml文件所在目录执行<font color="blue">mvn clean compile -U -DskipTests jib:dockerBuild</font>,即可将当前工程构建为镜像,名为<font color="blue">bolingcavalry/springboot-app-docker-health-check:0.0.1-SNAPSHOT</font> 4. 至此,支持容器健康检查的Java应用镜像构建成功,接下来验证容器的健康检查功能是否正常;
验证步骤
验证的步骤如下: a. 让应用容器正常工作,确保文件<font color="blue">/app/depend/abc.txt</font>是正常的,此时容器状态应该是<font color="red">healthy</font> b. 将文件<font color="blue">/app/depend/abc.txt</font>删除,此时应用hello接口返回码为403,并且容器状态变为<font color="red">unhealthy</font>
验证操作
-
创建文件<font color="blue">abc.txt</font>,完整路径是<font color="blue">/Users/zhaoqin/temp/201910/20/abc.txt</font>,文件内容是个字符串,例如:123456
-
执行以下命令,用新建的java应用镜像创建容器,该容器会将<font color="blue">test</font>文件夹映射到容器的<font color="blue">/app/depend</font>文件夹:
docker run --rm
--name=java-health-check
-p 8080:8080
-v /Users/zhaoqin/temp/201910/20:/app/depend
bolingcavalry/springboot-app-docker-health-check:0.0.1-SNAPSHOT -
控制台可见以下输出,表明健康检查接口已经被调用:
2019-10-20 14:16:34.875 INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2019-10-20 14:16:34.876 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2019-10-20 14:16:34.892 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 16 ms 2019-10-20 14:16:34.959 INFO 1 --- [nio-8080-exec-1] pringbootAppDockerHealthCheckApplication : service is healthy 2019-10-20 14:16:40.159 INFO 1 --- [nio-8080-exec-2] pringbootAppDockerHealthCheckApplication : service is healthy 2019-10-20 14:16:45.356 INFO 1 --- [nio-8080-exec-4] pringbootAppDockerHealthCheckApplication : service is healthy 2019-10-20 14:16:50.580 INFO 1 --- [nio-8080-exec-6] pringbootAppDockerHealthCheckApplication : service is healthy
-
执行命令<font color="blue">docker ps</font>查看容器状态,可见已经是<font color="red">healthy</font>:
(base) zhaoqindeMBP:20 zhaoqin$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 51572d2488fb bolingcavalry/springboot-app-docker-health-check:0.0.1-SNAPSHOT "java -Xms1g -Xmx1g …" About a minute ago Up About a minute (healthy) 0.0.0.0:8080->8080/tcp java-health-check
-
删除宿主机上的<font color="blue">/Users/zhaoqin/temp/201910/20/abc.txt</font>,相当于容器内的abc.txt文件被删除,此时控制台可见健康检查接口在被调用时发现文件不存在,已返回了403错误码:
019-10-20 14:22:37.490 ERROR 1 --- [nio-8080-exec-7] pringbootAppDockerHealthCheckApplication : service is unhealthy
-
健康检查接口被连续10次调用后,再执行命令<font color="blue">docker ps</font>查看容器状态,可见已经是<font color="red">unhealthy</font>:
(base) zhaoqindeMBP:20 zhaoqin$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 51572d2488fb bolingcavalry/springboot-app-docker-health-check:0.0.1-SNAPSHOT "java -Xms1g -Xmx1g …" 7 minutes ago Up 7 minutes (unhealthy) 0.0.0.0:8080->8080/tcp java-health-check
至此,Java应用在docker环境配置容器健康检查的实战就完成了,希望您在给自己的应用添加健康检查时,此文能给您一些参考。