
看到這行報錯我估摸著你八成已經(jīng)把安裝腳本重跑了好幾遍甚至開始琢磨要不要把節(jié)點格式化掉重來。先別急著動手failed to run Kubelet: misconfiguration: kubelet cgroup driver這個錯誤在 kubeasz 部署 Kubernetes 集群時其實非常典型它既不是你的網(wǎng)卡配置有問題也不是鏡像拉不下來更不是某個組件服務(wù)掛了而是一個純粹的“兩邊配置不一致”導致 kubelet 拒絕啟動的問題。我用 kubeasz 部署集群也有幾年了從 All-in-One 到多主多節(jié)點都搭過中間踩過不少類似的坑。這篇文章專門把這個問題掰開揉碎講清楚報錯背后的機制是什么、kubeasz 里到底哪些配置會影響它、正確的修復路徑怎么走以及我實操中總結(jié)出來的排查套路。無論你是準備用 kubeasz 裝一套新環(huán)境還是集群里某臺節(jié)點突然起不來了這篇都應(yīng)該能幫你省下不少折騰的時間。1. 拆解這條報錯kubelet 為什么寧可“死掉”也不起來1.1 kubelet 啟動時的“安檢”邏輯很多人看到misconfiguration這個詞就有點慌覺得是不是自己哪里配置寫錯了。其實準確地說這是 kubelet 啟動時的一個強校驗邏輯。kubelet 在啟動階段大概會做這幾件事讀取自己的配置文件、初始化容器運行時接口、檢查 cgroup driver 是否匹配、然后才去跟 API Server 注冊節(jié)點。問題就出在第三步。kubelet 會拿自己配置里的 cgroup driver 去和當前節(jié)點上容器運行時常見的就是 Docker 或 containerd實際使用的 cgroup driver 做對比。一旦發(fā)現(xiàn)兩邊不一致就不會繼續(xù)往下走直接拋出misconfiguration然后退出。Kubernetes 在這里選擇的是“寧可讓節(jié)點不加入集群也不能帶病運行”的策略。這個強校驗不是憑空設(shè)計的。因為 kubelet 和容器運行時都需要通過 cgroup 來管理容器的 CPU、內(nèi)存、PID 等資源限制。如果兩套組件分別用不同的 cgroup 管理方式它們對資源層級、子目錄、清理邏輯的理解就完全不一樣。輕則某個容器無法啟動重則系統(tǒng)層級的資源回收、節(jié)點驅(qū)逐、OOM 打分全都會亂套。你可以理解為兩個包工頭拿到了同一棟樓的兩張不同圖紙硬要一起施工最后必然出事。所以 kubelet 寧可啟動失敗也不愿意在這種狀態(tài)下工作。1.2 cgroupfs 與 systemd兩種驅(qū)動到底差在哪要理解這個報錯就必須知道 cgroup driver 到底是什么。Linux 內(nèi)核提供 cgroup 機制來對進程做資源隔離和限制而“driver”就是用戶態(tài)通過什么接口去操作 cgroup。目前主流的就是兩種cgroupfs driver直接讀寫/sys/fs/cgroup下的文件系統(tǒng)創(chuàng)建目錄、寫入限制值。這種方式比較底層、直接Docker 的默認值就是它。systemd driver把資源管理交給 systemd 自身的 cgroup 管理機制通過 systemd 的單元scope/slice來建立 cgroup 層級。Kubernetes 官方文檔明確建議在使用了 systemd 作為 init 系統(tǒng)的節(jié)點上kubelet 和容器運行時都應(yīng)該把 cgroup driver 配置為systemd。為什么官方推薦 systemd因為現(xiàn)代 Linux 發(fā)行版幾乎都是 systemd 作為 1 號進程。systemd 自己也會用 cgroup 來管理服務(wù)。如果 kubelet 和 Docker 繞過 systemd 直接用 cgroupfs就等于在 systemd 的眼皮底下另起爐灶兩套管理邏輯同時操作同一片資源樹很容易造成沖突。最典型的問題就是 systemd 在停止某個服務(wù)時可能會把 kubelet 創(chuàng)建的 cgroup 目錄一并清理掉或者反過來資源統(tǒng)計出現(xiàn)偏差。統(tǒng)一走systemddriver 之后所有進程的資源都由 systemd 統(tǒng)一規(guī)劃和接管整體上要干凈得多。1.3 報錯信息本身就把“答案”告訴你了failed to run Kubelet: misconfiguration: kubelet cgroup driver: xxx is different from docker cgroup driver: yyy注意這行報錯它其實寫得非常清楚xxx是 kubelet 當前使用的驅(qū)動yyy是 Docker 當前使用的驅(qū)動。兩邊不一樣所以 kubelet 拒絕啟動。換句話說問題范圍已經(jīng)被縮小到“節(jié)點上的 kubelet 配置”和“節(jié)點上的 Docker 配置”這兩處了。很多時候你不用去翻日志光看報錯末尾就知道該去改誰。不過有一點要提醒報錯里的字段不一定都是docker。如果你的集群用的容器運行時是 containerd一樣會報類似的信息只是末尾會變成containerd cgroup driver。處理方法大同小異后面我會把兩種運行時的情況都講到。2. 在 kubeasz 里cgroup driver 到底是在哪里被“寫死”的2.1 kubeasz 的配置層級kubeasz 這個項目本身是基于 Ansible 的一鍵部署方案它把 K8s 集群的安裝拆成很多 role比如 docker、kubelet、kube-master、kube-node 等等。它最大的特點是“二進制方式部署”和 kubeadm 的生成方式不同很多配置是直接由模板渲染出來的。以我常用的 kubeasz 3.x 目錄結(jié)構(gòu)為例你創(chuàng)建集群后主要配置都在/etc/kubeasz/clusters/集群名/config.yml里。這個文件里有節(jié)點列表、版本號、網(wǎng)絡(luò)插件、運行時類型等一堆參數(shù)。kubelet 的配置由roles/kube-node/templates/kubelet-config.yaml.j2這類模板渲染渲染時就會讀config.yml里定義的變量。如果你用的老版本 kubeasz 2.x配置位置則往往在/etc/ansible/roles/kube-node/defaults/main.yml這類 defaults 文件里。在動手之前建議先確認一下自己用的版本免得按 3.x 的路徑去找 2.x 的文件找了半天找不到。2.2 容器運行時那邊的 cgroup driver 怎么確認在 kubeasz 部署過程中容器運行時的初始化也會通過 Ansible role 完成。比如 Docker 的配置最終落在/etc/docker/daemon.json這里面就有一個關(guān)鍵字段{ exec-opts: [native.cgroupdriversystemd] }如果這個字段缺失Docker 默認會用cgroupfs。而 kubelet 的配置模板里cgroupDriver字段的值則來自 kubeasz 集群配置里的某個變量常見叫kubelet_cgroup_driver。如果模板里默認值取的是systemd而 Docker 那邊還是cgroupfs那啟動時必然對不上報錯也就順理成章了。所以排查的一個核心手段就是先看節(jié)點上兩個地方的實際值# 看 kubelet 當前配置里的 cgroupDriver cat /var/lib/kubelet/config.yaml | grep -i cgroup # 看 Docker 實際使用的驅(qū)動 docker info | grep -i cgroup # 如果是 containerd crictl info | grep -i cgroup注意/var/lib/kubelet/config.yaml是 kubelet 最終生效的配置文件它由 kubeasz 渲染生成。如果你手動改過 kubeasz 模板但沒重新跑 playbook這個文件里的值很可能還是舊的。2.3 為什么 kubeasz 的環(huán)境特別容易中招我復盤了多次踩坑經(jīng)歷發(fā)現(xiàn) kubeasz 部署時這個問題高發(fā)主要有三個原因第一kubeasz 支持在多種 Linux 發(fā)行版上部署而不同發(fā)行版預裝的 Docker 或 containerd 默認配置不一致。比如某些系統(tǒng)自帶 Docker它可能已經(jīng)寫好了自己的/etc/docker/daemon.json如果里面沒有cgroupdriver配置默認就是cgroupfs。第二kubeasz 在初始化節(jié)點時可能會因為探測系統(tǒng)類型或運行時類型的不同導致模板渲染出的 kubelet 配置和運行時配置沒有對齊。換句話說kubeasz 本身提供了參數(shù)去控制但如果你在創(chuàng)建集群時沒有顯式指定或者指定的值和節(jié)點上實際運行時不匹配就會出現(xiàn)“部署過程沒報錯、kubelet 起不來”的情況。第三也是最常見的部署人員為了省事先手動裝了 Docker之后才用 kubeasz 跑集群。手動安裝 Docker 時很少有人會特意去寫exec-opts于是 Docker 默認cgroupfs而 kubeasz 模板里如果默認渲染systemd兩邊就打架了。3. 三種修復姿勢從“最推薦”到“應(yīng)急”3.1 姿勢一改 kubeasz 集群配置重跑對應(yīng) playbook這是我認為最正規(guī)的修法適合還沒上線或者節(jié)點狀態(tài)還能忍受的集群。思路很簡單通過 kubeasz 自身的配置把 kubelet 的 cgroup driver 統(tǒng)一到和運行時一致然后讓 Ansible 重新生成配置并重啟服務(wù)。假設(shè)你確認了 Docker 那邊是cgroupfs但 kubelet 渲染出來是systemd可以這樣做。第一步進入 kubeasz 集群配置目錄找到你的集群配置文件cd /etc/kubeasz grep -R kubelet_cgroup_driver clusters/你的集群名/config.yml第二步把它的值改成和 Docker 一致或者干脆統(tǒng)一改成你期望的 driver。如果你用的是 systemd 系統(tǒng)我建議在配置里直接顯式設(shè)置kubelet_cgroup_driver: systemd同時把 Docker 也改成systemd一步到位。第三步重新執(zhí)行 kubeasz 的 node 角色 playbookansible-playbook -i clusters/你的集群名/hosts playbooks/02.kube-node.yml這個 playbook 會重新渲染 kubelet 的配置、啟動 kubelet并嘗試把節(jié)點加入集群。執(zhí)行完畢之后用kubectl get nodes看節(jié)點狀態(tài)。這里有兩個細節(jié)值得說。一是改配置前一定先備份原文件別問我是怎么知道的改壞了回滾的時候沒備份真的想哭。二是如果集群已經(jīng)在跑業(yè)務(wù)重跑 playbook 之前最好先評估一下影響面因為 kubelet 的重啟會短暫中斷該節(jié)點上的 Pod。3.2 姿勢二修改 Docker daemon 配置來對齊如果你不想動 kubelet 的配置或者你希望保留cgroupfs比如某些極端場景下你確實不需要 systemd driver那也可以反著來修改 Docker 的daemon.json讓 Docker 使用和 kubelet 一樣的驅(qū)動。先備份現(xiàn)有配置cp /etc/docker/daemon.json /etc/docker/daemon.json.bak然后編輯/etc/docker/daemon.json。如果文件不存在就新建一個但要注意如果原來有其他配置項比如鏡像加速、log 配置等一定要保留不要覆蓋掉。以統(tǒng)一到systemd為例{ exec-opts: [native.cgroupdriversystemd] }保存后重啟 Docker 和 kubeletsystemctl restart docker systemctl restart kubelet這里必須提醒一句重啟 Docker 是一個重操作。如果節(jié)點上已經(jīng)跑了很多容器Docker 重啟會把容器全部拉起但業(yè)務(wù)中斷的那幾秒是跑不掉的。所以這個方法更適合單機測試環(huán)境或者業(yè)務(wù)允許中斷的場景。線上環(huán)境優(yōu)先用第一種姿勢。還有個常見坑你改了daemon.json但 Docker 實際上沒有重新加載exec-opts。這是因為 Docker 對daemon.json的某些字段修改必須重啟 daemon 才生效不是systemctl reload就行的。如果你用systemctl reload docker很可能看到docker info里還是老樣子。老老實實restart。3.3 姿勢三單節(jié)點手動補丁快速恢復如果你只需要先把一臺節(jié)點救回來等之后再統(tǒng)一處理可以直接在節(jié)點上手動改配置。這個方式最靈活也最直接。kubelet 的最終配置文件通常是/var/lib/kubelet/config.yaml里面有一個頂層字段cgroupDriver: systemd找到它改成和 Docker 一致然后重啟 kubeletsystemctl restart kubelet不過這里我要潑一盆冷水手動改/var/lib/kubelet/config.yaml只對當前節(jié)點生效而且下次重跑 kubeasz 的 playbook、或者有人重新執(zhí)行了初始化腳本這個文件可能會被重新渲染覆蓋。所以手動補丁只能算“臨時措施”治標不治本。另一個手動修改的入口是 kubelet 的 systemd unit 參數(shù)。你可以查看cat /etc/systemd/system/kubelet.service.d/*.conf如果有類似--cgroup-driversystemd這樣的參數(shù)也可以直接改參數(shù)后執(zhí)行systemctl daemon-reload systemctl restart kubelet。但新版本 K8s 更推薦通過config.yaml來管理 kubelet 參數(shù)我一般優(yōu)先改配置文件。順便講一個判斷技巧如果你不確定 kubelet 到底用哪個配置生效可以執(zhí)行kubelet --print-config這個命令會打印出 kubelet 實際生效的完整配置包括cgroupDriver字段。這樣你就能確認它讀的到底是不是/var/lib/kubelet/config.yaml。3.4 修復完成后必須驗證的三件事不管用哪種姿勢改完之后都不能只看systemctl status kubelet是 active 就完事。我建議按這個順序驗證第一看 kubelet 日志里有沒有新的報錯journalctl -u kubelet -f如果報錯消失kubelet 會繼續(xù)往下走開始連 API Server 并注冊節(jié)點。第二核對兩邊的驅(qū)動確實一致docker info | grep -i cgroup grep -i cgroupDriver /var/lib/kubelet/config.yaml這兩條命令的輸出應(yīng)該是一致的一個寫cgroupfs就都是cgroupfs寫systemd就都是systemd。第三到控制平面看節(jié)點狀態(tài)kubectl get nodes如果狀態(tài)變成Ready說明節(jié)點已經(jīng)成功加入集群。如果還是NotReady那就得繼續(xù)往下看是不是其他問題比如 CNI 網(wǎng)絡(luò)沒通、容器運行時沒起來等。你也可以關(guān)注一下kubectl describe node 節(jié)點名里的 Conditions那里會告訴你節(jié)點當前卡在哪個環(huán)節(jié)。4. 真實排障場景與常見問題速查4.1 場景A全新集群剛裝完第一個 worker 節(jié)點就報錯有次我搭建測試環(huán)境主節(jié)點正常但第一個 worker 節(jié)點怎么都加不進來報錯就是開頭那段。當時第一反應(yīng)是檢查網(wǎng)絡(luò)結(jié)果發(fā)現(xiàn)主節(jié)點和 worker 節(jié)點之間通信完全正常。后來仔細看了報錯信息末尾寫的是docker cgroup driver: cgroupfs。我去 worker 節(jié)點上看了一下/var/lib/kubelet/config.yaml里面確實渲染成了systemd而 Docker 是系統(tǒng)自帶的默認cgroupfs。這個場景就是典型的“兩邊不一致”直接撞上強校驗。當時集群還沒上線我直接用了姿勢一修改 kubeasz 集群配置里的kubelet_cgroup_driver為cgroupfs重跑 playbook節(jié)點立即加入成功。雖然我個人不太喜歡cgroupfs但在測試環(huán)境里沒必要為了切換驅(qū)動折騰一圈能跑就行。如果是生產(chǎn)環(huán)境我還是建議統(tǒng)一到systemd后面再找窗口切換。要注意一個細節(jié)這個場景下 kubeasz 的部署日志里往往沒有任何異常因為 Ansible 認為配置都執(zhí)行完了只有 kubelet 自己啟動的時候才知道不對。這就是為什么很多人會反復重跑安裝腳本因為它根本不會在安裝階段暴露問題。4.2 場景B升級容器運行時后節(jié)點突然掛了另一種常見情況是節(jié)點原本跑得好好的某次運維操作后 kubelet 起不來了。比如你升級了 Docker 版本或者 containerd 版本重啟之后發(fā)現(xiàn)節(jié)點 NotReady。原因通常是容器運行時升級后默認的 cgroup driver 發(fā)生了變化或者升級后的運行時把config.toml/daemon.json里的某個參數(shù)重置了。我之前遇到過一個情況某臺節(jié)點上 containerd 的配置里SystemdCgroup原本是true但升級后配置文件被重置成了默認false于是 containerd 實際使用的是cgroupfs而 kubelet 配置還是systemd兩邊對不上kubelet 啟動時直接報錯。這種場景下處理思路就兩句話先確認運行時現(xiàn)在的實際 driver再決定改 kubelet 還是改運行時。千萬不要憑記憶覺得“之前是 systemd 就不用查了”一定要以當前實際狀態(tài)為準。如果是 containerd配置文件通常在/etc/containerd/config.toml關(guān)鍵字段是[plugins.io.containerd.grpc.v1.cri] systemd_cgroup true這里有個非常誤導人的地方systemd_cgroup false時 containerd 用的是cgroupfs很多人看到字段名帶systemd就以為設(shè)置成 false 才是“不用 systemd”其實反過來。一定要記住要使用systemddriver必須設(shè)置systemd_cgroup true。4.3 常見問題速查表我把幾種典型情況整理成了一張表方便你在排查時直接對照報錯特征實際原因處理方式kubelet 為 cgroupfsdocker 為 systemd有人改過 Docker 的 exec-opts或發(fā)行版 Docker 預設(shè)了 systemd將 kubelet 配置改為 systemd或改 daemon.json 回 cgroupfskubelet 為 systemddocker 為 cgroupfsDocker 默認配置kubeasz 模板渲染成了 systemd推薦改 Docker 為 systemd或改 kubeasz 配置為 cgroupfs錯誤提示包含 containerd cgroup drivercontainerd 配置里 systemd_cgroup 與實際不符修改/etc/containerd/config.toml中 systemd_cgroup 參數(shù)重跑 playbook 后配置又被覆蓋改的是節(jié)點本地文件kubeasz 模板值沒同步改 kubeasz 集群配置重跑對應(yīng) playbook部分節(jié)點 Ready、部分 NotReady節(jié)點間 Docker 或 kubelet 的配置存在差異逐臺檢查 daemon.json、kubelet config.yaml統(tǒng)一配置這張表看起來簡單但實際排查時 80% 的情況都能直接套用。核心就是“先看報錯末尾的 driver 是什么、再看本地各自的配置是什么”。5. 一些我踩過的坑和額外提醒5.1 別把 cgroupfs 和 systemd 混著用這個問題值得單獨拿出來說。雖然 Kubernetes 只強制要求“同一節(jié)點上 kubelet 和容器運行時一致”但并沒有要求“全集群所有節(jié)點一致”。理論上一個節(jié)點用cgroupfs、另一個節(jié)點用systemd也能組成集群但我不建議這么干。原因很簡單不同節(jié)點上 kubelet 的行為會因為驅(qū)動不同而產(chǎn)生細微差異尤其是在資源統(tǒng)計、驅(qū)逐閾值、Pod QoS 等級的判定上。混用狀態(tài)下的集群排查問題時你會多出一個“節(jié)點之間行為不一致”的干擾項。有次我圖省事只改了一臺 worker 的 kubelet 配置結(jié)果這臺節(jié)點后來出現(xiàn)了 cgroup 目錄殘留的問題和集群里其他節(jié)點的表現(xiàn)完全不同排查了大半天才發(fā)現(xiàn)是驅(qū)動混用導致的。所以我的建議是要么全集群cgroupfs要么全集群systemd推薦后者。5.2 版本差異3.x 和 2.x 的配置位置不一樣kubeasz 不同版本之間的配置路徑變動比較大。如果你在網(wǎng)上搜解決方案搜出來的路徑未必適用你的版本。以我之前用過的版本為例kubeasz 2.x 時代配置經(jīng)常直接寫在/etc/ansible/roles/kube-node/defaults/main.yml這類文件里改起來相對粗暴。kubeasz 3.x 之后引入了集群概念配置集中在/etc/kubeasz/clusters/xxx/config.yml并且推薦用ezctl命令來管理和創(chuàng)建集群。所以當你準備修改配置時先確認自己的版本。你可以在項目目錄下看 README 或者執(zhí)行ansible-playbook --version # 或者看 kubeasz 項目目錄里的版本文件 cat /etc/kubeasz/VERSION避免照著網(wǎng)上的舊教程改了半天的文件結(jié)果根本不是渲染模板的數(shù)據(jù)源那就白費勁了。5.3 主機本身的環(huán)境因素不要忽略有時候問題不在 kubeasz 配置里而在主機環(huán)境本身。比如某些云廠商或虛擬化平臺提供的鏡像里Docker 是預裝的而且daemon.json里已經(jīng)寫了exec-opts。有些鏡像甚至已經(jīng)把 Docker 改成了systemddriver而 kubeasz 模板還是按默認的cgroupfs渲染了 kubelet這種情況下報錯方向是反的。所以我的建議是不要一上來就去翻 kubeasz 集群配置先去節(jié)點上跑一遍 3.2 節(jié)里的兩條檢查命令拿到兩邊的真實 driver 再決定改哪邊。先收集事實再動手修改這個順序能幫你少走很多彎路。另外還有一種少見但確實存在的情況主機上之前安裝過其他容器編排工具或者手動掛載過 cgroup 文件系統(tǒng)導致 systemd 和 cgroupfs 的視圖不一致。這種環(huán)境問題不是改一行配置就能解決的可能需要重啟主機或者重新掛載 cgroup 子系統(tǒng)才能恢復干凈狀態(tài)。如果你排查到這一步建議先把主機上的歷史容器環(huán)境清理干凈再重新部署。最后分享一個小經(jīng)驗。我后來部署集群時會在 kubeasz 的集群配置文件里顯式寫出kubelet_cgroup_driver: systemd同時確認/etc/docker/daemon.json里存在native.cgroupdriversystemd再讓 ansible 開始干活。一次鎖定之后后面不管怎么擴容、怎么重裝節(jié)點都不會再被這個報錯騷擾。這個檢查步驟我建議你加到自己的部署前 checklist 里真的能省很多半夜排障的精力。