Kubernetes集群中SpringBoot服务的健康探测优化

2021年8月9日16:52:05 发表评论 4,455 ℃

今天在维护预生产环境的数据库的时候,发生了一个灾难性的故障(还好不是生产环境),集群中除了eureka和zuul的其他服务全部springboot服务都变成了不可用状态,容器在不停的重启中,出现这种情况,一般是Liveness和Readiness健康检查失败了。

Kubernetes集群中SpringBoot服务的健康探测优化

为什么会出现这样雪崩式的故障?接下来阿汤博客就kubernetes自愈和springboot健康检查分享下具体原因。

一、Kubernetes自愈

Kubernetes强大的自愈能力毋容置疑,如何自愈?默认实现方式是自动重启发生故障的容器,在非kubernetes集群中,要实现自愈是非常困难的一件事情。

那怎么判定容器发生了故障呢?kubernetes提供了Liveness和Readiness 探测机制,检测容器的健康度。

Liveness 探测让用户可以自定义判断容器是否健康的条件。如果探测失败,Kubernetes 就会重启容器,实现自愈。

Readiness 探测则是告诉 Kubernetes 什么时候可以将容器加入到 Service 负载均衡池中,对外提供服务。

Liveness VS Readiness 探测

1、Liveness 探测和 Readiness 探测是两种 Health Check 机制,如果不特意配置,Kubernetes 将对两种探测采取相同的默认行为,即通过判断容器启动进程的返回值是否为零来判断探测是否成功。

2、两种探测的配置方法完全一样,支持的配置参数也一样。不同之处在于探测失败后的行为:Liveness 探测是重启容器;Readiness 探测则是将容器设置为不可用,不接收 Service 转发的请求。

3、Liveness 探测和 Readiness 探测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用。用 Liveness 探测判断容器是否需要重启以实现自愈;用 Readiness 探测判断容器是否已经准备好对外提供服务。

我们项目健康探测配置:

readinessProbe:
  initialDelaySeconds: 10
  periodSeconds: 12
  timeoutSeconds: 2
  successThreshold: 1
  failureThreshold: 3
  httpGet:
    path: /health
    port: 8080
livenessProbe:
  initialDelaySeconds: 300
  periodSeconds: 12
  timeoutSeconds: 2
  successThreshold: 1
  failureThreshold: 3
  httpGet:
    path: /health
    port: 8080

二、SpringBoot的spring boot actuator中的监控检查机制

springboot服务中引用了依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

以后,对应的服务就提供了端点/health。

Actuator 的 health 端点是主要用来检查应用的运行状态,我们项目在kubernetes集群中也是使用此端点对springboot服务做的健康状态检测。

由于今天之前一直没对Actuator 的 health 深入研究,导致出现了今天的雪崩故障。这也是为什么我维护数据库的时候,导致所有服务都出现了不可用,无限重启的情况。

故障一开始虽然知道是健康检查失败了,但还是充满了疑惑,为什么失败呢?因为这期间除了维护了数据库,没做其他操作。

于是停掉了开发环境的数据库,马上去查看开发环境springboot服务pod的状态,没过多久服务全部为不可用状态,然后就开始重启容器尝试恢复。

经过反复测试,发现只要数据库一停,服务的/health端点,返回的就是DOWN状态。

我去查阅了关于spring boot actuator健康检查相关的文章,原来默认情况下/health端点会检测数据库连接、Redis链接、RocketMQ、diskSpace等等这些和服务相关的中间组件的状态,只要有一个不正常,则健康检查为失败。

Spring Boot自带的健康指示器
名称 键 描述
ApplicationHealthIndicator none 永远为up
DataSourceHealthIndicator db 如果数据库能连上,则内容是up和数据类型,否则为DOWN
DiskSpaceHealthIndicator diskSpace 如果可用空间大于阈值,则内容为UP和可用磁盘空间,如果空间不足则为DOWN
JmsHealthIndicator jms 如果能连上消息代理,则内容是up和JMS提供方的名称,否则为DOWN
MailHealthIndicator mail 如果能连上邮件服务器,则内容是up和邮件服务器主机和端口,否则为DOWN
MongoHealthIndicator mongo 如果能连上MongoDB服务器,则内容是up和MongoDB服务器版本,否则为DOWN
RabbitHealthIndicator rabbit 如果能连上RabbitMQ服务器,则内容是up和版本号,否则为DOWN
RedisHealthIndicator redis 如果能连上服务器,则内容是up和Redis服务器版本,否则为DOWN
SolrHealthIndicator solr 如果能连上solr服务器,则内容是up,否则为DOWN

因为我Liveness和Readiness探测都采用的spring boot actuator的/health端点,所以当我停掉mysql以后,所有使用了mysql的服务监控检查全部为DOWN状态,导致触发了kubernetes的重启自愈。

这样就导致所有服务都会不停的重启,导致服务器CPU一直处于满负载,然后就形成了连锁反应,可能会导致其他pod因为健康检查超时,出现失败,然后又重启的情况。

有了上面的问题,就需要对当前的健康检测配置进行优化调整。

优化思路:

当redis、mysql、MQ等中间服务出现短暂的网络超时或者不可用时,此时服务只是无法正常处理请求,本身并没有出现致命故障(比如:OOM),重启服务也无法恢复正常。

所以此时不应该让kubernetes去重启容器,只需要让他把容器标记为不可用即可,等网络、或者相关中间件恢复正常再标记为可用状态,提供正常服务即可。

优化以后:

readinessProbe:
  initialDelaySeconds: 10
  periodSeconds: 12
  timeoutSeconds: 2
  successThreshold: 1
  failureThreshold: 3
  httpGet:
    path: /health
    port: 8080
livenessProbe:
  initialDelaySeconds: 300
  periodSeconds: 12
  timeoutSeconds: 2
  successThreshold: 1
  failureThreshold: 3
  tcpSocket:  #或者使用httpGet path为/info
    port: 8080

由于我们的springboot服务并不是通过kubernetes的service去提供对外服务,标记为不可用以后没有什么实际意义,eureka server本身自己会对注册中的服务做健康状态检测,剔除不健康的服务。

这里需要注意,默认情况下eureka-server并不是通过spring boot actuator的/health端点进行健康状态探测,而是探测eureka client进程是否正常运行。

所以数据库、redis等出现故障,默认情况eureka 并不会把服务标记为down,我们需要在服务配置中添加eureka.client.healthcheck.enabled=true让eureka使用spring boot actuator的/health端点来对服务做健康探测。

【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: