Share Notes

chundev

View the Project on GitHub latteouka/share-notes

GPU 節點 driver/library mismatch 害 k8s worker 全掛的排查指南

日期:2026-05-27 環境:k3s cluster + NVIDIA GPU 節點(Ubuntu 24.04)+ nvidia-device-plugin


TL;DR

GPU worker pod 全部起不來、log 還是空的。根因不是程式,而是節點上 NVIDIA driver 被 apt 背景更新(unattended-upgrades),但節點一直沒重開機 — 正在跑的 kernel module 還是舊版,userspace 函式庫已是新版,兩者對不上。nvidia-smi 直接報 Driver/library version mismatch,device plugin 把 GPU 標成 unhealthy,容器在 OCI runtime 階段就被擋下。解法是重開節點,reload module 救不了。


背景

GPU 運算節點上跑一個 Celery worker(吃 GPU 做推論)。某天發現「任務都不會被處理」。API、Redis 都正常,唯獨 worker pod 一直不 Ready。worker 已經穩定跑了一個多月,期間沒有任何 deploy,所以第一直覺「不是程式改壞的」是對的。

這類「跑好好的突然死掉、沒人動過 code」的狀況,幾乎都是節點環境變了,而 GPU 節點最常見的環境變化就是 driver 被自動更新。


症狀 / 錯誤訊息

兩顆 worker pod 呈現兩種看似不同、其實同源的錯誤。

第一顆 — StartError,而且 log 全空:

State:          Terminated
  Reason:       StartError
  Message:      failed to create containerd task: failed to create shim task:
                OCI runtime create failed: ... failed to inject CDI devices:
                unresolvable CDI devices runtime.nvidia.com/gpu=GPU-xxxxxxxx-...
  Exit Code:    128
Restart Count:  567

第二顆 — UnexpectedAdmissionError

Reason:   UnexpectedAdmissionError
Message:  Pod was rejected: Allocate failed due to no healthy devices present;
          cannot allocate unhealthy devices nvidia.com/gpu, which is unexpected

⚠️ 關鍵指紋:StartError + log 完全空白。 這代表容器在 containerd 的 OCI runtime 階段就被擋下來,你的程式一行都還沒執行。所以往 app log、程式碼裡找永遠找不到 — 要往「容器 runtime 怎麼把 GPU 塞進容器」這層看。

unresolvable CDI devicesno healthy devices 不是兩個 bug,是同一個根因的兩種表現:device plugin 認為 GPU 壞了。


除錯過程

重點:每一步都是一條單純的指令,照順序貼就好,不需要任何複合 one-liner。

1. 先看整個 namespace 誰活誰死

kubectl get pods -n myapp

確認 api / redis 正常、只有 worker 掛 → 縮小範圍到「跟 GPU 有關的東西」。

2. 看掛掉的 pod 的 describe(事件 + 容器狀態)

kubectl describe pod worker-xxxxx -n myapp

State / Reason / Message 那幾行,前面那兩段錯誤訊息就是從這裡來的。

3. 想看 log,但 log 是空的

kubectl logs worker-xxxxx -n myapp

空的。這就驗證了「程式還沒跑」的推論,問題在容器 runtime 那層。

4. 上節點直接問 GPU 還活著嗎

unresolvable CDIunhealthy device 都把矛頭指向節點,所以 ssh 進去問 nvidia-smi

ssh user@gpu-node
nvidia-smi

結果直接破案:

Failed to initialize NVML: Driver/library version mismatch
NVML library version: 580.159

5. 確認到底哪邊是舊的、哪邊是新的

比對「正在跑的 kernel module 版本」跟「已安裝的套件版本」:

cat /proc/driver/nvidia/version
NVRM version: NVIDIA UNIX x86_64 Kernel Module  580.126.09
dpkg -l | grep nvidia-driver
ii  nvidia-driver-580   580.159.03-0ubuntu0.24.04.1   amd64

→ 正在跑的 kernel module 是 580.126.09,但套件已經被更新到 580.159.03。對不上。

6. 找兇手:誰更新了 driver、又為什麼還是舊 module

uptime
 up 61 days

節點 61 天沒重開機。再看 apt 更新紀錄:

grep -i nvidia /var/log/apt/history.log
Upgrade: nvidia-driver-580 (580.126.09..., 580.159.03...), ...

真相大白:unattended-upgrades 把 driver 更新成 580.159.03,但節點沒重開機,所以記憶體裡跑的還是一個多月前載入的舊 module。

7. 確認「能不能用 reload module 解、還是非重開不可」

uname -r
6.8.0-106-generic        ← 正在跑的 kernel
dpkg -l 'linux-image-6.8.0-*generic'
ii  linux-image-6.8.0-106-generic
ii  linux-image-6.8.0-117-generic   ← 新裝、還沒啟用

這一步很關鍵:那次更新同時換了 kernel(6.8.0-106 → 6.8.0-117),而對應正在跑的 6.8.0-106 的新版 nvidia module 已經被移除,只剩 build 給 6.8.0-117 的。也就是說 — 在不重開的情況下,根本沒有一個能對齊 userspace 的 module 可以載入。rmmod + modprobe 這條路走不通,只能重開機


根因

項目 狀態
正在跑的 kernel module 580.126.09(舊)
userspace 函式庫 / 已裝套件 580.159.03(新,apt 自動更新的)
正在跑的 kernel 6.8.0-106(很久沒重開)
已裝但未啟用的 kernel 6.8.0-117(新 module 是 build 給它的)

nvidia-smi 失敗 → device plugin 把 GPU 標 unhealthy + CDI spec 裡的 GPU UUID 失效 → 任何要 GPU 的容器在 runtime 階段就被擋。


解法

匹配的一組(新 kernel 6.8.0-117 + 新 module 580.159.03)其實都裝好了,只是要重開機才會生效

ssh user@gpu-node
sudo systemctl reboot

k8s 這邊不用手動做什麼:節點回來後 device plugin 會重新偵測到健康的 GPU、CDI spec 自動重生,worker 的 ReplicaSet 會自動拉新 pod。

⚠️ 如果這顆是 control-plane 節點、又跑著服務本體,要先讓相關人知道:重開期間控制平面 + 該節點上的服務會中斷數分鐘。正規做法是先 kubectl drain 再重開再 uncordon;小型 cluster 直接重開通常也可接受。


驗證(重開後逐條確認)

ssh user@gpu-node
uname -r          # 應該變成新 kernel 6.8.0-117-generic
nvidia-smi -L     # 應該列出 GPU,不再報 mismatch
kubectl get node gpu-node -o jsonpath='{.status.allocatable.nvidia\.com/gpu}'
# 回 1(或你的卡數)代表 GPU 重新可配置
kubectl get pods -n myapp -w
# 看 worker 從 ContainerCreating 變成 1/1 Running

最後別忘了把卡了一堆 restart 的舊 failed pod 清掉:

kubectl delete pod worker-old-1 worker-old-2 -n myapp

worker log 出現 Connected to redis://...celery@... ready. 就代表真的能收任務了 — Running 不等於能工作,要看到 ready 才算數。


學到的事


參考資料