
上個月接到一個需求要把機房那批 Atlas 800 服務器上的昇騰 NPU 接入到 Kubernetes 集群里。算法團隊手里有基于 CANN 的推理服務要上容器還準備跑 Swift Megatron 這類大模型訓練任務不能再像以前那樣手動指定--device、靠機器標記綁定算力了。折騰了大概一周把驅動、CANN、Ascend Docker Runtime、device-plugin、監(jiān)控全部串起來之后發(fā)現昇騰 NPU 進 K8s 的整套鏈路其實已經有比較成熟的做法只是資料散在各處版本匹配又特別敏感坑不少。這篇文章就把這次接入的完整過程拆開講清楚。我會先從整體架構講為什么需要這幾層組件再逐層說明安裝部署時的要點和驗證方法最后把我踩過的坑和排查思路整理成速查表。整個過程不局限于某一種特定的容器引擎或集群發(fā)行版只要你的節(jié)點是 x86_64 或 aarch64 的主流 Linux 發(fā)行版基本都能照做。1. 接入前的整體設計與思路拆解1.1 昇騰 NPU 在 K8s 里到底算什么資源在 Kubernetes 的調度模型里CPU 和內存是第一類資源NPU、GPU 這類設備屬于擴展資源也就是 Extended Resource。K8s 本身不關心這個資源到底是什么它只關心兩件事節(jié)點上這個資源有多少、某個 Pod 需要多少。真正讓 K8s 感知到 NPU 存在的工作是由 device-plugin 完成的。所以昇騰 NPU 接入 K8s 的第一步不是跑一堆 YAML而是想清楚這條鏈路里每層組件各自承擔什么職責。我們可以把過程拆成四層規(guī)約層device-plugin 把昇騰設備注冊到 kubelet以huawei.com/Ascend910這類資源名暴露給調度器。運行時層K8s 創(chuàng)建 Pod 后kubelet 調用容器運行時容器運行時通過 Ascend Docker Runtime 把 NPU 設備、驅動目錄、環(huán)境變量注入到容器。用戶態(tài)依賴CANN Toolkit 提供算子庫和運行時依賴容器的業(yè)務進程靠它來調用昇騰硬件。內核態(tài)底座驅動和固件讓昇騰設備在宿主機上可見、可用。一句話描述就是驅動讓節(jié)點看到設備CANN 讓應用能用設備Runtime 讓容器能掛設備device-plugin 讓 K8s 能調度設備。任何一層斷了現象可能都不一樣。我見過很多人只裝了驅動就在 K8s 里跑容器結果 Pod 報一堆 RuntimeError最后發(fā)現是 CANN 沒裝。1.2 為什么不能像 GPU 那樣直接掛設備用過 NVIDIA GPU 的朋友可能覺得昇騰接入的套路應該和 nvidia-device-plugin 差不多。思路確實像但有一點關鍵差異不能忽視NVIDIA 那套生態(tài)有 libnvidia-container 做了很完整的容器隔離而昇騰的 Ascend Docker Runtime 是一個基于 runc 的 OCI runtime 實現它做的事情更接近“把設備目錄和設備文件動態(tài)掛進容器”。具體來說Ascend Docker Runtime 會在容器啟動前動態(tài)注入這些內容/dev/davinci*設備文件、/dev/davinci_manager、/dev/hisi_hdc、/dev/devmm_svm以及/usr/local/Ascend/driver下面的驅動依賴。如果你跳過 Runtime 直接手動掛載也能跑起來但設備的分配、權限控制、多容器隔離就完全失控了。這也是為什么我不建議走“手工掛載設備”這條捷徑。短時間跑通很容易等你要做資源池化、配額控制、多租戶隔離的時候完全沒有抓手。1.3 版本選型先想清楚否則后面全白干昇騰的軟件棧版本聯動非常嚴格這可能是整套部署里最讓人頭大的事情。驅動、固件、CANN 三個東西并不是越新越好而是要互相匹配。官方每個版本會出配套關系表我這次用的是一套相對穩(wěn)定的組合具體版本號就不貼了避免誤導但選型的原則可以分享先確認硬件型號。Atlas 300I 推理卡、Atlas 300T 訓練卡、Atlas 800 訓練服務器、Atlas 910B/A2 這類訓練芯片對應的驅動和 CANN 版本策略都不一樣。查驅動和固件的配套表。驅動和固件必須成套升級不能只動其中一個。再查驅動和 CANN 的兼容矩陣。CANN 版本太新或太老都可能出現接口對不上。最后確認 Ascend Docker Runtime 和 device-plugin 兼容的驅動范圍。另一個容易忽略的事情是操作系統(tǒng)和內核。昇騰官方驅動對操作系統(tǒng)內核版本有明確限制尤其是 aarch64 架構 特殊內核經常需要匹配特定補丁。如果宿主機是定制化內核建議先在非生產節(jié)點上驗一下npu-smi info能不能正常輸出再往下走。2. 宿主機環(huán)境準備驅動與 CANN 安裝2.1 驅動與固件安裝的完整順序昇騰驅動安裝常見的坑是“固件亂刷”。官方把驅動和固件打包成Ascend HDK安裝包里面包含驅動、固件和配套工具。安裝時有幾個要點安裝包建議以 root 用戶執(zhí)行或者使用具有 sudo 權限的賬號。安裝前卸載舊版本卸載順序和安裝相反先卸載固件再卸載驅動。安裝命令大致是chmod x Ascend-hdk-*.run ./Ascend-hdk-*.run --full --install安裝完成后用npu-smi info驗證驅動是否正常。正常情況下會列出 npu 設備的編號、型號、溫度、HBM 使用率、AI Core 的利用率等信息。如果提示設備不存在或者報 HwHiAiUser 權限問題先檢查驅動是否成功加載ls /dev/davinci* ls /usr/local/Ascend/driver這里有第一個常見坑驅動安裝完后設備文件可能掛在/dev/davinci0這些節(jié)點上但如果容器運行時沒有權限還是會報錯。建議先用npu-smi info在宿主機上確認每張卡都能看到。2.2 CANN Toolkit 的安裝與定位CANN 是昇騰的計算架構包含 AscendCL、GE 圖引擎、算子庫、推理/訓練 runtime 等組件。裝 CANN 之前驅動必須已經能正常識別設備否則裝完 CANN 也無法工作。CANN Toolkit 安裝包是.run文件默認安裝到/usr/local/Ascend/ascend-toolkit/目錄下會帶一個latest軟鏈接指向當前版本。安裝命令chmod x Ascend-cann-toolkit_*.run ./Ascend-cann-toolkit_*.run --install安裝完后需要設置環(huán)境變量。我習慣把這部分變量固化到/etc/profile.d/ascend.sh然后 source 它export ASCEND_HOME/usr/local/Ascend/ascend-toolkit/latest export LD_LIBRARY_PATH$ASCEND_HOME/lib64:$ASCEND_HOME/lib64/plugin/opskernel:$ASCEND_HOME/lib64/plugin/nnengine:$LD_LIBRARY_PATH export PATH$ASCEND_HOME/bin:$ASCEND_HOME/compiler/ccec_compiler/bin:$PATH export PYTHONPATH$ASCEND_HOME/python/site-packages:$PYTHONPATH export ASCEND_AICPU_PATH$ASCEND_HOME export ASCEND_OPPER_PATH$ASCEND_HOME/opp注意宿主機上裝 CANN 主要是為了驗證驅動和跑 npu-smi 工具真正的算法依賴通常是通過鏡像把 CANN 打包進去。不過為了簡化鏡像構建也可以用“統(tǒng)一基礎鏡像 宿主機 CANN 映射”的方式這在昇騰場景里很常見。2.3 快速驗證宿主機環(huán)境是否可用環(huán)境準備完成后最有效的驗證方式是跑一個 Python 小腳本調用 AscendCL 初始化設備import acl ret acl.init() print(acl.init ret , ret) ret acl.finalize()如果返回不為 0基本就是 CANN 安裝有問題或者環(huán)境變量不對。還有一個小技巧用npu-smi info查看設備拓撲時可以順手記錄一下每張卡的 NUMA 節(jié)點。后面做 K8s 調度時如果要追求極致性能通常要結合 NUMA 親和性把容器綁到正確的 CPU 核心上這個信息后面有用。3. 接入容器生態(tài)Ascend Docker Runtime 部署3.1 Runtime 在容器啟動時到底幫你做了什么Ascend Docker Runtime 的作用是在容器運行時階段自動為容器注入 NPU 設備相關的資源。它本質是一個 OCI runtime wrapper啟動容器時會做動作解析環(huán)境變量ASCEND_VISIBLE_DEVICES指定容器能看到哪幾張卡。根據設備編號找到對應的/dev/davinciX設備文件。掛載/usr/local/Ascend/driver下的必要庫文件到容器內。注入設備管理相關的/dev節(jié)點和必要內核模塊路徑。你可以把它理解成 NVIDIA Container Toolkit 的昇騰版本只是它的實現更偏“傳統(tǒng)跑runC”的路子。安裝方式是在宿主機上裝一個.run的運行時包然后分別給 Docker 或 containerd 配置 runtime。3.2 Docker 側安裝配置昇騰官方提供了安裝腳本也可以手動下載.run包安裝。安裝完成后需要在 Docker 的 daemon.json 里增加 runtime 配置mkdir -p /etc/docker cat /etc/docker/daemon.json EOF { runtimes: { ascend: { path: /usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime, runtimeArgs: [] } } } EOF systemctl restart docker驗證方式也很直觀先看宿主機的 NPU IDnpu-smi info假設顯示 4 張卡編號 0 到 3下面這條命令可以把 0 號卡映射到容器里docker run --runtime ascend -e ASCEND_VISIBLE_DEVICES0 \ -it ascend/cann:latest npu-smi info進去之后npu-smi info如果能看到卡而且設備編號和宿主機一致說明 Runtime 工作正常。這里有個我認為很重要的細節(jié)ASCEND_VISIBLE_DEVICES的語義是“容器內可見的設備編號”你可以填0,1也可以填物理卡號還可以填-1表示不映射任何設備。但這個環(huán)境變量必須顯式傳給容器device-plugin 會自動生成它手工 docker run 時則必須手動指定。3.3 containerd 集群怎么處理現在很多 Ingress 和容器集群默認跑 containerd不再用 docker。昇騰 Runtime 對 containerd 的支持也做了插件適配需要在/etc/containerd/config.toml里聲明一個 runtime。常見的 containerd 配置片段是這樣的version 2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.ascend] runtime_type io.containerd.runc.v2 runtime_path /usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime不同 containerd 版本配置路徑略有差異我這邊用的是 containerd 1.7 的格式。配置完成后要重啟 containerdsystemctl restart containerd驗證 containerd 場景下有沒有生效最直接的方法是用 crictl 創(chuàng)建一個帶資源聲明的 Pod。如果 Pod 被調度到節(jié)點后一直處于 ContainerCreating大概率 runtime 沒配對看 kubelet 日志時會出現unknown runtime ascend之類的關鍵字。4. 讓 K8s 認識 NPUdevice-plugin 部署與驗證4.1 device-plugin 暴露資源的機制K8s 從 1.10 開始支持設備插件機制device-plugin 是運行在節(jié)點上的一個 gRPC 服務。它通過/var/lib/kubelet/device-plugins/kubelet.sock和 kubelet 通信做三件事上報設備列表告訴 kubelet 這個節(jié)點有哪些昇騰卡。擴展資源把設備數量注冊到capacity和allocatable。分配設備Pod 調度到節(jié)點后kubelet 讓 device-plugin 返回需要注入的環(huán)境變量和設備文件列表。昇騰官方的k8s-device-plugin項目在 Gitee 上有開源里面提供了 ConfigMap 和 DaemonSet 樣例。它支持按設備型號暴露不同資源名也支持 vNPU 虛擬化設備。4.2 部署 device-plugin 的具體 YAML我這次用的是 DaemonSet 方式部署保證每個有昇騰卡的節(jié)點都跑一個 plugin 實例。下面是一份可參考的 YAML注意我把驅動路徑、日志目錄等常見需要的掛載都配了進去apiVersion: v1 kind: ConfigMap metadata: name: ascend-device-plugin-config namespace: kube-system data: config.json: | { version: 1.0.0, plugins: [ { name: ascend-toolkit, version: 6.2, device-type: Ascend910, resource-name: huawei.com/Ascend910, ascend-visible: all } ] } --- apiVersion: apps/v1 kind: DaemonSet metadata: name: ascend-device-plugin namespace: kube-system spec: selector: matchLabels: app: ascend-device-plugin template: metadata: labels: app: ascend-device-plugin spec: hostNetwork: true tolerations: - operator: Exists containers: - name: device-plugin image: ascend-device-plugin:latest imagePullPolicy: IfNotPresent securityContext: privileged: true env: - name: ASCEND_PLUGIN_CONF value: /etc/ascend/config.json volumeMounts: - name: dp mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys - name: driver mountPath: /usr/local/Ascend/driver - name: config mountPath: /etc/ascend readOnly: true - name: log mountPath: /var/log volumes: - name: dp hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: driver hostPath: path: /usr/local/Ascend/driver - name: config configMap: name: ascend-device-plugin-config - name: log hostPath: path: /var/log部署命令就是kubectl apply -f ascend-device-plugin.yaml部署完成后看 DaemonSet 狀態(tài)kubectl -n kube-system get pods | grep ascend如果 Pod 狀態(tài)正常馬上看一下節(jié)點資源里有沒有昇騰資源kubectl get node node-name -o json | jq .status.allocatable預期在 allocatable 里能看到類似這樣的字段{ huawei.com/Ascend910: 4 }如果不能看到先看 device-plugin Pod 日志通常是因為配置文件里的device-type和宿主機實際型號不一致或者掛載的驅動路徑不對。4.3 提交一個真正使用 NPU 的 Pod資源注冊成功之后寫一個簡單的測試 Pod確認調度和資源申請鏈路是通的。在 K8s 里申請 NPU 資源不需要再像手工運行時那樣寫ASCEND_VISIBLE_DEVICESdevice-plugin 會自動處理apiVersion: v1 kind: Pod metadata: name: ascend-test spec: restartPolicy: Never containers: - name: ascend-test image: ascend/cann:latest command: [npu-smi, info] resources: limits: huawei.com/Ascend910: 1注意 limits 下寫的是huawei.com/Ascend910這個資源名必須和 ConfigMap 里注冊的名字完全一致。如果寫錯Pod 會一直 Pending 或者調度不到對應節(jié)點。成功運行后kubectl logs ascend-test的輸出里應該能看到一張昇騰卡的詳細信息。4.4 多卡共享和 vNPU 虛擬化怎么處理默認情況下huawei.com/Ascend910: 1表示分配整張物理卡。昇騰也支持把物理卡切成多個 vNPU 設備類似于 NVIDIA 的 MIG 功能。使用 vNPU 時ConfigMap 里的 device-plugin 配置要改成 vNPU 類型同時資源名和粒度的定義也要變。這里有一個實際操作中容易搞混的點物理卡申請和 vNPU 申請不要在同一個節(jié)點上混用至少我測下來這樣會出現設備沖突。建議要么整卡要么全走虛擬化。5. 監(jiān)控與排障5.1 昇騰設備的監(jiān)控指標從哪來NPU 的監(jiān)控數據和 GPU 不太一樣你沒法直接用 DCGM 那套。最底層的監(jiān)控來源是昇騰驅動自帶的npu-smi info但它輸出的是文本不適合直接給 Prometheus 采集。實踐中常見的做法是部署一個 Ascend Exporter它會周期性查詢驅動接口然后把指標暴露成 Prometheus 格式。常見的監(jiān)控指標大概有這幾類AI Core 利用率反映計算單元忙閑程度。HBM 顯存使用量和總量這個和 GPU 顯存一樣關鍵大模型訓練經常卡在顯存不足。芯片溫度、功耗、電壓用于機房散熱和功耗管理。設備狀態(tài)比如離線、降頻、錯誤等。5.2 將監(jiān)控接入 Prometheus 與 Grafana我這邊用 DaemonSet 方式部署 exporter讓它在宿主機網絡下運行然后配置 Prometheus 抓取。Prometheus 的抓取配置大致如下scrape_configs: - job_name: ascend-npu static_configs: - targets: [node-ip:9100]當然如果集群規(guī)模大、節(jié)點多更推薦用 Kubernetes SD 配置自動發(fā)現節(jié)點。拿到指標后在 Grafana 里可以按節(jié)點、按 NPU ID 分組看每張卡的 AI Core 利用率和 HBM 用量。接入 Prometheus 之后最有價值的一件事是在做調度測試時你能清楚看到每張卡的真實負載然后判斷要不要在 device-plugin 層做負載感知調度。默認 device-plugin 的資源分配是“先到先得”不會管卡和卡之間負載是否均衡。我曾經遇到過 0 號卡跑滿、1 號卡完全空閑但新 Pod 照樣被排到 0 號卡的情況。5.3 常見問題速查表這里把這次部署里最常踩的幾個問題直接整理成表格方便排查現象大概率原因排查方法驅動安裝后npu-smi info找不到設備固件和驅動版本不匹配或內核模塊未加載查看ls /dev/davinci*比對官方驅動固件配套表容器內看不到設備節(jié)點Runtime 配置未生效或容器缺權限確認 daemon.json runtime 路徑確認 privilegedPod 一直 Pending資源名寫錯或節(jié)點沒有對應資源查看kubectl describe node的 allocatablePod 報AscendCL init failedCANN 環(huán)境變量缺失或版本不兼容檢查鏡像內/usr/local/Ascend/ascend-toolkit/latest是否存在device-plugin Pod crash配置文件不規(guī)范或驅動路徑掛載錯誤查看 device-plugin 日志核對/usr/local/Ascend/driver多容器同卡性能互相影響整卡被多個容器共享確認huawei.com/Ascend910是否按整卡申請必要時用 vNPU5.4 幾個實測中容易踩的坑第一個坑是“改了資源聲明但調度沒變”。K8s 的節(jié)點 allocatable 一旦上報不會因為你改了 device-plugin 配置就實時更新。這時要重啟 kubelet或者刪除節(jié)點上的 device-plugin Pod 讓它重新注冊。第二個坑是鏡像里的驅動目錄和宿主機的驅動目錄沖突。因為我用 HostPath 把/usr/local/Ascend/driver掛進了容器所以容器鏡像里不應該再自己帶一套驅動。如果鏡像里已經存在同名目錄容器啟動時會互相覆蓋表現就是某些版本能跑某些版本不行。第三個坑是內核升級。Linux 內核更新后昇騰驅動模塊經常需要重新編譯或重裝。我遇到過一次宿主機內核自動更新結果重啟后所有 NPU 設備全部消失。如果你用了自動安全更新建議把內核包固定住或者設置恢復快照。第四個坑是 NUMA 親和性。昇騰卡掛在某個 NUMA 節(jié)點下如果容器綁核綁到遠端 CPU性能損耗可能達到一到兩成。訓練任務尤其明顯。建議在 Pod 里結合 CPU manager 和拓撲管理器做一致性調度先把cpuManagerPolicystatic打開再測試。另外如果你在 K8s 里同時跑普通業(yè)務和 NPU 業(yè)務建議給昇騰節(jié)點打上專用標簽然后給 NPU 任務加 nodeSelector。這樣能避免普通任務把核都占滿影響 NPU 任務的 CPU 性能。6. 一些實地部署后的個人體會整套昇騰 NPU 接入 K8s 的流程走下來我的感受是組件其實不多核心鏈路就是驅動、CANN、Runtime、device-plugin、exporter 這五件事但每一層都對版本非常敏感。最省心的方式是把硬件型號、操作系統(tǒng)、內核版本、驅動版本、固件版本、CANN 版本、Runtime 版本、device-plugin 版本全部記下來做成一張版本清單以后每次變更都對照著查。實際操作中我最推薦的落地順序是先在單個節(jié)點上完全不碰 K8s把驅動、CANN、Docker Runtime 跑通再部署 device-plugin最后再上 Prometheus 監(jiān)控。如果一開始就直接在集群里鋪開出問題時很難判斷是調度問題還是底層驅動問題。最后再分享一個小經驗昇騰這塊的官方文檔和社區(qū)資料更新很快不同版本之間命令參數、配置文件位置都會有變化。遇到版本不一致導致的報錯不要只看報錯本身先確認所有組件的版本都在官方兼容矩陣內。我這次有一半以上的時間都花在版本對齊上真正部署和改配置反而很快。希望這篇實操記錄能幫你少走點彎路。后面如果大家需要我可以繼續(xù)把昇騰設備在 K8s 上如何做彈性伸縮、如何結合負載感知調度做更精細的資源分配以及大模型訓練場景下多機多卡如何通過宿主網絡和高性能存儲配合調優(yōu)逐個展開聊聊。