首页
/ 深入解析 Kubernetes DRA 端到端测试驱动 dra-test-driver:架构、部署与实现原理

深入解析 Kubernetes DRA 端到端测试驱动 dra-test-driver:架构、部署与实现原理

2026-09-07 23:42:09作者:昌雅子Ethen

导读

本文以 test/e2e/dra/test-driver/README.md 为主体,系统讲解 Kubernetes 官方用于端到端验证动态资源分配(Dynamic Resource Allocation,DRA)机制的参考测试驱动 dra-test-driver:它如何在单个二进制中同时扮演"资源驱动控制器"与"kubelet 资源插件",如何通过 ConfigMap 参数控制设备注入行为并模拟网络附加/主机本地、共享/独占、受限/无限等多种资源供给模型。读完本文,你将掌握该驱动的完整命令行参数、单机 local-up-cluster.sh 上的端到端部署与验证步骤,并能对照 app 目录源码理解 DRA 从 ResourceClaim 分配到 CDI 设备注入的完整调用链,为编写自己的 DRA 驱动提供可直接借鉴的参考实现。


DRA 与测试驱动:它解决什么问题

动态资源分配(DRA)是 Kubernetes 中"让 Pod 声明式地申请并消耗非 CPU/内存类资源(GPU、FPGA、加速卡、网络设备等)"的基础设施机制,由 DynamicResourceAllocation feature gate 控制。该门控定义于 pkg/features/kube_features.go,并且它是一系列后续 DRA 子特性(DRAAdminAccessDRAConsumableCapacityDRAHealth 相关、DRAResourceClaimDeviceStatus 等)的依赖前置门控,在 pkg/features/kube_features.go 中可见其版本门控矩阵。

要验证这样一套跨 kube-apiserver、调度器、kubelet 与运行时(通过 CDI 注入)的复杂机制,Kubernetes 官方在 test/e2e/dra 中内置了一个名为 dra-test-driver 的测试驱动。它的设计哲学很明确:功能刻意保持最小化,但工程实践对齐真实驱动——支持 leader election、metrics、与 Kubernetes 一致的日志体系,作为 DRA 驱动开发的"最小可行样例"。其文档与源码即位于仓库的 test/e2e/dra/test-driver 目录。


架构与设计:单二进制、双角色

一个二进制承载两类组件

README 明确指出,本驱动在同一二进制中同时实现 DRA 架构中的两类核心组件:

  • controller(驱动控制器):负责与 ResourceClaim/DeviceClass 交互、执行分配决策、回写分配结果;
  • kubelet 资源插件(kubelet plugin):运行在节点上,负责向 kubelet 汇报资源可用性、在 Pod 启动前"准备"资源。

之所以放进单一二进制,是为了把样板代码量降到最低;真实驱动完全可以按需拆成两个独立二进制(例如控制器走 Deployment,节点插件走 DaemonSet)。README 还补充了两者的运行形态:控制器可以作为 Deployment 运行并配合 leader election 保证多副本互斥;kubelet 插件可用 DaemonSet 铺到所有节点。此外两者都能以集群外 Kubernetes client 方式运行——kubelet 插件在配合 port-forward 时也支持这种模式,这正是测试期间的使用方式。

从当前仓库源码快照看,app/server.go 中实际注册并完整暴露的子命令为 kubelet-plugin;控制器的分配逻辑则沉淀在 k8s.io/dynamic-resource-allocation 库族(见下节)以及 driver 自身的接口实现中。阅读源码时应以代码现状为准。

分层解耦:通用库 + 驱动接口

架构上驱动被刻意分成三层(见 Design 一节):

  1. 通用交互层k8s.io/dynamic-resource-allocation 库族负责与 Kubernetes 各组件通信。当前仓库快照中该库位于 staging/src/k8s.io/dynamic-resource-allocation,包含 kubeletpluginresourceclaimresourcesliceclientdeviceclassstructured 等子包。这些包"只依赖接口",与具体驱动逻辑解耦;
  2. 驱动适配层test-driver 自己实现库定义的接口,把"如何模拟设备"这类领域逻辑放进来;
  3. 入口编排层:Cobra 命令与各类 flag 组装,见下文。

README 同时提到,与 ResourceClaim 交互的通用 controller 部分长期来看可以被拆成可复用的独立工具包,体现了其"测试代码也按可演进标准组织"的取向。

入口与命令组装

二进制入口为 dra-test-driver.go,它调用 app.NewCommand() 构造 Cobra 命令,并用 k8s.io/component-base/cli.Run 启动。入口文件通过匿名导入一次性注册了:

  • JSON 日志输出支持(component-base/logs/json/register);
  • Prometheus 默认注册表中的 leader election、workqueue、restclient client 与版本 metrics(component-base/metrics/prometheus/... 系列)。

这意味着驱动在启动之初就复用了 Kubernetes 控制面组件标配的日志、metrics 与 leader election 设施——这正是 README 所说的"与 Kubernetes 相同的日志选项与 best practices"。

server.goNewCommand() 中可以看到标志分组(flag sets)的完整组织方式:

Flag 组 关键 flag 默认值 说明
logging -v--logging-format 复用 k8s.io/component-base/logs 的完整日志体系
Kubernetes client --kubeconfig 空(自动尝试 in-cluster) 集群外运行时的 kubeconfig 路径;也读 KUBECONFIG 环境变量
--kube-api-qps 50 与 apiserver 通信的 QPS
--kube-api-burst 100 与 apiserver 通信的突发上限
http server --http-endpoint 空(禁用) 诊断 HTTP 服务监听地址,如 :8080;开启后可提供 metrics/pprof/leader election 健康检查
--metrics-path /metrics Prometheus 指标路径,空则关闭
--pprof-path 空(禁用) pprof 性能剖析路径
CDI --drivername test-driver.cdi.k8s.io 资源驱动名,全局唯一标识
other --feature-gates 日志相关的 feature gate(含 JSON 日志等)

日志体系与 Kubernetes 完全同源(klog + logsapi),PersistentPreRunE 会在启动最早阶段 ValidateAndApply 日志配置并据此输出最终生效的 flags。


参数注入模型:从 ConfigMap 键值对到容器环境变量

README 用一小节概括了本驱动的"业务协议",而源码把它落得相当具体,是理解 DRA 数据流的最佳入口:

  1. 参数来源是键值对:驱动接受的"有效参数"是存放在 ConfigMap 中的 string→string 键值对(管理员与用户均可提供);
  2. 按来源加前缀写入状态:参数随分配结果回写到 ResourceClaimStatus,用户侧参数加 user_ 前缀、来自 DeviceClass 的管理员侧参数加 admin_ 前缀;
  3. 以 JSON map 形式存入 ResourceHandle:控制器负责把这些参数序列化为 JSON map 放进分配结果;
  4. kubelet 插件转成环境变量:节点插件在准备资源时读取这些参数,最终以环境变量形式注入到每个使用该资源的容器中。

源码对照:extractParametersnodePrepareResource

前缀与"参数→环境变量"的转换逻辑实现在 kubeletplugin.go

func extractParameters(parameters runtime.RawExtension, env *map[string]string, admin bool) error {
    kind := "user"
    if admin {
        kind = "admin"
    }
    ...
    for key, value := range data {
        (*env)[kind+"_"+key] = value
    }
    return nil
}

调用点位于 nodePrepareResourcekubeletplugin.go),其完整处理链路为:

  • 遍历 claim.Status.Allocation.Devices.Results,只处理 driverName 匹配本驱动的结果;
  • 通过 resourceclaim.ConfigForResult 取回对应本次分配使用的 config(含 AllocationConfigSourceClass 标记,用于区分来源是 DeviceClass 还是 ResourceClaim),并依次调用 extractParameters 合并进 env map(后者覆盖前者,"last one wins");
  • 额外注入一个标识变量:claim_<claimName>_<requestName>=true,容器可通过它反查"哪个设备被映射进了本容器",其中非字母数字字符会被替换为下划线(见 kubeletplugin.go);
  • 为每个设备构造 CDI 设备 ID,形如 test-driver.cdi.k8s.io/test=claim-<claimUID>-<requestName>,并把 env 拼成排序后的字符串列表写进 CDI spec 的 ContainerEdits.Env
  • 将 CDI spec 落盘为 JSON 文件,路径为 <cdi-dir>/<driverName>-<claimUID>-<requestName>.jsongetJSONFilePath)。

一个值得注意的健壮性细节:如果 env 列表为空,代码会跳过注入——因为 CDI 不支持空的 ContainerEdits,强行写入会导致 CDI device injection failedkubeletplugin.go),这体现了 DRA 与 CDI 交互的一个真实边界条件。

模拟不同资源供给模型

README 指出,驱动的资源可用性是"可配置的",用于在测试中模拟不同场景:

  • 网络附加型 vs 主机本地型:前者在所有运行了节点驱动的节点上均可用,后者只在分配发生的节点上可用;
  • 共享 vs 非共享分配:一个 claim/设备是否可被多个 Pod 同时引用;
  • 无限 vs 有限资源:有限时按"整个集群或单个节点上的分配次数"这一简单计数来约束上限。

在单节点模拟层面,--num-devices(默认 4)控制每个节点上报的模拟设备数量,代码会按 device-%02d 的命名规则生成设备并包装成 resourceslice.DriverResources 经插件发布(server.go)。


kubelet 插件运行细节:准备、清理与测试钩子

ExamplePlugin 通过断言 var _ kubeletplugin.DRAPlugin = &ExamplePlugin{} 在编译期保证完整实现插件接口(kubeletplugin.go)。

NodePrepareResource:幂等的设备准备

nodePrepareResourceprepared map[ClaimID][]kubeletplugin.Device 记录已准备的 claim,因此对同一 claim 的重复调用是幂等的——文件会被"再写一遍"而不会出错。unprepare 侧同样幂等:文件不存在时直接返回 nil(kubeletplugin.go)。这种"确定性文件名 + 幂等写/删"的设计让 prepare/unprepare 无需维护复杂状态机。

设备元数据与健康状态上报

面向较新的 DRA 子特性,插件还支持:

  • 设备元数据(device metadata)--enable-device-metadata 开启后,每个注入设备附带 driverName/pool/device 三个属性(kubeletplugin.go),API 版本固定使用最新的 metadataapi(v1beta1);
  • 设备健康状态流(WatchHealthStatus:插件实现版本中立的健康上报辅助 API,由 kubeletplugin helper 自动翻译成 kubelet 支持的 gRPC 版本(DRAResourceHealth v1/v1alpha1 可通过 Options.DisableHealthV1/DisableHealthV1alpha1 单独禁用以测试降级路径);上报内容来自一个可注入的控制通道 HealthControlChan,测试可据此动态把某设备标记为 Healthy/Unhealthy/Unknown(kubeletplugin.go);
  • 故障注入钩子SetNodePrepareResourcesFailureMode / SetNodeUnprepareResourcesFailureMode / SetGetInfoError / SetNotifyRegistrationStatusError 等方法让 e2e 测试能模拟 prepare 失败、注册失败、重试等异常路径(kubeletplugin.go);
  • gRPC 调用记录:通过 unary/stream 拦截器把所有入站调用的方法名、请求、响应与错误记录下来(GetGRPCCalls/CountCalls),供测试断言 kubelet 与插件之间的交互时序(kubeletplugin.go)。

插件启动时通过 StartPluginkubeletplugin.go)统一初始化:Unix socket 目录、CDI 目录(默认 /var/run/cdi)、插件注册目录、以及发布 ResourceSlice。收到 SIGINT/SIGTERM 后走优雅退出,删除 Unix socket。


端到端部署与验证:在 local-up-cluster 上跑起来

第一步:编译并以单机集群模式启动

按 README 指引,先构建好 Kubernetes,然后在第一个终端运行 hack/local-up-cluster.sh,并开启 DRA 特性:

RUNTIME_CONFIG="resource.k8s.io/v1alpha3" FEATURE_GATES=DynamicResourceAllocation=true ALLOW_PRIVILEGED=1 ./hack/local-up-cluster.sh -O

RUNTIME_CONFIG 需要与当前二进制所支持的 resource.k8s.io API 版本匹配。README 写作时使用 v1alpha3,而本仓库 deploy/example 下的 YAML 示例已使用稳定化的 resource.k8s.io/v1 API(对应 k8s.io/api/resource/v1)。请以你实际构建的 Kubernetes 版本所暴露的 API 版本为准调整。

第二步:准备目录并以 kubelet 插件模式运行驱动

在另一个终端中,先创建驱动运行所需的目录并放开权限,然后以集群外客户端方式运行 kubelet 插件(KUBECONFIG 指向 local-up 生成的 admin.kubeconfig):

sudo mkdir -p /var/run/cdi
sudo mkdir -p /var/lib/kubelet/plugins/test-driver.cdi.k8s.io
sudo mkdir -p /var/lib/kubelet/plugins_registry
sudo chmod a+rx /var/lib/kubelet /var/lib/kubelet/plugins
sudo chmod a+rwx /var/run/cdi /var/lib/kubelet/plugins_registry /var/lib/kubelet/plugins/test-driver.cdi.k8s.io
KUBECONFIG=/var/run/kubernetes/admin.kubeconfig go run ./test/e2e/dra/test-driver -v=5 kubelet-plugin --node-name=127.0.0.1

其中各目录的用途如下(对应 kubelet-plugin 子命令的 flags):

路径 对应 flag 用途
/var/run/cdi --cdi-dir(默认 /var/run/cdi 存放插件动态生成的 CDI JSON 文件,运行时(CRI)据此注入设备
/var/lib/kubelet/plugins/test-driver.cdi.k8s.io --datadir<plugins目录>/<driverName> 创建 DRA 驱动专属 Unix domain socket 的目录
/var/lib/kubelet/plugins_registry --plugin-registration-path kubelet 插件注册机制扫描 socket 的目录

--node-name=127.0.0.1 声明本插件负责的节点名(local-up 单机模式下节点即为 127.0.0.1);-v=5 提高日志级别便于观察 NodePrepareResource 等调用过程。

第三步:创建 DeviceClass 与使用内联 claim 的 Pod

导出 kubeconfig 后依次 apply 驱动示例清单:

$ export KUBECONFIG=/var/run/kubernetes/admin.kubeconfig
$ kubectl create -f test/e2e/dra/test-driver/deploy/example/deviceclass.yaml
resourceclass/example created
$ kubectl create -f test/e2e/dra/test-driver/deploy/example/pod-inline.yaml
configmap/pause-claim-parameters created
pod/pause created

$ kubectl get resourceclaims
NAME             CLASSNAME   EXAMPLE    AGE
pause-resource   example     allocated,reserved   19s

$ kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
pause   1/1     Running   0          23s

两点说明(以当前仓库 YAML 为准):

  • deviceclass.yaml 定义一个名为 exampleDeviceClass,用 CEL 选择器 device.driver == "test-driver.cdi.k8s.io" 圈定由本驱动管理的设备;
  • pod-inline.yaml 展示 DRA 的 inline claim(内联申请) 写法:定义 ResourceClaimTemplate,Pod 通过 spec.resourceClaims[].resourceClaimTemplateName 引用,并在容器 resources.claims 声明使用;模板中通过 config[].opaque.parameters(如 a: b)传入驱动参数。

看到 ResourceClaim 进入 allocated,reserved 状态且 pause Pod 进入 Running,即说明整套链路(调度分配 → 插件准备资源 → CDI 文件生成 → 运行时注入)已经打通。

更多场景示例

deploy/example 目录还提供了覆盖其他场景的清单,可在理解上述基础流程后逐一尝试:

这些清单通过 embed.go 被嵌入测试二进制,供 e2e 测试直接读取使用,这也是 deploy/example 既是"文档示例"又是"测试资产"的原因。

多节点集群的限制

README 特别提醒:截至其写作时点,还没有包含该测试驱动的容器镜像,因此它无法直接部署到"普通(多节点)集群"上跑 Deployment/DaemonSet。它在多节点场景下主要服务于 local-up-cluster.sh 与仓库内 e2e 测试框架(测试以集群外 client + port-forward 的方式驱动它,正如 README 的 Usage 一节 所述)。


在端到端测试中的角色

作为 test/e2e/dra 的测试支柱,该驱动提供了大量面向断言的测试面(上文已逐一列出):gRPC 调用记录(验证 kubelet 何时调用插件的 Prepare/Unprepare)、故障注入(验证失败与重试路径)、健康控制通道(验证健康上报与驱逐联动)。配套的 gomega/matchers.go 提供面向测试的匹配器,方便用 Ginkgo/Gomega 风格断言复杂对象状态。app/options.gooptions.go 中的函数式选项(TestOption/CancelMainContext)则是把"进程内直接启动插件"做成可测试组件的关键——e2e 框架可以不经 CLI,直接以库方式嵌入启动插件并注入取消钩子。


设计来源与先例

README 的 Prior art 一节说明,本驱动部分代码派生自 Kubernetes CSI 生态的 external-resizer,其 controller 逻辑又与 sig-storage-lib-external-provisioner 一脉相承。这解释了为何驱动的控制器侧会复用一套"通用外部控制器 + 驱动接口"的模式:DRA 的资源驱动本质上和 CSI 的外部控制器/provisioner 一样,都是"围绕特定 Kubernetes API 对象做状态机的独立进程",把与 API 交互的通用部分沉淀进库、把设备语义留给具体实现,正是这一族代码长期演化的最佳实践。


结语

dra-test-driver 规模不大,却是一份信息密度极高的"活文档":它演示了 DRA 驱动应有的分层(通用库 vs 接口实现)、生产级附属能力(logging/metrics/leader election)、以及测试友好设计(幂等、故障注入、调用记录、进程内启动)。无论你的目标是调试 Kubernetes 自身的 DRA 链路,还是参考它编写面向 GPU/加速器的真实驱动,都可以从 test/e2e/dra/test-driver/README.md 出发,对照 app/server.go(命令与参数)、app/kubeletplugin.go(插件核心逻辑)与 deploy/example(可运行清单)三份材料层层深入。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.79 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
390