
一、背景
随着 AI 推理和训练场景的普及,如何在 Kubernetes 集群中统一管理和调度异构算力芯片成为一个日益迫切的需求。Kubernetes 提供了 Device Plugin 机制,允许厂商通过标准的 gRPC 接口将专用硬件(GPU、NPU、FPGA 等)注册到集群中,实现与 CPU/内存一致资源的调度体验。
华为昇腾生态提供了 ascend-device-plugin(隶属于 MindX DL 项目),用于将昇腾 NPU 注册到 K8s。本文以 Atlas 200I A2 加速模块(搭载 310B1 芯片) 为目标硬件,记录完整的部署实战过程。
组件 | 版本 |
|---|---|
节点系统 | openEuler (aarch64) |
NPU 型号 | Ascend 310B1 (Atlas 200I A2) |
DCMI | 24.1.rc1 |
CANN | 8.0.RC1 |
K8s 运行时 | Docker |
目标设备插件 | ascend-device-plugin v26.0.0 |
在接入 K8s 之前,第一步一定是先在 Docker 中验证基础功能可用。这一步我们的目标是确认 device-plugin 容器能正确调用宿主机的 DCMI 接口,成功识别出 310B1 芯片。
npu-smi info

驱动正常,芯片健康。
按照官方文档:https://www.hiascend.com/developer/ascendhub/detail/a592da7bd2ab4dffa8864abd4eac5068
使用device-plugin:v7.3.1版本一直报错,即使加入了所有得依赖,最终依然报错HDC初始化失败,不论是否使用Ascend Docker Runtime都不行。最终决定直接使用docker运行device-plugin镜像,依然失败:

最终通过翻看官方文档,找到一处关键地方,如下,需要映射hdcBasic.cfg文件。加入此映射后,docker容器不再报错hdc初始化失败。

docker run --rm -it --privileged --network=host \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/lib64:/usr/lib64:ro \
-v /dev:/dev -v /sys:/sys \
-v /etc/hdcBasic.cfg:/etc/hdcBasic.cfg:ro \
-v /etc/sys_version.conf:/etc/sys_version.conf:ro \
-v /etc/ascend_install.info:/etc/ascend_install.info:ro \
-v /var/slogd:/var/slogd:ro \
-v /var/dmp_daemon:/var/dmp_daemon:ro \
-v /var/queue_schedule:/var/queue_schedule:ro \
-v /dev/shm:/dev/shm \
-e LD_LIBRARY_PATH=/usr/lib64:/usr/local/Ascend/driver/lib64/... \
ascend-k8sdeviceplugin:v7.3.1 \
/usr/lib64/ld-linux-aarch64.so.1 /usr/local/bin/device-plugin -logLevel=0
这里有几个不寻常的细节需要注意:
/var/slogd、/var/dmp_daemon、/var/queue_schedule 是可执行文件,不是目录也不是 socket,挂载时需要 type: File + readOnly/dev/shm 必须用 hostPath,不要用 emptyDir——dmp_daemon 在 /dev/shm/iam/ 和 /dev/shm/dmp/ 下有共享内存通信文件ld-linux-aarch64.so.1 前缀,因为容器内 glibc 版本与宿主机不兼容,直接执行会触发 SIGBUS 崩溃运行结果:
chipName: 310B1, devType: Ascend310B
[ERROR] get chip aicore count failed, err: invalid ai core num 1.000000
芯片识别到了,但报错 invalid ai core num 1.000000——这就是本文的核心问题。
错误日志非常明确:
devicefactory/entry.go:37 init device manager failed
server/manager.go:162 get chip aicore count failed, err: invalid ai core num 1.000000
从 server/manager.go:162 开始追踪调用链:
调用链 1:入口
// server/manager.go:162
cnt, err := hdm.manager.GetChipAiCoreCount()
调用链 2:DCMI 查询
// pkg/device/ascendcommon.go:1309
func (m *AscendManager) GetChipAiCoreCount() (int64, error) {
chipAICore := float64(m.TotalResource.Computing.Aic) // DCMI 返回 1.000000
cnt, err := getAiCoreCount(chipAICore)
...
}
调用链 3:致命 range check
// pkg/device/ascendcommon.go:1335
func getAiCoreCount(chipAICore float64) (int64, error) {
intAICore := int64(chipAICore)
if intAICore < common.MinAICoreNum || intAICore > common.MaxAICoreNum {
return 0, fmt.Errorf("invalid ai core num %f", chipAICore)
}
...
}
调用链 4:常量定义(问题所在)
// pkg/common/constants.go:340
MinAICoreNum = 8
MaxAICoreNum = 36
问题出在 constants.go 的第 340 行:
MinAICoreNum = 8
DCMI 24.1.rc1 对 310B1 芯片返回的 AI Core 数量是 浮点数1.000000(310B1 只有 1 个物理 AI Core)。getAiCoreCount 函数将其转为 int64(1),然后检查 [8, 36] 的合法范围——1 < 8,直接被拒绝。
换句话说:device-plugin 的 AI Core 数量范围默认假设芯片至少有 8 个 AI Core,而 310B1 只有 1 个。这是一个代码层面没有考虑 310B1 这类低功耗推理芯片的兼容性 bug。
在 pkg/common/constants.go 中,将 MinAICoreNum 从 8 改为 1:
// 修复前
MinAICoreNum = 8
// 修复后——310B1 只有 1 个 AI Core
MinAICoreNum = 1
仅需一行代码改动。但问题在于:device-plugin 的编译依赖 CGO(通过 dlopen 动态加载 DCMI 库),而且最终需要运行在 aarch64 架构上。
我们采用多阶段构建策略:第一阶段用 Go 交叉编译,第二阶段用轻量 Ubuntu 做运行时:
# ===== 阶段 1:编译 =====
FROM golang:1.23 AS builder
WORKDIR /build
COPY ascend-device-plugin/ /build/
COPY ascend-common/ /ascend-common/
RUN go env -w GOPROXY=https://goproxy.cn,direct && \
go mod download && \
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
go build -ldflags="-s -w" -o device-plugin .
# ===== 阶段 2:运行时 =====
FROM ubuntu:22.04
COPY --from=builder /build/device-plugin /usr/local/bin/
RUN chmod 550 /usr/local/bin/device-plugin
说明:基础镜像分别需要 golang:1.23 和 ubuntu:22.04,在国内环境建议通过 DaoCloud 代理拉取:
docker pull docker.m.daocloud.io/library/golang:1.23
docker pull docker.m.daocloud.io/library/ubuntu:22.04
docker tag docker.m.daocloud.io/library/golang:1.21 golang:1.21
docker tag docker.m.daocloud.io/library/ubuntu:22.04 ubuntu:22.04
tar -xzf device-plugin-310B1-fixed.tar.gz
bash build.sh
# 输出: Successfully tagged ascend-k8sdeviceplugin:v26.0.0.fixed
kubectl apply -f device-plugin-310B1.yaml
输出:
serviceaccount/ascend-device-plugin created
clusterrole.rbac.authorization.k8s.io/ascend-device-plugin created
clusterrolebinding.rbac.authorization.k8s.io/ascend-device-plugin created
daemonset.apps/ascend-device-plugin-daemonset created
kubectl logs -n kube-system -l name=ascend-device-plugin-ds --tail=20
关键日志行:
deviceManager get cardList is [0], cardList length equal to cardNum: 1
chipName: 310B1, devType: Ascend310B
init device manager success ← 初始化成功!
update node label success ← 节点标签更新成功!
register Ascend310 to kubelet success ← 向 kubelet 注册成功!
Ascend310-0 Healthy ← 设备状态健康!
$ kubectl describe node node1 | grep Ascend310
Capacity:
huawei.com/Ascend310: 1
huawei.com/Ascend310: 1——NPU 资源已经成功注册到 Kubernetes,可以像 CPU 和内存一样被调度使用了。
在本次实战中,我们踩过的主要坑和对应的解决方案:
# | 踩坑点 | 现象 | 解决方案 |
|---|---|---|---|
1 | MinAICoreNum = 8 | invalid ai core num 1.000000 | 修改源码为 MinAICoreNum = 1 |
2 | 可执行文件挂载为目录 | MountVolume 失败 | type: File + readOnly: true |
3 | /dev/shm 用 emptyDir | dm_send_msg fail:2 | 改为 hostPath(共享内存文件在宿主机上) |
4 | 容器 glibc 不兼容 | SIGBUS 退出码 135 | 用宿主 ld-linux-aarch64.so.1 前缀启动 |
5 | RBAC 权限不足 | cannot get resource "nodes" | 创建 SA + ClusterRole + ClusterRoleBinding |
6 | ConfigMap 权限缺失 | cannot update resource "configmaps" | ClusterRole 添加 configmaps CRUD 权限 |
7 | kubelet API 无法访问 | host ip is invalid | 设置 NODE_IP env var (status.hostIP) |
8 | Golang 基础镜像拉取慢 | Docker Hub 超时 | 通过 DaoCloud 代理 docker.m.daocloud.io |
如果你的环境有多台 310B1 节点需要接入 K8s,按以下顺序操作:
npu-smi info 正常golang:1.23 + ubuntu:22.04)build.sh)kubectl label node <node> accelerator=huawei-Ascend310kubectl apply -f device-plugin-310B1.yamlkubectl describe node <node> | grep Ascend310huawei.com/Ascend310: 1 验证容器内可调用 NPU这次实战经历可以总结为三个层次的工作:
国产 AI 芯片 + 云原生的结合正在快速发展,生态工具也在持续迭代。希望本文的实战记录能为走在这条路上的朋友节省一些排查时间。如果本文对你有帮助,欢迎分享给更多的同行。