昇腾NPU接入Kubernetes全流程:从驱动到资源调度实践 📅 发布时间:2026/9/18 15:43:15 👁 浏览次数: 上个月我把 CubeStudio 项目的昇腾 NPU 集群接进了 Kubernetes整套流程走下来驱动、固件、CANN、Ascend Docker Runtime、device-plugin、监控一个不少。这套环境现在已经在跑训练和推理任务调度、扩缩容、监控告警都能正常用。网上关于昇腾 NPU 接入 K8s 的资料比较分散要么只有驱动安装要么只讲 device-plugin 单独一块真正从裸机到资源调度的全流程操作分享很少。这篇就按我实际部署的顺序整理出来适合手里有昇腾硬件、想把 NPU 纳入 Kubernetes 统一调度的同学参考。文中的路径和包名以你下载到的实际版本为准但整体思路和排查手段是通用的。1. 昇腾 NPU 接入 K8s 的整体方案拆解1.1 为什么要费力把 NPU 接进 K8s先说说项目背景。CubeStudio 这边同时有多个团队要跑模型训练和推理服务如果每台机器都是手工分配 NPU很容易出现某张卡闲着、某个任务排队等卡的情况。把 NPU 纳入 Kubernetes 之后训练任务、推理服务就可以像申请 CPU 和内存一样申请 NPU 资源K8s 负责调度和排队运维只需要维护一个集群。昇腾 NPU 和 NVIDIA GPU 在接入 K8s 的思路上是类似的都是靠三件事自定义资源让 K8s 认识设备、Device Plugin 把设备数量和健康状态上报给 kubelet、Container Runtime 在启动容器时把设备注入容器。但华为这边的软件栈名称不同版本配套也更复杂驱动、CANN、容器运行时、device-plugin 每个环节都有对不上的可能。1.2 昇腾 NPU 软件栈的分层逻辑把昇腾 NPU 从硬件到 K8s 调度大致可以分成五层。理清楚这个分层后面排错就知道该看哪一层了。第一层硬件层。昇腾 310 是推理卡昇腾 910 是训练卡其他还有 310P、910B 等型号你得先确认手上卡的类型因为对应的驱动、固件、CANN 版本可能不一样。第二层驱动与固件层。操作系统通过昇腾驱动认识设备固件则负责芯片底层运行逻辑对应包一般叫 Ascend HDK。第三层用户态库层也就是 CANN。CANN 是昇腾的计算架构包含 runtime、算子库、图引擎、编译器类比 CUDA 在 NVIDIA 体系里的位置。第四层容器运行时层。Ascend Docker Runtime 是 OCI Runtime它拿到 ASCEND_VISIBLE_DEVICES 这个环境变量后把对应编号的 NPU 设备节点和驱动目录挂载进容器。第五层K8s 调度层。device-plugin 和 kubelet 通信上报节点上有几张 NPU、资源名是什么K8s 据此调度 Pod 到有资源的节点。1.3 版本配套是第一个大坑昇腾生态里最让人头疼的不是安装步骤多而是版本配套。驱动、固件、CANN、容器镜像里的 torch_npu、device-plugin、Ascend Docker Runtime甚至宿主机内核都有对应的兼容列表。我这次环境是 Atlas 800 推理服务器、昇腾 310P 卡、Ubuntu 20.04 内核 5.4配套的软件组合是Ascend HDK 24.1.rc1 版本对应的驱动和固件、CANN 8.0.RC1、Ascend Docker Runtime 24.1、社区版 device-plugin 1.0。这套组合实测下来能稳定跑。安装前一定要去昇腾社区把版本配套表下载下来先核对一遍别相信最新的一定最好。昇腾软件迭代速度很快有些新版本对旧硬件或旧内核要求更高盲目升版本反而容易翻车。1.4 本次部署的环境清单组件版本/说明服务器Atlas 800 (型号 3000)NPU昇腾 310P推理卡操作系统Ubuntu 20.04.6 LTS内核5.4.0-150-generic驱动/固件Ascend HDK 24.1.rc1CANN8.0.RC1Ascend Docker Runtime24.1device-plugin社区版 1.0Kubernetesv1.28.2Docker24.0.52. 宿主机环境准备与驱动、固件安装实操2.1 安装前的系统检查驱动安装失败的情况里十有八九是环境缺东西。装之前先花五分钟把这些检查一遍。先确认硬件能被系统识别lspci | grep -i ascend如果能输出类似Huawei Technologies Co., Ltd. Device 6326这样的信息说明 PCIe 层面的识别是正常的。如果这里什么都看不到先检查卡是不是没插好、服务器是不是开了 PCIe slot 白名单。接着确认内核开发包和编译工具uname -r apt install -y linux-headers-$(uname -r) gcc make驱动安装时需要对内核模块做编译如果缺少 linux-headers安装过程会在编译阶段直接中断。另外建议提前关闭 Secure Boot或者给驱动模块做签名否则加载内核模块时会被拦截。我这边直接在 BIOS 里关了 Secure Boot省事。还有一个比较容易忽略的是系统时间。NPU 设备、驱动和固件对时间比较敏感如果时间不对运行时握手会出各种诡异问题先date看一眼不对就同步一下。2.2 驱动与固件安装步骤昇腾的驱动和固件打包在 HDK 软件包里命名类似Ascend-hdk-xxx_linux-aarch64.run从昇腾社区下载对应版本。下载好之后先给执行权限chmod x Ascend-hdk-xxx_linux-aarch64.run ./Ascend-hdk-xxx_linux-aarch64.run --full--full参数会同时安装 driver 和 firmware。安装完成后默认路径是/usr/local/Ascend/driver相关二进制和库都在这个目录下。安装完第一件事是验证驱动是否加载成功npu-smi info正常情况会输出板卡列表包括芯片型号、温度、功耗、显存使用量。如果报错说没有设备按顺序排查# 检查内核模块是否加载 lsmod | grep drv # 检查设备节点是否存在 ls -l /dev/davinci* ls -l /dev/davinci_manager ls -l /dev/hisi_hdc ls -l /dev/devmm_svm如果lsmod没输出手动尝试加载模块后再看。如果设备节点缺失多半是 udev 规则没生效重启一次通常能解决。驱动安装这一步还有个容易犯的错驱动和固件虽然经常一起装但它们其实是两个独立组件。固件版本和驱动版本不匹配时npu-smi info有时也能显示卡但一跑算子就出错所以装完务必确认两个版本在同一兼容列表里。2.3 驱动版本管理的注意事项昇腾驱动升级不像普通软件那样直接覆盖安装。如果之前装过旧版本建议先卸载干净再装新的/usr/local/Ascend/driver/script/uninstall.sh --full卸载完重启服务器再装新版。我试过一次不卸载直接覆盖结果旧版本的内核模块残留npu-smi info显示两张卡一张在线一张离线排查了半天。驱动安装好之后宿主机这一层就基本打通了。接下来装 CANN 时要注意驱动是节点级共享的CANN 可以装在宿主机也可以只放在容器镜像里两种方式各有适用场景下面详细说。3. CANN Toolkit 安装与环境变量配置3.1 CANN 在整体架构里的位置CANN 是昇腾的计算架构包含运行时、算子库、图编译器和推理工具链。如果说驱动是让系统看得见NPU那 CANN 就是让程序用得上NPU。在 K8s 场景下CANN 有宿主机和容器两种放法。如果业务镜像里已经内置了 CANN 和 torch_npu宿主机只需要驱动和 runtime 就够了。但如果想在宿主机上跑atc模型转换工具或者调试时直接执行msame推理工具宿主机也需要一套 CANN。我的建议是宿主机和容器镜像都装同版本的 CANN。宿主机装一份用于调试和模型转换容器里装一份用于实际跑任务。两边版本不一致会在某些反序列化算子时出现奇怪的报错统一版本能省掉很多麻烦。3.2 CANN Toolkit 安装过程下载Ascend-cann-toolkit_xxx_linux-aarch64.run执行安装chmod x Ascend-cann-toolkit_xxx_linux-aarch64.run ./Ascend-cann-toolkit_xxx_linux-aarch64.run --install安装完成后工具链默认在/usr/local/Ascend/ascend-toolkit/latest目录下。验证安装是否成功cat /usr/local/Ascend/ascend-toolkit/latest/version.cfg source /usr/local/Ascend/ascend-toolkit/set_env.sh再看一眼 Python 侧能不能正常 import 昇腾相关库python3 -c import torch; import torch_npu; print(torch.__version__)能正常输出版本号说明 CANN 和 torch_npu 的 Python 绑定是通的。如果 torch_npu 导入报错先确认 CANN 版本和 torch_npu 版本是否匹配这个是继驱动配套之后第二个常见的版本坑。3.3 环境变量的正确配置方式CANN 装好后需要设置环境变量。手动 source 只能在当前 shell 生效K8s 场景下我更建议把环境变量写进一个独立脚本然后在/etc/profile.d/下加一个调用cat /etc/profile.d/ascend.sh EOF source /usr/local/Ascend/ascend-toolkit/set_env.sh export ASCEND_AUTOLOG_DIR/var/log/npu EOF chmod x /etc/profile.d/ascend.sh这样每次登录和容器启动时都能自动加载。注意不要把所有变量一股脑写进.bashrc因为容器里的 shell 可能不读.bashrc还是走全局 profile 文件比较稳。ASCEND_AUTOLOG_DIR这个变量建议提前设好否则运行算子报错时日志散落各处排查问题非常痛苦。日志统一之后算子报错、设备故障都能在一个目录下找到现场。4. Ascend Docker Runtime 配置与容器内验证4.1 为什么 Docker 不能直接映射 NPUDocker 默认只认识 CPU、内存和普通的 PCIe 设备。对 NPU 这种需要挂载多个设备节点、注入用户态库、配置环境变量的加速设备原生 Docker 没办法自动完成。手动方式也能跑运行容器时挂--device参数把/dev/davinci0、/dev/davinci_manager、/dev/hisi_hdc、/dev/devmm_svm都带上再把/usr/local/Ascend/driver挂进容器命令行参数会非常长而且没办法配合 K8s 做自动化调度。Ascend Docker Runtime 就是来解决这个问题的它是一个 OCI Runtime在容器启动时根据环境变量从节点上挑选指定编号的 NPU自动挂载正确的设备节点和驱动目录。4.2 配置 Docker daemon 接入 ascend runtime从昇腾社区或 GitHub 下载ascend-docker-runtime包解压后放到固定目录tar -xzf ascend-docker-runtime_xxx.tar.gz mv ascend-docker-runtime /usr/local/Ascend/Ascend-Docker-Runtime然后在/etc/docker/daemon.json中注册 runtime{ runtimes: { ascend: { path: /usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime, runtimeArgs: [] } } }重启 Docker 让配置生效systemctl restart docker验证 runtime 是否注册成功docker info | grep -A 5 runtimes看到 ascend 出现在 runtime 列表里就说明注册成功了。4.3 ASCEND_VISIBLE_DEVICES 的映射规则Ascend Docker Runtime 依赖ASCEND_VISIBLE_DEVICES环境变量决定把哪张卡放进容器。这个变量支持几种写法写法含义ASCEND_VISIBLE_DEVICES0只映射编号为 0 的卡ASCEND_VISIBLE_DEVICES0,1映射编号 0 和 1 两张卡ASCEND_VISIBLE_DEVICESall映射节点上所有卡ASCEND_VISIBLE_DEVICESnone不映射任何卡相当于纯 CPU 容器注意这个编号是宿主机的设备编号含义和npu-smi info里看到的编号一致。如果没设置这个变量Runtime 默认不会映射任何 NPU 设备容器里自然看不到卡。4.4 容器内 NPU 验证用宿主机装好的驱动目录挂载进容器验证docker run --rm -it \ --runtimeascend \ -e ASCEND_VISIBLE_DEVICES0 \ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \ -v /usr/local/dcmi:/usr/local/dcmi \ ubuntu:20.04 bash进入容器后执行npu-smi info如果能看到对应卡的信息说明 runtime 工作正常。这里有个细节容器内要执行npu-smi info要么容器镜像本身装了 npu-smi 工具要么把宿主机驱动目录里的工具挂进去。我上面命令里挂载了/usr/local/Ascend/driver就是为了让容器直接用宿主机编译好的二进制。如果容器里看不到卡按这个顺序排查Docker daemon 是否重启、runtime 路径是否对、ASCEND_VISIBLE_DEVICES是否设置、设备节点是否真的存在。这四个点能覆盖九成以上的问题。5. device-plugin 部署与 Kubernetes 资源调度5.1 device-plugin 的工作原理Kubernetes 提供了一种叫 Device Plugin 的机制让第三方硬件设备通过 gRPC 和 kubelet 通信。device-plugin 启动后会在节点的/var/lib/kubelet/device-plugins/目录下创建 socket通过这个 socket 向 kubelet 上报设备数量和健康状态。对昇腾 NPU 来说需要上报的资源名通常是huawei.com/Ascend310或huawei.com/Ascend910名字取决于卡的类型。K8s 把这种自定义资源当可计数资源处理调度时看节点上剩余数量够不够 Pod 的请求量。需要注意一点device-plugin 只负责让 K8s 知道节点上有多少资源真正把设备映射进容器的还是 Ascend Docker Runtime。所以接入完整的链路需要两个组件配合device-plugin 上报资源runtime 注入设备中间通过ASCEND_VISIBLE_DEVICES这个环境变量衔接。5.2 社区版 device-plugin 的部署有现成的昇腾 device-plugin 实现一般是一个 DaemonSet 的部署方式。部署前先拉取对应镜像在 yaml 里确认几个关键挂载volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: driver mountPath: /usr/local/Ascend/driver - name: dcmi mountPath: /usr/local/dcmi - name: log mountPath: /var/log/mindx其中/var/lib/kubelet/device-plugins是 kubelet 和 device-plugin 通信的 socket 目录必须挂载/usr/local/Ascend/driver和/usr/local/dcmi是 device-plugin 读取设备信息的依赖路径。部署命令kubectl apply -f ascend-device-plugin.yaml kubectl get pods -n kube-system | grep ascendPod 正常运行后查看 node 资源kubectl describe node node-name | grep Ascend正常情况下能看到huawei.com/Ascend310: 8这样的资源数量。如果这里没有输出说明 device-plugin 和 kubelet 的通信链路有问题去看 device-plugin 的 Pod 日志重点找 socket 连接失败、权限不足这两类报错。5.3 让 Pod 里的 runtime 知道该用哪张卡到这里还有一个容易被忽略的环节。Pod 调度到节点后容器启动时是靠ASCEND_VISIBLE_DEVICES决定用哪张卡但 device-plugin 本身并不负责设置这个环境变量。实际部署中要么在业务 Pod 的 yaml 里手动写明环境变量要么部署一个 mutating webhook 自动注入。我这边前期测试时是手动写 yaml后面任务多了就搭了一个 webhook 自动注入省得每个 yaml 都写一遍。手动方式的 Pod yaml 大致长这样apiVersion: v1 kind: Pod metadata: name: npu-test spec: containers: - name: npu-test image: ascendhub.huawei.com/public/ascend-pytorch:latest command: [bash, -c, npu-smi info sleep 3600] resources: limits: huawei.com/Ascend310: 1 requests: huawei.com/Ascend310: 1 env: - name: ASCEND_VISIBLE_DEVICES value: 0 volumeMounts: - name: driver mountPath: /usr/local/Ascend/driver volumes: - name: driver hostPath: path: /usr/local/Ascend/driver部署后看 Pod 是否调度到有资源的节点、容器内npu-smi info是否能看到卡。如果未设置环境变量Pod 即使调度成功容器内也看不到 NPU这是新手最容易踩的问题。5.4 资源调度测试验证调度器是否正确工作可以同时创建多个申请 NPU 的 Pod观察它们是均匀分布在不同节点上还是堆在同一个节点。再测试资源耗尽的情况在节点上把所有卡都申请完再创建一个新的 NPU Pod如果它一直 Pending说明调度器的资源统计是准确的。另外一个常被问到的问题是Pod 里能不能只请求不设置 limits。对昇腾 NPU 这种可计数设备K8s 要求 requests 和 limits 必须一致否则创建时会报错。这点和 CPU、内存的处理方式不一样写 yaml 时要注意。6. 监控体系接入与指标采集6.1 昇腾 NPU 的监控方案选型K8s 集群常规监控用 Prometheus 加 GrafanaNPU 部分需要额外一个 exporter 把昇腾设备状态转成 Prometheus 指标。昇腾生态里没有像 NVIDIA DCGM 那样统一的官方 exporter可用的方案主要有三种华为云 CCE 提供的 ascend-exporter功能完整但部分版本和社区集群集成方式有些耦合。社区开源的 ascend-npu-exporter基于 DCMI 接口读取指标部署灵活我这边用的就是这种。自己写脚本定时调npu-smi info输出临时应急可以稳定使用不建议。对比下来社区版 exporter 配合 DCMI 接口是最通用的方案。DCMI 是昇腾的设备管理接口驱动安装时已经具备不依赖额外的软件包。6.2 exporter 部署与指标验证exporter 一般也是部署成 Deployment通过环境变量指定监听端口apiVersion: apps/v1 kind: Deployment metadata: name: ascend-exporter namespace: monitoring spec: replicas: 1 selector: matchLabels: app: ascend-exporter template: metadata: labels: app: ascend-exporter spec: containers: - name: exporter image: ascend-exporter:latest ports: - containerPort: 9100 env: - name: ASCEND_EXPORTER_LISTEN_PORT value: 9100需要注意 exporter 必须能访问宿主机的 DCMI 设备接口一般通过挂载/usr/local/Ascend/driver、/usr/local/dcmi以及对应的设备节点来实现。部署后验证指标接口curl http://pod-ip:9100/metrics | grep ascend能看到类似ascend_npu_temperature、ascend_npu_memory_used_bytes、ascend_npu_ai_core_utilization这样的指标就说明采集正常。6.3 Prometheus 抓取配置如果是 kube-prometheus-stack 部署的 Prometheus可以通过 ServiceMonitor 来定义抓取规则apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: ascend-exporter namespace: monitoring spec: selector: matchLabels: app: ascend-exporter endpoints: - port: metrics interval: 15s如果 Prometheus 是手动部署的直接在 scrape_configs 里加 job 就行。6.4 Grafana 面板与告警规则指标进了 Prometheus 之后Grafana 里建面板把几个关键指标可视化NPU 温度ascend_npu_temperatureNPU 显存占用率ascend_npu_memory_used_bytes / ascend_npu_memory_total_bytesAI Core 利用率ascend_npu_ai_core_utilization板卡功耗ascend_npu_power_consumption告警规则方面我配了三个比较实用的NPU 温度超过 85 度持续 5 分钟触发警告。AI Core 利用率持续 5 分钟低于 10%检查是否有任务异常退出。节点 NPU 剩余数量低于阈值提醒扩容或排队。监控这一层不要追求指标数量多先把温度、显存、利用率、功耗这四个基础项抓好日常运维基本就够用了。7. 常见问题速查与排查技巧7.1 问题排查一览表现象可能原因排查方法npu-smi info看不到设备驱动未加载 / 设备节点缺失lsmod | grep drv检查/dev/davinci*驱动安装时报编译错误缺少 linux-headers / 内核版本不匹配安装对应版本的内核头文件容器内npu-smi info无输出ASCEND_VISIBLE_DEVICES 未设置确认环境变量和 runtime 配置Pod 一直 Pending节点没有可用 NPU 资源kubectl describe node看资源剩余Pod 启动但容器内无 NPU未注入设备 / 环境变量缺失检查 yaml 里 env 和挂载device-plugin 日志报错驱动目录没有正确挂载检查 DaemonSet 的 volume 配置exporter 抓取超时网络策略限制 / exporter 无法访问 DCMI确认 Service 端口和挂载配置7.2 排错实用技巧排错时有个快速定位思路先在宿主机上用npu-smi info确认硬件正常再手动运行 Docker 容器确认 runtime 正常然后通过 DaemonSet 部署确认设备插件正常最后看 K8s 资源。这四层逐层排查能快速缩小问题范围。CANN 算子运行报错时先看日志目录下的运行日志。设置ASCEND_GLOBAL_LOG_LEVEL1可以把日志级别调到 DEBUG能拿到更详细的算子报错信息。定位完问题记得把日志级别调回去否则生产环境会产生大量日志把磁盘塞满。还有一个我踩过两次的坑K8s 节点重启或 Docker 重启之后device-plugin Pod 可能因为 kubelet 的 device plugin socket 重建而变成 CrashLoopBackOff。这时候直接把 DaemonSet 滚动重启一遍就行不需要重新配置任何东西kubectl rollout restart daemonset ascend-device-plugin -n kube-system这类问题有时候不会立刻出现机器重启后才暴露所以我把 Device Plugin 的存活探针和重启策略写进了部署模板自动恢复省得半夜被报警钉起来。8. 个人实操中的几点体会昇腾 NPU 接 K8s 这套链路单个组件装起来都不算难难点在于版本配套和组件之间衔接。版本配套一定要在动手之前解决建议先在测试机跑通最小验证再往生产环境铺。一个小技巧在宿主机上把各环节验证命令整理成一个脚本安装完直接跑一遍就能定位问题所在。比如驱动验证跑npu-smi inforuntime 验证跑一次容器映射测试device-plugin 验证看 kubelet 日志里是否有设备上报记录。这样每次换机器、换版本的时候能省下大量手工逐个排查的时间。后续如果任务量继续增长还可以围绕这套环境做弹性伸缩、节点池管理、模型服务自动扩容这些都是建立在 NPU 被 K8s 纳管之后才有能力去做的方向。