今天在維護(hù)預(yù)生產(chǎn)環(huán)境的數(shù)據(jù)庫(kù)的時(shí)候,發(fā)生了一個(gè)災(zāi)難性的故障(還好不是生產(chǎn)環(huán)境),集群中除了eureka和zuul的其他服務(wù)全部springboot服務(wù)都變成了不可用狀態(tài),容器在不停的重啟中,出現(xiàn)這種情況,一般是Liveness和Readiness健康檢查失敗了。
為什么會(huì)出現(xiàn)這樣雪崩式的故障?接下來(lái)阿湯博客就kubernetes自愈和springboot健康檢查分享下具體原因。
一、Kubernetes自愈
Kubernetes強(qiáng)大的自愈能力毋容置疑,如何自愈?默認(rèn)實(shí)現(xiàn)方式是自動(dòng)重啟發(fā)生故障的容器,在非kubernetes集群中,要實(shí)現(xiàn)自愈是非常困難的一件事情。
那怎么判定容器發(fā)生了故障呢?kubernetes提供了Liveness和Readiness 探測(cè)機(jī)制,檢測(cè)容器的健康度。
Liveness 探測(cè)讓用戶可以自定義判斷容器是否健康的條件。如果探測(cè)失敗,Kubernetes 就會(huì)重啟容器,實(shí)現(xiàn)自愈。
Readiness 探測(cè)則是告訴 Kubernetes 什么時(shí)候可以將容器加入到 Service 負(fù)載均衡池中,對(duì)外提供服務(wù)。
Liveness VS Readiness 探測(cè)
1、Liveness 探測(cè)和 Readiness 探測(cè)是兩種 Health Check 機(jī)制,如果不特意配置,Kubernetes 將對(duì)兩種探測(cè)采取相同的默認(rèn)行為,即通過(guò)判斷容器啟動(dòng)進(jìn)程的返回值是否為零來(lái)判斷探測(cè)是否成功。
2、兩種探測(cè)的配置方法完全一樣,支持的配置參數(shù)也一樣。不同之處在于探測(cè)失敗后的行為:Liveness 探測(cè)是重啟容器;Readiness 探測(cè)則是將容器設(shè)置為不可用,不接收 Service 轉(zhuǎn)發(fā)的請(qǐng)求。
3、Liveness 探測(cè)和 Readiness 探測(cè)是獨(dú)立執(zhí)行的,二者之間沒(méi)有依賴(lài),所以可以單獨(dú)使用,也可以同時(shí)使用。用 Liveness 探測(cè)判斷容器是否需要重啟以實(shí)現(xiàn)自愈;用 Readiness 探測(cè)判斷容器是否已經(jīng)準(zhǔn)備好對(duì)外提供服務(wù)。
我們項(xiàng)目健康探測(cè)配置:
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中的監(jiān)控檢查機(jī)制
springboot服務(wù)中引用了依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
以后,對(duì)應(yīng)的服務(wù)就提供了端點(diǎn)/health。
Actuator 的 health 端點(diǎn)是主要用來(lái)檢查應(yīng)用的運(yùn)行狀態(tài),我們項(xiàng)目在kubernetes集群中也是使用此端點(diǎn)對(duì)springboot服務(wù)做的健康狀態(tài)檢測(cè)。
由于今天之前一直沒(méi)對(duì)Actuator 的 health 深入研究,導(dǎo)致出現(xiàn)了今天的雪崩故障。這也是為什么我維護(hù)數(shù)據(jù)庫(kù)的時(shí)候,導(dǎo)致所有服務(wù)都出現(xiàn)了不可用,無(wú)限重啟的情況。
故障一開(kāi)始雖然知道是健康檢查失敗了,但還是充滿了疑惑,為什么失敗呢?因?yàn)檫@期間除了維護(hù)了數(shù)據(jù)庫(kù),沒(méi)做其他操作。
于是停掉了開(kāi)發(fā)環(huán)境的數(shù)據(jù)庫(kù),馬上去查看開(kāi)發(fā)環(huán)境springboot服務(wù)pod的狀態(tài),沒(méi)過(guò)多久服務(wù)全部為不可用狀態(tài),然后就開(kāi)始重啟容器嘗試恢復(fù)。
經(jīng)過(guò)反復(fù)測(cè)試,發(fā)現(xiàn)只要數(shù)據(jù)庫(kù)一停,服務(wù)的/health端點(diǎn),返回的就是DOWN狀態(tài)。
我去查閱了關(guān)于spring boot actuator健康檢查相關(guān)的文章,原來(lái)默認(rèn)情況下/health端點(diǎn)會(huì)檢測(cè)數(shù)據(jù)庫(kù)連接、Redis鏈接、RocketMQ、diskSpace等等這些和服務(wù)相關(guān)的中間組件的狀態(tài),只要有一個(gè)不正常,則健康檢查為失敗。
名稱(chēng) | 鍵 | 描述 |
---|---|---|
ApplicationHealthIndicator | none | 永遠(yuǎn)為up |
DataSourceHealthIndicator | db | 如果數(shù)據(jù)庫(kù)能連上,則內(nèi)容是up和數(shù)據(jù)類(lèi)型,否則為DOWN |
DiskSpaceHealthIndicator | diskSpace | 如果可用空間大于閾值,則內(nèi)容為UP和可用磁盤(pán)空間,如果空間不足則為DOWN |
JmsHealthIndicator | jms | 如果能連上消息代理,則內(nèi)容是up和JMS提供方的名稱(chēng),否則為DOWN |
MailHealthIndicator | 如果能連上郵件服務(wù)器,則內(nèi)容是up和郵件服務(wù)器主機(jī)和端口,否則為DOWN | |
MongoHealthIndicator | mongo | 如果能連上MongoDB服務(wù)器,則內(nèi)容是up和MongoDB服務(wù)器版本,否則為DOWN |
RabbitHealthIndicator | rabbit | 如果能連上RabbitMQ服務(wù)器,則內(nèi)容是up和版本號(hào),否則為DOWN |
RedisHealthIndicator | redis | 如果能連上服務(wù)器,則內(nèi)容是up和Redis服務(wù)器版本,否則為DOWN |
SolrHealthIndicator | solr | 如果能連上solr服務(wù)器,則內(nèi)容是up,否則為DOWN |
因?yàn)槲襆iveness和Readiness探測(cè)都采用的spring boot actuator的/health端點(diǎn),所以當(dāng)我停掉mysql以后,所有使用了mysql的服務(wù)監(jiān)控檢查全部為DOWN狀態(tài),導(dǎo)致觸發(fā)了kubernetes的重啟自愈。
這樣就導(dǎo)致所有服務(wù)都會(huì)不停的重啟,導(dǎo)致服務(wù)器CPU一直處于滿負(fù)載,然后就形成了連鎖反應(yīng),可能會(huì)導(dǎo)致其他pod因?yàn)榻】禉z查超時(shí),出現(xiàn)失敗,然后又重啟的情況。
有了上面的問(wèn)題,就需要對(duì)當(dāng)前的健康檢測(cè)配置進(jìn)行優(yōu)化調(diào)整。
優(yōu)化思路:
當(dāng)redis、mysql、MQ等中間服務(wù)出現(xiàn)短暫的網(wǎng)絡(luò)超時(shí)或者不可用時(shí),此時(shí)服務(wù)只是無(wú)法正常處理請(qǐng)求,本身并沒(méi)有出現(xiàn)致命故障(比如:OOM),重啟服務(wù)也無(wú)法恢復(fù)正常。
所以此時(shí)不應(yīng)該讓kubernetes去重啟容器,只需要讓他把容器標(biāo)記為不可用即可,等網(wǎng)絡(luò)、或者相關(guān)中間件恢復(fù)正常再標(biāo)記為可用狀態(tài),提供正常服務(wù)即可。
優(yōu)化以后:
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服務(wù)并不是通過(guò)kubernetes的service去提供對(duì)外服務(wù),標(biāo)記為不可用以后沒(méi)有什么實(shí)際意義,eureka server本身自己會(huì)對(duì)注冊(cè)中的服務(wù)做健康狀態(tài)檢測(cè),剔除不健康的服務(wù)。
這里需要注意,默認(rèn)情況下eureka-server并不是通過(guò)spring boot actuator的/health端點(diǎn)進(jìn)行健康狀態(tài)探測(cè),而是探測(cè)eureka client進(jìn)程是否正常運(yùn)行。
所以數(shù)據(jù)庫(kù)、redis等出現(xiàn)故障,默認(rèn)情況eureka 并不會(huì)把服務(wù)標(biāo)記為down,我們需要在服務(wù)配置中添加eureka.client.healthcheck.enabled=true讓eureka使用spring boot actuator的/health端點(diǎn)來(lái)對(duì)服務(wù)做健康探測(cè)。