Java應用在docker環境配置容器健康檢查

  • 2019 年 10 月 21 日
  • 筆記

《極速體驗docker容器健康》一文已體驗了docker容器健康檢查功能,今天就來給java應用的容器加入健康檢查,使應用的狀態隨時都可以被監控和查看。

實戰環境資訊

  1. 作業系統:macOS Catalina 10.15
  2. Docker:19.03.2

    java應用簡介

    今天實戰的java應用,是用來模擬生產環境應用的,特點如下:

  3. 普通springboot應用,對外提供http服務,路徑:/hello
  4. springboot應用運行在docker容器,在容器的/app/depend/目錄下有名為abc.txt的文件;
  5. 上述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)| [email protected]:zq2599/blog_demos.git | 該項目源碼的倉庫地址,ssh協議 |

這個git項目中有多個文件夾,本章的應用在springboot-app-docker-health-check文件夾下,如下圖紅框所示:
在這裡插入圖片描述

步驟簡介

應用接入容器健康檢查的步驟如下:

  1. 將java應用製作成docker鏡像時需要基礎鏡像,因此先準備好基礎鏡像,將容器健康檢查的參數都配置在基礎鏡像中,包括提供容器健康資訊的介面路徑,這裡定為/getstate
  2. 改造java應用,提供/getstate介面服務,根據業務的實際情況決定當前應用是否健康,健康時返回碼為200,不健康時返回碼為403;
  3. 編譯構建應用並且生成docker鏡像;
  4. 驗證;

    製作基礎鏡像

  5. 創建名為Dockerfile的文件,內容如下:
# Docker file from bolingcavalry # VERSION 0.0.1  # Author: bolingcavalry    #基礎鏡像  FROM openjdk:8-jdk-stretch    #作者  MAINTAINER BolingCavalry <[email protected]>    #健康檢查參數設置,每5秒檢查一次,介面超時時間2秒,連續10次返回1就判定該容器不健康  HEALTHCHECK --interval=5s --timeout=2s --retries=10     CMD curl --silent --fail localhost:8080/getstate || exit 1

由上述可見Dockerfile的內容非常簡單,選定自身的基礎鏡像為openjdk:8-jdk-stretch,再配置好健康檢查參數:
| 參數名 | 作用 |
|–|–|
| health-cmd | 指定命令在容器內執行,用於檢查容器健康狀態 |
| health-interval | 每次健康檢查的間隔時間,默認30秒 |
| health-retries | 假設該值為3,表示若連續三次檢測的返回結果都是不健康,就判定該容器不健康,默認值為3 |
| health-timeout | 超時時間,默認30秒 |

  1. 在Dockerfile文件所在目錄執行命令docker build -t bolingcavalry/jdk8-healthcheck:0.0.1 .(最後那個點號不要漏掉),控制台輸出如下,提示鏡像構建成功:
(base) zhaoqindeMacBook-Pro:springboot-app-docker-health-check zhaoqin$ docker build -t bolingcavalry/jdk8-healthcheck:0.0.1 .  Sending build context to Docker daemon  217.6kB  Step 1/3 : FROM openjdk:8-jdk-stretch  8-jdk-stretch: Pulling from library/openjdk  9a0b0ce99936: Already exists  db3b6004c61a: Already exists  f8f075920295: Already exists  6ef14aff1139: Already exists  962785d3b7f9: Already exists  631589572f9b: Already exists  c55a0c6f4c7b: Already exists  Digest: sha256:8bce852e5ccd41b17bf9704c0047f962f891bdde3e401678a52d14a628defa49  Status: Downloaded newer image for openjdk:8-jdk-stretch   ---> 57c2c2d2643d  Step 2/3 : MAINTAINER BolingCavalry <[email protected]>   ---> Running in 270f78efa617  Removing intermediate container 270f78efa617   ---> 01b5df83611d  Step 3/3 : HEALTHCHECK --interval=5s --timeout=2s --retries=10   CMD curl --silent --fail localhost:8080/getstate || exit 1   ---> Running in 7cdd08b9ca22  Removing intermediate container 7cdd08b9ca22   ---> 9dd7ffb22df4  Successfully built 9dd7ffb22df4  Successfully tagged bolingcavalry/jdk8-healthcheck:0.0.1
  1. 此時宿主機上已經有了名為bolingcavalry/jdk8-healthcheck:0.0.1的鏡像,該鏡像帶有容器健康檢查的參數配置,以此作為基礎鏡像來構建的其他鏡像都集成了健康檢查的特性;
  2. 如果您已經在hub.docker.com上註冊過,就可以用docker login命令登錄,然後執行以下命令將本地鏡像推送到hub.docker.com給更多人使用:
docker push bolingcavalry/jdk8-healthcheck:0.0.1

改造Java應用

本次實戰的目標是讓Java應用支援docker的容器健康檢查功能,接下來一起創建這個Java應用:

  1. 這是個基於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>        <properties>          <java.version>1.8</java.version>      </properties>        <dependencies>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <optional>true</optional>          </dependency>          <dependency>              <groupId>commons-io</groupId>              <artifactId>commons-io</artifactId>              <version>2.5</version>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-test</artifactId>              <scope>test</scope>              <exclusions>                  <exclusion>                      <groupId>org.junit.vintage</groupId>                      <artifactId>junit-vintage-engine</artifactId>                  </exclusion>              </exclusions>          </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>                          <!--基礎鏡像是bolingcavalry/jdk8-healthcheck:0.0.1,該鏡像已配置了健康檢查參數-->                          <image>bolingcavalry/jdk8-healthcheck:0.0.1</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>              </plugin>          </plugins>      </build>  </project>

上述pom.xml有以下幾處需要注意:

a. 使用jib插件來將當前工程構建成docker鏡像;

b. 基礎鏡像是前面構建的bolingcavalry/jdk8-healthcheck:0.0.1,以此為基礎鏡像的鏡像都帶有健康檢查功能;

  1. 主要功能類是SpringbootAppDockerHealthCheckApplication.java
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 {        public static void main(String[] args) {          SpringApplication.run(SpringbootAppDockerHealthCheckApplication.class, args);      }        /**       * 讀取本地文本文件的內容並返回       * @return       */      private String getLocalFileContent() {          String content = null;            try{              InputStream is = new FileInputStream("/app/depend/abc.txt");              List<String> lines = IOUtils.readLines(is, "UTF-8");                if(null!=lines && lines.size()>0){                  content = lines.get(0);              }          } catch (FileNotFoundException e) {              log.error("local file not found", e);          } catch (IOException e) {              log.error("io exception", e);          }            return content;      }        /**       * 對外提供的http服務,讀取本地的txt文件將內容返回,       * 如果讀取不到內容返回碼為403       * @return       */      @RequestMapping(value = "/hello", method = RequestMethod.GET)      public ResponseEntity<String> hello(){          String localFileContent = getLocalFileContent();            if(StringUtils.isEmpty(localFileContent)) {              log.error("hello service error");              return ResponseEntity.status(403).build();          } else {              log.info("hello service success");              return ResponseEntity.status(200).body(localFileContent);          }      }        /**       * 該http服務返回當前應用是否正常,       * 如果能從本地txt文件成功讀取內容,當前應用就算正常,返回碼為200,       * 如果無法從本地txt文件成功讀取內容,當前應用就算異常,返回碼為403       * @return       */      @RequestMapping(value = "/getstate", method = RequestMethod.GET)      public ResponseEntity<String> getstate(){          String localFileContent = getLocalFileContent();            if(StringUtils.isEmpty(localFileContent)) {              log.error("service is unhealthy");              return ResponseEntity.status(403).build();          } else {              log.info("service is healthy");              return ResponseEntity.status(200).build();          }      }  }

上述程式碼有以下幾處需要注意:

a. hello方法是此應用對外提供的服務,如果本地文件abc.txt存在且內容不為空,hello方法的返回碼就是200,否則返回碼為403,表示當前服務出現異常;

b. getstate方法是新增的服務,該介面會被docke-daemon調用,如果返回碼是200,就表示容器健康,如果返回碼是403,表示容器不健康;

  1. 在pom.xml文件所在目錄執行mvn clean compile -U -DskipTests jib:dockerBuild,即可將當前工程構建為鏡像,名為bolingcavalry/springboot-app-docker-health-check:0.0.1-SNAPSHOT
  2. 至此,支援容器健康檢查的Java應用鏡像構建成功,接下來驗證容器的健康檢查功能是否正常;

    驗證步驟

    驗證的步驟如下:

a. 讓應用容器正常工作,確保文件/app/depend/abc.txt是正常的,此時容器狀態應該是healthy

b. 將文件/app/depend/abc.txt刪除,此時應用hello介面返回碼為403,並且容器狀態變為unhealthy

驗證操作

  1. 創建文件abc.txt,完整路徑是/Users/zhaoqin/temp/201910/20/abc.txt,文件內容是個字元串,例如:123456
  2. 執行以下命令,用新建的java應用鏡像創建容器,該容器會將test文件夾映射到容器的/app/depend文件夾:
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
  1. 控制台可見以下輸出,表明健康檢查介面已經被調用:
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
  1. 執行命令docker ps查看容器狀態,可見已經是healthy
(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
  1. 刪除宿主機上的/Users/zhaoqin/temp/201910/20/abc.txt,相當於容器內的abc.txt文件被刪除,此時控制台可見健康檢查介面在被調用時發現文件不存在,已返回了403錯誤碼:
019-10-20 14:22:37.490 ERROR 1 --- [nio-8080-exec-7] pringbootAppDockerHealthCheckApplication : service is unhealthy
  1. 健康檢查介面被連續10次調用後,再執行命令docker ps查看容器狀態,可見已經是unhealthy
(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環境配置容器健康檢查的實戰就完成了,希望您在給自己的應用添加健康檢查時,此文能給您一些參考。

歡迎關注公眾號:程式設計師欣宸