跳到正文
Hacker News· rzcavid·· 7 天前AI 评分50

KubeNPU:用 eBPF 实现 Kubernetes 上按 Pod 统计 NPU/GPU 用量

KubeNPU Per-Pod NPU/GPU Usage on Kubernetes via eBPF

AI 导读

KubeNPU 通过 eBPF 在 drm_ioctl 内核函数挂载 fentry 程序,按 Pod 统计 NPU/GPU 加速器调用次数并导出到 Prometheus,指标含 kubenpu_ioctl_total 与 kubenpu_device_info。

正文 · AI 翻译

在 Kubernetes 上按 Pod 查看 NPU 和加速器使用情况。

KubeNPU 使用 eBPF 按 Pod 统计加速器调用次数,并将其导出到 Prometheus。

Per-pod ioctl counters for an ffmpeg pod on an Intel iGPU

工作原理

每个 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-local
curl -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 找到了硬件,但尚无对应实现。

kubenpuctl devices showing a supported i915 iGPU

使用你自己的 vmlinux.h

bpf/vmlinux.h 已检入,可在任何支持 BTF 的内核上工作,因为该程序是 CO-RE 的,会在加载时重定位字段偏移。要从你自己的内核重新生成它:

bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
make generate
make build

Kubernetes

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