徹底搞懂 K8S Pod Pending 故障原因及解決方案
即使在高成熟度級(jí)別 Kubernetes 集群中 pod pending 也是無(wú)處不在。
如果您隨機(jī)詢(xún)問(wèn)任何使用 Kubernetes DevOps 工程師來(lái)確定折磨他們噩夢(mèng)的最常見(jiàn)錯(cuò)誤,pod pending 可能是非常常見(jiàn)的問(wèn)題(可能僅次于 CrashLoopBackOff)。
嘗試推送更新并看到它卡住會(huì)使 DevOps 緊張。即使解決方案相當(dāng)簡(jiǎn)單,找到 pod 掛起的原因并了解您需要應(yīng)用的更改也很重要(Kubernetes 故障排除很少是微不足道的)。
在本文中,我們將闡明導(dǎo)致此問(wèn)題的不同情況,讓 DevOps 團(tuán)隊(duì)能夠快速找到解決方案,最重要的是,盡可能避免它。
Kubernetes Pod pending 是什么意思?
Kubernetes 中的 Pod 的生命周期由幾個(gè)不同的階段組成:
- 創(chuàng)建 pod 時(shí),它從Pending階段開(kāi)始。
- 一旦 pod 被調(diào)度并且容器已經(jīng)啟動(dòng),pod 就會(huì)進(jìn)入Running階段。
大多數(shù) pod 只需要幾秒鐘就可以從 Pending 到 Running 并在該狀態(tài)下度過(guò)大部分時(shí)間。
至此,Pod 已被 Kubernetes 集群接受。但是一個(gè)或多個(gè)容器尚未準(zhǔn)備好對(duì)外提供服務(wù)。這包括 Pod 等待調(diào)度所花費(fèi)的時(shí)間以及通過(guò)網(wǎng)絡(luò)下載容器鏡像所花費(fèi)的時(shí)間。
當(dāng) pod 無(wú)法從 PendingtoRunning 階段前進(jìn)時(shí),生命周期將停止并保留 pod,直到阻止它前進(jìn)的問(wèn)題得到修復(fù)。
如果我們使用 kubectl 列出 pod,我們將看到顯示 Kubernetes pod 掛起情況的輸出:
$ kubectl -n troubleshooting get pods
NAME READY STATUS RESTARTS AGE
stress-6d6cbc8b9d-s4sbh 0/1 Pending 0 17s
除非我們解決問(wèn)題,否則 pod 被卡住并且不會(huì)運(yùn)行。
排查 Kubernetes pod Pending 的常見(jiàn)原因
有幾個(gè)原因可以阻止 Pod 運(yùn)行,但我們將描述三個(gè)主要問(wèn)題:
- 調(diào)度問(wèn)題:無(wú)法在任何節(jié)點(diǎn)上調(diào)度 Pod。
- 鏡像問(wèn)題:下載容器鏡像時(shí)出現(xiàn)問(wèn)題。
- 依賴(lài)性問(wèn)題:Pod 需要一個(gè)卷、Secret 或 ConfigMap 才能運(yùn)行。
第一個(gè)是最常見(jiàn)的,最后一個(gè)很少見(jiàn)。讓我們?cè)敿?xì)說(shuō)明每種情況。
調(diào)度問(wèn)題導(dǎo)致 Kubernetes Pod Pending
創(chuàng)建 Pod 后,Kubernetes 集群做的第一件事就是嘗試調(diào)度 Pod 在其中一個(gè)節(jié)點(diǎn)上運(yùn)行。這個(gè)過(guò)程通常非??欤⑶?pod 被快速分配給具有足夠資源來(lái)運(yùn)行它的節(jié)點(diǎn)。
為了放置它,集群中的 Pod 被分配給具有更多未請(qǐng)求資源的節(jié)點(diǎn),并繼續(xù)其快樂(lè)而美好的生活,其中充滿(mǎn)了對(duì)請(qǐng)求的符合 SLO 的回復(fù)。
但是,如果此過(guò)程每次都有效,有幾個(gè)因素可能導(dǎo)致集群無(wú)法分配 pod。
讓我們回顧一下最常見(jiàn)的。
任何節(jié)點(diǎn)中都沒(méi)有足夠的資源來(lái)分配 pod
Kubernetes 使用調(diào)度請(qǐng)求來(lái)決定fits節(jié)點(diǎn)中是否有 pod。資源的真正使用無(wú)關(guān)緊要,只有其他 pod 已經(jīng)請(qǐng)求的資源。
effective requests當(dāng)一個(gè) pod 有足夠的可請(qǐng)求資源來(lái)參與該 pod 的內(nèi)存和 CPU 時(shí),它將被調(diào)度到一個(gè)節(jié)點(diǎn)中。并且節(jié)點(diǎn)必須沒(méi)有達(dá)到它可以運(yùn)行的最大 pod 數(shù)。
當(dāng)沒(méi)有任何節(jié)點(diǎn)滿(mǎn)足 pod 的所有要求時(shí),它將保持在 Kubernetes pod 掛起狀態(tài),直到釋放一些資源。
不可調(diào)度的節(jié)點(diǎn)
由于不同的問(wèn)題(節(jié)點(diǎn)壓力)或人為行為(節(jié)點(diǎn)封鎖),節(jié)點(diǎn)可能會(huì)變?yōu)椴豢烧{(diào)度的狀態(tài)。這些節(jié)點(diǎn)在狀態(tài)發(fā)生變化之前不會(huì)調(diào)度任何 pod。
污點(diǎn)和容忍度
污點(diǎn)是 Kubernetes 的一種機(jī)制,它允許我們限制可以分配給不同節(jié)點(diǎn)的 pod。當(dāng)節(jié)點(diǎn)具有 taint 時(shí),只有匹配容忍度的 pod 才能在該節(jié)點(diǎn)中運(yùn)行。
這種機(jī)制允許 Kubernetes 的特殊用途,例如為不同的工作負(fù)載使用不同類(lèi)型的節(jié)點(diǎn)(具有 GPU 的節(jié)點(diǎn),具有不同的 CPU/內(nèi)存比率等)。
即使我們分別描述每個(gè)原因,調(diào)度問(wèn)題也往往是由這些問(wèn)題的組合引起的。通常,您無(wú)法調(diào)度,因?yàn)槟承┕?jié)點(diǎn)已滿(mǎn)而其他節(jié)點(diǎn)已被污染,或者某個(gè)節(jié)點(diǎn)可能由于內(nèi)存壓力而無(wú)法調(diào)度。
為了找出調(diào)度問(wèn)題是什么,您需要查看調(diào)度程序生成的關(guān)于 pod 的事件,其中將詳細(xì)描述阻止節(jié)點(diǎn)分配的原因。我們可以使用 kubectl describe 查看事件,例如:
$ kubectl -n troubleshooting describe pod stress-6d6cbc8b9d-s4sbh
Name: stress-6d6cbc8b9d-s4sbh
Namespace: troubleshooting
Priority: 0
Node: <none>
Labels: app=stress
pod-template-hash=6d6cbc8b9d
Annotations: <none>
Status: Pending
IP:
IPs: <none>
Controlled By: ReplicaSet/stress-6d6cbc8b9d
Containers:
stress:
Image: progrium/stress
Port: <none>
Host Port: <none>
Args:
--cpu
1
--vm
2
--vm-bytes
150M
Limits:
cpu: 300m
memory: 120000Mi
Requests:
cpu: 200m
memory: 100000Mi
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-snrww (ro)
Conditions:
Type Status
PodScheduled False
Volumes:
kube-api-access-snrww:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 4m17s (x41 over 34m) default-scheduler 0/5 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 4 Insufficient memory.
我們可以在輸出中看到消息中的確切原因:
0/5 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 4 Insufficient memory.
- 其中一個(gè)節(jié)點(diǎn)被污染。
- 其中四個(gè)節(jié)點(diǎn)沒(méi)有足夠的可請(qǐng)求內(nèi)存。
為了解決這個(gè)問(wèn)題,我們有兩個(gè)選擇:
- 減少 pod 定義中的資源請(qǐng)求大小。
- 通過(guò)添加更多節(jié)點(diǎn)或增加每個(gè)節(jié)點(diǎn)的大小來(lái)增加集群的容量。
如果要更新當(dāng)前運(yùn)行的工作負(fù)載,還需要考慮另一個(gè)重要因素:升級(jí)策略。
由于此策略,Kubernetes 可以允許工作負(fù)載在更新過(guò)程中創(chuàng)建比平時(shí)更多的 Pod,在創(chuàng)建新 Pod 時(shí)保留舊 Pod 一段時(shí)間。這意味著工作負(fù)載可能會(huì)在一段時(shí)間內(nèi)請(qǐng)求比預(yù)期更多的資源。如果集群沒(méi)有足夠的備用資源,更新將被阻塞,留下一些 pod 待處理,直到進(jìn)程被解除阻塞(或回滾超時(shí)停止更新)。
由于鏡像問(wèn)題,Pod Pending
一旦在一個(gè)節(jié)點(diǎn)中分配了 pod,kubelet就會(huì)嘗試啟動(dòng) pod 中的所有容器。為此,它將嘗試下載鏡像并運(yùn)行它。
有幾個(gè)錯(cuò)誤會(huì)阻止鏡像被下載:
- 鏡象名稱(chēng)錯(cuò)誤。
- 錯(cuò)誤的鏡像標(biāo)簽。
- 錯(cuò)誤的存儲(chǔ)倉(cāng)庫(kù)。
- 存儲(chǔ)倉(cāng)庫(kù)需要身份驗(yàn)證。
Kubernetes Pod 由于依賴(lài)問(wèn)題而掛起
在 pod 啟動(dòng)之前,kubelet將嘗試檢查與其他 Kubernetes 元素的所有依賴(lài)關(guān)系。如果無(wú)法滿(mǎn)足這些依賴(lài)項(xiàng)之一,則 pod 將保持掛起狀態(tài),直到滿(mǎn)足依賴(lài)項(xiàng)。
在這種情況下,kubectl 將像這樣顯示 pod:
$ kubectl -n mysql get pods
NAME READY STATUS RESTARTS AGE
mysql-0 0/1 ContainerCreating 0 97s
在事件中,我們可以看到如下內(nèi)容:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m19s default-scheduler Successfully assigned mysql/mysql-0 to ip-172-20-38-115.eu-west-1.compute.internal
Warning FailedMount 76s kubelet Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[kube-api-access-gxjf8 data config]: timed out waiting for the condition
Warning FailedMount 71s (x9 over 3m19s) kubelet MountVolume.SetUp failed for volume "config" : configmap "mysql" not found
該 Message 列將為您提供足夠的信息,以便能夠查明缺失的元素。常見(jiàn)的原因有:
- 尚未創(chuàng)建 ConfigMap 或者 Secret,或提供的名稱(chēng)不正確。
- 無(wú)法在節(jié)點(diǎn)中掛載卷,因?yàn)樗形幢涣硪粋€(gè)節(jié)點(diǎn)釋放。這尤其發(fā)生在更新 statefulset 的過(guò)程中,掛載的卷必須與舊 pod 相同。
結(jié)論
了解 pod 保持在該 Pending 階段的原因是在 Kubernetes 中安全部署和更新工作負(fù)載的關(guān)鍵。能夠快速定位問(wèn)題并加快部署進(jìn)度將為您省去一些麻煩并減少停機(jī)時(shí)間。