Kubernetes集群中SpringBoot服務的健康探測優化

2021年8月9日16:52:05 發表評論 3,742 ℃

今天在維護預生產環境的數據庫的時候,發生了一個災難性的故障(還好不是生產環境),集群中除了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: