环境准备分几段走先创建 K8s 集群再用 GPU Operator 把 GPU Driver / Container Runtime 准备好最后装 Kueue 和 NVIDIA DRA Driver。1.1 创建 Kubernetes 集群集群使用 KubeClipper 创建。KubeClipper 1.6.0 默认支持 Kubernetes 1.36.1、containerd 2.2.4和本文环境一致详细步骤可以参考KubeClipper 1.6.0 发布kcctl 优化与 K8s 1.36 支持。快速创建单节点集群的命令如下curl -sfL https://oss.kubeclipper.io/get-kubeclipper.sh | KC_REGIONcn bash -kcctl deploykcctl create cluster --name demo --master NODE_IP --untaint-master集群起来后确认版本$ kubectl get node -owideNAME STATUS ROLES VERSION CONTAINER-RUNTIMElixd-test-gpu Ready control-plane v1.36.1 containerd://2.2.41.2 GPU 环境准备GPU OperatorGPU Driver、NVIDIA Container Toolkit 等基础组件使用 GPU Operator 安装。完整说明可以参考之前这篇GPU 环境搭建指南使用 GPU Operator 加速 Kubernetes GPU 环境搭建。本文后面会安装 NVIDIA DRA Driver所以安装 GPU Operator 时需要关闭 DevicePluginhelm repo add nvidia https://helm.ngc.nvidia.com/nvidia helm repo updatehelm upgrade --install --wait gpu-operator-n gpu-operator --create-namespacenvidia/gpu-operator–versionv26.3.1–set driver.enabledtrue–set devicePlugin.enabledfalse–set devicePlugin.enabledfalse关闭 DevicePlugin避免与后续安装的 DRA Driver 冲突。安装完成后确认 GPU Operator 组件正常运行$ kubectl get pods -n gpu-operatorNAME READY STATUSgpu-operator-7bcbd487f5-h8dsd 1/1 Runningnvidia-container-toolkit-daemonset-zbpbv 1/1 Runningnvidia-operator-validator-8l2r5 1/1 Running再确认节点能看到 GPU$ kubectl get node lixd-test-gpu -o jsonpath‘{.metadata.labels.nvidia.com/gpu.product}{“\n”}’Tesla-T41.3 安装 KueueKueue 使用 0.18.1安装方式和前两篇一样helm install kueue oci://registry.k8s.io/kueue/charts/kueue–version0.18.1–namespace kueue-system–create-namespace–wait --timeout 300s如果测试环境访问 registry.k8s.io 不稳定也可以用 GitHub Release 里的 chart 包helm install kueuehttps://github.com/kubernetes-sigs/kueue/releases/download/v0.18.1/kueue-0.18.1.tgz–namespace kueue-system–create-namespace–wait --timeout 300s1.4 接入 NVIDIA DRA Driver最后安装 NVIDIA DRA Driver 25.12.0helm upgrade --install nvidia-dra-driver-gpunvidia/nvidia-dra-driver-gpu–version 25.12.0-n nvidia-dra-driver-gpu–set gpuResourcesEnabledOverridetrue–create-namespace–wait --timeout 300s安装完成后先看 DRA Driver 组件$ kubectl get pods -n nvidia-dra-driver-gpuNAME READY STATUSnvidia-dra-driver-gpu-controller-76864c66cb-s8tch 1/1 Runningnvidia-dra-driver-gpu-kubelet-plugin-grkbg 2/2 Running再看 DeviceClass$ kubectl get deviceclassNAME AGEcompute-domain-daemon.nvidia.com 2m47scompute-domain-default-channel.nvidia.com 2m47sgpu.nvidia.com 2m47smig.nvidia.com 2m47svfio.gpu.nvidia.com 2m47s整卡调度用的是 gpu.nvidia.com。对应的 ResourceSlice 里能看到节点上的 T4$ kubectl get resourcesliceNAME NODE DRIVER POOLlixd-test-gpu-gpu.nvidia.com-p8cdq lixd-test-gpu gpu.nvidia.com lixd-test-gpu后面的 Job 会直接引用 gpu.nvidia.com 这个 DeviceClass实际可分配设备则来自这些 ResourceSlice。Demo使用 Kueue 调度 DRA 模式下的 GPU2.1 修改 Kueue DRA 配置DRA 和 Kueue 使用的资源名称并不是同一个。ResourceClaimTemplate 里写的是 deviceClassName: gpu.nvidia.com而 ClusterQueue 里扣配额用的是资源名所以这里需要做一次映射apiVersion: config.kueue.x-k8s.io/v1beta2kind: Configurationintegrations:frameworks:batch/jobresources:deviceClassMappings:name: nvidia.com/gpudeviceClassNames:gpu.nvidia.com这段配置的意思是只要 Workload 通过 ResourceClaimTemplate 申请 gpu.nvidia.com 这个 DeviceClassKueue 就把它折算成 nvidia.com/gpu 这个逻辑资源来扣配额。deviceClassMappings 将 DRA DeviceClass 映射为 Kueue 配额资源修改 Kueue manager config 后需要重启 kueue-controller-manager 让新配置生效。2.2 创建队列这次只用一个队列GPU 配额只给 1 张 T4。这样后面再提交第二个 Job 时Pending 状态会看得很清楚。apiVersion: v1kind: Namespacemetadata:name: p3-dra-demoapiVersion: kueue.x-k8s.io/v1beta2kind: ResourceFlavormetadata:name: p3-t4spec:nodeLabels:nvidia.com/gpu.product: Tesla-T4apiVersion: kueue.x-k8s.io/v1beta2kind: ClusterQueuemetadata:name: p3-dra-cqspec:namespaceSelector: {}resourceGroups:coveredResources:cpumemorynvidia.com/gpuflavors:name: p3-t4resources:name: cpunominalQuota: “8”name: memorynominalQuota: 16Giname: nvidia.com/gpunominalQuota: 1apiVersion: kueue.x-k8s.io/v1beta2kind: LocalQueuemetadata:name: p3-dra-queuenamespace: p3-dra-demospec:clusterQueue: p3-dra-cq注意这里的 coveredResources 里包含 nvidia.com/gpu。这是映射后的逻辑资源名不是 Pod 里直接写的扩展资源。2.3 用 ResourceClaimTemplate 申请整卡使用 DRA 之后Job 不再写 resources.limits.nvidia.com/gpu: 1而是引用一个独立的 ResourceClaimTemplate。这里 Kueue 做的事情很直接读取 Workload 引用的 ResourceClaimTemplate识别里面的 deviceClassName 和 count再通过 deviceClassMappings 折算成 ClusterQueue 里的配额资源。本文不展开 extended resource 路径避免把两种 DRA 接入方式混在一起。先创建 ResourceClaimTemplateapiVersion: resource.k8s.io/v1kind: ResourceClaimTemplatemetadata:name: single-gpunamespace: p3-dra-demospec:spec:devices:requests:- name: gpuexactly:deviceClassName: gpu.nvidia.comallocationMode: ExactCountcount: 1这里有两个细节容易混deviceClassName 是 gpu.nvidia.com不是 nvidia.com/gpuExactCount count: 1 表示申请 1 张整卡再提交 Kueue 管理的 JobapiVersion: batch/v1kind: Jobmetadata:name: kueue-dra-running-anamespace: p3-dra-demolabels:kueue.x-k8s.io/queue-name: p3-dra-queuespec:parallelism: 1completions: 1suspend: truetemplate:spec:resourceClaims:- name: gpu-claimresourceClaimTemplateName: single-gpurestartPolicy: Nevercontainers:- name: cuda-testimage: nvcr.io/nvidia/cuda:12.6.0-base-ubuntu24.04command: [“bash”, “-c”, “nvidia-smi -L echo KUEUE_DRA_RUNNING_A sleep 3600”]resources:claims:- name: gpu-claimJob 被 Kueue 准入后会从 Suspended 变成 Running$ kubectl get job,pod,workload -n p3-dra-demoNAME STATUS COMPLETIONSjob.batch/kueue-dra-running-a Running 0/1NAME READY STATUSpod/kueue-dra-running-a-kj7nw 1/1 RunningNAME QUEUE RESERVED IN ADMITTEDworkload.kueue.x-k8s.io/job-kueue-dra-running-a-c3b3c p3-dra-queue p3-dra-cq TrueResourceClaim 已经分配$ kubectl get resourceclaim -n p3-dra-demoNAME STATEkueue-dra-running-a-kj7nw-gpu-claim-c6j4j allocated,reserved容器里能看到 T4$ kubectl logs -n p3-dra-demo -l job-namekueue-dra-running-aGPU 0: Tesla T4 (UUID: GPU-6d8fdeb7-6247-e04e-81a7-7b42dc45180c)KUEUE_DRA_RUNNING_A2.4 看 Kueue 是否真的扣了 GPU 配额直接看 Workload 的准入结果status:admission:clusterQueue: p3-dra-cqpodSetAssignments:- count: 1flavors:nvidia.com/gpu: p3-t4resourceUsage:nvidia.com/gpu: “1”这说明 deviceClassMappings 已经生效用户写的是 ResourceClaimTemplateKueue 扣的是 nvidia.com/gpu 这个逻辑配额。再看看 ClusterQueue可以发现 GPU 配额已经被扣掉了status:admittedWorkloads: 1flavorsUsage:name: p3-t4resources:name: nvidia.com/gputotal: “1”2.5 第二个整卡 Job 会被 Kueue 卡住队列里只有 1 张 GPU 配额。如果再提交一个同样申请 single-gpu 的 Jobkueue-dra-running-a1 x gpu.nvidia.comkueue-dra-pending-b1 x gpu.nvidia.com$ kubectl get job,workload -n p3-dra-demoNAME STATUSjob.batch/kueue-dra-running-a Runningjob.batch/kueue-dra-pending-b SuspendedNAME QUEUE RESERVED IN ADMITTEDworkload.kueue.x-k8s.io/job-kueue-dra-running-a-c3b3c p3-dra-queue p3-dra-cq Trueworkload.kueue.x-k8s.io/job-kueue-dra-pending-b-494a3 p3-dra-queue再看 Workload 状态就能发现为什么第二个 Job 一直起不来status:conditions:reason: Pendingmessage: ‘couldn’‘t assign flavors to pod set main: insufficient unused quotafor nvidia.com/gpu in flavor p3-t4, 1 more needed’resourceRequests:name: mainresources:nvidia.com/gpu: “1”几个容易踩坑的点DeviceClass 和配额资源不是同一个东西ResourceClaimTemplate 里写的是deviceClassName: gpu.nvidia.comClusterQueue 里写的是coveredResources:nvidia.com/gpu两者靠 Kueue 配置里的 deviceClassMappings 关联起来。少了这段映射Workload 会被标成 Inadmissible原因类似DeviceClass gpu.nvidia.com is not mapped in DRA configurationResourceClaimTemplate 要单独创建这两个名字最容易看混single-gpu前面单独创建的 ResourceClaimTemplate定义“我要 1 张 gpu.nvidia.com 整卡”。gpu-claimPod 里的本地 claim 名字后面容器通过它来使用 GPU。Pod 里这段不是重新定义一个模板而是引用已经存在的 single-gpu 模板resourceClaims:name: gpu-claimresourceClaimTemplateName: single-gpu容器里再通过同一个 gpu-claim 关联到这次申请到的设备resources:claims:name: gpu-claim所以完整关系是ResourceClaimTemplate(single-gpu) - Pod resourceClaims(gpu-claim) - container resources.claims(gpu-claim)。Kueue 准入不等于设备已经分配这里要注意一点Kueue 只是负责“准入”真正把 GPU 分配给 Pod 的还是 kube-scheduler 和 DRA Driver。所以生产环境里建议同时关注 ResourceClaim 状态和 Pod 状态。如果希望 Workload 在 Pod 长时间起不来时释放 Kueue 配额可以结合 waitForPodsReady 做保护。小结到这里可以看到Kueue 并没有直接参与 GPU 分配而是站在 Job 准入这一层通过 deviceClassMappings 把 DRA 的设备申请转换成队列里的配额资源。这样既保留了 DRA 的设备模型也让 GPU 可以继续纳入 Kueue 的统一配额管理。组件 负责什么NVIDIA DRA Driver 把 GPU 作为 gpu.nvidia.com DeviceClass / ResourceSlice 发布出来Kueue 通过 deviceClassMappings 把 DRA 设备折算成 nvidia.com/gpu 配额kube-scheduler 在 Pod 调度阶段完成 ResourceClaim 的实际设备分配