習(xí)Kubernetes:從kubelet到containerd的故障排查實(shí)戰(zhàn))
1. 寫在前面為什么我推薦用“破壞式”學(xué)習(xí) Kubernetes第五篇了我先把話說在前面這篇文章不是給你講 kubectl get pods 怎么用也不是抄一遍 Kubernetes 官方文檔。我想分享的是一套我自己驗(yàn)證過、也一直在帶新人時(shí)用的學(xué)習(xí)方式——把集群“故意搞壞”再一步步修好。在這個(gè)破壞、觀察、修復(fù)、復(fù)盤的過程中Kubernetes 的組件邊界、調(diào)用鏈、故障表象都會(huì)變得異常清晰。我?guī)У倪\(yùn)維新人經(jīng)常問我“Kubernetes 概念太多了Pod、Deployment、Service、Ingress、CRI、OCI、CNI、CSI……學(xué)完就忘怎么才能記住”我給的答案很簡(jiǎn)單去把一個(gè)正在運(yùn)行的 Pod 弄掛把一個(gè)節(jié)點(diǎn)標(biāo)記成 NotReady把 CoreDNS 副本數(shù)縮到 0然后親手把它們修好。你踩過一次 ImagePullBackOff 的坑比背十遍 kubelet 工作原理都管用。這一篇是系列第五篇前四篇我們已經(jīng)把 Kubernetes 的架構(gòu)組成、集群部署、工作負(fù)載、網(wǎng)絡(luò)與存儲(chǔ)都過了一遍。這一篇我打算徹底換一個(gè)畫風(fēng)先跟你把 kubelet 到 containerd 的實(shí)體調(diào)用鏈捋明白因?yàn)檫@是所有“運(yùn)行時(shí)相關(guān)故障”的底層邏輯然后基于這條調(diào)用鏈把從 Pod 到集群的 20 類常見故障全部分類拆開講清楚再分享一套我自己的排錯(cuò)方法、排查命令和破壞式實(shí)驗(yàn)設(shè)計(jì)最后把這些實(shí)戰(zhàn)經(jīng)驗(yàn)對(duì)應(yīng)到 Kubernetes 面試高頻題上。內(nèi)容有點(diǎn)長(zhǎng)但都是可以直接抄作業(yè)的。2. 先搞懂調(diào)用鏈kubelet 到底是怎么調(diào)用 containerd 的做故障排查之前我建議先把“一次 Pod 創(chuàng)建”背后的調(diào)用鏈刻在腦子里。因?yàn)榧豪?80% 的運(yùn)行時(shí)故障表象千奇百怪本質(zhì)都是這條鏈路上某個(gè)環(huán)節(jié)斷了。你只有知道了正常的時(shí)候數(shù)據(jù)是怎么流的才能在異常的時(shí)候快速定位斷點(diǎn)。2.1 從 kubelet 到 CRI一層抽象解決“運(yùn)行時(shí)”之爭(zhēng)Kubernetes 早期版本是直接內(nèi)置支持 Docker 的kubelet 通過 Docker API 操作容器。后來容器運(yùn)行時(shí)越來越多containerd、CRI-O、Kata Containers 紛紛登場(chǎng)Kubernetes 社區(qū)做了一個(gè)很重要的決定抽象出一層 CRIContainer Runtime Interface用一套統(tǒng)一的 gRPC 接口把 kubelet 和具體的容器運(yùn)行時(shí)解耦。CRI 定義了兩類核心服務(wù)RuntimeService管理 Pod 沙箱Sandbox和容器生命周期比如 RunPodSandbox、CreateContainer、StartContainer、StopContainer。ImageService管理鏡像比如 PullImage、ListImages、RemoveImage。你可以把 CRI 理解成一個(gè)“電源插座”kubelet 只需要認(rèn)準(zhǔn)插座規(guī)格至于插座后面插的是 containerd 還是 CRI-O它不關(guān)心。而 containerd 為了接入 Kubernetes在它內(nèi)部實(shí)現(xiàn)了一個(gè) CRI Plugin在 config.toml 里通常能看到它把 containerd 原生的 API 翻譯成了 CRI 語(yǔ)義。這一層翻譯是理解整套調(diào)用鏈的鑰匙。2.2 一次 Pod 創(chuàng)建到底發(fā)生了什么實(shí)體調(diào)用鏈我直接用文字把這條鏈路一步步畫出來你在看的時(shí)候可以想象自己在追一條請(qǐng)求你執(zhí)行 kubectl run 或者創(chuàng)建 Deployment請(qǐng)求先到 kube-apiserver經(jīng)過認(rèn)證、授權(quán)、準(zhǔn)入控制后寫入 etcd。kube-scheduler 通過 watch 機(jī)制發(fā)現(xiàn)這個(gè)新 Pod經(jīng)過調(diào)度算法選出一個(gè)最合適的節(jié)點(diǎn)并把調(diào)度結(jié)果寫回 API Server。目標(biāo)節(jié)點(diǎn)上的 kubelet 通過 watch 拿到這個(gè) Pod進(jìn)入 syncPod 流程。kubelet 調(diào)用內(nèi)部的 Container Runtime Manager由它通過 CRI 客戶端向 containerd 的 CRI Plugin 發(fā)起 gRPC 請(qǐng)求。首個(gè)請(qǐng)求通常是 RunPodSandbox。containerd 的 CRI Plugin 會(huì)先去拉取 sandbox_image默認(rèn)就是 pause 鏡像然后通過 containerd 的 Task Service 啟動(dòng)一個(gè) pause 容器作為整個(gè) Pod 的網(wǎng)絡(luò)、IPC、UTS 等命名空間的“錨點(diǎn)”同時(shí)調(diào)用 CNI 插件完成 Pod 網(wǎng)絡(luò)配置。沙箱創(chuàng)建完成后kubelet 繼續(xù)發(fā)起 CreateContainer、StartContainer 請(qǐng)求containerd 開始真正創(chuàng)建業(yè)務(wù)容器。此時(shí) containerd 會(huì)為這個(gè)容器拉起一個(gè)獨(dú)立的 containerd-shim 進(jìn)程我這套環(huán)境里是 containerd-shim-runc-v2。shim 進(jìn)程負(fù)責(zé)調(diào)用 runc create、runc start最終由 runc 通過 Linux 內(nèi)核的 namespace、cgroup、mount 等機(jī)制把容器真正跑起來。這里有兩個(gè)特別容易忽略的實(shí)體pause 容器。它是 Pod 里最先被創(chuàng)建、生命周期貫穿始終的“占位容器”。業(yè)務(wù)容器無論怎么重啟只要 pause 在Pod 的網(wǎng)絡(luò)標(biāo)識(shí)和沙箱資源就不會(huì)變。這也是為什么你在節(jié)點(diǎn)上用 crictl ps 會(huì)看到每個(gè) Pod 都對(duì)應(yīng)一個(gè) pause 容器。containerd-shim。它的存在是為了不讓 containerd 主進(jìn)程直接當(dāng)容器的父進(jìn)程。這樣一來containerd 重啟、升級(jí)都不會(huì)殺掉正在運(yùn)行的容器。每個(gè) shim 對(duì)應(yīng)一個(gè)容器它負(fù)責(zé)接管容器的標(biāo)準(zhǔn)輸入輸出、退出狀態(tài)上報(bào)并作為 runc 和 containerd 之間的中間人。2.3 順著 socket 摸下去在節(jié)點(diǎn)上“眼見為實(shí)”原理講再多不如自己在節(jié)點(diǎn)上敲幾條命令。Kubernetes 和 containerd 之間是 gRPC 通信socket 文件一般位于 /run/containerd/containerd.sock。kubelet 的啟動(dòng)參數(shù)里一般有--container-runtime-endpointunix:///run/containerd/containerd.sock你可以先在節(jié)點(diǎn)上看一下這個(gè) socket 是否真實(shí)存在ls -l /run/containerd/containerd.sock然后重點(diǎn)練熟 crictl 這一組命令它是我們排障時(shí)最順手的工具因?yàn)?crictl 走的正是 CRI 接口也就是說你手動(dòng)用 crictl 操作的路徑和 kubelet 調(diào)用 containerd 的路徑是同一個(gè)非常有助于把調(diào)用鏈“實(shí)體化”crictl pods查看節(jié)點(diǎn)上的 Pod 沙箱列表。crictl ps -a查看所有容器包含已退出的注意區(qū)分 sandbox 容器和業(yè)務(wù)容器。crictl inspect 查看單個(gè)容器的詳細(xì) spec、掛載、PID 等。crictl logs 直接拿容器日志不經(jīng)過 kubectl。crictl pull手動(dòng)拉鏡像復(fù)現(xiàn) ImagePullBackOff 時(shí)好用。另外還有一套工具鏈?zhǔn)?ctr namespaces list它直接調(diào)用 containerd 原生 API和 crictl 的視角不同。兩者區(qū)別要搞清楚crictl 是 CRI 視角能看懂 Pod 和容器ctr 是 containerd 原生視角看不到 Pod 概念。排障時(shí)優(yōu)先用 crictl涉及 containerd 底層鏡像、快照、事件時(shí)再用 ctr 輔助。2.4 原理照進(jìn)排錯(cuò)調(diào)用鏈能幫你做什么為什么要花這么大力氣講調(diào)用鏈因?yàn)楣收吓挪榈谋举|(zhì)就是沿著這條鏈逐段做排除。舉個(gè)例子業(yè)務(wù)容器一直 CreateContainerErrorkubectl describe pod 里只顯示一句失敗的容器創(chuàng)建很多人就懵了。但如果你知道這條鏈?zhǔn)?kubelet → CRI → containerd → shim → runc你的排查思路立刻就有了第一段kubelet 是否正???journalctl -u kubelet。第二段CRI 接口是否通直接 crictl ps 看能不能連上 containerd socket。第三段containerd 是否正常看 journalctl -u containerd 和 containerd 的日志。第四段runc 啟動(dòng)容器時(shí)內(nèi)核報(bào)了什么錯(cuò)看 containerd 日志里帶 runc 字樣的 Error。順序排查永遠(yuǎn)比盯著 kubelet 日志硬猜要快。后面我們講故障分類時(shí)你會(huì)發(fā)現(xiàn)所有故障最終都能落到這條鏈路的具體某一段上。3. 從 Pod 到集群20 類常見故障全解析接下來進(jìn)入正題。我把平時(shí)線上和測(cè)試環(huán)境里遇過的高頻故障按“從 Pod 到集群”的維度整理成 20 類。先給一張速查表再挑幾類最容易讓人卡殼的展開講排查邏輯和修復(fù)手法。3.1 一張速查表先打底故障現(xiàn)象、根因、排查命令層級(jí)故障現(xiàn)象常見根因核心排查命令處理方向PodImagePullBackOff鏡像名錯(cuò)誤、倉(cāng)庫(kù)不存在、認(rèn)證失敗kubectl describe pod修鏡像名、配 imagePullSecretPodErrImageNeverPullimagePullPolicyNever 但本地?zé)o鏡像kubectl describe pod換鏡像拉取策略或預(yù)置鏡像PodInvalidImageName鏡像名不合法kubectl describe pod修正鏡像格式PodCrashLoopBackOff啟動(dòng)命令失敗、配置錯(cuò)誤、依賴未就緒kubectl logs、kubectl describe pod修應(yīng)用啟動(dòng)邏輯PodOOMKilled容器內(nèi)存超 limitkubectl describe pod調(diào) resources.limitsPodPending資源不足、親和性/污點(diǎn)不滿足、PVC 未綁定kubectl describe pod擴(kuò)容、調(diào)整調(diào)度約束PodCreateContainerError鏡像或運(yùn)行時(shí)層錯(cuò)誤crictl ps -a、journalctl -u kubelet看 containerd 日志PodCreateContainerConfigErrorConfigMap/Secret 不存在或字段缺失kubectl describe pod檢查引用資源PodRunContainerError運(yùn)行時(shí)啟動(dòng)容器失敗journalctl -u containerd看 runc 報(bào)錯(cuò)PodDeadlineExceededPod 終止超時(shí)kubectl get pod -o yaml調(diào) terminationGracePeriod 或強(qiáng)刪PodInit:CrashLoopBackOffinitContainer 反復(fù)失敗kubectl logs pod -c init容器修初始化邏輯節(jié)點(diǎn)NotReadykubelet 心跳中斷、運(yùn)行時(shí)異常kubectl describe node、journalctl -u kubelet逐段排查 kubelet節(jié)點(diǎn)DiskPressure節(jié)點(diǎn)磁盤到達(dá)驅(qū)逐閾值df -h、crictl rmi 清理鏡像清鏡像、清日志、加磁盤節(jié)點(diǎn)MemoryPressure節(jié)點(diǎn)內(nèi)存不足free -m驅(qū)逐 Pod、加節(jié)點(diǎn)節(jié)點(diǎn)PIDPressurePID 耗盡cat /proc/sys/kernel/pid_max、ps -eLf查進(jìn)程泄漏網(wǎng)絡(luò)DNS 解析失敗CoreDNS 異常、上游 DNS 失效kubectl exec -it pod -- nslookup查 CoreDNS 狀態(tài)網(wǎng)絡(luò)Service 不通Endpoints 為空、kube-proxy 規(guī)則異常kubectl get endpoints、iptables-save查 selector網(wǎng)絡(luò)跨節(jié)點(diǎn) Pod 不通CNI 配置異常、underlay 丟包ping、traceroute、查 CNI 日志查 CNI 插件網(wǎng)絡(luò)NodePort 訪問不通防火墻、安全組、端口占用ss -lntp檢查集群外鏈路存儲(chǔ)PV/PVC 掛載失敗StorageClass 不存在、權(quán)限不足kubectl describe pvc檢查存儲(chǔ)插件與權(quán)限3.2 Pod 生命周期類從 ImagePullBackOff 到 OOMKilled先講出現(xiàn)頻率最高的 ImagePullBackOff。它的表象是 Pod 卡在 ContainerCreatingEvents 里能看到 Failed to pull image。我從排查動(dòng)作給你拆開第一步kubectl describe pod 看 Events 里的具體報(bào)錯(cuò)。如果報(bào) ErrImagePull多半是鏡像倉(cāng)庫(kù)路徑寫錯(cuò)、鏡像不存在、或者倉(cāng)庫(kù)需要認(rèn)證。注意有時(shí)候鏡像名寫對(duì)了但 tag 打錯(cuò)了也會(huì)報(bào)同樣的錯(cuò)誤。第二步手動(dòng)在節(jié)點(diǎn)上用 crictl pull 拉一次相同鏡像。這一步能排除“kubelet 到鏡像倉(cāng)庫(kù)的網(wǎng)絡(luò)問題”和“倉(cāng)庫(kù)本身問題”。第三步如果是私有倉(cāng)庫(kù)檢查 Pod 里是否配置了 imagePullSecrets。我踩過最隱蔽的坑是secret 存在但 service account 沒綁定kubelet 壓根沒把 secret 帶給 containerd。再看 CrashLoopBackOff。這個(gè)狀態(tài)說明容器起來了但啟動(dòng)后立刻退出然后又重啟反復(fù)循環(huán)。很多人一看到這個(gè)狀態(tài)就慌了其實(shí)排查路徑非常固定kubectl logs 拿標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出看應(yīng)用為什么退出。如果日志為空加 --previous 看上一次容器的日志。如果應(yīng)用是 init 進(jìn)程直接退出可能是 entrypoint 腳本問題如果涉及依賴服務(wù)數(shù)據(jù)庫(kù)、配置中心優(yōu)先看網(wǎng)絡(luò)和配置能否連通。我覺得 CrashLoopBackOff 最容易翻車的地方是進(jìn)程“假啟動(dòng)”。比如一個(gè) Java 應(yīng)用JVM 起來了但連不上配置中心又在代碼里設(shè)了啟動(dòng)失敗即退出。這時(shí)候日志可能會(huì)在啟動(dòng)后 30 秒才刷出來需要耐心看完整日志。然后是 OOMKilled。表象是容器狀態(tài)顯示 OOMKilled退出碼 137。根因往往是容器內(nèi)存超過 resources.limits被 cgroup OOM killer 殺掉。排查時(shí)kubectl describe pod 能看到最后狀態(tài)是 OOMKilled以及 reason 為 OOMKilled。用 free -m 看節(jié)點(diǎn)內(nèi)存再用 crictl stats 看各容器真實(shí)內(nèi)存占用。如果應(yīng)用是 Java注意 JVM 默認(rèn)堆大小可能和容器 limits 不匹配。我的經(jīng)驗(yàn)是壓測(cè)環(huán)境下這類問題特別多JVM 還沒觸發(fā)自己的 OOM就先被 cgroup 殺了。3.3 節(jié)點(diǎn)與 kubeletNotReady、磁盤壓力、運(yùn)行時(shí)失聯(lián)節(jié)點(diǎn)層故障牽扯面大因?yàn)橐粋€(gè)節(jié)點(diǎn)掛掉上面所有 Pod 都要重建對(duì)業(yè)務(wù)的影響往往呈指數(shù)級(jí)放大。先看 kubectl get node 輸出里 STATUS 為 NotReady 的節(jié)點(diǎn)再用 kubectl describe node 查看 Conditions里面會(huì)寫明當(dāng)前節(jié)點(diǎn)處于哪種壓力狀態(tài)。NotReady 最常見的三種原因我按概率排一下kubelet 與 API Server 的通信斷了??赡苁蔷W(wǎng)絡(luò)問題、證書過期、或 kubelet 本身崩潰。排查命令是 journalctl -u kubelet -f一定要看實(shí)時(shí)日志因?yàn)楹芏鄨?bào)錯(cuò)轉(zhuǎn)瞬即逝。節(jié)點(diǎn)負(fù)載過高導(dǎo)致 kubelet 的心跳上報(bào)超時(shí)。這時(shí)候 ssh 上節(jié)點(diǎn)top、free、df 三連看優(yōu)先確認(rèn)資源水位。容器運(yùn)行時(shí)掛了。也就是 containerd 進(jìn)程異常kubelet 調(diào)用 CRI 接口超時(shí)被迫把節(jié)點(diǎn)標(biāo)記為 NotReady。排查 containerd 狀態(tài)systemctl status containerd、journalctl -u containerd。DiskPressure、MemoryPressure、PIDPressure 這三類壓力本質(zhì)都是節(jié)點(diǎn)資源達(dá)到驅(qū)逐閾值kubelet 開始按照 QoS 等級(jí)驅(qū)逐 Pod。排查時(shí)DiskPressuredf -h 看根分區(qū)和容器數(shù)據(jù)目錄分區(qū)。很多時(shí)候是容器日志、鏡像、已停止容器殘留占滿磁盤。清理思路是先刪無用的鏡像crictl rmi再清理日志journalctl --vacuum-size最后看有沒有被誤寫進(jìn)容器目錄的大文件。MemoryPressurefree -m 先看可用內(nèi)存用 ps 按內(nèi)存排序找進(jìn)程。PIDPressure看 /proc/sys/kernel/pid_max 和當(dāng)前 pid 數(shù)量。這種往往是有進(jìn)程泄漏瘋狂創(chuàng)建線程或子進(jìn)程。我遇到過一次 Java 應(yīng)用線程池參數(shù)寫錯(cuò)把機(jī)器 PID 直接打滿。另外一個(gè)很容易被忽略的是 kubelet 和 containerd 之間的狀態(tài)不一致。比如 containerd 重啟過但 kubelet 沒有感知crictl 能看到容器kubectl 里 Pod 一直異常。這種時(shí)候先重啟 kubelet 讓狀態(tài)重新對(duì)賬往往能自愈。3.4 網(wǎng)絡(luò)與服務(wù)DNS 解析失敗、Service 不通、跨節(jié)點(diǎn)連不上網(wǎng)絡(luò)類故障是排障里最燒腦的因?yàn)樯婕拔锢砭W(wǎng)絡(luò)、CNI、kube-proxy、DNS、Service 多層疊加。我按“先從 Pod 內(nèi)部往外逐層測(cè)”的方法講。Pod 內(nèi) DNS 解析失敗先做一件事kubectl exec -it -- nslookup 然后根據(jù)報(bào)錯(cuò)分兩種情況如果 getaddrinfo 直接報(bào)錯(cuò)說明 Pod 里的 /etc/resolv.conf 有問題常見原因是 dnsPolicy 被改成 Default導(dǎo)致 Pod 沒有用集群的 CoreDNS。如果能解析到 IP 但訪問超時(shí)說明 CoreDNS 本身異常。查 CoreDNS Pod 狀態(tài)和日志看看是否有上游 DNS 配置錯(cuò)誤。我踩過的一個(gè)坑是宿主機(jī) /etc/resolv.conf 里的 nameserver 指向了內(nèi)網(wǎng) DNS但 CoreDNS 把它當(dāng)上游解析外網(wǎng)域名時(shí)經(jīng)常超時(shí)。Service 訪問不通按這個(gè)順序查kubectl get endpoints 看 Endpoints 是否有 IP。如果沒有說明 Service 的 selector 和 Pod 的 label 不匹配這是最最常見的低級(jí)錯(cuò)誤。如果 Endpoints 有 IP就在集群內(nèi)隨便挑一個(gè) Podcurl 一下 Service 的 ClusterIP看通不通。不通的話檢查 kube-proxy 的規(guī)則。kube-proxy 默認(rèn) iptables 模式下用 iptables-save | grep 能看到規(guī)則。如果規(guī)則不存在重啟 kube-proxy Pod 或者直接看它的日志??绻?jié)點(diǎn) Pod 網(wǎng)絡(luò)不通屬于 CNI 問題。排查思路先確認(rèn) CNI 插件是什么。查看節(jié)點(diǎn)上的 /etc/cni/net.d/ 目錄。查看 CNI Pod 是否正常比如 Calico 的話就是 calico-node 和 calico-kube-controllers。在源 Pod 里 ping 目標(biāo) Pod 的 IP逐跳看丟在哪同時(shí)檢查節(jié)點(diǎn)的路由表比如 route -n 是否包含到 Pod 網(wǎng)段的路由。如果節(jié)點(diǎn)上有多個(gè)網(wǎng)卡常常是因?yàn)?CNI 選錯(cuò)了主網(wǎng)卡導(dǎo)致 VXLAN 或 BGP 隧道建不起來。這種問題用 kubectl logs 看 CNI 組件日志一般都能看到明確的網(wǎng)卡異常提示。3.5 存儲(chǔ)與控制面PV/PVC 掛載失敗、etcd 抖動(dòng)存儲(chǔ)類故障從使用者視角看就是 Pod 一直 ContainerCreatingEvents 里提示 FailedMount。排查 PV/PVC 有無綁定成功是很關(guān)鍵的一步kubectl get pvc 看 STATUS 是否為 Bound。Pending 狀態(tài)說明 StorageClass 或存儲(chǔ)插件有問題已經(jīng) Bound 但掛載失敗則要看存儲(chǔ)協(xié)議本身比如 NFS 掛載超時(shí)、CSI 插件未安裝。我個(gè)人的建議是排存儲(chǔ)問題時(shí)一定先把 kubelet 日志翻出來看完整報(bào)錯(cuò)不要只看 Events因?yàn)轭l繁遇到的是宿主機(jī)缺少 nfs-utils 這類基礎(chǔ)依賴報(bào)錯(cuò)只出現(xiàn)在 kubelet 的日志里??刂泼婀收侠飁tcd 抖動(dòng)最有代表性。etcd 是 Kubernetes 所有狀態(tài)的底座如果它異常你會(huì)看到 kube-apiserver 報(bào) etcdserver: request timed out整個(gè)集群開始“僵住”。排查時(shí)先看 etcd 集群健康狀態(tài)etcdctl endpoint health --cluster。再關(guān)注磁盤 IOetcd 對(duì)磁盤延遲極其敏感fsync 太慢會(huì)觸發(fā) leader 頻繁切換。用 iostat 看 etcd 數(shù)據(jù)盤的 await 值如果常年高于 50ms就得考慮換 SSD 或者走獨(dú)立盤。還有網(wǎng)絡(luò)延遲三個(gè) etcd 節(jié)點(diǎn)之間延遲高也會(huì)導(dǎo)致心跳超時(shí)。這類問題我踩過一次調(diào)試時(shí)發(fā)現(xiàn) etcd 和業(yè)務(wù)混部在同一批機(jī)器流量高峰期直接拖垮了存儲(chǔ)鏈路。4. 破壞式實(shí)驗(yàn)怎么設(shè)計(jì)我的排錯(cuò)方法論與工具鏈看到這里你可能會(huì)說你講的故障我也都見過但每次都是靠運(yùn)氣或者到處搜怎么能系統(tǒng)地練出排障手感下面這部分就是答案。我把“破壞式學(xué)習(xí)”落成了一套可執(zhí)行的方法你不妨照著做在測(cè)試環(huán)境里把故障一個(gè)個(gè)制造出來再親手修掉。4.1 分層排查法把故障“釘”在某一段無論遇到什么問題我的第一個(gè)判斷永遠(yuǎn)是這個(gè)故障現(xiàn)在發(fā)生在調(diào)用鏈的哪一段我把 Kubernetes 排障分成四個(gè)層次從小到大容器層Pod鏡像、容器創(chuàng)建、應(yīng)用進(jìn)程、資源限制。節(jié)點(diǎn)層Nodekubelet、containerd、磁盤、內(nèi)存、網(wǎng)絡(luò)底層。集群網(wǎng)絡(luò)層CoreDNS、Service、Ingress、CNI、網(wǎng)絡(luò)策略??刂泼鎸觡ube-apiserver、etcd、kube-scheduler、controller-manager。這個(gè)分層和調(diào)用鏈?zhǔn)菍?duì)應(yīng)的。排查時(shí)從上往下走先看最貼近業(yè)務(wù)、最容易觀察的一層不要一上來就查 etcd。我有一次帶新人排 Pod 創(chuàng)建失敗新人直接去查 kube-apiserver 日志查了半天發(fā)現(xiàn) API Server 一切正常問題其實(shí)是鏡像倉(cāng)庫(kù)地址寫錯(cuò)了。這就是沒分層導(dǎo)致的“繞遠(yuǎn)路”。4.2 一組可以直接照做的“破壞實(shí)驗(yàn)”清單你如果不知道從哪里開始破壞我推薦從這 10 個(gè)動(dòng)作開始。每一個(gè)做完都要記錄“現(xiàn)象—根因—修復(fù)”三段筆記刪掉一個(gè)正在運(yùn)行的 Deployment觀察 ReplicaSet 怎么重建。把某 Pod 的鏡像名改錯(cuò)觀察 ImagePullBackOff。給 Pod 設(shè)置一個(gè)極小的 memory limit 并壓測(cè)觀察 OOMKilled。把 Deployment 的 replicas 調(diào)到調(diào)度器無法滿足的數(shù)量觀察 Pending。手動(dòng)給節(jié)點(diǎn)添加一個(gè)污點(diǎn) taint觀察已有 Pod 是否被驅(qū)逐、新 Pod 是否調(diào)度不上。停掉 kubelet 服務(wù) 30 秒再啟動(dòng)觀察節(jié)點(diǎn) NotReady 到 Ready 的轉(zhuǎn)換。在節(jié)點(diǎn)上手動(dòng) kill 掉一個(gè)業(yè)務(wù)容器的進(jìn)程觀察容器重啟策略。把 CoreDNS 的 Deployment 縮到 0觀察集群內(nèi)域名解析癥狀。改掉 Service 的 selector觀察 Endpoints 為空、Service 不通。刪除一個(gè) PVC 對(duì)應(yīng)的底層存儲(chǔ)目錄觀察 FailedMount。做完這些你會(huì)對(duì)“Kubernetes 是一個(gè)自愈系統(tǒng)但自愈的前提是故障能被它識(shí)別到”這件事有極其深的理解。比如你手動(dòng) kill 進(jìn)程后kubelet 會(huì)按照 restartPolicy 把容器重新拉起來但你如果把節(jié)點(diǎn)的 kubelet 停了節(jié)點(diǎn)整個(gè)進(jìn)入 NotReady反而不會(huì)有人管它。這個(gè)邊界不親手做一次破壞是體會(huì)不到的。4.3 我在線上驗(yàn)證過的固定排錯(cuò)順序線上和測(cè)試不一樣最快止損永遠(yuǎn)比弄清原理更重要。所以我給自己定了一套固定順序你自己也可以按這套來先看全局kubectl get nodes、kubectl get pods -A確認(rèn)故障范圍是單 Pod、單節(jié)點(diǎn)還是整個(gè)集群。再看事件kubectl describe pod/node 里的 Events 往往能直接指向根因。然后看日志按 pod → kubelet → containerd 的順序逐層追日志。最后動(dòng)手修復(fù)能滾動(dòng)重啟就先滾能刪異常 Pod 就先刪等業(yè)務(wù)恢復(fù)后再二次復(fù)盤根因。這套順序最大的價(jià)值是防止在排查階段花太久。記住線上場(chǎng)景下恢復(fù)業(yè)務(wù)優(yōu)先級(jí)永遠(yuǎn)是第一位的。等到故障解除再帶著從現(xiàn)場(chǎng)截取的日志去深挖原因。4.4 順手整理一下排障工具鏈kubectl一切入口。describe、get、logs、exec 是高頻動(dòng)作。crictl節(jié)點(diǎn)上繞開 kubectl 直接看容器運(yùn)行時(shí)狀態(tài)。ctrcontainerd 原生調(diào)試工具。journalctl看 kubelet 和 containerd 系統(tǒng)服務(wù)日志。iptables-save / ipvsadm查 kube-proxy 規(guī)則。nsenter / netstat / ss進(jìn)到容器的網(wǎng)絡(luò)命名空間里做網(wǎng)絡(luò)排查。etcdctl控制面 etcd 健康檢查和數(shù)據(jù)目錄檢查。5. 從實(shí)戰(zhàn)到面試Kubernetes 高頻問題延伸思考很多運(yùn)維去面試前瘋狂背八股我的建議恰恰相反把實(shí)戰(zhàn)里驗(yàn)證過的東西用自己的話講出來比背概念高級(jí)得多。接下來我把這個(gè)系列涉及的核心能力對(duì)應(yīng)到面試中最常被問的問題上。5.1 調(diào)用鏈相關(guān)kubelet 和 containerd 的關(guān)系怎么答面試題直接問“kubelet 是如何調(diào)用 containerd 的”其實(shí)考察的就是你是否理解 CRI 抽象。你可以這樣組織答案kubelet 并不直接調(diào)用 containerd而是通過 CRI 接口以 gRPC 方式調(diào)用 containerd 內(nèi)置的 CRI Plugin。調(diào)用路徑是 kubelet → CRI gRPC 客戶端 → /run/containerd/containerd.sock → containerd CRI Plugin → containerd-shim → runc。創(chuàng)建 Pod 時(shí)第一個(gè)關(guān)鍵請(qǐng)求是 RunPodSandboxcontainerd 會(huì)拉取 pause 鏡像并創(chuàng)建沙箱隨后 kubelet 發(fā)起 CreateContainer 和 StartContainercontainerd 啟動(dòng)一個(gè) shim 進(jìn)程shim 再調(diào)用 runc 完成容器創(chuàng)建。如果能再補(bǔ)上 pause 容器和 shim 進(jìn)程的作用面試官基本就能確定你是真做過底層排查的人。5.2 故障排查類用“分層排錯(cuò)”回答拉開差距比如面試官問“Pod 一直 Pending你怎么排查”。很多人上來就答“資源不足”但更好的回答是先 kubectl describe pod 看 EventsPending 的根因可能有資源不足、節(jié)點(diǎn)親和性不滿足、存在污點(diǎn)、PVC 未綁定、或者調(diào)度器異常。資源不足要看 allocatable 和 request污點(diǎn)要看 node 的 taints 和 Pod 的 tolerationsPVC 要看 pvc 的 STATUS 是否 Bound。把每個(gè)可能都給出對(duì)應(yīng)驗(yàn)證命令再給出解決方案這就是“有實(shí)戰(zhàn)經(jīng)驗(yàn)”的回答。另一道高頻題“Service 訪問不通如何排查”我建議按這個(gè)順序先 get endpoints 確認(rèn)后端 Pod 是否被正確關(guān)聯(lián)然后進(jìn)入集群內(nèi) Pod 直接訪問 ClusterIP 驗(yàn)證網(wǎng)絡(luò)通路如果還是不通檢查 kube-proxy 模式和 iptables/IPVS 規(guī)則最后看 CNI 底層是否存在跨節(jié)點(diǎn)路由問題。這個(gè)回答天然帶著分層排錯(cuò)的邏輯面試官會(huì)看到你腦子里有一條清晰的鏈路。5.3 原理型問題從“會(huì)用”到“說清楚”還有一類面試題問的是“為什么 Pod 是最小調(diào)度單元”“為什么不直接在一個(gè)容器里跑多個(gè)進(jìn)程”。這些問題的核心其實(shí)是 pause 容器和命名空間共享機(jī)制。一個(gè) Pod 里的多個(gè)容器共享同一個(gè)網(wǎng)絡(luò)命名空間、IPC 命名空間、UTS 命名空間也能共享 Volume但它們的進(jìn)程命名空間默認(rèn)不共享。這種設(shè)計(jì)讓“一個(gè) Pod 里放一個(gè)主容器和幾個(gè)輔助容器比如日志收集 sidecar”成了可能。如果你能順手解釋一下為什么業(yè)務(wù)容器重啟而 Pod IP 不變化——因?yàn)?pause 容器決定了網(wǎng)絡(luò)命名空間的生命周期——那這題基本就滿分了。6. 寫在最后我的一點(diǎn)個(gè)人體會(huì)寫這個(gè)系列之前我以為自己已經(jīng)對(duì) Kubernetes 的故障有免疫力了結(jié)果今年在一次壓測(cè)環(huán)境里還是被一個(gè) kubelet 版本和 containerd 版本不完全兼容的毛病折騰到凌晨三點(diǎn)。版本不匹配這種問題不親手踩一次光看升級(jí)文檔你是永遠(yuǎn)記不住的。這也是我為什么一直堅(jiān)持“破壞式學(xué)習(xí)”——教訓(xùn)往往比經(jīng)驗(yàn)更深刻。如果你現(xiàn)在還在入門階段我的建議是不要怕弄壞集群。搭一套單節(jié)點(diǎn)的 kind 或者 minikube然后照著 4.2 節(jié)里的破壞實(shí)驗(yàn)清單一天破壞一個(gè)堅(jiān)持兩周。等你親手修好了十來個(gè)故障再回頭看文檔以前看不懂的部分會(huì)變得異常清晰。Kubernetes 這個(gè)系統(tǒng)天賦不夠沒關(guān)系踩坑來湊踩得多了你就是那個(gè)能一眼定位斷點(diǎn)的人。