備與常見故障排查)
最近有個(gè)朋友問我能不能在不折騰網(wǎng)絡(luò)的前提下用 kubeadm 裝一個(gè)指定版本的 Kubernetes 集群。他試過幾次kubeadm init每次都在拉取registry.k8s.io這一步超時(shí)要么就是預(yù)檢階段卡了幾分鐘直接報(bào)錯(cuò)。這個(gè)問題在受網(wǎng)絡(luò)限制的環(huán)境里非常典型kubeadm 本身設(shè)計(jì)簡(jiǎn)單安裝失敗往往不是因?yàn)槊顚戝e(cuò)而是組件鏡像拿不到。這篇文章我會(huì)把一條不依賴“海外直連”、完全基于國(guó)內(nèi)可見鏡像倉(cāng)庫(kù)和離線鏡像包導(dǎo)入的 kubeadm 安裝路徑完整寫一遍。從版本對(duì)應(yīng)關(guān)系、鏡像準(zhǔn)備、初始化配置到工作節(jié)點(diǎn)加入和常見故障排查全部覆蓋。適合需要固定 Kubernetes 版本、打算部署測(cè)試環(huán)境或者建設(shè)內(nèi)網(wǎng)集群的運(yùn)維和開發(fā)同學(xué)參考。我盡量把每一步的“為什么這么做”也講清楚這樣你換一個(gè)版本或者換一套網(wǎng)絡(luò)環(huán)境也能自己推導(dǎo)出正確做法。1. 動(dòng)手前先把版本關(guān)系和高版本陷阱理清1.1 組件版本必須鎖成一個(gè)整體很多人裝集群喜歡“先裝最新版”或者隨手apt install kubeadm然后發(fā)現(xiàn)裝出來的 kubelet 是 1.29初始化配置卻寫的 1.28部署出來控制面經(jīng)常出現(xiàn)各種奇怪的兼容問題。Kubernetes 的版本策略是kubelet 允許比 kube-apiserver 低一個(gè)小版本但 kubeadm、kubelet、kubectl 三者最好保持同一個(gè)版本控制面核心組件apiserver、controller-manager、scheduler也必須和 kubeadm 里指定的kubernetesVersion保持一致。在我實(shí)際部署中最省心的做法是“先選 Kubernetes 版本再反推工具版本”。比如我要裝 1.28.x就統(tǒng)一安裝這三個(gè)二進(jìn)制kubeadm 1.28.xkubelet 1.28.xkubectl 1.28.x然后把容器運(yùn)行時(shí)也固定下來。現(xiàn)在 containerd 1.7.x 搭配 Kubernetes 1.28 是非常成熟穩(wěn)定的組合比 Docker 加 cri-dockerd 少一層轉(zhuǎn)換配置也簡(jiǎn)單。建議不要在這個(gè)環(huán)節(jié)引入“最新穩(wěn)定版”網(wǎng)絡(luò)環(huán)境本來就受限沒必要再給排查兼容性增加難度。這里有一個(gè)容易被忽略的細(xì)節(jié)控制面的鏡像 tag 里帶著 Kubernetes 版本號(hào)比如kube-apiserver:v1.28.2。如果你的 kubeadm 版本是 1.28.2而kubernetesVersion寫的是v1.28.2kubeadm 才會(huì)去拉取這個(gè)精確的鏡像。版本號(hào)必須完全匹配少寫一個(gè).2它會(huì)去拉一個(gè)不存在的 tag直接報(bào)NotFound。1.2 節(jié)點(diǎn)環(huán)境準(zhǔn)備清單我建議每臺(tái)節(jié)點(diǎn)都提前完成下面這些基礎(chǔ)工作不要等到 kubeadm 預(yù)檢報(bào)錯(cuò)了再回來補(bǔ)操作系統(tǒng)Ubuntu 22.04 或者 CentOS 7.9 以上都行內(nèi)核不要太老。資源要求控制平面節(jié)點(diǎn)至少 2 核 2G工作節(jié)點(diǎn)至少 2 核 2G這是底線。關(guān)閉 swapkubelet 默認(rèn)不支持 swap 開啟的環(huán)境kubeadm init預(yù)檢會(huì)直接提示失敗。需要swapoff -a并且把/etc/fstab里的 swap 條目注釋掉否則重啟又回來了。加載內(nèi)核模塊overlay和br_netfilter這兩個(gè)必須啟用否則后續(xù) Pod 網(wǎng)絡(luò)和流量轉(zhuǎn)發(fā)會(huì)有問題。設(shè)置系統(tǒng)參數(shù)net.bridge.bridge-nf-call-iptables1、net.ipv4.ip_forward1這類參數(shù)建議寫進(jìn)/etc/sysctl.d/k8s.conf。這些操作本身沒有難度但值得花十分鐘核對(duì)一遍。預(yù)檢階段是可以自動(dòng)發(fā)現(xiàn)大部分問題的真正麻煩的是預(yù)檢通過之后容器運(yùn)行時(shí)和 kubelet 之間出現(xiàn)隱性問題那種報(bào)錯(cuò)信息繞來繞去排查成本高得多。2. 鏡像獲取的三種思路mirror 配置、離線包導(dǎo)入與 imageRepository 替換Kubernetes 控制面啟動(dòng)時(shí)會(huì)用到一組固定鏡像kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、coredns、pause。默認(rèn)情況下kubeadm 從registry.k8s.io這個(gè)地址下拉取它們。網(wǎng)絡(luò)受限時(shí)問題就出在這里。2.1 方案一為 containerd 配置鏡像加速如果你有可用的國(guó)內(nèi)公共鏡像倉(cāng)庫(kù)最順滑的辦法是把 containerd 配置成“從鏡像倉(cāng)庫(kù)拉取官方鏡像”也就是 pull-through mirror。這樣 kubeadm 和 kubelet 完全不用改仍然寫registry.k8s.iocontainerd 在背后自動(dòng)去配置的鏡像源拉取。containerd 的配置文件默認(rèn)在/etc/containerd/config.toml。如果你還沒有生成過它先執(zhí)行containerd config default /etc/containerd/config.toml然后在配置里找到 registry.mirrors 部分為registry.k8s.io增加一個(gè) endpoint[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.registry.k8s.io] endpoint [https://你的鏡像倉(cāng)庫(kù)地址]注意修改完一定要重啟 containerdsystemctl restart containerd。這個(gè)操作不會(huì)破壞已有鏡像可以放心做。這個(gè)方案對(duì) kubeadm 完全透明后續(xù)加節(jié)點(diǎn)、跑工作負(fù)載都不用再操心鏡像地址。唯一的風(fēng)險(xiǎn)是你選的鏡像倉(cāng)庫(kù)必須持續(xù)可用而且同步的鏡像路徑要和官方一致。我的習(xí)慣是先在命令行驗(yàn)證一下用ctr -n k8s.io images pull registry.k8s.io/pause:3.9拉一次能成功說明路徑?jīng)]問題。2.2 方案二離線鏡像包整體導(dǎo)入對(duì)于完全隔離內(nèi)網(wǎng)的環(huán)境mirror 方案也失效了只能用“離線包”思路。流程很簡(jiǎn)單在一臺(tái)能訪問鏡像倉(cāng)庫(kù)的機(jī)器上用ctr把需要的鏡像全部導(dǎo)出成 tar 文件拷到目標(biāo)機(jī)器上再用ctr導(dǎo)入到 containerd。獲取鏡像列表最穩(wěn)妥的命令是kubeadm config images list --kubernetes-versionv1.28.2這里不建議手動(dòng)拼鏡像名因?yàn)楹苋菀茁┑魀ause或者etcd。拿到列表后逐條拉取并導(dǎo)出ctr -n k8s.io images pull registry.k8s.io/kube-apiserver:v1.28.2 ctr -n k8s.io images export kube-apiserver.tar registry.k8s.io/kube-apiserver:v1.28.2在目標(biāo)機(jī)器上導(dǎo)入ctr -n k8s.io images import kube-apiserver.tar這里有兩個(gè)關(guān)鍵點(diǎn)。第一必須指定命名空間k8s.io因?yàn)?containerd 的 CRI 插件默認(rèn)使用k8s.io這個(gè)命名空間kubelet 創(chuàng)建的容器鏡像都在這里面。導(dǎo)入到別的命名空間kubelet 根本看不到。第二鏡像的完整名稱要保持不變包括registry.k8s.io/這個(gè)前綴否則 containerd 會(huì)認(rèn)為這是另一個(gè)鏡像仍然嘗試去遠(yuǎn)端拉取。這個(gè)方案對(duì)網(wǎng)絡(luò)要求最低也最可控。缺點(diǎn)是更新版本時(shí)要重新導(dǎo)出導(dǎo)入一遍稍微麻煩一點(diǎn)。如果只是偶爾部署一次集群我反而推薦這個(gè)方案因?yàn)樗选澳懿荒苎b上”和“網(wǎng)絡(luò)好不好”徹底解耦了。2.3 方案三直接替換 imageRepositorykubeadm 的初始化配置里有一個(gè)imageRepository字段默認(rèn)是registry.k8s.io。如果你用的是某個(gè)已經(jīng)同步了 Kubernetes 官方鏡像的國(guó)內(nèi)倉(cāng)庫(kù)可以直接把imageRepository改成那個(gè)倉(cāng)庫(kù)地址。kubeadm 拉取的鏡像地址就會(huì)變成你的倉(cāng)庫(kù)地址/kube-apiserver:v1.28.2。需要注意路徑問題。官方鏡像路徑是registry.k8s.io/kube-apiserver很多鏡像倉(cāng)庫(kù)并不會(huì)原樣保留這個(gè)路徑而是放在類似你的倉(cāng)庫(kù)地址/google_containers/kube-apiserver的路徑下。如果你的鏡像倉(cāng)庫(kù)布局不一樣光改imageRepository是不夠的還要保證鏡像的“倉(cāng)庫(kù)路徑 鏡像名”和 containerd 里實(shí)際存在的完整名稱一致。最穩(wěn)妥的辦法是先用ctr -n k8s.io images pull 你的倉(cāng)庫(kù)地址/kube-apiserver:v1.28.2驗(yàn)證一遍確認(rèn)能拉下來再寫進(jìn)配置。2.4 我的選型建議經(jīng)常有人問我“到底用哪個(gè)方案”。我的判斷標(biāo)準(zhǔn)很簡(jiǎn)單場(chǎng)景推薦方案理由測(cè)試環(huán)境 網(wǎng)絡(luò)尚可mirror 配置改動(dòng)最小加節(jié)點(diǎn)最方便隔離內(nèi)網(wǎng)離線包導(dǎo)入不依賴任何外部服務(wù)絕對(duì)可控已有內(nèi)部鏡像倉(cāng)庫(kù)替換 imageRepository統(tǒng)一管理后續(xù)版本升級(jí)路徑清晰我個(gè)人最常用的是方案二也就是離線包導(dǎo)入。原因很實(shí)在它不依賴鏡像倉(cāng)庫(kù)的穩(wěn)定性排障路徑最短。出了任何鏡像相關(guān)的問題我只需要檢查 containerd 的k8s.io命名空間里有沒有對(duì)應(yīng)的鏡像其他的不用考慮。3. kubeadm init 配置逐字段拆解從生成配置到正式開始安裝3.1 一個(gè)完整可用的初始化配置文件kubeadm 支持純命令行參數(shù)但版本多、參數(shù)多的時(shí)候很容易漏而且不方便審計(jì)。我強(qiáng)烈建議提前寫好一個(gè) YAML 文件所有節(jié)點(diǎn)都用同一套配置。下面是一個(gè)以 1.28.2 為例的配置apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock name: master01 imagePullPolicy: IfNotPresent --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 imageRepository: registry.k8s.io networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd三個(gè)文檔塊分別對(duì)應(yīng) InitConfiguration、ClusterConfiguration、KubeletConfiguration用---分隔。如果你之前對(duì) kubeadm 配置文件不熟可以執(zhí)行kubeadm config print init-defaults生成一份默認(rèn)配置作為底稿再照著上面把關(guān)鍵字段改掉就行。3.2 幾個(gè)關(guān)鍵字段為什么要這么寫先看advertiseAddress。它必須寫當(dāng)前節(jié)點(diǎn)的內(nèi)網(wǎng) IP不能寫 127.0.0.1也不能寫公網(wǎng) IP否則 kube-apiserver 通告給其他節(jié)點(diǎn)的地址不對(duì)工作節(jié)點(diǎn) join 之后會(huì)連不上 API Server。這是一個(gè)很常見但是報(bào)錯(cuò)信息不太直觀的問題。criSocket指向/var/run/containerd/containerd.sock這是 containerd 的標(biāo)準(zhǔn) socket 路徑。如果你用的是 Docker這個(gè)值就要改成unix:///var/run/cri-dockerd.sock但正如前面說的我建議直接使用 containerd省掉一層轉(zhuǎn)換。imagePullPolicy: IfNotPresent這個(gè)字段容易被忽略。默認(rèn)情況下kubeadm 在某些版本組合中會(huì)嘗試先拉取遠(yuǎn)端鏡像即使本地已經(jīng)導(dǎo)入了一份。對(duì)于離線場(chǎng)景顯式設(shè)置成IfNotPresent可以強(qiáng)制 kubeadm 優(yōu)先檢查本地 containerd這是一個(gè)很實(shí)用的細(xì)節(jié)網(wǎng)上不少離線安裝教程都會(huì)漏掉這一步導(dǎo)致鏡像明明導(dǎo)入了預(yù)檢仍然報(bào)拉取失敗。podSubnet寫10.244.0.0/16是 Calico 網(wǎng)絡(luò)插件的慣例網(wǎng)段。并不是 kubeadm 強(qiáng)制要求這個(gè)值而是 Calico 的默認(rèn)配置期望在這個(gè)網(wǎng)段內(nèi)分配 Pod IP。如果用其他網(wǎng)段后面安裝網(wǎng)絡(luò)插件時(shí)要額外修改 IPPool 配置容易出錯(cuò)。新手建議直接沿用這個(gè)網(wǎng)段。cgroupDriver: systemd必須和 containerd 的 cgroup 驅(qū)動(dòng)一致。這個(gè)后面專門講這里先記住一個(gè)結(jié)論Kubernetes 1.24 之后官方推薦并且默認(rèn)使用systemd作為 cgroup 驅(qū)動(dòng)containerd 也要配合啟用SystemdCgroup true。3.3 先拉鏡像再初始化避免預(yù)檢卡死如果走 mirror 或離線導(dǎo)入方案建議先手動(dòng)預(yù)拉一遍鏡像確認(rèn)鏡像全部就緒再執(zhí)行初始化kubeadm config images pull --config kubeadm-config.yaml這條命令會(huì)按照配置里的kubernetesVersion和imageRepository去拉取所有需要的鏡像。它執(zhí)行成功說明鏡像層面已經(jīng)沒有問題接下來初始化大概率不會(huì)被卡住。如果網(wǎng)絡(luò)確實(shí)不通預(yù)拉會(huì)失敗沒關(guān)系只要你確認(rèn)鏡像已經(jīng)通過ctr導(dǎo)入到k8s.io命名空間同樣可以直接進(jìn)入初始化kubeadm init --config kubeadm-config.yaml初始化過程會(huì)做很多事包括生成證書、寫入 kubeconfig、啟動(dòng)控制面靜態(tài) Pod。通常幾分鐘內(nèi)會(huì)看到類似Your Kubernetes control-plane has initialized successfully的輸出??吹竭@個(gè)輸出控制面節(jié)點(diǎn)就已經(jīng)起來了。4. 控制平面起來之后立刻要處理的網(wǎng)絡(luò)插件和本機(jī)調(diào)度4.1 配置 kubectl 訪問集群初始化完成后kubeadm 會(huì)給出三行提示讓你把 kubeconfig 拷到當(dāng)前用戶目錄下mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config這一步不做的話kubectl 默認(rèn)找不到集群執(zhí)行任何命令都會(huì)報(bào)connection refused或者unable to connect。在 root 用戶下很多人習(xí)慣直接用環(huán)境變量export KUBECONFIG/etc/kubernetes/admin.conf兩種方式都可以。但我建議把 kubeconfig 放到$HOME/.kube/config因?yàn)楹罄m(xù) kubectl 的各種插件和工具都會(huì)默認(rèn)讀這個(gè)位置省得每次開終端都要 export。4.2 安裝 Calico 網(wǎng)絡(luò)插件控制面初始化成功不等于集群可用。這時(shí)候kubectl get nodes會(huì)發(fā)現(xiàn) master 節(jié)點(diǎn)的狀態(tài)是NotReady這是正常的因?yàn)闆]有安裝 CNI 網(wǎng)絡(luò)插件Node 的 network readiness 檢查一直無法通過。安裝 Calico 的方式有很多種我習(xí)慣用官方提供的 manifest 文件。到一個(gè)有外網(wǎng)訪問能力的機(jī)器上下載對(duì)應(yīng)版本的 calico.yaml然后拷貝到集群節(jié)點(diǎn)上執(zhí)行kubectl apply -f calico.yamlCalico 的鏡像需要從 Docker Hub 拉取離線環(huán)境下一樣會(huì)拉不下來。如果你走的是離線導(dǎo)入方案記得把 Calico 相關(guān)的鏡像也一并導(dǎo)出導(dǎo)入到所有節(jié)點(diǎn)否則 Calico Pod 會(huì)一直停留在ImagePullBackOff。裝完之后可以看 Pod 狀態(tài)kubectl get pods -n kube-system等待calico-node和calico-kube-controllers都變成Running再執(zhí)行kubectl get nodes你會(huì)發(fā)現(xiàn)節(jié)點(diǎn)狀態(tài)變成了Ready。這里需要一點(diǎn)耐心CNI 組件從啟動(dòng)到就緒通常需要幾十秒到幾分鐘。4.3 單節(jié)點(diǎn)集群是否需要移除 taint默認(rèn)情況下控制面節(jié)點(diǎn)帶有一個(gè)node-role.kubernetes.io/control-plane的 taint普通工作負(fù)載不會(huì)調(diào)度到 master 節(jié)點(diǎn)上。如果你只是搭一個(gè)單節(jié)點(diǎn)的測(cè)試環(huán)境希望 Pod 也能跑在 master 上需要手動(dòng)移除這個(gè) taintkubectl taint nodes --all node-role.kubernetes.io/control-plane-如果你后續(xù)一定會(huì)加入工作節(jié)點(diǎn)這個(gè)操作可以跳過保持控制面節(jié)點(diǎn)“只跑系統(tǒng)組件”的干凈狀態(tài)。很多人在單節(jié)點(diǎn)測(cè)試環(huán)境里忘了這一步部署業(yè)務(wù) Deployment 之后發(fā)現(xiàn) Pod 一直處于Pending其實(shí)就是 taint 導(dǎo)致的。5. 工作節(jié)點(diǎn)加入預(yù)導(dǎo)鏡像比現(xiàn)場(chǎng)拉取省心得多5.1 獲取 join 命令控制面初始化成功時(shí)kubeadm 會(huì)在輸出末尾打印一段kubeadm join命令包含了 token 和 CA 證書哈希。如果當(dāng)時(shí)忘了保存也可以隨時(shí)重新生成kubeadm token create --print-join-command拿到的命令大致長(zhǎng)這樣kubeadm join 192.168.1.10:6443 --token 你的token --discovery-token-ca-cert-hash sha256:你的哈希token 默認(rèn)有效期是 24 小時(shí)過期之后重新生成即可。5.2 工作節(jié)點(diǎn)的準(zhǔn)備工作工作節(jié)點(diǎn)需要安裝和 master 相同版本的 kubeadm、kubelet并且配置好 containerd。這里的重點(diǎn)還是鏡像。kubeadm join本身不負(fù)責(zé)拉取業(yè)務(wù)鏡像但工作節(jié)點(diǎn)上的 kubelet、kube-proxy 依然會(huì)用到 Kubernetes 組件鏡像。如果你走離線導(dǎo)入方案我建議把 master 上預(yù)拉的那一批鏡像 tar 包直接拷到工作節(jié)點(diǎn)用同樣的ctr -n k8s.io images import命令導(dǎo)入一遍。另外一個(gè)容易被忽略的是pause鏡像。containerd 啟動(dòng)每個(gè) Pod 之前需要先創(chuàng)建一個(gè)pause容器這個(gè)鏡像的地址由 containerd 配置里的sandbox_image決定。如果工作節(jié)點(diǎn)上缺失 pause 鏡像且無法訪問遠(yuǎn)端鏡像源節(jié)點(diǎn) join 成功后調(diào)度到該節(jié)點(diǎn)的 Pod 會(huì)全部卡在ContainerCreating。還有一個(gè)細(xì)節(jié)kube-proxy 是 DaemonSet會(huì)在每個(gè)節(jié)點(diǎn)上以 Pod 形式運(yùn)行它的鏡像拉取發(fā)生在工作節(jié)點(diǎn)本地。如果工作節(jié)點(diǎn)沒有 kube-proxy 鏡像也沒有配置 mirror即使節(jié)點(diǎn)已經(jīng)顯示 Readykube-proxy 的 Pod 也會(huì)報(bào)ImagePullBackOff。這種問題最容易被忽略因?yàn)楣?jié)點(diǎn)狀態(tài)看起來一切正常實(shí)際上 Pod 網(wǎng)絡(luò)轉(zhuǎn)發(fā)完全不可用。5.3 驗(yàn)證節(jié)點(diǎn)加入結(jié)果在工作節(jié)點(diǎn)上執(zhí)行kubeadm join之后回到 master 節(jié)點(diǎn)執(zhí)行kubectl get nodes正常情況下新節(jié)點(diǎn)會(huì)先處于NotReady隨后轉(zhuǎn)為Ready。同時(shí)檢查一次所有系統(tǒng)命名空間的 Podkubectl get pods -A重點(diǎn)關(guān)注 kube-system 下是否有非 Running 狀態(tài)的 Pod。如果發(fā)現(xiàn) kube-flannel 或者 calico-node 沒起來大概率是網(wǎng)絡(luò)插件鏡像缺失或者版本不兼容。逐個(gè)排查比最后統(tǒng)一排查輕松得多我通常每加入一個(gè)工作節(jié)點(diǎn)就檢查一次不攢著。6. 初始化過程中最容易翻車的三個(gè)細(xì)節(jié)cgroup、sandbox 鏡像和版本漂移6.1 cgroup 驅(qū)動(dòng)不匹配kubelet 陷入重啟循環(huán)這是新手踩坑率最高的問題。kubelet 默認(rèn)使用systemd作為 cgroup 驅(qū)動(dòng)而 containerd 默認(rèn)配置可能用的還是cgroupfs。兩者不一致時(shí)kubelet 啟動(dòng)會(huì)報(bào)類似failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs的錯(cuò)誤。排查起來也很直接。containerd 配置文件里搜SystemdCgroup把它設(shè)為true[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true實(shí)際操作中我發(fā)現(xiàn)一個(gè)更隱蔽的情況配置文件里SystemdCgroup前面被注釋掉了表面上字段存在實(shí)際上 containerd 還在用默認(rèn)的false。所以改完配置后一定要執(zhí)行containerd config dump檢查一下生效值再重啟 containerd 和 kubelet。6.2 sandbox 鏡像版本不一致導(dǎo)致 Pod 起不來Pod 創(chuàng)建的底層機(jī)制是先啟動(dòng)一個(gè) sandbox 容器pause 容器再往里塞業(yè)務(wù)容器。如果 containerd 配置里的sandbox_image和 kubeadm 期望的 pause 鏡像版本不一致kubelet 會(huì)反復(fù)嘗試?yán)″e(cuò)誤的鏡像Pod 一直處于ContainerCreating事件里會(huì)看到FailedCreatePodSandBox。解決辦法是讓 containerd 的sandbox_image和 kubeadm 的 pause 版本保持統(tǒng)一。最直接的驗(yàn)證方式kubeadm config images list --kubernetes-versionv1.28.2看列表里 pause 的 tag然后對(duì)照 containerd 配置里的sandbox_image。如果不一樣以 kubeadm 列表里的為準(zhǔn)修改 containerd 配置。這里不要想當(dāng)然用最新版 pauseKubernetes 版本和 pause 版本之間有一個(gè)兼容矩陣跟著 kubeadm 走最安全。6.3 版本漂移系統(tǒng)包源和集群配置不一致版本漂移這個(gè)問題比較隱蔽常見于使用系統(tǒng)包管理器安裝 kubeadm 的場(chǎng)景。你初始化配置里寫的是v1.28.2但是操作系統(tǒng)的軟件源里 kubeadm 和 kubelet 已經(jīng)自動(dòng)升級(jí)到了 1.29.x。初始化過程中kubeadm 二進(jìn)制是 1.29 寫的證書和配置但是控制面鏡像按照v1.28.2拉取最后 kubelet 的二進(jìn)制版本和鏡像版本對(duì)不上Node 會(huì)反復(fù)上報(bào)錯(cuò)誤。解決思路是把軟件源的版本固定住。以 apt 為例安裝時(shí)直接指定版本號(hào)apt-get install -y kubeadm1.28.2-00 kubelet1.28.2-00 kubectl1.28.2-00這樣后續(xù)執(zhí)行apt-get upgrade也不會(huì)把你帶偏。如果集群已經(jīng)裝到一半發(fā)現(xiàn)版本對(duì)不上先統(tǒng)一好二進(jìn)制版本再考慮是否重新初始化。我的建議是發(fā)現(xiàn)版本漂移時(shí)不要硬著頭皮繼續(xù)清理掉已有配置重新初始化一次二十分鐘能解決的事繼續(xù)排查可能要花兩個(gè)小時(shí)。7. 初始化失敗后的快速恢復(fù)路徑不管前期準(zhǔn)備做得多充分初始化都有可能失敗。失敗本身不可怕最怕的是“半初始化”狀態(tài)殘留導(dǎo)致第二次初始化時(shí)報(bào)一些莫名其妙的沖突。我的習(xí)慣是準(zhǔn)備一個(gè)一鍵清理腳本內(nèi)容大約如下kubeadm reset -f rm -rf /etc/kubernetes /var/lib/kubelet /var/lib/etcd rm -rf /etc/cni/net.d iptables -F iptables -t nat -F iptables -t mangle -F systemctl restart containerd這個(gè)清理由重到輕kubeadm reset負(fù)責(zé)回收組件刪除目錄是清理證書和狀態(tài)數(shù)據(jù)清 iptables 是去掉殘留的規(guī)則。清理完再檢查一遍swapoff -a和ip link確認(rèn)網(wǎng)絡(luò)設(shè)備也被釋放干凈。很多人第二次初始化失敗都是因?yàn)闅埩襞渲梦募热?etc/kubernetes/admin.conf已經(jīng)存在kubeadm 會(huì)認(rèn)為集群已經(jīng)存在而拒絕執(zhí)行。清理腳本跑一遍基本能排除大部分雜音。8. 最后再分享一點(diǎn)個(gè)人體會(huì)這套流程我用過很多次從 1.26 到 1.28 都跑通過。最大的感觸是kubeadm 安裝集群真正的難點(diǎn)從來不在 kubeadm 本身而在于“鏡像能不能到位”和“底層配置是否一致”這兩件事。網(wǎng)絡(luò)受限反而幫你把每一步都逼著走扎實(shí)因?yàn)槟銢]法靠“拉一個(gè)新的鏡像”來繞過問題只能老老實(shí)實(shí)把版本、cgroup、sandbox 這些細(xì)節(jié)對(duì)齊。如果你現(xiàn)在正準(zhǔn)備搭一個(gè)指定版本的集群我的建議是先花半小時(shí)做鏡像準(zhǔn)備再花十分鐘檢查 cgroup 和版本一致性最后再執(zhí)行初始化。前面準(zhǔn)備做得越充分初始化那一遍就越順利。千萬別把時(shí)間花在反復(fù)kubeadm init和kubeadm reset上那是效率最低的排錯(cuò)方式。