一、概述

在 spring boot 2.3 中引入了容器探针,也就是减少了 /actuator/health/liveness/actuator/health/readiness 这两个健康检查门路,对于部署在 k8s 中的利用,spring-boot-actuator 将通过这两个门路主动进行健康检查。本文次要依据官网文档的形容实际并记录应用流程,从如下几个方面进行介绍:

  1. k8s 中的健康检查
  2. spring-boot-actuator 中的 k8s 探针
  3. spring boot 健康检查在 k8s 中的实际

二、spring boot 健康检查在 k8s 中的实际

本次实际的思路来自下文的参考文章,这里应用 spring boot 2.5.1 进行实际

1. 实际环境

  • 开发工具: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. 创立一个 spring boot 我的项目

    1. 应用 idea 创立一个 spring boot 我的项目:

2. 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.5.1</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.example</groupId>    <artifactId>probedemo</artifactId>    <version>0.0.1-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     -->        <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>

3. 创立一个监听类,能够监听存活和就绪状态的变动:

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;/** * 监听系统事件的类 * * @className: AvailabilityListener * @date: 2021/6/15 10:44 */@Slf4j@Componentpublic class AvailabilityListener {    /**     * 基于 spring 的事件监听机制,监听系统的音讯     * 当监听到 AvailabilityChangeEvent 事件会触发此办法的调用     * 这里应用日志记录事件的状态     * @param event     */    @EventListener    public void onStateChange(AvailabilityChangeEvent<? extends AvailabilityState> event) {        log.info(event.getState().getClass().getSimpleName() + ": " + event.getState());    }}

@EventListener 注解阐明:

将办法标记为应用程序事件侦听器的注解。

如果带注解的办法反对单个事件类型,则该办法能够申明一个反映要侦听的事件类型的参数。如果带注解的办法反对多个事件类型,则此注解能够应用classes属性援用一个或多个受反对的事件类型。无关详细信息,请参见类javadoc。

事件能够是ApplicationEvent实例,也能够是任意对象。

@EventListener注解的解决通过外部EventListenerMethodProcessor bean执行,该bean在应用Java config时主动注册,或者通过<context:annotation-config/>或者<context:component-scan/>应用XML配置时的元素。

带注解的办法可能具备非void返回类型。当它们这样做时,办法调用的后果将作为新事件发送。如果返回类型是数组或汇合,则每个元素将作为新的单个事件发送。

此注解可用作元注解,以创立自定义组合注解。

  • 异样解决:尽管事件侦听器能够申明它抛出任意异样类型,然而从事件侦听器抛出的任何选中的异样都将包装在未声明的ThrowableException中,因为事件公布器只能解决运行时异样。
  • 异步侦听器:如果心愿某个特定的侦听器异步处理事件,能够应用Spring的@Async反对,但在应用异步事件时要留神以下限度。如果异步事件侦听器抛出异样,则不会将其流传到调用方。无关详细信息,请参阅AsyncUncaughtExceptionHandler。异步事件侦听器办法无奈通过返回值来公布后续事件。如果须要作为处理结果公布另一个事件,请插入ApplicationEventPublisher以手动公布该事件。
  • 排序侦听器:还能够定义调用某个事件的侦听器的程序。为此,将Spring的公共@Order注解增加到这个事件侦听器注解旁边。

4. 创立一个 stateController 用来批改状态

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;/** * 测试批改状态的 controller * * @className: StateWriter * @date: 2021/6/15 14:17 */@RestController@RequestMapping("/state")public class StateController {    @Autowired    private ApplicationEventPublisher applicationEventPublisher;    /**     * 将存活状态改为 BROKEN     * 这会导致 k8s 杀死 pod,并依据重启策略重启 pod     *     * @return     */    @GetMapping("broken")    public String broken() {        AvailabilityChangeEvent.publish(applicationEventPublisher, this, LivenessState.BROKEN);        return "success broken, " + new Date();    }    /**     * 将存活状态批改为 correct     * @return     */    @GetMapping("correct")    public String correct() {        AvailabilityChangeEvent.publish(applicationEventPublisher, this, LivenessState.CORRECT);        return "success correct, " + new Date();    }    /**     * 将就绪状态批改为 ACCEPTING_TRAFFIC (承受流量)     * k8s 会将内部申请转发到此 pod     * @return     */    @GetMapping("accept")    public String accept() {        AvailabilityChangeEvent.publish(applicationEventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);        return "success accept, " + new Date();    }    /**     * 将就绪状态批改为 REFUSING_TRAFFIC     * k8s 通过将 service 对应的后端 endpoint 中此 pod 的ip移除来回绝内部申请     * @return     */    @GetMapping("refuse")    public String refuse() {        AvailabilityChangeEvent.publish(applicationEventPublisher, this, ReadinessState.REFUSING_TRAFFIC);        return "success refuse, " + new Date();    }}

5. 制作 docker 镜像

在pom.xml所在目录创立文件Dockerfile,内容如下:

# 指定根底镜像,这是多阶段构建的后期阶段FROM openjdk:11-jre-slim as builder# 指定工作目录,目录不存在会主动创立WORKDIR /app# 将生成的 jar 复制到容器镜像中COPY target/*.jar application.jar# 通过工具spring-boot-jarmode-layertools从application.jar中提取拆分后的构建后果RUN java -Djarmode=layertools -jar application.jar extract# 正式构建镜像FROM openjdk:11-jre-slim# 指定工作目录,目录不存在会主动创立WORKDIR /app# 前一阶段从jar中提取除了多个文件,这里别离执行COPY命令复制到镜像空间中,每次COPY都是一个layerCOPY --from=builder app/dependencies ./COPY --from=builder app/spring-boot-loader ./COPY --from=builder app/snapshot-dependencies ./COPY --from=builder app/application ./# 指定时区ENV TZ="Asia/Shanghai"RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 定义一些环境变量,不便环境变量传参ENV JVM_OPTS=""ENV JAVA_OPTS=""# 指定裸露的端口,起到阐明的作用,不指定也会裸露对应端口EXPOSE 8080# 启动 jar 的命令ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]

应用以下命令编译构建我的项目:

mvn clean package -U -DskipTests

应用以下命令构建 docker 镜像(最初有一个 . 示意当前目录作为docker构建的上下文环境):

docker build -t probedemo:1.0.0 .

应用上面的命令将 docker 镜像推送到近程仓库(这里推送到docker hub仓库,须要本人注册一个账号):

# 给镜像打一个标签,[仓库地址/镜像名:镜像标签]docker tag probedemo:1.0.0 wangedison98/probedemo:1.0.0# 推送到近程仓库docker push wangedison98/probedemo:1.0.0

6. k8s 部署 deployment 和 service

创立名为probedemo.yaml的文件:

apiVersion: apps/v1kind: Deploymentmetadata:  name: probedemo  labels:    app: probedemospec:  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: v1kind: Servicemetadata:  name: probedemospec:  ports:    - port: 8080      targetPort: 8080  selector:    app: probedemo  type: NodePort

这里要重点关注的是 livenessProbeinitialDelaySecondsfailureThreshold 参数,initialDelaySeconds 等于5,示意 pod 创立5秒后查看存活探针,如果10秒内利用没有实现启动,存活探针不返回200,就会重试10次(failureThreshold等于10),每一次期待 5 秒(periodSeconds 等于5),如果重试10次,也就是50秒后,存活探针仍旧无奈返回200,该pod就会被kubernetes杀死重建,要是每次启动都耗时这么长,pod就会不停的被杀死重建,这种状况下能够思考缩短 failureThreshold 失败重试的次数。

应用如下命令创立 deployment 和 service:

kubectl apply -f probedemo.yaml

查看运行的 pod:

应用如下命令裸露服务端口:

kubectl port-forward service/probedemo 8080 8080

调用存活性查看的 broken 事件,地址如下:

curl http://localhost:8080/state/broken

期待大略一分钟,发现 pod 曾经重启一次

申请回绝流量,地址如下:

curl http://localhost:8080/state/refuse

能够看到服务曾经处于未筹备状态:

查看 pod 的事件:

kubectl describe  probedemo-86cb7cc84b-djrjn

当再次调用承受流量的申请:

curl http://localhost:8080/state/accept

发现服务曾经恢复正常:

依据这个个性,能够通过程序控制什么时候对外提供服务,当解决一些异常情况时,能够手动拒绝请求,待恢复正常后再提供服务。

三、总结

通过下面的实际,咱们测试了spring boot 利用在 k8s 中的健康检查,配置非常简单:

  1. 只须要引入 spring-boot-starter-actuator 依赖即可,不须要其余额定配置
  2. 在 k8s 的部署清单中依据官网文档做如下配置:

参考文章

https://blog.csdn.net/boling_...

https://docs.spring.io/spring...