KubeNPU:用 eBPF 实现 Kubernetes 上按 Pod 统计 NPU/GPU 用量
KubeNPU Per-Pod NPU/GPU Usage on Kubernetes via eBPF
KubeNPU 通过 eBPF 在 drm_ioctl 内核函数挂载 fentry 程序,按 Pod 统计 NPU/GPU 加速器调用次数并导出到 Prometheus,指标含 kubenpu_ioctl_total 与 kubenpu_device_info。
在 Kubernetes 上按 Pod 查看 NPU 和加速器使用情况。
KubeNPU 使用 eBPF 按 Pod 统计加速器调用次数,并将其导出到 Prometheus。
工作原理
每个 DRM 和 accel 驱动都会让用户态经过同一个内核函数,
drm_ioctl。agent 在该处挂载一个 fentry 程序并在内核内进行过滤:
ffmpeg in a pod
└─ ioctl(fd, DRM_IOCTL_I915_GEM_EXECBUFFER2)
└─ drm_ioctl()
└─ fentry program
├─ filp->f_inode->i_rdev → 226:128
├─ devices[226:128] → vendor id, or drop
├─ cmd & 0xff → 0x69
├─ ioctls[vendor, 0x69] → kind=submit, or drop
└─ ringbuf ← {timestamp, cgroup_id, tgid, cmd, major, minor, kind}
这两个 map 都在程序挂载前由 Go 填充:设备发现会遍历 /dev/dri 和 sysfs,根据驱动名称匹配厂商,并上传其 ioctl 表。超过一半的调用根本不会到达用户态。
用户态随后将 cgroup_id 转换为 Pod 名称:
cgroup_id 117316
→ /sys/fs/cgroup/kubepods.slice/.../cri-containerd-079adc….scope
→ CRI ContainerStatus
→ default / ffmpeg-vaapi-test / ffmpeg
cgroup 索引在未命中时重建,而不是按定时器重建——启动后创建的 Pod 会在一个事件内被捕获。
这在各厂商之间表现一致,因为 ioctl 编号是冻结的 uAPI。与内核函数名不同,它们不会在不同版本之间变化。
指标
kubenpu_ioctl_total{pod,namespace,container,device,vendor,kind="submit|alloc|wait"}
kubenpu_device_info{device,vendor,driver,pci_id,numa_node}
kubenpu_events_dropped_total{reason}
kubenpu_cgroup_index_rebuilds_total
device 是 PCI 地址,重启后保持不变。card1 则不行,因为其次设备号可能会变化。节点名称位于 kubenpu_device_info 中。
计数是精确的,从不估算。它们是调用次数,而非利用率:在 Intel UHD 620 上,提交量多 4 倍的工作负载使用的设备时间反而少 7 倍。
计数从 agent 启动时开始,而不是从 Pod 开始。Pod 在此之前所做的任何操作都会缺失,主要是 kind="alloc",它只在启动时发生一次。每次 DaemonSet 滚动更新后也是如此。
快速测试
Docker,单主机
docker build -t kubenpu-local .
docker run --rm --name kubenpu --privileged \
-p 127.0.0.1:8080:8080 \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro \
-v /sys:/sys:ro -v /dev:/dev:ro \
-v /run/k3s/containerd:/run/k3s/containerd:ro \
kubenpu-localcurl -s localhost:8080/metrics | grep kubenpu_--privileged 仅用于快速测试。在 Kubernetes 中,agent 以 CAP_BPF 和 CAP_PERFMON 运行。
本地运行,不使用容器
make build sudo ./bin/agent -debug
-debug 会开启完整的验证器日志——没有它,被拒绝的程序只会打印两行无用信息。
要在接触内核之前查看 agent 所看到的内容:
make build-kubenpuctl ./bin/kubenpuctl devices
DRIVER PCI ID ADDRESS NODES VENDOR
i915 8086:5917 0000:00:02.0 226:1,226:128 i915
这仅读取 sysfs,不需要任何特权。如果你的设备在 VENDOR 列中显示为 -,说明 KubeNPU 找到了硬件,但尚无对应实现。
使用你自己的 vmlinux.h
bpf/vmlinux.h 已检入,可在任何支持 BTF 的内核上工作,因为该程序是 CO-RE 的,会在加载时重定位字段偏移。要从你自己的内核重新生成它:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
make generate
make buildKubernetes
Helm
helm install kubenpu deploy/helm -n kubenpu --create-namespace
kubectl -n kubenpu port-forward daemonset/kubenpu 8080:8080
curl -s localhost:8080/metrics | grep kubenpu_ioctl_total如果你运行 Prometheus Operator,请设置 serviceMonitor.enabled=true。
Kustomize
kubectl apply -k deploy/kustomize/base
可选组件:deploy/kustomize/components/servicemonitor 和
deploy/kustomize/components/accel。
Grafana Tanka
cd deploy/tanka tk env set environments/default --server=<api-server-url> tk apply environments/default
在 environments/default/main.jsonnet 中覆盖 _config,键与 Helm 的 values.yaml 相同。
要求
- Linux 6.1+ 且支持 BTF(
CONFIG_DEBUG_INFO_BTF=y) - cgroup v2,使用 systemd 或 cgroupfs cgroup 驱动
- CRI v1 运行时,socket 可被 agent 访问
检查节点是否使用 cgroup v2:
ls /sys/fs/cgroup/cgroup.controllers
两种 kubelet cgroup 驱动均受支持:/sys/fs/cgroup 下的 kubepods.slice 是 systemd 驱动,单独的 kubepods 是 cgroupfs。
已测试硬件
| 驱动 | 设备 | ioctl | 状态 | 硬件 | 内核 | 集群 | 工作负载 |
|---|---|---|---|---|---|---|---|
i915 |
Intel iGPU | submit、alloc、wait | 已验证 | Intel UHD 620,8086:5917 |
7.0,Ubuntu | k3s、containerd、cgroup v2、systemd 驱动 | ffmpeg、h264_vaapi |
i915 |
Intel iGPU | submit、alloc、wait | 已验证 | Intel UHD 620,8086:5917 |
7.0,Ubuntu 26.04(Proxmox KVM 客户机,VFIO) | kubeadm v1.37、containerd 2.2、cgroup v2、cgroupfs 驱动 | ffmpeg、h264_vaapi |
ivpu |
Intel NPU,Core Ultra | submit、alloc、wait | 已验证 | Intel Arrow Lake NPU,8086:ad1d |
7.0,Ubuntu 24.04(KVM 客户机,VFIO,force_snoop=1) |
k3s、containerd、cgroup v2、systemd 驱动 | OpenVINO benchmark_app -d NPU |
不支持 NVIDIA
CUDA 不经过 drm_ioctl,因此 KubeNPU 所做的一切都不适用于它。
请使用 DCGM exporter。
添加你的硬件
复制 pkg/hw/ivpu/ 并填入内核 uapi 头文件中的编号。
路线图
- 更多硬件,从生产环境中使用的加速器开始,例如
Qualcomm Cloud AI 100(
qaic,可在 AWS DL2q 上使用)和 Intel Gaudi (habanalabs)。这两个驱动都经过drm_ioctl。 - TPU 及其他驱动不使用
drm_ioctl的加速器。这需要 不同的钩子,目前仍在研究中。 sched_ext放置提示:在不更改应用的情况下,在靠近其使用的加速器的 CPU 上运行每个任务。- BPF LSM,因此 KubeNPU 还可以允许或阻止 Pod 对设备的访问,而不仅仅是观察它。
许可证
Apache 2.0。bpf/ 下的 eBPF 程序是 GPL,它使用的辅助函数仅限 GPL,否则程序将无法加载。
来源:Hacker News · github.com

