一、概述
在 spring boot 2.3 中引入了容器探针,也就是减少了 /actuator/health/liveness
和 /actuator/health/readiness
这两个健康检查门路,对于部署在 k8s 中的利用,spring-boot-actuator 将通过这两个门路主动进行健康检查。本文次要依据官网文档的形容实际并记录应用流程,从如下几个方面进行介绍:
- k8s 中的健康检查
- spring-boot-actuator 中的 k8s 探针
- 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
@Component
public 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 都是一个 layer
COPY --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/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
这里要重点关注的是 livenessProbe
的 initialDelaySeconds
和 failureThreshold
参数,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 中的健康检查,配置非常简单:
- 只须要引入
spring-boot-starter-actuator
依赖即可,不须要其余额定配置 - 在 k8s 的部署清单中依据官网文档做如下配置:
参考文章
https://blog.csdn.net/boling_…
https://docs.spring.io/spring…