Java application configuring container health check in docker environment

Posted by rob.weaver on Mon, 21 Oct 2019 12:40:44 +0200

stay Fast experience docker container health This article has experienced the health check function of docker container. Today, we will add health check to the container of java application, so that the application status can be monitored and viewed at any time.

Actual combat environment information

  1. Operating system: macOS Catalina 10.15
  2. Docker: 19.03.2

    Introduction to java application

    Today's actual java application is used to simulate the application of production environment. Its characteristics are as follows:
  3. General springboot application, providing http service externally, path: / hello
  4. The springboot application runs in the docker container. The file named abc.txt is located in the / APP / dependent / directory of the container.
  5. When the above abc.txt file exists, the hello interface of the springboot application is normal. If abc.txt does not exist, the springboot application will not provide external services, which is equivalent to unhealthy status (to simulate application exceptions).

    Source download

    If you don't want to write code, the source code of the springboot application can be downloaded from GitHub. The address and link information are shown in the following table:
    |Name | link | comment|
    | :-------- | :----| :----|
    |Project home page | https://github.com/zq2599/blog | demos | the project's home page on GitHub|
    |Git warehouse address (HTTPS) | https://github.com/zq2599/blog | demos.git | the warehouse address of the project source code, HTTPS protocol|
    |Git warehouse address (ssh) | git@github.com: zq2599 / blog | demos.git | the warehouse address of the project source code, ssh protocol|

There are multiple folders in this git project. The application of this chapter is in the springboot app docker health check folder, as shown in the red box below:

Brief introduction of steps

The steps to apply the access container health check are as follows:

  1. When making java application into docker image, basic image is needed. Therefore, prepare the basic image first, and configure the parameters of container health check in the basic image, including the interface path to provide container health information. Here it is set as / getstate;
  2. Modify java application, provide / getstate interface service, and determine whether the current application is healthy according to the actual situation of the business. The return code is 200 when it is healthy and 403 when it is unhealthy.
  3. Compile and build application and generate docker image;
  4. Verification;

    Make basic image

  5. Create a file named Dockerfile with the following contents:
# Docker file from bolingcavalry # VERSION 0.0.1
# Author: bolingcavalry

#base image
FROM openjdk:8-jdk-stretch

#author
MAINTAINER BolingCavalry <zq2599@gmail.com>

#Health check parameter setting, check every 5 seconds, interface timeout 2 seconds, return 1 for 10 consecutive times to determine that the container is unhealthy
HEALTHCHECK --interval=5s --timeout=2s --retries=10 \
  CMD curl --silent --fail localhost:8080/getstate || exit 1

It can be seen from the above that the content of Dockerfile is very simple. Select openjdk:8-jdk-stretch as its basic image, and then configure the health check parameters:
|Parameter name function|
|--|--|
|Health CMD specifies that the command is executed in the container to check the health status of the container|
|Health interval | interval time of each health check, default 30 seconds|
|Health retries | assuming that the value is 3, it means that if the returned results of three consecutive tests are unhealthy, the container is determined to be unhealthy. The default value is 3.|
|Health timeout | timeout, 30 seconds by default|

  1. Execute the command docker build - t bolingcavalry / jdk8 healthcheck: 0.0.1 in the directory where the Dockerfile file is located. (do not leave out the last point number). The console output is as follows, indicating that the image construction is successful:
(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 <zq2599@gmail.com>
 ---> 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. At this time, there is an image named bolingcavalry / jdk8 healthcheck: 0.0.1 on the host. The image has the parameter configuration of container health check, and other images built as the basic image integrate the characteristics of health check.
  2. If you have already registered on hub.docker.com, you can log in with the docker login command, and then execute the following command to push the local image to hub.docker.com for more people to use:
docker push bolingcavalry/jdk8-healthcheck:0.0.1

Transforming Java applications

The goal of this practice is to enable Java applications to support the container health check function of docker. Next, create this Java application together:

  1. This is a springboot project based on maven. The content of pom.xml is as follows:
<?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>


            <!--Use jib Plug-in unit-->
            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>1.7.0</version>
                <configuration>
                    <!--from The node is used to set the basic image of the image, which is equivalent to Docerkfile Medium FROM Keyword-->
                    <from>
                        <!--The base image is bolingcavalry/jdk8-healthcheck:0.0.1,The image has health check parameters configured-->
                        <image>bolingcavalry/jdk8-healthcheck:0.0.1</image>
                    </from>
                    <to>
                        <!--Image name and tag,Used mvn Built-in variables ${project.version},For the current project version-->
                        <image>bolingcavalry/${project.artifactId}:${project.version}</image>
                    </to>
                    <!--Container related properties-->
                    <container>
                        <!--jvm Memory parameters-->
                        <jvmFlags>
                            <jvmFlag>-Xms1g</jvmFlag>
                            <jvmFlag>-Xmx1g</jvmFlag>
                        </jvmFlags>
                        <!--Ports to expose-->
                        <ports>
                            <port>8080</port>
                        </ports>
                        <!--Use this parameter to set the creation time and system time of the mirror to-->
                        <useCurrentTimestamp>true</useCurrentTimestamp>
                    </container>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

The pom.xml above has the following points to note:

A. use the jib plug-in to build the current project into a docker image;

b. the basic image is the bolingcavalry/jdk8-healthcheck:0.0.1, which is built before. The basic image has the function of health check.

  1. The main function class is springbootappdockerhealtheckapplication.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);
    }

    /**
     * Read the contents of the local text file and return
     * @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;
    }

    /**
     * For the external http service, read the local txt file and return the content.
     * If the content cannot be read, the return code is 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);
        }
    }

    /**
     * The http service returns whether the current application is normal.
     * If the content can be successfully read from the local txt file, the current application is normal, and the return code is 200.
     * If the content cannot be read from the local txt file successfully, the current application will be regarded as an exception, and the return code is 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();
        }
    }
}

The above codes have the following points to note:

a. hello method is the external service provided by this application. If the local file abc.txt exists and the content is not empty, the return code of hello method is 200. Otherwise, the return code is 403, indicating that the current service has an exception.

B. the getstate method is a new service. The interface will be called by the docke daemon. If the return code is 200, it means the container is healthy. If the return code is 403, it means the container is unhealthy.

  1. Execute mvn clean compile -U -DskipTests jib:dockerBuild in the directory of pom.xml file to build the current project as an image, named bolingcavalry / springboot app docker health check: 0.0.1-snapshot
  2. At this point, the Java application image supporting container health check has been built successfully. Next, verify whether the health check function of the container is normal.

    Verification steps

    The steps of verification are as follows:

a. make the application container work normally. Make sure the file / APP / dependent / abc.txt is normal. At this time, the container state should be healthy.

b. delete the file / APP / dependent / abc.txt. At this time, the application hello interface return code is 403, and the container status changes to unhealthy.

Verification operation

  1. Create the file abc.txt. The full path is / Users/zhaoqin/temp/201910/20/abc.txt. The content of the file is a string, for example: 123456
  2. Execute the following command to create a container with the new java application image. The container will map the test folder to the container's / APP / dependent folder:
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. The console can see the following output to indicate that the health check interface has been called:
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. Execute the command docker ps to view the container status. It can be seen that it is already 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. Delete / Users/zhaoqin/temp/201910/20/abc.txt on the host, which is equivalent to deleting the abc.txt file in the container. At this time, the console can see that the health check interface found that the file does not exist when it was called, and has returned 403 error code:
019-10-20 14:22:37.490 ERROR 1 --- [nio-8080-exec-7] pringbootAppDockerHealthCheckApplication : service is unhealthy
  1. After the health check interface is called 10 times in a row, execute the command docker ps to check the container status. It can be seen that it is 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

At this point, the practice of configuring container health check for Java application in docker environment is completed. I hope this article can give you some reference when you add health check to your application.

Welcome to the public account: Xinchen, programmer

Topics: Java Docker SpringBoot Maven