問(wèn)題背景
生產(chǎn)環(huán)境中 Kubernetes 集群的節(jié)點(diǎn)突然變成NotReady是非常常見(jiàn)的故障場(chǎng)景。節(jié)點(diǎn)一旦進(jìn)入NotReady狀態(tài),該節(jié)點(diǎn)上調(diào)度的 Pod 會(huì)持續(xù)處于Pending或者被驅(qū)逐的狀態(tài),如果集群規(guī)模較小或者副本數(shù)不足,會(huì)直接導(dǎo)致業(yè)務(wù)服務(wù)中斷。NotReady 本身不是根因,只是一個(gè)癥狀表現(xiàn)——它背后的原因可能是kubelet 進(jìn)程崩潰、容器運(yùn)行時(shí)異常、網(wǎng)絡(luò)不通、etcd 連接故障、磁盤空間不足、內(nèi)核問(wèn)題、證書(shū)過(guò)期等等。不同原因?qū)?yīng)的處理方式完全不同,如果不做系統(tǒng)排查,上來(lái)就重啟節(jié)點(diǎn),很可能把現(xiàn)場(chǎng)破壞掉,導(dǎo)致問(wèn)題復(fù)現(xiàn)困難。
這篇文章面向初中級(jí) Kubernetes 運(yùn)維工程師,以一個(gè)真實(shí)的 NotReady 故障為線索,完整走一遍從現(xiàn)象觀察、層層排查、定位根因到修復(fù)驗(yàn)證的全過(guò)程。文章涉及的所有排查命令均在 Kubernetes 1.24 及以上版本驗(yàn)證通過(guò),部分命令在更早版本中表現(xiàn)可能略有差異,文中會(huì)做說(shuō)明。所有涉及破壞性操作(如刪除 Pod、驅(qū)逐節(jié)點(diǎn)、重啟服務(wù))的命令均會(huì)標(biāo)注風(fēng)險(xiǎn)級(jí)別和必要的確認(rèn)步驟。
適用場(chǎng)景
集群中有節(jié)點(diǎn)顯示NotReady狀態(tài)超過(guò) 5 分鐘仍未恢復(fù)
業(yè)務(wù) Pod 被驅(qū)逐,副本數(shù)下降,監(jiān)控告警觸發(fā)
新增節(jié)點(diǎn)加入集群后始終無(wú)法 Ready
節(jié)點(diǎn)運(yùn)行一段時(shí)間后自發(fā)變成 NotReady(而非升級(jí)或維護(hù)導(dǎo)致)
升級(jí) K8s 版本后部分節(jié)點(diǎn) NotReady
集群節(jié)點(diǎn)數(shù)較少(小于 3),單節(jié)點(diǎn) NotReady 已影響核心業(yè)務(wù)
核心知識(shí)點(diǎn):Kubelet 節(jié)點(diǎn)狀態(tài)機(jī)制
理解節(jié)點(diǎn) NotReady 的根因之前,必須先弄清楚 kubelet 是如何向 kube-apiserver 上報(bào)節(jié)點(diǎn)狀態(tài)的。kubelet 會(huì)在每個(gè)nodeStatusReportFrequency周期(默認(rèn) 10 秒)內(nèi)向 API Server 更新節(jié)點(diǎn)狀態(tài)。如果 kubelet 連續(xù)失敗超過(guò)某個(gè)閾值,節(jié)點(diǎn)狀態(tài)就會(huì)被標(biāo)記為Unknown或NotReady。
具體判斷邏輯在 kube-controller-manager 的nodecontroller中:
kubelet 每 10 秒上報(bào)一次節(jié)點(diǎn)狀態(tài)(可通過(guò)--node-status-update-frequency修改,但不要盲目調(diào)大)
nodecontroller 會(huì)在節(jié)點(diǎn)連續(xù) 40 秒未更新時(shí)將狀態(tài)置為Unknown
節(jié)點(diǎn)處于Unknown狀態(tài)超過(guò) 5 分鐘(pod-eviction-timeout,默認(rèn) 5 分鐘)后,RC/Deployment 的 replicas 控制器會(huì)觸發(fā) Pod 驅(qū)逐
如果 kubelet 能恢復(fù),則節(jié)點(diǎn)狀態(tài)恢復(fù)為Ready;如果超過(guò)termination-duration(默認(rèn) 5 分鐘)仍未恢復(fù),節(jié)點(diǎn)會(huì)被徹底標(biāo)記為NotReady
需要注意的是,kubelet 報(bào)告的Ready條件是一個(gè)組合條件,由多個(gè) Condition 組成:
Conditions: Type Status MemoryPressure False DiskPressure False PIDPressure False NetworkUnavailable False kubeletReady True <-- 這個(gè)綜合狀態(tài)由 kubelet 自己計(jì)算
kubeletReady這個(gè) Condition 如果不是True,在 kubectl get node 輸出中就會(huì)顯示NotReady。但實(shí)際上 kubelet 內(nèi)部還會(huì)細(xì)分:它會(huì)檢查容器運(yùn)行時(shí)是否可用、根文件系統(tǒng)是否可寫、節(jié)點(diǎn)網(wǎng)絡(luò)是否配置正確等。任何一個(gè)檢查失敗都可能導(dǎo)致kubeletReady變?yōu)镕alse,從而導(dǎo)致節(jié)點(diǎn) NotReady。
排查路徑總覽
當(dāng)發(fā)現(xiàn)節(jié)點(diǎn) NotReady 時(shí),推薦按以下順序排查,避免走彎路:
1. 確認(rèn) NotReady 現(xiàn)象和受影響范圍 └── kubectl get node(看節(jié)點(diǎn)狀態(tài)和年齡) └── kubectl describe node(看 Reason 和 Condition) 2. 檢查 kubelet 是否在運(yùn)行 └── systemctl status kubelet └── journalctl -u kubelet -n 200 --no-pager 3. 檢查容器運(yùn)行時(shí)是否正常 └── systemctl status containerd(或 docker) └── crictl info / docker info 4. 檢查節(jié)點(diǎn)資源是否耗盡 └── df -h(磁盤) └── free -m(內(nèi)存) └── top / uptime(CPU 負(fù)載) 5. 檢查網(wǎng)絡(luò)連通性 └── ping(api server) └── curl -k https:// /healthz └── hostname -i / ip addr 6. 檢查證書(shū)是否過(guò)期 └── kubeadm certs check-expiration └── openssl x509 -in/var/lib/kubelet/pki/cert.crt -noout -dates 7. 定位根因并修復(fù) 8. 驗(yàn)證恢復(fù) └── kubectl get node └── 在節(jié)點(diǎn)上跑一個(gè)測(cè)試 Pod
以上順序不是絕對(duì)的,要根據(jù)現(xiàn)場(chǎng)實(shí)際情況靈活調(diào)整。比如磁盤空間不足是最容易快速確認(rèn)的項(xiàng)目,優(yōu)先檢查;如果節(jié)點(diǎn)一開(kāi)始是 Ready 后來(lái)突然 NotReady,則 kubelet 進(jìn)程問(wèn)題概率更大;如果新增節(jié)點(diǎn)始終無(wú)法 Ready,則網(wǎng)絡(luò)配置或 kubelet 啟動(dòng)參數(shù)問(wèn)題概率更大。
第一步:確認(rèn) NotReady 現(xiàn)象和受影響范圍
用 kubectl 連接目標(biāo)集群(如果本地?zé)o法連接集群,可以找一臺(tái)有 kubectl 配置的節(jié)點(diǎn)):
# 查看所有節(jié)點(diǎn)狀態(tài),-o wide 看更多信息 kubectl get nodes -o wide # 輸出示例: # NAME STATUS ROLES AGE VERSION # k8s-master Ready control-plane 45d v1.28.4 # k8s-node-1 NotReady30d v1.28.4 # k8s-node-2 Ready 30d v1.28.4
如果節(jié)點(diǎn)數(shù)量較多,可以用 grep 過(guò)濾:
kubectl get nodes | grep -v Ready
確認(rèn) NotReady 節(jié)點(diǎn)名稱和數(shù)量后,用 describe 查看詳細(xì)信息:
kubectl describe node k8s-node-1
describe 輸出的最后幾行Conditions是最關(guān)鍵的:
Conditions: Type Status LastHeartbeatTime LastTransitionTime Reason Message ---- ------ ----------------- ------------------ ------ ------- MemoryPressure False 2026-04-29T1034Z 2026-04-29T0812Z KubeletHasSufficientMemory kubelet has sufficient memory available DiskPressure True 2026-04-29T1034Z 2026-04-29T1000Z KubeletHasDiskPressure kubelet has disk pressure; low disk space PIDPressure False 2026-04-29T1034Z 2026-04-29T0812Z KubeletHasSufficientPID kubelet has sufficient PID available NetworkUnavailable False 2026-04-29T1034Z 2026-04-29T0812Z RouteCreated Route created successfully kubeletReady False 2026-04-29T1034Z 2026-04-29T1000Z KubeletNotReady kubelet status is (matchNodeConditionMode)
從上面這個(gè)示例可以看到,DiskPressure為True且kubeletReady為False,Message 中明確寫了"low disk space"。這就是根因線索——磁盤空間不足導(dǎo)致 kubelet 認(rèn)為節(jié)點(diǎn)不健康。
但要注意,DiskPressure和kubeletReady是兩個(gè)獨(dú)立的 Condition。kubeletReady為 False 不一定是因?yàn)镈iskPressure,也可能是因?yàn)槿萜鬟\(yùn)行時(shí)無(wú)響應(yīng)、容器網(wǎng)絡(luò)配置錯(cuò)誤等。需要綜合判斷。
再看 Nodes Conditions 中NotReady對(duì)應(yīng)的Reason字段,常見(jiàn)的原因值如下:
| Reason | 含義 | 常見(jiàn)根因 |
|---|---|---|
| KubeletNotReady | kubelet 未就緒 | kubelet 進(jìn)程崩潰、容器運(yùn)行時(shí)異常、磁盤/內(nèi)存壓力 |
| NodeNotReady | 節(jié)點(diǎn)整體不健康 | kubelet 完全無(wú)響應(yīng)、證書(shū)過(guò)期、網(wǎng)絡(luò)不通 |
| KubeletHasNoDiskPressure | kubelet 認(rèn)為磁盤正常 | 僅狀態(tài)展示用,不表示問(wèn)題 |
| MemoryPressure | 內(nèi)存壓力 | 節(jié)點(diǎn)內(nèi)存耗盡 |
| NetworkUnavailable | 網(wǎng)絡(luò)不可用 | CNI 配置錯(cuò)誤、節(jié)點(diǎn)網(wǎng)絡(luò)未正確配置 |
接下來(lái)按照不同場(chǎng)景分別講解排查步驟。
第二步:檢查 kubelet 進(jìn)程是否正常運(yùn)行
kubelet 是節(jié)點(diǎn)上最核心的 K8s 組件。如果 kubelet 進(jìn)程掛了,節(jié)點(diǎn)狀態(tài)立即就會(huì)變成 NotReady。因此排查的第一步就是確認(rèn) kubelet 進(jìn)程狀態(tài)。
2.1 查看 kubelet 服務(wù)狀態(tài)
# 查看 kubelet 服務(wù)狀態(tài)(systemd 管理方式) sudo systemctl status kubelet # 如果是 containerd 運(yùn)行時(shí),可能看到: # kubelet.service - kubelet: The Kubernetes Node Agent # Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled) # Active: active (running) since ... # 或者 # Active: failed (Result: start-limit-hit) since ...
如果看到Active: failed,說(shuō)明 kubelet 啟動(dòng)失敗了。需要立即查看日志。
2.2 查看 kubelet 日志
# 查看最近 300 行日志,不分頁(yè)直接輸出 sudo journalctl -u kubelet -n 300 --no-pager # 從頭查看(啟動(dòng)日志) sudo journalctl -u kubelet --no-pager | head -500 # 查看某個(gè)時(shí)間點(diǎn)之后的日志(結(jié)合故障時(shí)間) sudo journalctl -u kubelet --since"2026-04-29 0900"--no-pager # 查看 kubelet 啟動(dòng)失敗的相關(guān)日志 sudo journalctl -u kubelet -b --no-pager | grep -i error
kubelet 日志中最常見(jiàn)的錯(cuò)誤類型和處理方式如下。
錯(cuò)誤類型一:容器運(yùn)行時(shí)連接失敗
Failed to run Kubelet: failed to load Kubelet config file"/var/lib/kubelet/config.yaml": error adding watch on /var/lib/kubelet/cpu_manager_state": context deadline exceeded
這類錯(cuò)誤通常是 kubelet 啟動(dòng)參數(shù)和容器運(yùn)行時(shí)配置不匹配。如果是 Docker 切換到 containerd,或者升級(jí)了容器運(yùn)行時(shí)版本后出現(xiàn),重點(diǎn)檢查/etc/containerd/config.toml和 kubelet 的--container-runtime-endpoint參數(shù)是否一致。
錯(cuò)誤類型二:證書(shū)相關(guān)
Failed to start kubelet: unable to load client CA file /etc/kubernetes/pki/kubelet-client-latest/current: open /etc/kubernetes/pki/kubelet-client-latest/current: no such file or directory
證書(shū)文件缺失或不完整,通常發(fā)生在手動(dòng)修改了/etc/kubernetes/目錄之后,或者升級(jí)過(guò)程中證書(shū)沒(méi)有正確續(xù)期。可以用以下命令檢查證書(shū)狀態(tài):
# 檢查 kubelet 證書(shū)是否過(guò)期(需要先 SSH 到目標(biāo)節(jié)點(diǎn)) sudo kubeadm certs check-expiration --cert-dir /etc/kubernetes/pki # 手動(dòng)檢查某個(gè)具體證書(shū) sudo openssl x509 -in/var/lib/kubelet/pki/cert.crt -noout -dates
錯(cuò)誤類型三:ETCD 連接超時(shí)
Failed to start kubelet: Post"https://192.168.1.10:2379/vote": net/http: request canceled
說(shuō)明 kubelet 無(wú)法連接到集群的 etcd。可能是網(wǎng)絡(luò)問(wèn)題,也可能是 etcd 本身響應(yīng)慢。如果集群所有節(jié)點(diǎn)同時(shí)出現(xiàn)這個(gè)問(wèn)題,則 etcd 故障的概率更高;如果只有某一個(gè)節(jié)點(diǎn)出現(xiàn),則該節(jié)點(diǎn)的網(wǎng)絡(luò)配置問(wèn)題概率更大。
2.3 手動(dòng)重啟 kubelet
風(fēng)險(xiǎn)提醒:重啟 kubelet 會(huì)導(dǎo)致該節(jié)點(diǎn)上的 Pod 被驅(qū)逐和重新調(diào)度,對(duì)業(yè)務(wù)有影響。生產(chǎn)環(huán)境中執(zhí)行前必須:1)確認(rèn)業(yè)務(wù)副本數(shù)足夠撐過(guò)驅(qū)逐和重新調(diào)度;2)通知相關(guān)業(yè)務(wù)方;3)準(zhǔn)備好回滾方案。
# 先查看節(jié)點(diǎn)上運(yùn)行了哪些 Pod,評(píng)估影響 kubectl get pods -o wide --all-namespaces | grep k8s-node-1 # 確認(rèn)無(wú)誤后,重啟 kubelet sudo systemctl restart kubelet # 查看重啟后的狀態(tài) sudo systemctl status kubelet sleep 30 kubectl get node k8s-node-1
如果 kubelet 重啟后能正常啟動(dòng),節(jié)點(diǎn)恢復(fù) Ready,則說(shuō)明是臨時(shí)故障(比如 OOM 殺掉了 kubelet)。但這只是治標(biāo),要找到根因,還需要結(jié)合后面的步驟分析日志,判斷是什么導(dǎo)致的 kubelet 崩潰。
第三步:檢查容器運(yùn)行時(shí)是否正常
容器運(yùn)行時(shí)(containerd 或 dockerd)是 kubelet 工作的前提。如果容器運(yùn)行時(shí)異常,kubelet 雖然進(jìn)程在,但無(wú)法創(chuàng)建/管理容器,對(duì)應(yīng)的 Pod 狀態(tài)會(huì)變成ContainerCreating或Error,同時(shí) kubelet 會(huì)認(rèn)為節(jié)點(diǎn)不健康。
3.1 檢查 containerd 服務(wù)狀態(tài)
# 查看 containerd 服務(wù)狀態(tài) sudo systemctl status containerd # 查看 containerd 日志 sudo journalctl -u containerd -n 200 --no-pager # 檢查 containerd 進(jìn)程是否存活 ps aux | grep containerd | grep -v grep
3.2 使用 crictl 檢查容器運(yùn)行時(shí)
crictl 是 containerd 提供的命令行工具,用于直接和 containerd 通信,繞過(guò) kubelet 排查問(wèn)題。
# 檢查 containerd 版本和信息 sudo crictl info # 如果報(bào)錯(cuò) "runtime endpoint not connected",說(shuō)明 containerd 未正常響應(yīng)
正常情況下,crictl info會(huì)輸出 containerd 的配置信息,包括存儲(chǔ)驅(qū)動(dòng)、網(wǎng)絡(luò)插件等。如果這一步失敗,問(wèn)題很可能在 containerd 本身。
# 查看 containerd 的 socket 文件是否存在 ls -la /run/containerd/containerd.sock ls -la /var/run/containerd/containerd.sock
socket 文件不存在通常意味著 containerd 進(jìn)程沒(méi)有正常創(chuàng)建 socket,或者 containerd 服務(wù)配置了非默認(rèn)路徑。
3.3 檢查 dockerd 服務(wù)(如果使用 docker 作為運(yùn)行時(shí))
sudo systemctl status docker # 查看 dockerd 日志 sudo journalctl -u docker -n 200 --no-pager # 檢查 docker 是否能列出容器(驗(yàn)證 daemon 響應(yīng)) sudo docker ps -a # 檢查 docker 網(wǎng)絡(luò)驅(qū)動(dòng)是否正常 sudo docker info
docker info輸出中重點(diǎn)關(guān)注以下幾個(gè)字段:
Container runtime: containerd # 確認(rèn)使用的運(yùn)行時(shí) Storage Driver: overlay2 # 確認(rèn)存儲(chǔ)驅(qū)動(dòng) Logging Driver: json-file Cgroup Driver: systemd # 注意:docker 和 kubelet 的 cgroup driver 必須一致
如果Cgroup Driver顯示cgroupfs而 kubelet 使用的是systemd,就會(huì)導(dǎo)致資源管理不一致,節(jié)點(diǎn)可能出現(xiàn)莫名的內(nèi)存壓力或 CPU 限制失效。
3.4 檢查 crictl 可見(jiàn)的容器和鏡像
# 列出所有容器(包括已停止的) sudo crictl ps -a # 列出所有鏡像 sudo crictl images # 查看運(yùn)行中的容器 sudo crictl ps
如果crictl ps能正常列出容器,但 kubelet 報(bào)告節(jié)點(diǎn) NotReady,說(shuō)明問(wèn)題可能在 kubelet 和容器運(yùn)行時(shí)通信的 endpoint 配置上。這種情況比較少見(jiàn),但容易讓人誤判。
第四步:檢查節(jié)點(diǎn)資源是否耗盡
節(jié)點(diǎn)資源(磁盤、內(nèi)存、CPU)耗盡是導(dǎo)致 NotReady 的最常見(jiàn)原因。kubelet 內(nèi)置了資源壓力檢測(cè),當(dāng)檢測(cè)到節(jié)點(diǎn)資源不足時(shí),會(huì)自動(dòng)設(shè)置對(duì)應(yīng)的 Condition 為 True,同時(shí)降低節(jié)點(diǎn)評(píng)分甚至拒絕調(diào)度新 Pod。
4.1 檢查磁盤空間
# 查看所有掛載點(diǎn)的磁盤使用率,重點(diǎn)關(guān)注 / 和 /var/lib/containerd df -h # 輸出示例: # Filesystem Size Used Avail Use% Mounted on # /dev/sda1 100G 95G 5G 95% / <-- 危險(xiǎn):超過(guò) 90% # /dev/sdb1 ? ? ? 500G ?200G ?300G ?40% /data # overlay ? ? ? ? 100G ? 95G ? ?5G ?95% /var/lib/containerd ?<-- 這個(gè)是 containerd 使用的overlay
磁盤使用率超過(guò) 90% 是非常危險(xiǎn)的。kubelet 的DiskPressure檢測(cè)默認(rèn)閾值是:磁盤空間低于 10%(可通過(guò) kubelet 配置--eviction-hard調(diào)整)。當(dāng)磁盤空間不足時(shí):
容器無(wú)法寫入日志(寫入 /var/log 或 stdout)
鏡像無(wú)法解壓加載
kubelet 自身寫cpu_manager_state、memory_manager_state等文件失敗
節(jié)點(diǎn)逐漸失控
風(fēng)險(xiǎn)提醒:不要盲目刪除 /var/lib/containerd/overlay2 下的鏡像文件來(lái)釋放磁盤空間。容器鏡像層存儲(chǔ)在 overlay2 目錄中,手動(dòng)刪除可能導(dǎo)致正在運(yùn)行的容器異常。用ctr -n k8s.io images prune來(lái)清理未使用的鏡像(ctr 是 containerd 自帶的命令行工具),不要用rm -rf直接刪 overlay2 目錄。
清理磁盤空間的正確步驟如下:
# SSH 到目標(biāo)節(jié)點(diǎn)后,先評(píng)估可以清理的空間
# 1. 查看 /var/log 下的日志文件大小
sudo du -sh /var/log/*
sudo find /var/log-name"*.log"-size +100M -execls -lh {} ;
# 2. 查看是否有過(guò)大的 core dump
sudo find /var/lib/systemd/coredump -typef -size +100M 2>/dev/null | xargs ls -lh
# 3. 查看 containerd 鏡像緩存
sudo ctr -n k8s.io images list
# 4. 清理未使用的鏡像(不刪除正在使用的)
sudo ctr -n k8s.io images prune -f
# 5. 清理舊的 journal 日志(保留最近 3 天)
sudo journalctl --vacuum-time=3d
# 6. 清理已停止的容器
sudo crictl rm -f $(sudo crictl ps -a --state EXITED -q)
如果磁盤使用率已經(jīng)超過(guò) 95%,上述清理手段可能不夠,需要臨時(shí)擴(kuò)容或者遷移數(shù)據(jù)。要評(píng)估是哪些目錄占用了最多空間:
# 查看 /var/lib 目錄大小分布 sudo du -sh /var/lib/*
通常/var/lib/containerd和/var/lib/kubelet是最大的兩個(gè)目錄。containerd 存儲(chǔ)鏡像數(shù)據(jù),kubelet 存儲(chǔ) Pod 沙盒數(shù)據(jù)。
4.2 檢查內(nèi)存使用
free -m # 輸出示例: # total used free shared buff/cache available # Mem: 32000 28000 4000 2000 8000 2000 # Swap: 8192 0 8192
如果available列的值非常低(低于總內(nèi)存的 10%),說(shuō)明節(jié)點(diǎn)處于內(nèi)存壓力狀態(tài)。Linux 會(huì)使用 buff/cache 作為可用內(nèi)存的一部分,但如果free列持續(xù)為 0 且available也很低,說(shuō)明物理內(nèi)存確實(shí)不夠用了。
查看具體是哪些進(jìn)程消耗了最多內(nèi)存:
# 按內(nèi)存使用排序查看進(jìn)程 ps aux --sort=-%mem | head -20 # 查看 kubelet 進(jìn)程的內(nèi)存使用 ps -p $(pgrep kubelet) -o pid,vsz,rss,comm
如果發(fā)現(xiàn)某個(gè)進(jìn)程(不一定是 kubelet)內(nèi)存占用異常高,比如 Elasticsearch、Java 應(yīng)用等,可以進(jìn)一步分析是該進(jìn)程自身的內(nèi)存泄漏還是配置不合理(比如 JVM 堆內(nèi)存設(shè)置過(guò)大)。但無(wú)論如何,如果節(jié)點(diǎn)的available內(nèi)存持續(xù)很低,即使找到根因,節(jié)點(diǎn)也可能在根因修復(fù)前就已經(jīng) OOM 了。
4.3 檢查 CPU 負(fù)載
# 查看系統(tǒng)負(fù)載和 CPU 使用率 uptime # 輸出示例: # 1032 up 45 days, 3:22, 2 users, load average: 8.52, 6.31, 5.12 # 這個(gè)節(jié)點(diǎn)有 8 個(gè) CPU 核心,load average 8.52 意味著 CPU 隊(duì)列長(zhǎng)度為 8.52 # 接近核心數(shù),說(shuō)明 CPU 資源非常緊張 # 查看 CPU 使用詳情 top -bn1 | head -30
load average 是 1分鐘、5分鐘、15分鐘的系統(tǒng)平均負(fù)載。如果 load average 持續(xù)高于 CPU 核心數(shù),說(shuō)明 CPU 資源不足。但 CPU 不足一般不會(huì)直接導(dǎo)致 NotReady(除非導(dǎo)致 kubelet 響應(yīng)超時(shí)),更多是間接影響。直接導(dǎo)致 NotReady 的是 kubelet 進(jìn)程本身被 OOM kill 或者 CPU 饑餓導(dǎo)致無(wú)法正常心跳。
4.4 綜合資源診斷腳本
以下腳本可以快速輸出節(jié)點(diǎn)資源全景,發(fā)現(xiàn)明顯異常:
#!/bin/bash
# save as: node_diag.sh
# 執(zhí)行方式: sudo bash node_diag.sh
echo"===== 節(jié)點(diǎn)基本信息 ====="
uname -r
cat /etc/os-release | grep PRETTY_NAME
echo""
echo"===== CPU 核心數(shù)和負(fù)載 ====="
nproc
uptime
echo""
echo"===== 內(nèi)存使用 ====="
free -m
echo""
echo"===== 磁盤使用 ====="
df -h | grep -v tmpfs | grep -v devtmpfs
echo""
echo"===== 主要進(jìn)程 CPU/MEM 占用 Top 10 ====="
ps aux --sort=-%cpu | awk'NR==1{print} NR>1 && NR<=12'
echo?""
echo?"===== kubelet 進(jìn)程狀態(tài) ====="
systemctl is-active kubelet
systemctl is-failed kubelet 2>/dev/null ||echo"not failed"
echo""
echo"===== containerd/docker 狀態(tài) ====="
systemctl is-active containerd 2>/dev/null ||echo"containerd not active"
systemctl is-active docker 2>/dev/null ||echo"docker not active"
echo""
echo"===== kubelet 日志最新錯(cuò)誤 ====="
journalctl -u kubelet -n 20 --no-pager | grep -i error
echo""
echo"===== inode 使用情況(文件名耗盡也導(dǎo)致磁盤問(wèn)題)====="
df -i | grep -v tmpfs | grep -v devtmpfs
第五步:檢查網(wǎng)絡(luò)連通性
網(wǎng)絡(luò)問(wèn)題也是導(dǎo)致 NotReady 的常見(jiàn)原因。kubelet 需要和 API Server 保持通信,如果網(wǎng)絡(luò)不通,kubelet 的心跳就無(wú)法送達(dá),節(jié)點(diǎn)就會(huì)被標(biāo)記為 NotReady。
5.1 從節(jié)點(diǎn)上測(cè)試到 API Server 的連通性
# 查看 API Server 的 endpoint(需要先從kubectl配置中獲?。?APISERVER=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}')
echo"API Server:$APISERVER"
# 測(cè)試 HTTPS 連通性(使用內(nèi)置 CA 驗(yàn)證,不驗(yàn)證證書(shū)可以加 -k)
curl -sk --max-time 5${APISERVER}/healthz
# 正常返回: {"status":"OK"}
# 如果報(bào)錯(cuò): Connection refused / Timeout,說(shuō)明網(wǎng)絡(luò)不通
5.2 測(cè)試 DNS 解析
# 查看 kubelet 的 --cluster-dns 配置
grep -r"clusterDNS"/var/lib/kubelet/config.yaml 2>/dev/null ||echo"not in config.yaml"
grep"cluster-dns"/etc/kubernetes/kubelet.conf 2>/dev/null | head -2
# 測(cè)試集群 DNS 是否可達(dá)
ping -c 3 kubernetes.default.svc
ping -c 3 $(grep nameserver /etc/resolv.conf | awk'{print $2}'| head -1)
# 如果 DNS 不通,檢查 CoreDNS 是否在運(yùn)行
kubectl get pods -n kube-system -o wide | grep coredns
5.3 檢查節(jié)點(diǎn)網(wǎng)絡(luò)接口和路由
# 查看網(wǎng)絡(luò)接口配置
ip addr
# 重點(diǎn)關(guān)注:
# 1. 是否有網(wǎng)卡未 UP
# 2. 是否有網(wǎng)卡配置了正確的 IP 和掩碼
# 3. CNI 插件創(chuàng)建的網(wǎng)橋和 veth 設(shè)備是否存在(如 cni0, flannel.1, calico* 等)
# 查看路由表
ip route
# 確認(rèn)是否有到 API Server IP 的路由
ip route | grep $(echo$APISERVER| awk -F/'{print $3}'| awk -F:'{print $1}')
5.4 檢查 CNI 插件狀態(tài)
不同集群使用的 CNI 插件不同(Flannel、Calico、Cilium、Weave 等),檢查方式也不同。
如果是 Flannel:
# 查看 flannel 網(wǎng)卡是否存在 ip addr | grep flannel # 查看 flannel.1 接口信息 ip addr show flannel.1 # 查看 kube-flannel 的 Pod 日志 kubectl logs -n kube-system -l app=flannel --tail=50
如果是 Calico:
# 查看 calico-node Pod 狀態(tài) kubectl get pods -n kube-system -l k8s-app=calico-node -o wide # 查看 calico-node 日志 kubectl logs -n kube-system -l k8s-app=calico-node --tail=50 # 查看 BGP 對(duì)等連接狀態(tài)(需要 calicoctl) calicoctl node status # 查看 calico IP 池 calicoctl get ippool -o wide
通用排查方法——查看所有 kube-system Pod 的網(wǎng)絡(luò)相關(guān)組件:
kubectl get pods -n kube-system -o wide | grep -v Running # 查看所有網(wǎng)絡(luò)相關(guān) Pod kubectl get pods -n kube-system | grep -E"cni|network|calico|flannel|cilium|weave"
5.5 檢查節(jié)點(diǎn)是否被網(wǎng)絡(luò)策略誤傷
如果集群?jiǎn)⒂昧?NetworkPolicy,而針對(duì)特定命名空間或 Pod 設(shè)置了過(guò)于嚴(yán)格的策略,可能導(dǎo)致 kubelet 和 API Server 通信被阻斷。檢查方法:
# 查看某個(gè)命名空間的 NetworkPolicy kubectl get networkpolicy -A # 查看具體的 NetworkPolicy 規(guī)則 kubectl get networkpolicy-n -o yaml
不過(guò)這種情況比較罕見(jiàn),因?yàn)?kube-system 命名空間通常不受 NetworkPolicy 影響。更常見(jiàn)的是業(yè)務(wù)命名空間的 Policy 影響了業(yè)務(wù) Pod 和 API Server 的通信,導(dǎo)致 Pod 啟動(dòng)失敗,而不是節(jié)點(diǎn) NotReady。
第六步:檢查證書(shū)是否過(guò)期
kubelet 的證書(shū)默認(rèn)有效期為 1 年(通過(guò) kubeadm 部署的集群)。如果證書(shū)過(guò)期,kubelet 將無(wú)法和 API Server 通信,導(dǎo)致節(jié)點(diǎn) NotReady。這種情況在集群運(yùn)行超過(guò) 1 年后首次出現(xiàn),或者在手動(dòng)調(diào)整過(guò)系統(tǒng)時(shí)間后出現(xiàn)。
6.1 檢查 kubelet 證書(shū)過(guò)期時(shí)間
在 NotReady 節(jié)點(diǎn)上執(zhí)行:
# 用 kubeadm 檢查證書(shū)過(guò)期時(shí)間 sudo kubeadm certs check-expiration --cert-dir /etc/kubernetes/pki # 如果提示 "certificates signed by unknown authority",說(shuō)明證書(shū)鏈有問(wèn)題 # 重點(diǎn)關(guān)注以下證書(shū)的過(guò)期時(shí)間: # - kubelet client certificate (/etc/kubernetes/pki/kubelet-client-latest/current) # - kubelet server certificate (/var/lib/kubelet/pki/cert.crt)
注意:kubeadm certs check-expiration檢查的是/etc/kubernetes/pki/目錄下的控制面證書(shū),而不是 kubelet 自己的證書(shū)。kubelet 的證書(shū)存放在/var/lib/kubelet/pki/。兩個(gè)目錄不要混淆。
6.2 手動(dòng)檢查 kubelet 證書(shū)
# 查看 kubelet 證書(shū)文件 sudo ls -la /var/lib/kubelet/pki/ # 檢查證書(shū)過(guò)期時(shí)間 sudo openssl x509 -in/var/lib/kubelet/pki/cert.crt -noout -dates # 對(duì)比當(dāng)前時(shí)間 date
如果證書(shū)已過(guò)期或者即將過(guò)期(剩余有效期不足 7 天),需要續(xù)期。
6.3 續(xù)期 kubelet 證書(shū)
風(fēng)險(xiǎn)提醒:續(xù)期證書(shū)需要重啟 kubelet,會(huì)觸發(fā)節(jié)點(diǎn)上的 Pod 重新調(diào)度。生產(chǎn)環(huán)境操作前必須確認(rèn)業(yè)務(wù)副本數(shù)和影響范圍。
# 方式一:通過(guò) kubeadm 續(xù)期(推薦) # 先備份現(xiàn)有證書(shū) sudo cp -r /etc/kubernetes/pki /etc/kubernetes/pki.bak.$(date +%Y%m%d) sudo cp -r /var/lib/kubelet/pki /var/lib/kubelet/pki.bak.$(date +%Y%m%d) # 續(xù)期 kubelet 證書(shū)(需要 API Server 可達(dá)) sudo kubeadm alpha certs renew kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf # 方式二:刪除舊證書(shū)讓 kubelet 自動(dòng)重新申請(qǐng) sudo systemctl stop kubelet sudo rm -rf /var/lib/kubelet/pki/* sudo rm -rf /etc/kubernetes/pki/kubelet-client-latest sudo systemctl start kubelet
方式二會(huì)讓 kubelet 啟動(dòng)時(shí)自動(dòng)向 API Server 申請(qǐng)新的證書(shū),適合緊急情況。方式一更可控,但需要證書(shū)配置正確。
第七步:常見(jiàn)根因場(chǎng)景與修復(fù)方案
通過(guò)以上排查步驟,大多數(shù) NotReady 問(wèn)題可以定位到根因。下面整理常見(jiàn)根因?qū)?yīng)的修復(fù)方案。
場(chǎng)景一:磁盤空間不足(最常見(jiàn))
根因:節(jié)點(diǎn)磁盤被日志、鏡像、臨時(shí)文件填滿,kubelet 檢測(cè)到DiskPressure后將節(jié)點(diǎn)標(biāo)記為 NotReady。
修復(fù)方案:
# 1. 確認(rèn)根因
df -h
# 看到 /dev/sda1 使用率 95%+
# 2. 評(píng)估可以清理的內(nèi)容
sudo du -sh /var/lib/containerd/* 2>/dev/null | sort -rh | head -10
sudo du -sh /var/log/* 2>/dev/null | sort -rh | head -10
# 3. 清理未使用鏡像(先查看鏡像列表,評(píng)估哪些可以刪)
sudo crictl images
# 查看未被任何容器使用的鏡像(ctr 命令)
sudo ctr -n k8s.io images list | awk'{print $1":"$2}'| head -20
# 確認(rèn)無(wú)誤后執(zhí)行清理(ctr 是 containerd 自帶的命令行工具)
# 清理所有未被任何容器使用的鏡像
sudo ctr -n k8s.io images prune -f
# 4. 清理舊的 journal 日志
sudo journalctl --vacuum-size=500M
# 5. 驗(yàn)證磁盤空間恢復(fù)
df -h
# 6. 等待 kubelet 重新評(píng)估磁盤狀態(tài)(通常 1-2 分鐘內(nèi)自動(dòng)恢復(fù))
sleep 120
kubectl get node k8s-node-1
如果磁盤使用率是因?yàn)槿萜麋R像本身太大(比如拉取了過(guò)多大型鏡像),需要從鏡像管理入手:設(shè)置鏡像配額、定期清理未使用的鏡像、使用更小的基礎(chǔ)鏡像。
從根源上防止磁盤壓力:
# 在 kubelet 配置中設(shè)置 eviction threshold # 編輯 /var/lib/kubelet/config.yaml 或通過(guò) kubelet 啟動(dòng)參數(shù) # 示例:設(shè)置更嚴(yán)格的磁盤空間 eviction 閾值(85% 開(kāi)始告警,80% 開(kāi)始驅(qū)逐) evictionHard: imagefs.available:"15%" memory.available:"100Mi" nodefs.available:"10%"
場(chǎng)景二:kubelet 進(jìn)程 OOM 被 kill
根因:節(jié)點(diǎn)內(nèi)存不足,Linux OOM Killer 殺掉了 kubelet 進(jìn)程。kubelet 進(jìn)程被 kill 后無(wú)法上報(bào)心跳,節(jié)點(diǎn)立即變?yōu)?NotReady。
診斷依據(jù):
# 查看系統(tǒng)日志中的 OOM kill 記錄 sudo dmesg | grep -i"out of memory" sudo dmesg | grep -i"kubelet" # 如果 dmesg 輸出被清除(環(huán)形緩沖區(qū)大小有限),查看 systemd journal sudo journalctl -x | grep -i"memory"| tail -50 sudo journalctl -xb | grep -i"oom"| tail -20
如果看到類似如下記錄:
[12345.678901] kubelet invoked oom-killer: gfp_mask=0x6200x2502ca, order=0, oom_score_adj=999 [12345.678902] Memory cgroup out of memory: Killed process 12345 (kubelet)
說(shuō)明 kubelet 確實(shí)被 OOM Killer 殺掉了。
修復(fù)方案:
先恢復(fù)內(nèi)存:清理內(nèi)存占用高的進(jìn)程(不重要的業(yè)務(wù) Pod),騰出空間
設(shè)置 kubelet 的 OOM Score Adj 為負(fù)數(shù),讓 kubelet 更不容易被 kill
# 查看 kubelet 進(jìn)程當(dāng)前的 oom_score_adj cat /proc/$(pgrep kubelet)/oom_score_adj # 默認(rèn)值通常是 0 或負(fù)數(shù)(如 -999) # 如果需要調(diào)整(在 systemd service 文件中添加) # 編輯 /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf # 在 [Service] 段添加: Environment="KUBELET_OPTS=--oom-score-adj=-999"
根本解決:增加節(jié)點(diǎn)內(nèi)存或減少節(jié)點(diǎn)上部署的 Pod 數(shù)量??梢圆榭垂?jié)點(diǎn)上當(dāng)前的 Pod 數(shù)量和內(nèi)存請(qǐng)求:
kubectl get pods -o json --all-namespaces | jq'[.items[].spec.containers[].resources.requests.memory // "0Mi" | gsub("Ki";"") | gsub("Mi";"") | tonumber] | add'
場(chǎng)景三:容器運(yùn)行時(shí)配置變更后無(wú)法通信
根因:修改了/etc/containerd/config.toml后沒(méi)有重啟 containerd,或者 containerd 和 kubelet 的 cgroup driver 配置不一致。
診斷依據(jù):
# 查看 kubelet 的 container runtime endpoint grep container-runtime /var/lib/kubelet/config.yaml # 查看 containerd 的 cgroup driver 配置 grep SystemdCgroup /etc/containerd/config.toml # 對(duì)比兩者是否一致 # containerd config.toml 中: SystemdCgroup = true # kubelet config.yaml 中: cgroupDriver: systemd # 兩者必須匹配,否則 kubelet 無(wú)法管理容器
修復(fù)方案:
# 1. 檢查配置一致性 grep -i"SystemdCgroup"/etc/containerd/config.toml grep -i"cgroupDriver"/var/lib/kubelet/config.yaml # 2. 如果不一致,修改 containerd 配置 # 編輯 /etc/containerd/config.toml # 確保 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] # 段中有: SystemdCgroup = true # 3. 重啟 containerd sudo systemctl restart containerd # 4. 重啟 kubelet sudo systemctl restart kubelet # 5. 等待恢復(fù) sleep 60 kubectl get node k8s-node-1
風(fēng)險(xiǎn)提醒:重啟 containerd 會(huì)導(dǎo)致節(jié)點(diǎn)上所有容器停止并重新創(chuàng)建。對(duì)有狀態(tài)應(yīng)用(MySQL、Redis)和敏感業(yè)務(wù)有嚴(yán)重影響,必須在業(yè)務(wù)方確認(rèn)后執(zhí)行。
場(chǎng)景四:節(jié)點(diǎn)內(nèi)核問(wèn)題導(dǎo)致網(wǎng)絡(luò)或存儲(chǔ)異常
根因:內(nèi)核版本過(guò)舊或存在已知的 BUG,導(dǎo)致某些 K8s 功能不正常。例如內(nèi)核 5.x 之前的版本對(duì) overlay2 文件系統(tǒng)支持不完善,或者某些內(nèi)核版本對(duì) cgroup v2 支持有問(wèn)題。
診斷依據(jù):
# 查看內(nèi)核版本 uname -r # 查看操作系統(tǒng)版本 cat /etc/os-release # 查看內(nèi)核日志中是否有硬件或文件系統(tǒng)錯(cuò)誤 sudo dmesg -T | grep -iE"error|warn|fail|timeout|offline"| tail -50 # 查看是否有文件系統(tǒng)只讀的錯(cuò)誤 sudo dmesg -T | grep -i"readonly"
修復(fù)方案:升級(jí)內(nèi)核或重裝節(jié)點(diǎn)。如果內(nèi)核問(wèn)題導(dǎo)致根文件系統(tǒng)變成只讀,必須立即重啟節(jié)點(diǎn),但要做好 Pod 驅(qū)逐和業(yè)務(wù)切換的準(zhǔn)備。
# 查看當(dāng)前內(nèi)核相關(guān)參數(shù) sysctl -a | grep -i"bridge|netfilter|forward"| head -20 # 檢查 br_netfilter 模塊是否加載 lsmod | grep br_netfilter
場(chǎng)景五:kubelet 配置文件錯(cuò)誤
根因:手動(dòng)修改了/var/lib/kubelet/config.yaml或 kubelet 啟動(dòng)參數(shù),導(dǎo)致 kubelet 啟動(dòng)失敗。
診斷依據(jù):
# 查看 kubelet 的完整啟動(dòng)命令 ps aux | grep kubelet | grep -v grep # 查看 kubelet 配置文件 cat /var/lib/kubelet/config.yaml # 嘗試手動(dòng)啟動(dòng) kubelet 看報(bào)錯(cuò) sudo /usr/bin/kubelet --config=/var/lib/kubelet/config.yaml --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf
常見(jiàn)配置文件錯(cuò)誤:
cgroupDriver值和容器運(yùn)行時(shí)不一致
evictionHard配置格式錯(cuò)誤(缺少引號(hào)、格式不對(duì))
containerLogMaxSize和containerLogMaxFiles格式不對(duì)
serializeImagePulls值寫成了字符串而不是布爾值
場(chǎng)景六:etcd 連接超時(shí)(影響所有節(jié)點(diǎn))
根因:etcd 是 K8s 的狀態(tài)存儲(chǔ)后端,如果 etcd 響應(yīng)慢或不可達(dá),所有節(jié)點(diǎn)的 kubelet 心跳都無(wú)法處理,節(jié)點(diǎn)會(huì)陸續(xù)變成 NotReady。
診斷依據(jù):
# 在任意節(jié)點(diǎn)測(cè)試 etcd 連通性
ETCD_ENDPOINT=$(kubectl get endpoints kube-apiserver -n default -o jsonpath='{.subsets[0].addresses[0].targetRef.name}')
ETCD_POD_NAME=$(kubectl get pods -n kube-system -l component=etcd -o jsonpath='{.items[0].metadata.name}')
# 測(cè)試 etcd 健康狀態(tài)
kubectlexec-n kube-system${ETCD_POD_NAME}-- etcdctl --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health
# 查看 etcd pod 是否 Running
kubectl get pods -n kube-system -l component=etcd -o wide
修復(fù)方案:如果 etcd 故障,需要優(yōu)先恢復(fù) etcd 集群。etcd 恢復(fù)是另一個(gè)大話題,這里不展開(kāi),但需要知道:etcd 故障導(dǎo)致的 NotReady,節(jié)點(diǎn)本身沒(méi)問(wèn)題,重啟 etcd 后節(jié)點(diǎn)會(huì)自動(dòng)恢復(fù)。
第八步:驗(yàn)證修復(fù)結(jié)果
修復(fù)完成后,必須驗(yàn)證節(jié)點(diǎn)確實(shí)恢復(fù)到 Ready 狀態(tài),并且業(yè)務(wù)恢復(fù)正常。
8.1 節(jié)點(diǎn)狀態(tài)驗(yàn)證
# 查看節(jié)點(diǎn)狀態(tài),等待所有節(jié)點(diǎn)變?yōu)?Ready kubectl get nodes -o wide # 查看節(jié)點(diǎn)的 Conditions,確認(rèn)所有狀態(tài)為 False 或 True(如 DiskPressure=False) kubectl describe node k8s-node-1 | grep -A 20"Conditions" # 如果節(jié)點(diǎn)狀態(tài)是 Ready 但某些 Condition 仍為 True,說(shuō)明 kubelet 已恢復(fù)但資源壓力未完全解除
8.2 Pod 調(diào)度驗(yàn)證
# 查看 NotReady 節(jié)點(diǎn)上之前的 Pod 是否已經(jīng)重新調(diào)度并運(yùn)行 kubectl get pods -o wide --all-namespaces | grep k8s-node-1 # 查看是否有 Pod 處于 Pending、ContainerCreating、CrashLoopBackOff 等異常狀態(tài) kubectl get pods --all-namespaces | grep -v Running | grep -v Completed # 如果有異常 Pod,分析原因 kubectl describe pod-n kubectl logs -n --previous
8.3 業(yè)務(wù)功能驗(yàn)證
# 查看業(yè)務(wù)相關(guān)的 Deployment replicas 是否滿足預(yù)期 kubectl get deployment -A # 查看 Services 是否正常 kubectl get svc -A # 如果有 Ingress,測(cè)試 Ingress 訪問(wèn) curl -sk https:/// -w" %{http_code} " # 測(cè)試 DNS 解析 kubectl run dnsutils --image=tbuskey/dnsutils:1.0 --restart=Never -it --rm -- sh # 在容器內(nèi)執(zhí)行:nslookup kubernetes.default
8.4 節(jié)點(diǎn)資源驗(yàn)證
# 在節(jié)點(diǎn)上執(zhí)行最終資源檢查 ssh k8s-node-1"df -h / && free -m && uptime" # 確認(rèn)磁盤空間已釋放到安全范圍 # 確認(rèn)內(nèi)存使用恢復(fù)正常 # 確認(rèn)負(fù)載回到正常水平
第九步:預(yù)防措施和日常巡檢
故障修復(fù)后,更重要的是建立長(zhǎng)效機(jī)制,避免同類問(wèn)題反復(fù)發(fā)生。
9.1 建立節(jié)點(diǎn)健康巡檢機(jī)制
推薦每日?qǐng)?zhí)行一次節(jié)點(diǎn)健康檢查,可以通過(guò) Ansible、Prometheus 告警或定時(shí)腳本來(lái)實(shí)現(xiàn):
#!/bin/bash
# save as: k8s_node_health_check.sh
# 建議通過(guò) cron 每日?qǐng)?zhí)行,結(jié)果寫入日志文件
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
ALERT_MSG=""
forNODEin$NODES;do
# 檢查節(jié)點(diǎn)狀態(tài)
STATUS=$(kubectl get node$NODE-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
if["$STATUS"!="True"];then
ALERT_MSG="${ALERT_MSG}
[node NotReady]$NODE"
kubectl describe node$NODE| grep -A 5"Conditions">> /var/log/k8s-health.log
fi
# 檢查磁盤使用率(閾值 85%)
DISK_USAGE=$(ssh$NODE"df -h / | tail -1 | awk '{print $5}' | sed 's/%//'")
if["$DISK_USAGE"-gt 85 ];then
ALERT_MSG="${ALERT_MSG}
[disk pressure]$NODE:${DISK_USAGE}%"
fi
# 檢查內(nèi)存使用率(閾值 90%)
MEM_AVAILABLE=$(ssh$NODE"free -m | tail -2 | head -1 | awk '{print $7}'")
MEM_TOTAL=$(ssh$NODE"free -m | tail -2 | head -1 | awk '{print $2}'")
MEM_USAGE=$(( (MEM_TOTAL - MEM_AVAILABLE) * 100 / MEM_TOTAL ))
if["$MEM_USAGE"-gt 90 ];then
ALERT_MSG="${ALERT_MSG}
[memory pressure]$NODE:${MEM_USAGE}%"
fi
# 檢查 kubelet 和容器運(yùn)行時(shí)狀態(tài)
KUBELET_ACTIVE=$(ssh$NODE"systemctl is-active kubelet")
CONTAINERD_ACTIVE=$(ssh$NODE"systemctl is-active containerd 2>/dev/null"||echo"unknown")
if["$KUBELET_ACTIVE"!="active"];then
ALERT_MSG="${ALERT_MSG}
[kubelet down]$NODE:$KUBELET_ACTIVE"
fi
done
if[ -n"$ALERT_MSG"];then
echo-e"K8s Node Health Alert:$ALERT_MSG"| tee -a /var/log/k8s-health.log
# 這里可以接入告警系統(tǒng)(如釘釘、企業(yè)微信、飛書(shū)、郵件等)
else
echo"$(date): All nodes healthy">> /var/log/k8s-health.log
fi
9.2 合理配置 kubelet eviction threshold
不要使用默認(rèn)的 eviction threshold,因?yàn)槟J(rèn)值比較激進(jìn),可能在磁盤空間稍微不足時(shí)就觸發(fā) NotReady。建議根據(jù)實(shí)際業(yè)務(wù)負(fù)載調(diào)整:
# /var/lib/kubelet/config.yaml apiVersion:kubelet.config.k8s.io/v1beta1 kind:KubeletConfiguration # evictionHard: 不夠用時(shí)直接驅(qū)逐,不給緩沖 evictionHard: memory.available:"100Mi" # 內(nèi)存低于 100Mi 時(shí)才觸發(fā) eviction nodefs.available:"5%" # 磁盤空間低于 5% 時(shí)觸發(fā)(保守設(shè)置) imagefs.available:"10%" # 鏡像存儲(chǔ)低于 10% 時(shí)觸發(fā) # evictionSoft: 軟閾值,觸發(fā)告警但不立即驅(qū)逐,給運(yùn)維人員響應(yīng)時(shí)間 evictionSoft: memory.available:"200Mi" nodefs.available:"10%" imagefs.available:"15%" evictionSoftGracePeriod: memory.available:"2m" nodefs.available:"2m" imagefs.available:"2m" # evictionPressureTransitionPeriod: 進(jìn)入壓力狀態(tài)后,等待這段時(shí)間再?zèng)Q定是否開(kāi)始驅(qū)逐 evictionPressureTransitionPeriod:"2m"
9.3 部署節(jié)點(diǎn)資源監(jiān)控告警
使用 Prometheus Node Exporter 配合 AlertManager,可以對(duì)節(jié)點(diǎn)資源問(wèn)題做到提前發(fā)現(xiàn):
# PrometheusRule 示例:節(jié)點(diǎn)磁盤空間告警
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:node-disk-alerts
namespace:monitoring
spec:
groups:
-name:node-resources
rules:
-alert:NodeDiskPressure
expr:node_filesystem_avail_bytes{mountpoint="/"}/node_filesystem_size_bytes{mountpoint="/"}0.15
? ? ??for:?5m
? ? ??labels:
? ? ? ??severity:?warning
? ? ??annotations:
? ? ? ??summary:?"Node?{{ $labels.instance }}?has less than 15% disk space"
? ? ? ??description:?"Disk usage at?{{ $value | humanizePercentage }}"
? ??-?alert:?NodeDiskPressureCritical
? ? ??expr:?node_filesystem_avail_bytes{mountpoint="/"}?/?node_filesystem_size_bytes{mountpoint="/"}?0.05
? ? ??for:?2m
? ? ??labels:
? ? ? ??severity:?critical
? ? ??annotations:
? ? ? ??summary:?"Node?{{ $labels.instance }}?has less than 5% disk space - imminent"
9.4 證書(shū)自動(dòng)續(xù)期
通過(guò) kubeadm 部署的集群,kubelet 證書(shū)會(huì)在過(guò)期前自動(dòng)續(xù)期(kubelet 使用的是 bootstrap 機(jī)制)。但如果手動(dòng)管理證書(shū),需要建立定時(shí)任務(wù):
# 在所有 master 節(jié)點(diǎn)上創(chuàng)建 cronjob 每月檢查一次證書(shū) # 添加到 crontab: 0 3 1 * * /usr/bin/kubeadm certs renew all --kubeconfig=/etc/kubernetes/admin.conf
9.5 節(jié)點(diǎn)資源限制
在生產(chǎn)環(huán)境中,建議對(duì)每個(gè)節(jié)點(diǎn)的 Pod 數(shù)量和資源使用設(shè)置硬限制,防止單節(jié)點(diǎn)過(guò)載:
# 查看節(jié)點(diǎn)最大 Pod 數(shù)量配置(kubelet --max-pods 參數(shù),默認(rèn) 110) ps aux | grep kubelet | grep max-pods # 推薦在 /var/lib/kubelet/config.yaml 中設(shè)置: maxPods: 110
總結(jié)
Kubernetes 節(jié)點(diǎn) NotReady 故障的排查,核心在于理解 kubelet 是如何向 API Server 報(bào)告節(jié)點(diǎn)狀態(tài)的,以及哪些系統(tǒng)級(jí)問(wèn)題會(huì)導(dǎo)致 kubelet 無(wú)法正常工作。
標(biāo)準(zhǔn)排查流程回顧:
kubectl describe node:看 Conditions,判斷是哪種壓力(磁盤、內(nèi)存、網(wǎng)絡(luò)、PID)
systemctl status kubelet:確認(rèn) kubelet 進(jìn)程是否存活
journalctl -u kubelet:看 kubelet 日志,找啟動(dòng)失敗或異常退出的原因
df -h / free -m / uptime:檢查磁盤、內(nèi)存、CPU 資源
crictl info / docker info:驗(yàn)證容器運(yùn)行時(shí)是否正常
ping / curl api-server:驗(yàn)證節(jié)點(diǎn)網(wǎng)絡(luò)和 API Server 連通性
kubeadm certs check-expiration / openssl x509:檢查證書(shū)是否過(guò)期
crictl ps -a / kubectl get pods:看節(jié)點(diǎn)上的 Pod 狀態(tài),輔助判斷
最常見(jiàn)的三個(gè)根因:
磁盤空間不足(占比最高):日志/鏡像/臨時(shí)文件占滿磁盤,kubelet 檢測(cè)到 DiskPressure
kubelet OOM 被 kill(占比第二):節(jié)點(diǎn)內(nèi)存不足,OOM Killer 優(yōu)先殺掉 kubelet
容器運(yùn)行時(shí)配置不一致(升級(jí)后常見(jiàn)):containerd 和 kubelet 的 cgroup driver 配置不匹配
修復(fù)后必須驗(yàn)證:
節(jié)點(diǎn)狀態(tài)恢復(fù) Ready
原有 Pod 重新調(diào)度并 Running
業(yè)務(wù)功能正常
資源使用回到安全范圍
長(zhǎng)期預(yù)防:
建立每日節(jié)點(diǎn)健康巡檢
合理配置 eviction threshold(不要全用默認(rèn))
部署 Prometheus 監(jiān)控和告警,提前發(fā)現(xiàn)資源問(wèn)題
證書(shū)到期前自動(dòng)續(xù)期
控制單節(jié)點(diǎn) Pod 數(shù)量和資源請(qǐng)求/限制
遇到 NotReady 問(wèn)題時(shí),保持冷靜,按順序排查,不要急于重啟節(jié)點(diǎn)。重啟節(jié)點(diǎn)會(huì)清除現(xiàn)場(chǎng),讓很多根因信息丟失。先查日志、再查資源、最后再?zèng)Q定是否重啟?,F(xiàn)場(chǎng)保留得越完整,定位根因就越快,下次預(yù)防也就越有針對(duì)性。
-
節(jié)點(diǎn)
+關(guān)注
關(guān)注
0文章
231瀏覽量
25686 -
集群
+關(guān)注
關(guān)注
0文章
155瀏覽量
17696 -
kubernetes
+關(guān)注
關(guān)注
0文章
277瀏覽量
9539
原文標(biāo)題:Kubernetes 節(jié)點(diǎn) NotReady 怎么排查?從現(xiàn)象到根因的完整排查流程
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
Kubernetes架構(gòu)和核心組件組成 Kubernetes節(jié)點(diǎn)“容器運(yùn)行時(shí)”技術(shù)分析
Kubernetes 網(wǎng)絡(luò)模型如何實(shí)現(xiàn)常見(jiàn)網(wǎng)絡(luò)任務(wù)
Kubernetes的Device Plugin設(shè)計(jì)解讀
Kubernetes Ingress 高可靠部署最佳實(shí)踐
Kubernetes網(wǎng)絡(luò)隔離NetworkPolicy實(shí)驗(yàn)
在Windows 10上創(chuàng)建單節(jié)點(diǎn)的Kubernetes實(shí)施示例
深入研究Kubernetes調(diào)度
Kubernetes網(wǎng)絡(luò)模型介紹以及如何實(shí)現(xiàn)常見(jiàn)網(wǎng)絡(luò)任務(wù)
Kubernetes網(wǎng)絡(luò)模型的基礎(chǔ)知識(shí)
在Kubernetes集群發(fā)生網(wǎng)絡(luò)異常時(shí)如何排查
帶你快速了解 kubernetes
Kubernetes的集群部署
Kubernetes集群中如何選擇工作節(jié)點(diǎn)
Kubernetes節(jié)點(diǎn)NotReady怎么排查
評(píng)論