Kubernetes容器狀態(tài)探測的藝術(shù)

在Kubernetes集群中維護(hù)容器狀態(tài)更像是一種藝術(shù),而不是科學(xué)。原文: The Art and Science of Probing a Kubernetes Container

在Kubernetes集群中維護(hù)容器狀態(tài)更像是一種藝術(shù),而不是科學(xué)。

本文將帶你深入理解容器探測,并特別關(guān)注相對較新的啟動(dòng)探測。在此過程中,通過文中的推薦鏈接,可以進(jìn)一步了解相關(guān)領(lǐng)域,以實(shí)現(xiàn)文中的各種建議。

啟動(dòng)……不對……是在Kubernetes集群中請求啟動(dòng)新容器相對簡單: 只需要為集群提供一個(gè)pod規(guī)范,尤其是封裝了各種工作負(fù)載資源(比如DeploymentJob)的pod模板。在接收到pod規(guī)范后,kube-scheduler將為pod分配一個(gè)節(jié)點(diǎn),然后該節(jié)點(diǎn)的kubelet負(fù)責(zé)啟動(dòng)pod中的容器。

pod遵循明確的生命周期,其中就允許kubelet探測pod容器,以確保始終都有響應(yīng)。探測器遵循如下契約: pod容器通告端點(diǎn),kubelet從端點(diǎn)輪詢其內(nèi)部不同狀態(tài)。

簡單來說,有三種類型的探針來表示容器的內(nèi)部狀態(tài):

  • 準(zhǔn)備就緒探針(Readiness probes): 該探針告訴kubelet容器何時(shí)準(zhǔn)備好處理請求,是最普遍的容器探測。
  • 活躍探針(Liveness probes): 該探針在緊急情況下會(huì)觸發(fā)容器中斷。kubelet將終止在指定時(shí)間間隔內(nèi)沒有成功響應(yīng)的容器,理想情況下,容器應(yīng)該在意識(shí)到無法繼續(xù)工作后退出,但在有bug的情況下很少能夠優(yōu)雅退出。
  • 啟動(dòng)探針(Startup probes): 意思是"我剛到這里,別管我"。它告訴kubelet何時(shí)開始對readiness和liveness進(jìn)行探測。
圖1: 容器探測和pod生命周期之間的關(guān)系

Readiness probes

如果容器未能響應(yīng)其readiness探測,kubelet將從服務(wù)負(fù)載均衡器中刪除容器,將流量從容器中轉(zhuǎn)移。在這種情況下,開發(fā)人員希望其他地方的副本可以處理流量。

readiness探測的設(shè)計(jì)比較簡單,只需要考慮依賴狀態(tài)和容器中的資源使用情況:

  • 依賴關(guān)系(Dependencies)。 假設(shè)容器依賴數(shù)據(jù)庫服務(wù)器或另一個(gè)遠(yuǎn)程服務(wù),在這種情況下,這些依賴項(xiàng)往往會(huì)提供一個(gè)端點(diǎn)或命令行接口來評估它們的就緒情況。如果依賴關(guān)系處于關(guān)鍵路徑中,則在計(jì)算探測的readiness狀態(tài)時(shí)需要考慮依賴關(guān)系狀態(tài)。
  • 允許的最大連接數(shù)。 許多框架都有接受多少新連接的閾值,因此考慮這些限制并在超過閾值時(shí)報(bào)告readiness失敗。
  • 系統(tǒng)資源。 這一點(diǎn)并不明顯,但是在接近內(nèi)存上限和文件系統(tǒng)空間不足的情況下運(yùn)行會(huì)導(dǎo)致進(jìn)程不穩(wěn)定。我們希望集群在耗盡系統(tǒng)資源之前停止向pod發(fā)送流量,因此可以考慮在這些限制達(dá)到最大值的某個(gè)閾值時(shí)探測調(diào)用失敗。我最不喜歡的資源耗盡形式之一是耗盡文件句柄,這比簡單的磁盤空間不足更難發(fā)現(xiàn),甚至可能阻礙最基本的故障排除任務(wù)。

要做:

  • 實(shí)現(xiàn)探針。 始終為運(yùn)行時(shí)容器定義readiness探測??赡苣阌X得容器客戶機(jī)可以處理容器無響應(yīng)問題。盡管如此,讓集群將請求路由到還沒有準(zhǔn)備好處理請求的容器,從來都不是個(gè)好選擇。
  • 準(zhǔn)備好狀態(tài)。 考慮用單獨(dú)的readiness線程向外部匯報(bào)遠(yuǎn)程依賴和資源利用狀態(tài)。readiness探測超時(shí)時(shí),最好立即返回清晰的故障代碼,而不是冒著暫停響應(yīng)的風(fēng)險(xiǎn)從所有依賴項(xiàng)收集輸入。

不要做:

  • 不要超過liveness探針的探測時(shí)間。 如果容器也有l(wèi)iveness探測,不要使最大超時(shí)時(shí)間(failureThreshold * periodSeconds)超過liveness探測的最大時(shí)間。這會(huì)讓集群將請求路由到可能沒有希望的即將崩潰的容器。
  • 不要把反應(yīng)緩慢和readiness混在一起。 緩慢的反應(yīng)也是一種反應(yīng)。由于依賴關(guān)系處理請求的時(shí)間比通常要長得多,可能你會(huì)覺得容器需要讓調(diào)用者知道它還沒有準(zhǔn)備好。不過,監(jiān)視服務(wù)性能是應(yīng)用級的關(guān)注點(diǎn),最好通過可觀察性解決。

Liveness probes

如果容器不能連續(xù)響應(yīng)此探測,kubelet將終止容器。具體是"終止"還是"重啟",取決于pod的重啟策略。

眾所周知,對這些探針進(jìn)行正確編碼非常困難,因?yàn)樘结橀_發(fā)人員的目標(biāo)是預(yù)測意外情況,比如進(jìn)程中的bug可能會(huì)使整個(gè)容器處于不可恢復(fù)狀態(tài)這樣的情況。

如果探測過于寬松,容器可能會(huì)處于無響應(yīng)狀態(tài)而不會(huì)被終止,從而減少了可以提供服務(wù)的pod副本數(shù)量。

如果探測過于嚴(yán)格,容器可能會(huì)一直被不必要的終止,這種情況在間歇性發(fā)生時(shí)很難察覺,當(dāng)你排查問題時(shí),pod可能看起來很健康。

要做:

  • 定義liveness探針。 沒有l(wèi)iveness探測意味著容器可能會(huì)因?yàn)槟硞€(gè)錯(cuò)誤而永遠(yuǎn)無法響應(yīng),所以即使無法完全確定萬無一失的liveness標(biāo)準(zhǔn),還是要嘗試定義。
  • 監(jiān)控資源。 覆蓋文件系統(tǒng)空間、文件句柄和內(nèi)存等資源。這些資源在耗盡時(shí)會(huì)發(fā)生容器鎖定這樣臭名昭著的現(xiàn)象。當(dāng)內(nèi)存利用率超過90%時(shí)返回錯(cuò)誤,比在達(dá)到100%后完全失去響應(yīng)更明智。探測有超時(shí)值,但最好返回清晰的失敗代碼,而不是讓kubelet從超時(shí)來推斷崩潰。
  • 使用命令。 調(diào)用命令而不是tcp或http請求。這個(gè)建議可能有爭議,但是調(diào)用shell命令將使用較低級別的接口,反過來又有更多機(jī)會(huì)評估容器內(nèi)部狀態(tài)。我遇到過一些網(wǎng)絡(luò)服務(wù)器,即使由于內(nèi)存不足而半死不活,仍然能夠響應(yīng)簡單的ping請求。
  • 微控制面(Micro control-planes)。 如果可能的話,使用與用于服務(wù)客戶流量的連接池不同的連接池,或者專門為探測設(shè)置連接,將其設(shè)想為集群中與常規(guī)工作負(fù)載不同(但規(guī)模要小得多)的控制平面。除非liveness探針具有響應(yīng)探測的專用連接,否則容器可能正在忙于服務(wù)客戶流量(并通過readiness探針報(bào)告了未準(zhǔn)備就緒狀態(tài))。在這種情況下終止容器對業(yè)務(wù)有害,因?yàn)榧鹤詈?臨時(shí))只有更少的容器來處理業(yè)務(wù)流量。
  • 準(zhǔn)備好狀態(tài)。 類似于liveness探針一節(jié)中的建議,考慮用單獨(dú)的liveness探針服務(wù)線程向外部匯報(bào)活躍狀態(tài)。liveness探測超時(shí)時(shí),最好立即返回清晰的故障代碼,而不是冒著暫停響應(yīng)的風(fēng)險(xiǎn)從所有依賴項(xiàng)收集輸入。

不要做:

  • readiness不是liveness: 不要檢查依賴關(guān)系的可用性,如果失敗,這是readiness探針的責(zé)任,終止pod不太可能有幫助。
  • 不要重用readiness條件: 經(jīng)??吹饺萜魈綔y使用相同的端點(diǎn),但在failureThresholdperiodSeconds中使用不同的閾值。liveness探測的關(guān)注點(diǎn)不同于readiness探測的關(guān)注點(diǎn)。容器可能由于外部因素而無法處理流量,而liveness探測使用與readiness探測相同的端點(diǎn),可能會(huì)告訴kubelet終止容器,從而加劇問題。
  • 超時(shí)時(shí)間超過readiness探測: 無論是查看failureThreshold、periodSeconds還是考慮容器中系統(tǒng)資源的可用性,都要確保liveness探測的超時(shí)范圍超過readiness探測的超時(shí)范圍。例如,最大超時(shí)時(shí)間(initialDelaySeconds + failureThreshold * periodSeconds)比readiness探測的最大超時(shí)時(shí)間更短會(huì)造成容器在仍然為遠(yuǎn)程請求服務(wù)的情況下被過早終止。
  • 不要過于保守: 在有疑問時(shí),寧可寬大處理,也要為failureThreshold、periodSecondsinitialDelaySeconds設(shè)置更高的值,給容器足夠的自由度來報(bào)告生存狀態(tài)。一個(gè)不錯(cuò)的經(jīng)驗(yàn)法則是使用比readiness探針的最大超時(shí)時(shí)間長兩倍或更長的總超時(shí)時(shí)間。意外掛起進(jìn)程是一種邊緣情況,比響應(yīng)緩慢的進(jìn)程更罕見,liveness探測應(yīng)該支持最常見的情況。
圖2: liveness探測應(yīng)該比readiness探測的時(shí)間更長。

Startup probes

startup探針是容器探針中相對較新的成員,2020年底在Kubernetes 1.20中達(dá)到了GA。注意,這要?dú)w功于我的同事,博學(xué)的Nathan Brophy,他指出這個(gè)功能在Kubernetes 1.18盡管還處于測試階段,但已經(jīng)默認(rèn)可用了。

startup探測在容器生命周期中為需要大量時(shí)間才能準(zhǔn)備就緒的容器創(chuàng)建了一個(gè)"緩沖區(qū)"。

在過去,在沒有startup探測的情況下,開發(fā)人員使用初始化容器,并為readiness探測和liveness探測設(shè)置較長的initialDelaySeconds值,每一種都有自己的權(quán)衡:

  • 可以等待,但不應(yīng)該等待。 初始化容器允許開發(fā)人員將特定工具和安全特權(quán)與運(yùn)行時(shí)容器隔離開來,但這是一種等待外部條件的笨拙方式。初始化容器是獨(dú)立容器,因此將持續(xù)運(yùn)行直到完成,將它們的工作結(jié)果轉(zhuǎn)移到pod中的其他容器中可能會(huì)很麻煩。
  • 啟動(dòng)緩慢。 在readiness探測上通過initialDelaySeconds字段設(shè)置長時(shí)間等待會(huì)在容器啟動(dòng)期間浪費(fèi)時(shí)間,因?yàn)閗ubelet總是需要在將流量發(fā)送到pod之前等待這么長時(shí)間。

考慮現(xiàn)有l(wèi)iveness探測和readiness探測是否需要相對較長的時(shí)間來啟動(dòng),并將較大的initialDelaySeconds值替換為等效的startup探測。

例如,下面的容器規(guī)范:

spec:
  containers:
  - name: myslowstarter
    image: ...
    ...
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 600
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 600
      periodSeconds: 20

可以通過將延遲挪到startup探測中來顯著改善,如下例所示:

spec:
  containers:
  - name: myslowstarter
    image: ...
    ...
    readinessProbe:
      tcpSocket:
        port: 8080
      # i?n?i?t?i?a?l?D?e?l?a?y?S?e?c?o?n?d?s?:? ?6?0?0?
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      # i?n?i?t?i?a?l?D?e?l?a?y?S?e?c?o?n?d?s?:? ?6?0?0?
      periodSeconds: 20
    startupProbe:
      tcpSocket:
        port: 8080
      failureThreshold: 60
      periodSeconds: 10

在第一個(gè)示例中,kubelet在評估readiness和liveness之前等待600秒。相比之下,在第二個(gè)示例中,kubelet以10秒的間隔檢查最多60次,從而使容器能夠在滿足啟動(dòng)條件時(shí)立即啟動(dòng)。

在startup探測中頻繁檢查的隱藏好處是,允許開發(fā)人員為failureThresholdperiodSeconds設(shè)置較高的值,而不用擔(dān)心減慢容器啟動(dòng)速度。相反,死板的設(shè)置initialDelaySeconds給開發(fā)人員施加了壓力,忽略了邊緣情況,只能通過設(shè)置較低的值才能讓整個(gè)應(yīng)用更快啟動(dòng)。根據(jù)我的經(jīng)驗(yàn),"邊緣情況"是"我們在開發(fā)過程中沒有看到的東西"的同義詞,這意味著在某些生產(chǎn)環(huán)境中會(huì)出現(xiàn)不穩(wěn)定的容器。

根據(jù)經(jīng)驗(yàn),如果readiness探測和liveness探測中的initialDelaySeconds字段超過failureThreshold * periodSeconds字段指定的總時(shí)間,則使用startup探測。另一條經(jīng)驗(yàn)法則是,readiness探測或liveness探測中initialDelaySeconds的時(shí)間如果超過60秒,就說明應(yīng)用將受益于使用startup探測。

速度就是一切

在看到本文的建議后,你可能不可避免的會(huì)問下面的問題:

"那么我應(yīng)該用什么來設(shè)置探針呢?"

我們通常希望readiness探針比較敏感,并且在容器開始難以響應(yīng)請求時(shí)就報(bào)告失敗。另一方面,希望liveness探針稍微寬松一點(diǎn),只在代碼失去對有效內(nèi)部狀態(tài)的控制時(shí)才報(bào)告失敗。

對于timeoutSeconds,我建議保持默認(rèn)值(1秒)。這個(gè)建議建立在另一個(gè)建議之上,即通過用于評估響應(yīng)kubelet請求的線程之外的探針的響應(yīng)。使用較高的值將擴(kuò)大窗口,集群會(huì)將流量路由到無法處理請求的容器。

對于periodSecondsfailureThreshold的組合,在相同間隔內(nèi)進(jìn)行更多檢查往往比較少檢查更準(zhǔn)確。假設(shè)我們遵循了將容器狀態(tài)與響應(yīng)請求的線程分開評估的建議,那么更頻繁的檢查不會(huì)給容器增加顯著的開銷。

注意CPU限制

不同的集群,不同的速度。

探測(尤其是liveness探測)的一個(gè)常見問題是,假設(shè)集群總是按照要求為容器提供足夠的CPU,另一個(gè)常見錯(cuò)誤是假設(shè)集群總是能夠精確觀察到部分請求。

從hypervisor和承載工作節(jié)點(diǎn)的VM開始,一直到pod規(guī)范中的CPU限制,容器有無數(shù)原因可以以不同速度運(yùn)行同一段代碼。

以下是最容易讓開發(fā)者措手不及的因素:

  • hypervisor中的cpu復(fù)用: IaaS供應(yīng)商的共享虛擬機(jī),即使硬件和網(wǎng)絡(luò)速度相同,也會(huì)偶爾受到"鄰居"的影響,從而導(dǎo)致CPU使用量激增。現(xiàn)代hypervisors非常擅長補(bǔ)償這樣的突發(fā)狀況,甚至?xí)扇」?jié)流措施。盡管如此,IaaS還是可能會(huì)超賣CPU,并假設(shè)不會(huì)同時(shí)出現(xiàn)流量突發(fā)狀況。
  • 無窮小的CPU請求: 將CPU分配給容器的CPU限制設(shè)置為20ms似乎是一個(gè)負(fù)責(zé)任的、有意識(shí)的決定,因?yàn)槿萜骱苌龠M(jìn)行任何處理。然而,在現(xiàn)實(shí)世界中,工作節(jié)點(diǎn)的vCPU并不只有完整vCPU大小的2%。工作節(jié)點(diǎn)試圖通過在短時(shí)間內(nèi)為容器提供整個(gè)vCPU來模擬這個(gè)微小的vCPU,從而導(dǎo)致對容器CPU分配產(chǎn)生碎片。因此,容器可能在短時(shí)間內(nèi)運(yùn)行比所請求的更多的CPU時(shí)間,然后暫停比預(yù)期更長的時(shí)間。

了解一些關(guān)于IaaS提供商的硬件特性和超賣設(shè)置的知識(shí),可以幫助我們決定將安全乘數(shù)添加到timeoutSeconds、failureThresholdperiodSeconds等設(shè)置中。在為探針,特別是liveness探針設(shè)置時(shí),請記住這兩個(gè)因素。根據(jù)所了解的內(nèi)容,還可以重新考慮CPU requests和limits的設(shè)置,以便探針有足夠的處理能力及時(shí)響應(yīng)請求。

圖3: 由于嚴(yán)格的CPU限制而終止正常運(yùn)行的容器
結(jié)論

本文提供了一系列建議來提高容器探測的精度和性能,使容器能夠更快啟動(dòng)并運(yùn)行更長時(shí)間。

下一步是仔細(xì)分析容器中運(yùn)行的內(nèi)容,并研究在不同集群和條件下的實(shí)際運(yùn)行時(shí)行為,模擬依賴關(guān)系失敗或者降低系統(tǒng)資源可用性。使用kubectl及其格式化和過濾內(nèi)容的功能,是查找重新啟動(dòng)多次以及探測不充分的容器的好方法,在The art and science of probing a Kubernetes container, part 2: kubectl queries這篇文章中有更多相關(guān)技術(shù)內(nèi)容。將PromQL與Kubernetes指標(biāo)結(jié)合使用可以用各種時(shí)間序列圖表進(jìn)一步擴(kuò)展該技術(shù),這也是那篇文章的主題。

總之,在編寫探針時(shí)牢記目標(biāo),并確??焖倏煽康倪\(yùn)行,在對kubelet的響應(yīng)中以盡可能(如果有的話)精確的方式提供清晰的信息。然后,信任集群會(huì)以最佳方式處理數(shù)據(jù),確保容器對其客戶端提供最大的可用性。


你好,我是俞凡,在Motorola做過研發(fā),現(xiàn)在在Mavenir做技術(shù)工作,對通信、網(wǎng)絡(luò)、后端架構(gòu)、云原生、DevOps、CICD、區(qū)塊鏈、AI等技術(shù)始終保持著濃厚的興趣,平時(shí)喜歡閱讀、思考,相信持續(xù)學(xué)習(xí)、終身成長,歡迎一起交流學(xué)習(xí)。
微信公眾號(hào):DeepNoMind

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容