今天在維護預生產環境的數據庫的時候,發生了一個災難性的故障(還好不是生產環境),集群中除了eureka和zuul的其他服務全部springboot服務都變成了不可用狀態,容器在不停的重啟中,出現這種情況,一般是Liveness和Readiness健康檢查失敗了。

為什么會出現這樣雪崩式的故障?接下來阿湯博客就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等等這些和服務相關的中間組件的狀態,只要有一個不正常,則健康檢查為失敗。
| 名稱 | 鍵 | 描述 |
|---|---|---|
| ApplicationHealthIndicator | none | 永遠為up |
| DataSourceHealthIndicator | db | 如果數據庫能連上,則內容是up和數據類型,否則為DOWN |
| DiskSpaceHealthIndicator | diskSpace | 如果可用空間大于閾值,則內容為UP和可用磁盤空間,如果空間不足則為DOWN |
| JmsHealthIndicator | jms | 如果能連上消息代理,則內容是up和JMS提供方的名稱,否則為DOWN |
| MailHealthIndicator | 如果能連上郵件服務器,則內容是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端點來對服務做健康探測。


