This is the sixth day of my participation in Gwen Challenge

An overview,

Are introduced in the spring in the boot 2.3 container probe, which is added/physical/health/liveness and/physical/health/readiness both health inspection path, for the deployment of the application in k8s, The Spring-boot-ACTUATOR automatically performs health checks through these two paths. Based on the practice described in official documents and the usage process recorded, this paper introduces it from the following aspects:

  1. Health check in K8S
  2. K8s probes in Spring-boot-ACTUATOR
  3. Practice of Spring Boot health check in K8S

Practice of Spring Boot health check in K8S

The ideas for this practice come from the reference article below, and spring Boot 2.5.1 is used for this practice

1. Practice environment

  • Development tools: IntelliJ IDEA 2021.1.1 (Ultimate Edition)
  • JDK 1.8
  • Apache Maven 3.6.3
  • Docker 20.10.5
  • Minikube v1.18.1
  • Spring boot 2.5.1

2. Create a Spring Boot project

1. Create a Spring Boot project with idea

2. pom.xmlIs configured as follows:


      
<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.5.1</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>probedemo</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>probedemo</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>
        <! -- starter for health checks -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Copy the code

3. Create a listener class to listen for changes in the live and ready states:

package com.example.probedemo.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/** * class that listens for system events **@className: AvailabilityListener
 * @date: 2021/6/15 10:44 * /
@Slf4j
@Component
public class AvailabilityListener {

    AvailabilityChangeEvent (AvailabilityChangeEvent) : AvailabilityChangeEvent (AvailabilityChangeEvent) : AvailabilityChangeEvent (AvailabilityChangeEvent)@param event
     */
    @EventListener
    public void onStateChange(AvailabilityChangeEvent<? extends AvailabilityState> event) {
        log.info(event.getState().getClass().getSimpleName() + ":"+ event.getState()); }}Copy the code

@EventListenerNotes:

Annotation that marks the method as an application event listener.

If an annotated method supports a single event type, the method can declare a parameter that reflects the type of event to listen for. If an annotated method supports multiple event types, the annotation can use the CLASSES attribute to reference one or more supported event types. For more information, see Class Javadoc.

The event can be an ApplicationEvent instance or any object.

@ EventListener annotation processing through internal EventListenerMethodProcessor bean implementation, when the bean is using Java config automatic registration, Or use XML configuration elements via
or
.

Annotated methods may have non-void return types. When they do, the result of the method call is sent as a new event. If the return type is array or collection, each element is sent as a new single event.

This annotation can be used as a meta-annotation to create custom composite annotations.

  • Exception handling: While the event listener can declare that it throws any type of exception, any selected exception thrown from the event listener will be wrapped in an undeclared ThrowableException, because the event publisher can only handle runtime exceptions.

  • Asynchronous listeners: If you want a particular listener to handle events asynchronously, you can use Spring’s @async support, but be aware of the following limitations when using asynchronous events. If an asynchronous event listener throws an exception, it is not propagated to the caller. For more information, please refer to the AsyncUncaughtExceptionHandler. Asynchronous event listener methods cannot publish subsequent events by returning values. If you need as a processing result release another event, please insert ApplicationEventPublisher to manually release the event.

  • Sort listeners: You can also define the order in which listeners for an event are called. To do this, add Spring’s public @Order annotation next to the event listener annotation.

4. Create a stateController to change the status

package com.example.probedemo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/** * Tests the state of the controller **@className: StateWriter
 * @date: 2021/6/15 * / to him
@RestController
@RequestMapping("/state")
public class StateController {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /** * This will cause K8S to kill the pod and restart it according to the restart policy **@return* /
    @GetMapping("broken")
    public String broken(a) {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, LivenessState.BROKEN);
        return "success broken, " + new Date();
    }

    /** * Change the survival state to correct *@return* /
    @GetMapping("correct")
    public String correct(a) {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, LivenessState.CORRECT);
        return "success correct, " + new Date();
    }

    /** * change the ready state to ACCEPTING_TRAFFIC * k8s forwards external requests to this pod *@return* /
    @GetMapping("accept")
    public String accept(a) {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);
        return "success accept, " + new Date();
    }

    /** * Change the ready state to REFUSING_TRAFFIC * k8s reject external requests * by removing the IP address of this pod from the back-end endpoint corresponding to the service@return* /
    @GetMapping("refuse")
    public String refuse(a) {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
        return "success refuse, " + newDate(); }}Copy the code

5. Make the Docker image

Create a Dockerfile in the pom.xml directory with the following contents:

# Specify the base image, which is the early stage of a multi-phase build
FROM openjdk:11-jre-slim as builder
# specify a working directory. If the directory does not exist, it will be created automatically
WORKDIR /app
Copy the generated JAR into the container image
COPY target/*.jar application.jar
# Use spring-boot-jarmode-layerTools to extract the split build result from application.jar
RUN java -Djarmode=layertools -jar application.jar extract

# Officially build the image
FROM openjdk:11-jre-slim
# specify a working directory. If the directory does not exist, it will be created automatically
WORKDIR /app
In the previous phase, multiple files were extracted from the JAR and copied to the mirror space by executing the COPY command separately, each COPY is a layer
COPY --from=builder app/dependencies ./
COPY --from=builder app/spring-boot-loader ./
COPY --from=builder app/snapshot-dependencies ./
COPY --from=builder app/application ./
# specify time zone
ENV TZ="Asia/Shanghai"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# define some environment variables to make it easier for environment variables to be passed
ENV JVM_OPTS=""
ENV JAVA_OPTS=""
If you do not specify the port to be exposed, it will expose the corresponding port
EXPOSE 8080
Start jar command
ENTRYPOINT ["sh"."-c"."java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]


Copy the code

Compile the build project with the following command:

mvn clean package -U -DskipTests
Copy the code

Use the following command to build the docker image (with a. At the end to indicate the current directory as the docker build context) :

Docker build-t probeDemo :1.0.0Copy the code

Use the following command to push the docker image to the remote repository (here push to the Docker Hub repository, you need to register an account) :

# label the image with [warehouse address/image name: image tag]Docker tag probedemo: 1.0.0 wangedison98 / probedemo: 1.0.0Push to remote repositoryDocker push wangedison98 / probedemo: 1.0.0Copy the code

6. K8s deploys deployment and Service

Create a file named probedemo.yaml:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: probedemo
  labels:
    app: probedemo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: probedemo
  template:
    metadata:
      labels:
        app: probedemo
    spec:
      containers:
        - name: probedemo
          imagePullPolicy: IfNotPresent
          image: Wangedison98 / probedemo: 1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: "512Mi"
              cpu: "100m"
            limits:
              memory: "1Gi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 5
            failureThreshold: 10
            timeoutSeconds: 10
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 10
            periodSeconds: 5


---
apiVersion: v1
kind: Service
metadata:
  name: probedemo
spec:
  ports:
    - port: 8080
      targetPort: 8080
  selector:
    app: probedemo
  type: NodePort

Copy the code

What is important here is the livenessProbe’s initialDelaySeconds and failureThreshold parameters. InitialDelaySeconds is equal to 5, indicating that pod checks the survival probe 5 seconds after creation. If the application is not started within 10 seconds, the probe will retry 10 times (failureThreshold is 10) and wait 5 periodSeconds each time. If the probe tries 10 times (50 seconds), it still cannot return 200. The POD will be killed and rebuilt by Kubernetes. If each startup takes so long, the POD will be killed and rebuilt continuously. In this case, we can consider extending the failureThreshold.

Create deployment and service using the following command:

kubectl apply -f probedemo.yaml
Copy the code

View the pod in action:

Use the following command to expose the service port:

kubectl port-forward service/probedemo 8080 8080
Copy the code

Call the broken event for the viability check with the following address:

curl http://localhost:8080/state/broken
Copy the code

Wait about a minute and find that the POD has been restarted

Request to deny traffic at the following address:

curl http://localhost:8080/state/refuse
Copy the code

You can see that the service is not ready:

To view pod events:

kubectl describe  probedemo-86cb7cc84b-djrjn
Copy the code

When the request to accept traffic is called again:

curl http://localhost:8080/state/accept
Copy the code

The discovery service has returned to normal:

According to this feature, you can control when to provide external services through programs. When handling some exceptions, you can manually reject the request and provide services after the recovery.

Third, summary

Through the above practice, we tested the Health check of spring Boot application in K8S, and the configuration was very simple:

  1. I just need to introducespring-boot-starter-actuatorNo additional configuration is required
  2. Perform the following configuration in the K8S deployment list according to the official document:

Refer to the article

Blog.csdn.net/boling_cava…

Docs. Spring. IO/spring – the boot…