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

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

今天在维护预生产环境的数据库的时候,发生了一个灾难性的故障(还好不是生产环境),集群中除了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: