Kubernetes之健康檢查與服務依賴處理
【編者的話】對線上業務來說,保證服務的正常穩定是重中之重,對故障服務的及時處理避免影響業務以及快速恢復一直是開發運維的難點。Kubernetes提供了健康檢查服務,對於檢測到故障服務會被及時自動下線,以及通過重啟服務的方式使服務自動恢復。而對於服務依賴,無論資源描述檔案是pod, rc或deployment, 對應yaml檔案中描述僅是container啟動順序而非Container中服務啟動順序,Kubernetes提供了的Init Container,可處理服務之間依賴。
簡單例子說明什麼是健康檢查以及服務依賴,比如一個應用分別有A、B、 C 3個服務,健康檢查就是如何確定A、B與C所在的Container處於執行狀態且服務工作正常;而對於服務依賴,假設服務A必須在服務B成功啟動之前啟動,而服務B必須在服務C成功啟動之前啟動,即啟動順序為A->B->C。
健康檢查
使用Liveness及Readness探針
其中Liveness探針主要用於判斷Container是否處於執行狀態,比如當服務crash或者死鎖等情況發生時,kubelet會kill掉Container, 然後根據其設定的restart policy進行相應操作(可能會在本機重新啟動Container,或者因為設定Kubernetes QoS,本機沒有資源情況下會被分發的其他機器上重新啟動)。
而Readness探針主要用於判斷服務是否已經正常工作,如果服務沒有載入完成或工作異常,服務所在的Pod的IP地址會從服務的endpoints中被移除,也就是說,當服務沒有ready時,會將其從服務的load balancer中移除,不會再接受或響應任何請求。
探針處理Handler型別
無論對於Readness或Liveness探針,Handler均支援以下3種類型:ExecAction, TCPSocketAction, HTTPGetAction。每種型別說明與舉例如下:
- ExecAction:Container內部執行某個具體的命令,例子。
- TCPSocketAction:通過container的IP、port執行tcp進行檢查, 例子。
- HTTPGetAction: 通過container的IP、port、path,用HTTP Get請求進行檢查,例子。
探針檢查結果
探針檢查結果分為3種情況:
- 成功(Success):通過檢查。
- 失敗(Failure):檢查失敗。
- 未知(Unknown):檢查未知,需要人工干預。
健康檢查總結
探針型別說明通過健康檢查標準ExecAction container內部執行shell命令 shell命令返回0TCPSocketAction通過container的IP、port執行tcp進行檢查 port是否開啟HTTPGetAction通過container的IP、port、path,用HTTP Get請求進行檢查200<=返回值<400
服務可用性與自動恢復
- 如果服務的健康檢查(readiness)失敗,故障的服務例項從service endpoint中下線,外部請求將不會再轉發到該服務上,一定程度上保證正在提供的服務的正確性,如果服務自我恢復了(比如網路問題),會自動重新加入service endpoint對外提供服務。
- 另外,如果設定了Container(liveness)的探針,對故障服務的Container(liveness)的探針同樣會失敗,container會被kill掉,並根據原設定的container重啟策略,系統傾向於在其原所在的機器上重啟該container、或其他機器重新建立一個pod。
- 由於上面的機制,整個服務實現了自身可用與自動恢復。
使用建議
- 建議對全部服務同時設定服務(readiness)和Container(liveness)的健康檢查
- 通過TCP對埠檢查形式(TCPSocketAction),僅適用於埠已關閉或程序停止情況。因為即使服務異常,只要埠是開啟狀態,健康檢查仍然是通過的。
- 基於第二點,一般建議用ExecAction自定義健康檢查邏輯,或採用HTTP Get請求進行檢查(HTTPGetAction)。
- 無論採用哪種型別的探針,一般建議設定檢查服務(readiness)的時間短於檢查Container(liveness)的時間,也可以將檢查服務(readiness)的探針與Container(liveness)的探針設定為一致。目的是故障服務先下線,如果過一段時間還無法自動恢復,那麼根據重啟策略,重啟該container、或其他機器重新建立一個pod恢復故障服務。
服務依賴
理解Init Container
一個pod中可以有一或多個Init Container。Pod的中多個Init Container啟動順序為yaml檔案中的描述順序,且序列方式啟動,下一個Init/app Container必須等待上一個Init Container完成後方可啟動。例如,Init Container1-> … -> Init Containern -> app Container[1-n]。Init Container1成功啟動並且完成後,後續的Init Container和app Container才可以啟動,如Init Container啟動或執行相關檢查失敗,後續的init Container和應用Container將不會被執行啟動命令。
因此可利用Init Container來判斷app Container中被依賴的服務是否成功啟動。如被依賴的app Container服務啟動失敗,那麼利用Init Container啟動失敗可以阻止後續app Container服務的啟動。
由於Init Container必須要在pod狀態變為Ready之前完成,所以其不需要readiness探針。另外在資源的requests與limits上與普通Container有細微差別,詳見 Resources,除以上2點外,Init Container與普通Container並無明顯區別。
Init Containers用途
- 前文已經提及,由於Init Container必須在app Containers啟動之前完成,所以可利用其特性,用作服務依賴處理。比如某一個服務A,需依賴db或memcached,那麼可以利用服務A pod的Init Container判斷db/memcached是否正常提供服務,如果啟動服務失敗或工作異常,設定Init Container啟動失敗,那麼pod中的服務A就不會被啟動了。
- 應用映象因安全原因等沒辦法安裝或執行的工具,可放到Init Container中執行。另外,Init Container獨立於業務服務,與業務無關的工具如sed, awk, python, dig等也可以按需放到Init Container之中。最後,Init Container也可以被授權訪問應用Container無權訪問的內容。
Init Container處理服務依賴應用舉例
serviceA服務依賴serviceB,而serviceB採用上文提及Readness探針的HTTPGetAction Handler。
spec: initContainers:- name: init-serviceA image: registry.docker.dev.fwmrm.net/busybox:latest command:['sh','-c',"curl --connect-timeout 3 --max-time 5 --retry 10 --retry-delay 5 --retry-max-time 60 serviceB:portB/pathB/"] containers:
如果啟動serviceA Pod時,serviceB還沒有ready,通過kubectl get po -o wide檢視pod處於Init狀態
NAME READY STATUS RESTARTS AGE IP .l NODE serviceA-3071943788-8x27q0/1Init:0/1020s10.244.1.172 bjo-ep-svc-017.dev.fwmrm.net
通過kubectl describe po serviceA-3071943788-g03wt檢視,可以看出app Container的啟動時在init Container啟動併成功完成後:
Events:FirstSeenLastSeenCountFromSubObjectPathTypeReasonMessage------------------------------------------------------------25s25s1default-scheduler NormalScheduledSuccessfully assigned serviceA-3071943788-g03wt to bjo-ep-dep-040.dev.fwmrm.net 25s25s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net NormalSuccessfulMountVolumeMountVolume.SetUp succeeded for volume "serviceA-config-volume"25s25s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net NormalSuccessfulMountVolumeMountVolume.SetUp succeeded for volume "default-token-2c9j1"24s24s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.initContainers{init-myservice}NormalPulling pulling image "registry.docker.dev.fwmrm.net/ui-search-solr-data:latest"24s24s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.initContainers{init-myservice}NormalPulledSuccessfully pulled image "registry.docker.dev.fwmrm.net/busybox:latest"24s24s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.initContainers{init-myservice}NormalCreatedCreated container 24s24s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.initContainers{init-myservice}NormalStartedStarted container 20s20s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.containers{is}NormalPulling pulling image "registry.docker.dev.fwmrm.net/infra/is:latest"20s20s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.containers{is}NormalPulledSuccessfully pulled image "registry.docker.dev.fwmrm.net/infra/is:latest"20s20s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.containers{is}NormalCreatedCreated container 19s19s1 kubelet, bjo-ep-dep-040.dev.fwmrm.net spec.containers{is}NormalStartedStarted container
檢視docker Container log,init Container正在按照預先的設定,每3秒輪詢驗證serviceB健康檢查點serviceB:portB/pathB/
docker logs 4fd58bf54f76 waiting for serviceB service waiting for serviceB service ......
等待一段時間後,再次通過kubectl get po -o wide檢視pod處於Running狀態
NAME READY STATUS RESTARTS AGE IP .l NODE serviceA-3071943788-g03wt 1/1Running01m10.244.2.68 bjo-ep-dep-040.dev.fwmrm.net
如果pod重啟了,所有init Container都需重新執行。Kubernetes禁止Init Container使用readiness探針,可以使用Pod定義 activeDeadlineSeconds 和 Container的 livenessProbe 防止 init containers 一直重複失敗. activeDeadlineSeconds 包含了 init container 啟動的時間。
參考資料
歡迎轉載,請註明作者出處:張夏,FreeWheel Lead Engineer,Kubernetes中文社群