Kubernetes 常見錯誤、原因及處理方法
個人筆記,不保證正確!
博客已遷移至:https://thiscute.world/posts/kubernetes-common-errors-and-solutions/

Pod 常見錯誤
- OOMKilled: Pod 的內存使用超出了 resources.limits 中的限制,被強制殺死。
- SandboxChanged: Pod sandbox changed, it will be killed and re-created: 很可能是由于內存限制導致容器被 OOMKilled,或者其他資源不足
- 如果是 OOM,容器通常會被重啟,
kubectl describe能看到容器上次被重啟的原因State.Last State.Reason = OOMKilled, Exit Code=137.
- 如果是 OOM,容器通常會被重啟,
- Pod 不斷被重啟,
kubectl describe顯示重啟原因State.Last State.Reason = Error, Exit Code=137,137 對應 SIGKILL(kill -9) 信號,說明容器被強制重啟。可能的原因:- 最有可能的原因是,存活探針(livenessProbe)檢查失敗
- 節點資源不足,內核強制關閉了進程以釋放資源
- CrashLoopBackoff: Pod 進入 崩潰-重啟循環,重啟間隔時間從 10 20 40 80 一直翻倍到上限 300 秒,然后以 300 秒為間隔無限重啟。
- Pod 一直 Pending: 這說明沒有任何節點能滿足 Pod 的要求,容器無法被調度。比如端口被別的容器用 hostPort 占用,節點有污點等。
- FailedCreateSandBox: Failed create pod sandbox: rpc error: code = DeadlineExceeded desc = context deadline exceeded:很可能是 CNI 網絡插件的問題(比如 ip 地址溢出),
- FailedSync: error determining status: rpc error: code = DeadlineExceeded desc = context deadline exceeded: 常和前兩個錯誤先后出現,很可能是 CNI 網絡插件的問題。
- 開發集群,一次性部署所有服務時,各 Pod 互相爭搶資源,導致 Pod 生存探針失敗,不斷重啟,重啟進一步加重資源使用。惡性循環。
- 需要給每個 Pod 加上 resources.requests,這樣資源不足時,后續 Pod 會停止調度,直到資源恢復正常。
- Pod 出現大量的 Failed 記錄,Deployment 一直重復建立 Pod: 通過
kubectl describe/edit pod <pod-name>查看 podEvents和Status,一般會看到失敗信息,如節點異常導致 Pod 被驅逐。 - Kubernetes 問題排查:Pod 狀態一直 Terminating
- 創建了 Deployment 后,卻沒有自動創建 Pod: 缺少某些創建 Pod 必要的東西,比如設定的 ServiceAccount 不存在。
- Pod 運行失敗,狀態為 MatchNodeSelector: 對主節點進行關機、遷移等操作,導致主調度器下線時,會在一段時間內導致 Pod 調度失敗,調度失敗會報這個錯。
- Pod 仍然存在,但是
Service的 Endpoints 卻為空,找不到對應的 Pod IPs: 遇到過一次,是因為時間跳變(從未來的時間改回了當前時間)導致的問題。
控制面故障可能會導致各類奇怪的異常現象
對于生產環境的集群,因為有高可用,通常我們比較少遇到控制面故障問題。但是一旦控制面發生故障,就可能會導致各類奇怪的異常現象。
如果能在排查問題時,把控制面異常考慮進來,在這種情況下,就能節約大量的排查時間,快速定位到問題。
其中比較隱晦的就是 controller-manager 故障導致的異常:
- 節點的服務器已經被終止,但是 Kuberntes 里還顯示 node 為 Ready 狀態,不會更新為 NotReady.
- 被刪除的 Pods 可能會卡在 Terminating 狀態,只有強制刪除才能刪除掉它們。并且確認 Pod 沒有
metadata.finalizers屬性 - HPA 的動態伸縮功能失效
- ...
如果這些現象同時發生,就要懷疑是否是 kube-controller-manager 出問題了.
其他控制面異常的詳細分析,參見 kubernetes 控制面故障現象及分析
Pod 無法刪除
可能是某些資源無法被GC,這會導致容器已經 Exited 了,但是 Pod 一直處于 Terminating 狀態。
這個問題在網上能搜到很多案例,但大都只是提供了如下的強制清理命令,未分析具體原因:
kubectl delete pods <pod> --grace-period=0 --force
最近找到幾篇詳細的原因分析文章,值得一看:
- 騰訊云原生 -【Pod Terminating原因追蹤系列】之 containerd 中被漏掉的 runc 錯誤信息
- 騰訊云原生 -【Pod Terminating原因追蹤系列之二】exec連接未關閉導致的事件阻塞
- 騰訊云原生 -【Pod Terminating原因追蹤系列之三】讓docker事件處理罷工的cancel狀態碼
- Pod terminating - 問題排查 - KaKu Li
大致總結一下,主要原因來自 docker 18.06 以及 kubernetes 的 docker-shim 運行時的底層邏輯,已經在新版本被修復了。
initContainers 不斷 restart,但是 Containers 卻都顯示已 ready
Kubernetes 應該確保所有 initContainers 都 Completed,然后才能啟動 Containers.
但是我們發現有一個節點上,所有包含 initContainers 的 Pod,狀態全都是 Init:CrashLoopBackOff 或者 Init:Error.
而且進一步 kubectl describe po 查看細節,發現 initContainer 的狀態為:
...
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 2
Started: Tue, 03 Aug 2021 06:02:42 +0000
Finished: Tue, 03 Aug 2021 06:02:42 +0000
Ready: False
Restart Count: 67
...
而 Containers 的狀態居然是 ready:
...
Host Port: 0/TCP
State: Running
Started: Tue, 03 Aug 2021 00:35:30 +0000
Ready: True
Restart Count: 0
...
initContainers 還未運行成功,而 Containers 卻 Ready 了,非常疑惑。
仔細想了下,早上因為磁盤余量告警,有手動運行過 docker system prune 命令,那么問題可能就是這條命令清理掉了已經 exited 的 initContainers 容器,導致 k8s 故障,不斷嘗試重啟該容器。
網上一搜確實有相關的信息:
- https://stackoverflow.com/questions/62333064/cant-delete-exited-init-container
- https://github.com/kubernetes/kubernetes/issues/62362
結論:使用外部的垃圾清理命令可能導致 k8s 行為異常。
節點常見錯誤
- DiskPressure:節點的可用空間不足。(通過
df -h查看,保證可用空間不小于 15%) - The node was low on resource: ephemeral-storage: 同上,節點的存儲空間不夠了。
節點存儲告警可能的原因:
- kubelet 的資源 GC 設置有問題,遺留的鏡像等資源未及時 GC 導致告警
- 存在運行的 pod 使用了大量存儲空間,在節點上通過
docker ps -a --size | grep G可以查看到 - 如果使用的是 EKS,并且磁盤告警的掛載點為
/var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/us-east-1b/vol-xxxxx- 顯然是 EBS 存儲卷快滿了導致的
- 可通過
kubectl get pv -A -o yaml | grep -C 30 vol-xxxxx來定位到具體的存儲卷
網絡常見錯誤
1. Ingress/Istio Gateway 返回值
- 404:不存在該 Service/Istio Gateway,或者是服務自身返回 404
- 500:大概率是服務自身的錯誤導致 500,小概率是代理(Sidecar/Ingress 等)的錯誤
- 503:服務不可用,有如下幾種可能的原因:
- Service 對應的 Pods 不存在,endpoints 為空
- Service 對應的 Pods 全部都 NotReady,導致 endpoints 為空
- 也有可能是服務自身出錯返回的 503
- 如果你使用了 envoy sidecar, 503 可能的原因就多了。基本上 sidecar 與主容器通信過程中的任何問題都會使 envoy 返回 503,使客戶端重試。
- 502:Bad Gateway,通常是由于上游未返回正確的響應導致的,可能的根本原因:
- 應用程序未正確處理 SIGTERM 信號,在請求未處理完畢時直接終止了進程。詳見 優雅停止(Gracful Shutdown)與 502/504 報錯 - K8s 最佳實踐
- 網絡插件 bug
- 504:網關請求 upstream 超時,主要有兩種可能
- 考慮是不是 Ingress Controller 的 IP 列表未更新,將請求代理到了不存在的 ip,導致得不到響應
- Service Endpoints 移除不夠及時,在 Pod 已經被終止后,仍然有個別請求被路由到了該 Pod,得不到響應導致 504。詳見 優雅停止(Gracful Shutdown)與 502/504 報錯 - K8s 最佳實踐
- Pod 響應太慢,代碼問題
再總結一下常見的幾種錯誤:
- 未設置優雅停止,導致 Pod 被重新終止時,有概率出現 502/504
- 服務的所有 Pods 的狀態在「就緒」和「未就緒」之間擺動,導致間歇性地出現大量 503 錯誤
- 服務返回 5xx 錯誤導致客戶端不斷重試,請求流量被放大,導致服務一直起不來
- 解決辦法:限流、熔斷(網關層直接返回固定的相應內容)
Ingress 相關網絡問題的排查流程:
- Which ingress controller?
- Timeout between client and ingress controller, or between ingress controller and backend service/pod?
- HTTP/504 generated by the ingress controller, proven by logs from the ingress controller?
- If you port-forward to skip the internet between client and ingress controller, does the timeout still happen?
2. 上了 istio sidecar 后,應用程序偶爾(間隔幾天半個月)會 redis 連接相關的錯誤
考慮是否和 tcp 長時間使用有關,比如連接長時間空閑的話,可能會被 istio sidecar 斷開。
如果程序自身的重連機制有問題,就會導致這種現象。
確認方法:
- 檢查 istio 的
idleTimeout時長(默認 1h) - 創建三五個沒流量的 Pod 放置 1h(與 istio idleTimeout 時長一致),看看是否會準時開始報 redis 的錯。
- 對照組:創建三五個同樣沒流量的 Pod,但是不注入 istio sidecar,應該一直很正常
這樣就能確認問題,后續處理:
- 抓包觀察程序在出錯后的 tcp 層行為
- 查閱 redis sdk 的相關 issue、代碼,通過升級 SDK 應該能解決問題。
名字空間常見錯誤
名字空間無法刪除
這通常是某些資源如 CR(custom resources)/存儲等資源無法釋放導致的。
比如常見的 monitoring 名字空間無法刪除,應該就是 CR 無法 GC 導致的。
可手動刪除 namespace 配置中的析構器(spec.finalizer,在名字空間生命周期結束前會生成的配置項),這樣名字空間就會直接跳過 GC 步驟:
# 編輯名字空間的配置
kubectl edit namespace <ns-name>
# 將 spec.finalizers 改成空列表 []
如果上述方法也無法刪除名字空間,也找不到具體的問題,就只能直接從 etcd 中刪除掉它了(有風險,謹慎操作!)。方法如下:
# 登錄到 etcd 容器中,執行如下命令:
export ETCDCTL_API=3
cd /etc/kubernetes/pki/etcd/
# 列出所有名字空間
etcdctl --cacert ca.crt --cert peer.crt --key peer.key get /registry/namespaces --prefix --keys-only
# (謹慎操作!!!)強制刪除名字空間 `monitoring`。這可能導致相關資源無法被 GC!
etcdctl --cacert ca.crt --cert peer.crt --key peer.key del /registry/namespaces/monitoring
kubectl/istioctl 等客戶端工具異常
socat not found: kubectl 使用socat進行端口轉發,集群的所有節點,以及本機都必須安裝有socat工具。
批量清理 Evicted 記錄
有時候 Pod 因為節點選擇器的問題,被不斷調度到有問題的 Node 上,就會不斷被 Evicted,導致出現大量的 Evicted Pods。
排查完問題后,需要手動清理掉這些 Evicted Pods.
批量刪除 Evicted 記錄:
kubectl get pods | grep Evicted | awk '{print $1}' | xargs kubectl delete pod
容器鏡像GC、Pod驅逐以及節點壓力
節點壓力 DiskPressure 會導致 Pod 被驅逐,也會觸發容器鏡像的 GC。
根據官方文檔 配置資源不足時的處理方式,Kubelet 提供如下用于配置容器 GC 及 Evicetion 的閾值:
--eviction-hard和eviction-soft: 對應舊參數--image-gc-high-threshold,這兩個參數配置鏡像 GC 及驅逐的觸發閾值。磁盤使用率的閾值默認為 85%- 區別在于
eviction-hard是立即驅逐,而eviction-soft在超過eviction-soft-grace-period之后才驅逐。
- 區別在于
--eviction-minimum-reclaim: 對應舊參數--image-gc-low-threshold。這是進行資源回收(鏡像GC、Pod驅逐等)后期望達到的磁盤使用率百分比。磁盤使用率的閾值默認值為 80%。
問:能否為 ImageGC 設置一個比 DiskPressure 更低的閾值?因為我們希望能自動進行鏡像 GC,但是不想立即觸發 Pod 驅逐。
答:這應該可以通過設置 eviction-soft 和長一點的 eviction-soft-grace-period 來實現。
另外 --eviction-minimum-reclaim 也可以設小一點,清理得更干凈。示例如下:
--eviction-soft=memory.available<1Gi,nodefs.available<2Gi,imagefs.available<200Gi
--eviction-soft-grace-period=3m
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=1Gi,imagefs.available=2Gi
其他問題
隔天 Istio 等工具的 sidecar 自動注入莫名其妙失效了
如果服務器晚上會關機,可能導致第二天網絡插件出問題,導致 sidecar 注入器無法觀察到 pod 的創建,也就無法完成 sidecar 注入。
如何重新運行一個 Job?
我們有一個 Job 因為外部原因運行失敗了,修復好后就需要重新運行它。
方法是:刪除舊的 Job,再使用同一份配置重建 Job.
如果你使用的是 fluxcd 這類 GitOps 工具,就只需要手工刪除舊 Pod,fluxcd 會定時自動 apply 所有配置,這就完成了 Job 的重建。

浙公網安備 33010602011771號