哦哇資訊網

解讀K8s Pod的13種典型異常

由 阿里開發者 發表于 美食2023-01-26

簡介: 在K8s中,Pod作為工作負載的執行載體,是最為核心的一個資源物件。Pod具有複雜的生命週期,在其生命週期的每一個階段,可能發生多種不同的異常情況。K8s作為一個複雜系統,異常診斷往往要求強大的知識和經驗儲備。結合實戰經歷以及EDAS使用者真實場景的歸納,我們總結了K8s Pod的13種常見異常場景,給出各個場景的常見錯誤狀態,分析其原因和排查思路。

在K8s中,Pod作為工作負載的執行載體,是最為核心的一個資源物件。Pod具有複雜的生命週期,在其生命週期的每一個階段,可能發生多種不同的異常情況。K8s作為一個複雜系統,異常診斷往往要求強大的知識和經驗儲備。結合實戰經歷以及EDAS使用者真實場景的歸納,我們總結了K8s Pod的13種常見異常場景,給出各個場景的常見錯誤狀態,分析其原因和排查思路。

Pod生命週期

在整個生命週期中,Pod會出現5種階段(Phase)。

Pending:Pod被K8s創建出來後,起始於Pending階段。在Pending階段,Pod將經過排程,被分配至目標節點開始拉取映象、載入依賴項、建立容器。

Running:當Pod所有容器都已被建立,且至少一個容器已經在執行中,Pod將進入Running階段。

Succeeded:當Pod中的所有容器都執行完成後終止,並且不會再重啟,Pod將進入Succeeded階段。

Failed:若Pod中的所有容器都已終止,並且至少有一個容器是因為失敗終止,也就是說容器以非0狀態異常退出或被系統終止,Pod將進入Failed階段。

Unkonwn:因為某些原因無法取得 Pod 狀態,這種情況Pod將被置為Unkonwn狀態。

一般來說,對於Job型別的負載,Pod在成功執行完任務之後將會以Succeeded狀態為終態。而對於Deployment等負載,一般期望Pod能夠持續提供服務,直到Pod因刪除消失,或者因異常退出/被系統終止而進入Failed階段。

Pod的5個階段是 Pod 在其生命週期中所處位置的簡單宏觀概述,並不是對容器或 Pod 狀態的綜合彙總。Pod有一些細分狀態( PodConditions ),例如Ready/NotReady、Initialized、 PodScheduled/Unschedulable等。這些細分狀態描述造成Pod所處階段的具體成因是什麼。比如,Pod 當前階段是Pending,對應的細分狀態是 Unschedulable,這就意味著Pod排程出現了問題。

容器也有其生命週期狀態(State):Waiting、Running和 Terminated。並且也有其對應的狀態原因(Reason),例如ContainerCreating、Error、OOMKilled、CrashLoopBackOff、Completed等。而對於發生過重啟或終止的容器,上一個狀態(LastState)欄位不僅包含狀態原因,還包含上一次退出的狀態碼(Exit Code)。例如容器上一次退出狀態碼是137,狀態原因是OOMKilled,說明容器是因為OOM被系統強行終止。在異常診斷過程中,容器的退出狀態是至關重要的資訊。

除了必要的叢集和應用監控,一般還需要透過kubectl命令蒐集異常狀態資訊。

// 獲取Pod當前物件描述檔案kubectl get pod -n -o yaml // 獲取Pod資訊和事件(Events)kubectl describe pod -n // 獲取Pod容器日誌kubectl logs -n // 在容器中執行命令kubectl exec -n -c ——

Pod異常場景

Pod在其生命週期的許多時間點可能發生不同的異常,按照Pod容器是否執行為標誌點,我們將異常場景大致分為兩類:

在Pod進行排程並建立容器過程中發生異常,此時Pod將卡在Pending階段。

Pod容器執行中發生異常,此時Pod按照具體場景處在不同階段。

下文將對這具體的13種場景進行描述和分析。

排程失敗

常見錯誤狀態:Unschedulable

Pod被建立後進入排程階段,K8s排程器依據Pod宣告的資源請求量和排程規則,為Pod挑選一個適合執行的節點。當叢集節點均不滿足Pod排程需求時,Pod將會處於Pending狀態。造成排程失敗的典型原因如下:

節點資源不足

K8s將節點資源(CPU、記憶體、磁碟等)進行數值量化,定義出節點資源容量(Capacity)和節點資源可分配額(Allocatable)。資源容量是指 Kubelet 獲取的計算節點當前的資源資訊,而資源可分配額是Pod可用的資源。Pod容器有兩種資源額度概念:請求值Request和限制值Limit,容器至少能獲取請求值大小、至多能獲取限制值的資源量。Pod 的資源請求量是Pod中所有容器的資源請求之和,Pod的資源限制量是Pod中所有容器的資源限制之和。K8s預設排程器按照較小的請求值作為排程依據,保障可排程節點的資源可分配額一定不小於Pod資源請求值。當叢集沒有一個節點滿足Pod的資源請求量,則Pod將卡在Pending狀態。

Pod因為無法滿足資源需求而被Pending,可能是因為叢集資源不足,需要進行擴容,也有可能是叢集碎片導致。以一個典型場景為例,使用者叢集有10幾個4c8g的節點,整個叢集資源使用率在60%左右,每個節點都有碎片,但因為碎片太小導致擴不出來一個2c4g的Pod。一般來說,小節點叢集會更容易產生資源碎片,而碎片資源無法供Pod排程使用。如果想最大限度地減少資源浪費,使用更大的節點可能會帶來更好的結果。

超過Namespace資源配額

K8s使用者可以透過資源配額(Resource Quota)對Namespace進行資源使用量限制,包括兩個維度:

1。 限定某個物件型別(如Pod)可建立物件的總數。

2。 限定某個物件型別可消耗的資源總數。

如果在建立或更新Pod時申請的資源超過了資源配額,則Pod將排程失敗。此時需要檢查Namespace資源配額狀態,做出適當調整。

不滿足 NodeSelector節點選擇器

Pod透過NodeSelector節點選擇器指定排程到帶有特定Label的節點,若不存在滿足 NodeSelector的可用節點,Pod將無法被排程,需要對NodeSelector或節點Label進行合理調整。

不滿足親和性

節點親和性(Affinity)和反親和性(Anti-Affinity)用於約束Pod排程到哪些節點,而親和性又細分為軟親和(Preferred)和硬親和(Required)。對於軟親和規則,K8s排程器會嘗試尋找滿足對應規則的節點,如果找不到匹配的節點,排程器仍然會排程該 Pod。而當硬親和規則不被滿足時,Pod將無法被排程,需要檢查Pod排程規則和目標節點狀態,對排程規則或節點進行合理調整。

節點存在汙點

K8s提供汙點(Taints)和容忍(Tolerations)機制,用於避免 Pod 被分配到不合適的節點上。假如節點上存在汙點,而 Pod 沒有設定相應的容忍,Pod 將不會排程到該 節點。此時需要確認節點是否有攜帶汙點的必要,如果不必要的話可以移除汙點;若Pod可以分配到帶有汙點的節點,則可以給Pod增加汙點容忍。

沒有可用節點

節點可能會因為資源不足、網路不通、Kubelet未就緒等原因導致不可用(NotReady)。當叢集中沒有可排程的節點,也會導致Pod卡在Pending狀態。此時需要檢視節點狀態,排查不可用節點問題並修復,或進行叢集擴容。

映象拉取失敗

常見錯誤狀態:ImagePullBackOff

Pod經過排程後分配到目標節點,節點需要拉取Pod所需的映象為建立容器做準備。拉取映象階段可能存在以下幾種原因導致失敗:

映象名字拼寫錯誤或配置了錯誤的映象

出現映象拉取失敗後首先要確認映象地址是否配置錯誤。

私有倉庫的免密配置錯誤

叢集需要進行免密配置才能拉取私有映象。自建映象倉庫時需要在叢集建立免密憑證Secret,在Pod指定ImagePullSecrets,或者將Secret嵌入ServicAccount,讓Pod使用對應的ServiceAccount。而對於acr等映象服務雲產品一般會提供免密外掛,需要在叢集中正確安裝免密外掛才能拉取倉庫內的映象。免密外掛的異常包括:叢集免密外掛未安裝、免密外掛Pod異常、免密外掛配置錯誤,需要檢視相關資訊進行進一步排查。

網路不通

網路不通的常見場景有三個:

1。 叢集透過公網訪問映象倉庫,而映象倉庫未配置公網的訪問策略。對於自建倉庫,可能是埠未開放,或是映象服務未監聽公網IP;對於acr等映象服務雲產品,需要確認開啟公網的訪問入口,配置白名單等訪問控制策略。

2。 叢集位於專有網路,需要為映象服務配置專有網路的訪問控制,才能建立叢集節點與映象服務之間的連線。

3。 拉取海外映象例如gcr。io倉庫映象,需配置映象加速服務。

映象拉取超時

常見於頻寬不足或映象體積太大,導致拉取超時。可以嘗試在節點上手動拉取映象,觀察傳輸速率和傳輸時間,必要時可以對叢集頻寬進行升配,或者適當調整 Kubelet 的 ——image-pull-progress-deadline 和 ——runtime-request-timeout 選項。

同時拉取多個映象,觸發並行度控制

常見於使用者彈性擴容出一個節點,大量待排程Pod被同時排程上去,導致一個節點同時有大量Pod啟動,同時從映象倉庫拉取多個映象。而受限於叢集頻寬、映象倉庫服務穩定性、容器執行時映象拉取並行度控制等因素,映象拉取並不支援大量並行。這種情況可以手動打斷一些映象的拉取,按照優先順序讓映象分批拉取。

依賴項錯誤

常見錯誤狀態:Error

在 Pod 啟動之前,Kubelet將嘗試檢查與其他 K8s 元素的所有依賴關係。主要存在的依賴項有三種:PersistentVolume、ConfigMap和Secret。當這些依賴項不存在或者無法讀取時,Pod容器將無法正常建立,Pod會處於Pending狀態直到滿足依賴性。當這些依賴項能被正確讀取,但出現配置錯誤時,也會出現無法建立容器的情況。比如將一個只讀的持久化儲存卷PersistentVolume以可讀寫的形式掛載到容器,或者將儲存卷掛載到/proc等非法路徑,也會導致容器建立失敗。

容器建立失敗

常見錯誤狀態:Error

Pod容器建立過程中出現了錯誤。常見原因包括:

違反叢集的安全策略,比如違反了 PodSecurityPolicy 等。

容器無權操作叢集內的資源,比如開啟 RBAC 後,需要為 ServiceAccount 配置角色繫結。

缺少啟動命令,Pod描述檔案和映象Dockerfile中均未指定啟動命令。

啟動命令配置錯誤。Pod配置檔案可以透過command欄位定義命令列,透過args欄位給命令列定義引數。啟動命令配置錯誤的情況非常多見,要格外注意命令及引數的格式。正確的填寫方式可參考:

初始化失敗

常見錯誤狀態:CrashLoopBackOff

K8s提供Init Container特性,用於在啟動應用容器之前啟動一個或多個初始化容器,完成應用程式所需的預置條件。Init container與應用容器本質上是一樣的,但它們是僅執行一次就結束的任務,並且必須在執行完成後,系統才能繼續執行下一個容器。如果 Pod 的Init Container執行失敗,將會block業務容器的啟動。透過檢視Pod狀態和事件定位到Init Container故障後,需要檢視Init Container日誌進一步排查故障點。

回撥失敗

常見錯誤狀態:FailedPostStartHook或FailedPreStopHook事件

K8s提供PostStart和PreStop兩種容器生命週期回撥,分別在容器中的程序啟動前或者容器中的程序終止之前執行。PostStart 在容器建立之後立即執行,但由於是非同步執行,無法保證和容器啟動命令的執行順序相關聯。PreStop 在容器終止之前被同步阻塞呼叫,常用於在容器結束前優雅地釋放資源。如果PostStart或者PreStop 回撥程式執行失敗,容器將被終止,按照重啟策略決定是否重啟。當出現回撥失敗,會出現FailedPostStartHook或FailedPreStopHook事件,進一步結合容器打出的日誌進行故障排查。

就緒探針失敗

常見錯誤狀態:容器已經全部啟動,但是Pod處於NotReady狀態,服務流量無法從Service達到Pod

K8s使用Readiness Probe(就緒探針)來確定容器是否已經就緒可以接受流量。只有當Pod 中的容器都處於就緒狀態時,K8s 才認定該Pod 處於就緒狀態,才會將服務流量轉發到該容器。一般就緒探針失敗分為幾種情況:

容器內應用原因: 健康檢查所配置規則對應的埠或者指令碼,無法成功探測,如容器內應用沒正常啟動等。

探針配置不當:寫錯檢查埠導致探測失敗;檢測間隔和失敗閾值設定不合理,例如每次檢查間隔1s,一次不透過即失敗;啟動延遲設定太短,例如應用正常啟動需要15s,而設定容器啟動10s後啟用探針。

系統層問題:節點負載高,導致容器程序hang住。

CPU資源不足:CPU資源限制值過低,導致容器程序響應慢。

需要特別說明的是,對於微服務應用,服務的註冊和發現由註冊中心管理,流量不會經過Service,直接從上游Pod流到下游Pod。然而註冊中心並沒有如K8s就緒探針的檢查機制,對於啟動較慢的JAVA應用來說,服務註冊成功後所需資源仍然可能在初始化中,導致出現上線後流量有損的情況。對於這一類場景,EDAS提供延遲註冊和服務預熱等解決方案,解決K8s微服務應用上線有損的問題。

存活探針失敗

常見錯誤狀態:CrashLoopBackOff

K8s使用Liveness Probe(存活探針)來確定容器是否正在執行。如果存活態探測失敗,則容器會被殺死,隨之按照重啟策略決定是否重啟。存活探針失敗的原因與就緒探針類似,然而存活探針失敗後容器會被kill消失,所以排障過程要棘手得多。一個典型的使用者場景是,使用者在壓測期間透過HPA彈性擴容出多個新Pod,然而新Pod一啟動就被大流量阻塞,無法響應存活探針,導致Pod被kill。kill後又重啟,重啟完又掛掉,一直在Running和CrashLoopBackOff狀態中振盪。微服務場景下可以使用延遲註冊和服務預熱等手段,避免瞬時流量打掛容器。如果是程式本身問題導致執行阻塞,建議先將Liveness探針移除,透過Pod啟動後的監控和程序堆疊資訊,找出流量湧入後進程阻塞的根因。

容器退出

常見錯誤狀態:CrashLoopBackOff

容器退出分為兩種場景:

啟動後立即退出,可能原因是:

1。 啟動命令的路徑未包含在環境變數PATH中。

2。 啟動命令引用了不存在的檔案或目錄。

3。 啟動命令執行失敗,可能因為執行環境缺少依賴,也可能是程式本身原因。

4。 啟動命令沒有執行許可權。

5。 容器中沒有前臺程序。容器應該至少包含一個long-running的前臺程序,不能後臺執行,比如透過nohup這種方式去啟動程序,或是用tomcat的startup。sh指令碼。

對於容器啟動後立即退出的情況,通常因為容器直接消失,無法獲取其輸出流日誌,很難直接透過現場定位問題。一個簡易的排查方式是,透過設定特殊的啟動命令卡住容器(比如使用tail -f /dev/null),然後進到容器中手動執行命令看看結果,確認問題原因。

執行一段時間後退出,這種情況一般是容器內1程序Crash或者被系統終止導致退出。此時首先檢視容器退出狀態碼,然後進一步檢視上下文資訊進行錯誤定位。這種情況發生時容器已經刪除消失,無法進入容器中檢視日誌和堆疊等現場資訊,所以一般推薦使用者對日誌、錯誤記錄等檔案配置持久化儲存,留存更多現場資訊。幾種常見的狀態碼如下:

狀態碼

含義

分析

0

正常退出

容器的啟動程式不是一個long-running的程式。如果正常退出不符合預期,需要檢查容器日誌,對程式的執行邏輯進行調整。

137

外部終止

137 表示容器已收到來自主機作業系統的 SIGKILL 訊號。該訊號指示程序立即終止,沒有寬限期。可能原因包含:容器執行時將容器kill,例如docker kill命令;Linux 使用者向程序傳送 kill -9 命令觸發;K8s嘗試終止容器,超過優雅下線視窗期後直接kill容器;由節點系統觸發,比如遭遇了OOM。

139

段錯誤

139表示容器收到了來自作業系統的 SIGSEGV 訊號。這表示分段錯誤 —— 記憶體違規,由容器試圖訪問它無權訪問的記憶體位置引起。

143

優雅終止

143 表示容器收到來自作業系統的 SIGTERM 訊號,該訊號要求容器正常終止。該退出碼可能的原因是:容器引擎停止容器,例如使用 docker stop 停止了容器;K8s終止了容器,比如縮容行為將Pod刪除。

OOMKilled

常見錯誤狀態:OOMKilled

K8s中有兩種資源概念:可壓縮資源(CPU)和不可壓縮資源(記憶體,磁碟 )。當CPU這種可壓縮資源不足時,Pod只會“飢餓”,但不會退出;而當記憶體和磁碟IO這種不可壓縮資源不足時,Pod會被kill或者驅逐。因為記憶體資源不足/超限所導致的Pod異常退出的現象被稱為Pod OOMKilled。K8s存在兩種導致Pod OOMKilled的場景:

Container Limit Reached,容器記憶體用量超限

Pod內的每一個容器都可以配置其記憶體資源限額,當容器實際佔用的記憶體超額,該容器將被OOMKilled並以狀態碼137退出。OOMKilled往往發生在Pod已經正常執行一段時間後,可能是由於流量增加或是長期執行累積的記憶體逐漸增加。這種情況需要檢視程式日誌以瞭解為什麼Pod使用的記憶體超出了預期,是否出現異常行為。如果發現程式只是按照預期執行就發生了OOM,就需要適當提高Pod記憶體限制值。一個很常見的錯誤場景是,JAVA容器設定了記憶體資源限制值Limit,然而JVM堆大小限制值比記憶體Limit更大,導致程序在執行期間堆空間越開越大,最終因為OOM被終止。對於JAVA容器來說,一般建議容器記憶體限制值Limit需要比JVM 最大堆記憶體稍大一些。

Limit Overcommit,節點記憶體耗盡

K8s有兩種資源額度概念:請求值Request和限制值Limit,預設排程器按照較小的請求值作為排程依據,保障節點的所有Pod資源請求值總和不超過節點容量,而限制值總和允許超過節點容量,這就是K8s資源設計中的Overcommit(超賣)現象。超賣設計在一定程度上能提高吞吐量和資源利用率,但會出現節點資源被耗盡的情況。當節點上的Pod實際使用的記憶體總和超過某個閾值,K8s將會終止其中的一個或多個Pod。為了儘量避免這種情況,建議在建立Pod時選擇大小相等或相近的記憶體請求值和限制值,也可以利用排程規則將記憶體敏感型Pod打散到不同節點。

Pod驅逐

常見錯誤狀態:Pod Evicted

當節點記憶體、磁碟這種不可壓縮資源不足時,K8s會按照QoS等級對節點上的某些Pod進行驅逐,釋放資源保證節點可用性。當Pod發生驅逐後,上層控制器例如Deployment會新建Pod以維持副本數,新Pod會經過排程分配到其他節點建立執行。對於記憶體資源,前文已經分析過可以透過設定合理的請求值和限制值,避免節點記憶體耗盡。而對於磁碟資源,Pod在執行期間會產生臨時檔案、日誌,所以必須對Pod磁碟容量進行限制,否則某些Pod可能很快將磁碟寫滿。類似限制記憶體、CPU 用量的方式,在建立Pod時可以對本地臨時儲存用量(ephemeral-storage)進行限制。同時,Kubelet驅逐條件預設磁碟可用空間在10%以下,可以調整雲監控磁碟告警閾值以提前告警。

Pod失聯

常見錯誤狀態:Unkonwn

Pod處於Unkonwn狀態,無法獲取其詳細資訊,一般是因為所在節點Kubelet異常,無法向APIServer上報Pod資訊。首先檢查節點狀態,透過Kubelet和容器執行時的日誌資訊定位錯誤,進行修復。如果無法及時修復節點,可以先將該節點從叢集中刪除。

點選檢視原文,獲取更多福利!

https://developer。aliyun。com/article/1072614?utm_content=g_1000364335

版權宣告:本文內容由阿里雲實名註冊使用者自發貢獻,版權歸原作者所有,阿里雲開發者社群不擁有其著作權,亦不承擔相應法律責任。具體規則請檢視《阿里雲開發者社群使用者服務協議》和《阿里雲開發者社群智慧財產權保護指引》。如果您發現本社群中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社群將立刻刪除涉嫌侵權內容。

TAG: POD容器節點映象K8s