聊聊 Kubernetes Pod or Namespace 卡在 Terminating 狀態的場景
這個話題,想必玩過 kubernetes 的同學當不陌生,我會分 Pod 和 Namespace 分別來談。
開門見山,為什麼 Pod 會卡在 Terminating 狀態?
一句話,本質是 API Server 雖然標記了物件的刪除,但是作為實際清理的控制器 kubelet, 並不能關停 Pod 或相關資源, 因而沒能通知 API Server 做實際物件的清理。
原因何在?要解開這個原因,我們先來看 Pod Terminating 的基本流程:
- 客戶端 (比如 kubectl) 提交刪除請求到 API Server
- 可選傳遞 --grace-period 引數
- API Server 接受到請求之後,做 Graceful Deletion 檢查
- 若需要 graceful 刪除時,則更新物件的 metadata.deletionGracePeriodSeconds 和 metadata.deletionTimestamp 欄位。這時候 describe 檢視物件的話,會發現其已經變成 Terminating 狀態了
- Pod 所在的節點,kubelet 檢測到 Pod 處於 Terminating 狀態時,就會開啟 Pod 的真正刪除流程
- 如果 Pod 中的容器有定義 preStop hook 事件,那 kubelet 會先執行這些容器的 hook 事件
- 之後,kubelet 就會 Trigger 容器執行時發起
TERM
signal 給該 Pod 中的每個容器
- 在 Kubelet 開啟 Graceful Shutdown 的同時,Control Plane 也會從目標 Service 的 Endpoints 中摘除要關閉的 Pod。ReplicaSet 和其他的 workload 服務也會認定這個 Pod 不是個有效副本了。同時,Kube-proxy 也會摘除這個 Pod 的 Endpoint,這樣即使 Pod 關閉很慢,也不會有流量再打到它上面。
- 如果容器正常關閉那很好,但如果在 grace period 時間內,容器仍然執行,kubelet 會開始強制 shutdown。容器執行時會傳送
SIGKILL
訊號給 Pod 中所有執行的程序進行強制關閉 - 注意在開啟 Pod 刪除的同時,kubelet 的其它控制器也會處理 Pod 相關的其他資源的清理動作,比如 Volume。而待一切都清理乾淨之後,Kubelet 才透過把 Pod 的 grace period 時間設為 0 來通知 API Server 強制刪除 Pod 物件。
參考連結: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination
只有執行完第六步,Pod 的 API 物件才會被真正刪除。那怎樣才認為是"一切都清理乾淨了"呢?我們來看原始碼:
// PodResourcesAreReclaimed returns true if all required node-level resources that a pod was consuming have
// been reclaimed by the kubelet. Reclaiming resources is a prerequisite to deleting a pod from theAPI Server.
func (kl *Kubelet) PodResourcesAreReclaimed(pod *v1.Pod, status v1.PodStatus) bool {
if kl.podWorkers.CouldHaveRunningContainers(pod.UID) {
// We shouldn't delete pods that still have running containers
klog.V(3).InfoS("Pod is terminated, but some containers are still running", "pod", klog.KObj(pod))
return false
}
if count := countRunningContainerStatus(status); count > 0 {
// We shouldn't delete pods until the reported pod status contains no more running containers (the previous
// check ensures no more status can be generated, this check verifies we have seen enough of the status)
klog.V(3).InfoS("Pod is terminated, but some container status has not yet been reported", "pod", klog.KObj(pod), "running", count)
return false
}
if kl.podVolumesExist(pod.UID) && !kl.keepTerminatedPodVolumes {
// We shouldn't delete pods whose volumes have not been cleaned up if we are not keeping terminated pod volumes
klog.V(3).InfoS("Pod is terminated, but some volumes have not been cleaned up", "pod", klog.KObj(pod))
return false
}
if kl.kubeletConfiguration.CgroupsPerQOS {
pcm := kl.containerManager.NewPodContainerManager()
if pcm.Exists(pod) {
klog.V(3).InfoS("Pod is terminated, but pod cgroup sandbox has not been cleaned up", "pod", klog.KObj(pod))
return false
}
}
// Note: we leave pod containers to be reclaimed in the background since dockershim requires the
// container for retrieving logs and we want to make sure logs are available until the pod is
// physically deleted.
klog.V(3).InfoS("Pod is terminated and all resources are reclaimed", "pod", klog.KObj(pod))
return true
}
原始碼位置: https://github.com/kubernetes/kubernetes/blob/1f2813368eb0eb17140caa354ccbb0e72dcd6a69/pkg/kubelet/kubelet_pods.go#L923
是不是很清晰?總結下來就三個原因:
- Pod 裡沒有 Running 的容器
- Pod 的 Volume 也清理乾淨了
- Pod 的 cgroup 設定也沒了
如是而已。
自然,其反向對應的就是各個異常場景了。我們來細看:
- 容器停不掉 - 這種屬於 CRI 範疇,常見的一般使用 docker 作為容器執行時。筆者就曾經遇到過個場景,用
docker ps
能看到目標容器是Up
狀態,但是執行docker stop or rm
卻沒有任何反應,而執行docker exec
,會報no such container
的錯誤。也就是說此時這個容器的狀態是錯亂的,docker 自己都沒法清理這個容器,可想而知 kubelet 更是無能無力。workaround 恢復操作也簡單,此時我只是簡單的重啟了下 docker,目標容器就消失了,Pod 的卡住狀態也很快恢復了。當然,若要深究,就需要看看 docker 側,為何這個容器的狀態錯亂了。- 更常見的情況是出現了殭屍程序,對應容器清理不了,Pod 自然也會卡在 Terminating 狀態。此時要想恢復,可能就只能重啟機器了。
- Volume 清理不了 - 我們知道在 PV 的"兩階段處理流程中",Attach&Dettach 由 Volume Controller 負責,而 Mount&Unmount 則是 kubelet 要參與負責。筆者在日常中有看到一些因為自定義 CSI 的不完善,導致 kubelet 不能 Unmount Volume,從而讓 Pod 卡住的場景。所以我們在日常開發和測試自定義 CSI 時,要小心這一點。
- cgroups 沒刪除 - 啟用 QoS 功能來管理 Pod 的服務質量時,kubelet 需要為 Pod 設定合適的 cgroup level,而這是需要在相應的位置寫入合適配置檔案的。自然,這個配置也需要在 Pod 刪除時清理掉。筆者日常到是沒有碰到過 cgroups 清理不了的場景,所以此處暫且不表。
現實中導致 Pod 卡住的細分場景可能還有很多,但不用擔心,其實多數情況下透過檢視 kubelet 日誌都能很快定位出來的。之後順藤摸瓜,恢復方案也大多不難。
當然還有一些系統級或者基礎設施級異常,比如 kubelet 掛了,節點訪問不了 API Server 了,甚至節點當機等等,已經超過了 kubelet 的能力範疇,不在此討論範圍之類。
還有個注意點,如果你發現 kubelet 裡面的日誌有效資訊很少,要注意看是不是 Log Level 等級過低了。從原始碼看,很多更具體的資訊,是需要大於等於 3 級別才輸出的。
那 Namespace 卡在 Terminating 狀態的原因是啥?
顯而易見,刪除 Namespace 意味著要刪除其下的所有資源,而如果其中 Pod 刪除卡住了,那 Namespace 必然也會卡在 Terminating 狀態。
除此之外,結合日常使用,筆者發現 CRD 資源發生刪不掉的情況也比較高。這是為什麼呢?至此,那就不得不聊聊 Finalizers 機制了。
官方有篇部落格專門講到了這個,裡面有個實驗挺有意思。隨便給一個 configmap,加上個 finalizers 欄位之後,然後使用kubectl delete
刪除它就會發現,直接是卡住的,kubernetes 自身永遠也刪不了它。
參考: https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/#understanding-finalizers
原因何在?
原來 Finalizers 在設計上就是個 pre-delete 的鉤子,其目的是讓相關控制器有機會做自定義的清理動作。通常控制器在清理完資源後,會將物件的 finalizers 欄位清空,然後 kubernetes 才能接著刪除物件。而像上面的實驗,沒有相關控制器能處理我們隨意新增的 finalizers 欄位,那物件當然會一直卡在 Terminating 狀態了。
自己開發 CRD 及 Controller,因成熟度等因素,發生問題的機率自然比較大。除此之外,引入 webhook(mutatingwebhookconfigurations/validatingwebhookconfigurations) 出問題的機率也比較大,日常也要比較注意。
綜合來看,遇 Namespace 刪除卡住的場景,筆者認為,基本可以按以下思路排查:
-
kubectl get ns $NAMESPACE -o yaml
, 檢視conditions
欄位,看看是否有相關資訊 - 如果上面不明顯,那就可以具體分析空間下,還遺留哪些資源,然後做更針對性處理
- 參考命令:
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n $NAMESPACE
- 參考命令:
找準了問題原因,然後做相應處理,kubernetes 自然能夠清理對應的 ns 物件。不建議直接清空 ns 的 finalizers 欄位做強制刪除,這會引入不可控風險。
參考: https://github.com/kubernetes/kubernetes/issues/60807#issuecomment-524772920
相關閱讀
前同事也有幾篇關於 kubernetes 資源刪除的文章,寫的非常好,推薦大家讀讀:
- https://zhuanlan.zhihu.com/p/164601470
- https://zhuanlan.zhihu.com/p/161072336
相關文章
- Kubernetes怎麼處理一直在Terminating狀態的namespacenamespace
- 結合具體場景,聊聊 React 的狀態管理方案React
- k8s pod狀態有哪些K8S
- 傲視Kubernetes(三):Kubernetes中的Pod
- 如何確保有狀態 Kubernetes 的穩定性
- 說說HTTP 常見的狀態碼有哪些,適用場景?HTTP
- Kubernetes的Pod進階(十一)
- 螞蟻流場景狀態演進和最佳化
- 電商交易場景狀態機方案探索及應用
- Serverless 場景下 Pod 建立效率最佳化Server
- 從React Redux的實際業務場景來看有限狀態機ReactRedux
- Kubernetes-POD的健康檢查
- 聊聊如何變更pod的流量路由路由
- 聊聊如何停止某個pod的流量
- Kubernetes部署單元-Pod
- Kubernetes Pod 全面知識
- Kubernetes:Pod總結(二)
- Kubernetes Pod驅逐策略
- kubernetes之pod中斷
- Kubernetes之Pod排程
- Kubernetes 實戰——有狀態應用(StatefulSet)
- Kubernetes的優勢、適應場景及其特點
- 從中國的山水畫談談遊戲場景設計該有的狀態遊戲
- 聊聊只狼(sekiro)中的場景設計規劃
- Kubernetes之Pod工作負載負載
- Kubernetes Pod OOM 排查日記OOM
- Kubernetes:Pod 升級、回滾
- Kubernetes:28---pod託管(Job:任務型pod)
- 重定向Kubernetes pod中的tcpdump輸出TCP
- python kubernetes 獲取 pod 的 cpu 佔用率Python
- Kubernetes原始碼分析之Pod的刪除原始碼
- 聊聊spring事務失效的12種場景,太坑了Spring
- 揭祕有狀態服務上 Kubernetes 的核心技術
- 詳解Kubernetes Pod優雅退出
- 技術分享 | kubernetes pod 簡介
- kubernetes-pod驅逐機制
- kubernetes如何訪問pod服務
- 重學 Java 設計模式:實戰狀態模式「模擬系統營銷活動,狀態流程稽核釋出上線場景」Java設計模式