# DRA P2---理解 DRA：ResourceSlice、Claim、Class 三角关系



![dra-p2.png](https://img.lixueduan.com/kubernetes/cover/dra-p2.png)

在上一篇 [DRA P1：DRA 能解决什么问题？从部署到使用的完整体验](https://www.lixueduan.com/posts/kubernetes/54-dra-p1-quickstart/) 中，我们部署了 DRA 并成功运行了第一个 GPU Pod。DRA 相比 DevicePlugin 最大的区别在于：资源展示更详细，资源申请也更灵活。靠三个 API 对象来实现：ResourceSlice、DeviceClass、ResourceClaim。

那么这三个对象各自干什么，它们之间怎么配合？本篇就来回答这个问题。

<!--more-->


## 1. 从 CSI 看 DRA：同一套设计思路

如果对 K8s 比较熟悉的同学就会发现，DRA 的设计思路和 CSI 有点类似，DRA 把异构设备的管理拆成了同样的三层：

![CSI vs DRA 三层架构对比](https://img.lixueduan.com/kubernetes/dra/dra-p2-comparison-csi-vs-dra.jpg)

---

| | 存储 (CSI) | 设备 (DRA) | 谁创建 |
|---|---|---|---|
| 供给 | PV | ResourceSlice | 驱动 (Driver) |
| 分类 | StorageClass | DeviceClass | 管理员 (Admin) |
| 需求 | PVC | ResourceClaim | 用户 (User) |


ps：严格来说 ResourceSlice 和 PV 不能完全等同——PV 是一块具体的存储卷，ResourceSlice 更像是节点上设备的目录清单。但从供需关系看，模式是一样的：

* **CSI**：PV 代表可用存储 → StorageClass 分类 → PVC 申请。
* **DRA**：ResourceSlice 代表可用设备 → DeviceClass 分类 → ResourceClaim 申请。

DRA 三者之间的关系，按数据流方向串起来就是：

```
ResourceSlice ──→ DeviceClass ──→ ResourceClaim ──→ 调度器分配
  (供给)            (分类)          (需求)           (决策)
```

- **ResourceSlice**：DRA 驱动发布节点上的设备信息，包括型号、显存、驱动版本等属性，以及可用容量
- **DeviceClass**：管理员用 CEL 表达式从 ResourceSlice 的设备属性中筛选出一类设备形成用户友好的分组，比如"所有 NVIDIA GPU"或"仅 A100"
- **ResourceClaim**：用户通过 `deviceClassName` 引用某个 DeviceClass，声明需要几个该类设备
- **调度器**：综合 ResourceSlice 的库存和 ResourceClaim 的需求，选出满足条件的节点和具体设备，完成分配

逐个来看。


## 2. ResourceSlice：资源提供方（Driver 视角）

### 2.1 从 DevicePlugin 到 ResourceSlice 

ResourceSlice 是 DRA 驱动向集群"公告"设备信息的方式, 可以理解为是对 DevicePlugin 提供的 `nvidia.com/gpu:4` 这个信息的扩展。

```bash
root@GB200-POD2-F06-Node05:~# kubectl get node gb200-pod2-f06-node05 -oyaml|grep "capacity:" -A 7
  capacity:
    cpu: "144"
    ephemeral-storage: 1840577300Ki
    hugepages-2Mi: "0"
    hugepages-16Gi: "0"
    hugepages-512Mi: "0"
    memory: 1002716864Ki
    nvidia.com/gpu: "4"
```



HAMi 提供的 `hami-device-plugin-nvidia` 为了能够感知 GPU 显存信息，也是只能通过给节点打 Annotations 方式实现，DRA 中的 ResourceSlice 则是原生支持了这些。

> 推荐阅读：[HAMi VGPU 原理分析 Part1：hami-Device-Plugin-Nvidia 实现](https://www.lixueduan.com/posts/kubernetes/29-hami-analyze-1-device-plugin-nvidia/#watchandregister)



### 2.2 结构

一个标准的 ResourceSlice 内容如下所示：

```yaml
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: gb200-pod2-f06-node05-gpu.nvidia.com-j7ggv
  ownerReferences:
  - apiVersion: v1
    controller: true
    kind: Node
    name: gb200-pod2-f06-node05
spec:
  devices:
  - attributes:
      addressingMode:
        string: ATS
      architecture:
        string: Blackwell
      brand:
        string: Nvidia
      cudaComputeCapability:
        version: 10.0.0
      driverVersion:
        version: 580.126.20
      productName:
        string: NVIDIA GB200
      type:
        string: gpu
      uuid:
        string: GPU-137d7996-cb8e-7683-e47d-9c99e6f49eb5
    capacity:
      memory:
        value: 189471Mi
    name: gpu-0
  # ... 省略 gpu-1, gpu-2, gpu-3 的类似内容
  driver: gpu.nvidia.com
  nodeName: gb200-pod2-f06-node05
  pool:
    name: gb200-pod2-f06-node05
```

可以看到，相比 DevicePlugin 只能报一个 `nvidia.com/gpu:4` 数量信息，ResourceSlice 把 GPU 架构、型号、显存、驱动版本都暴露出来了。有了这些信息，"只要 A100"、"显存大于 40Gi 的 GPU"这类调度策略才能做到。



* driver：DRA 驱动
* nodeName：资源所属节点
* pool：资源池，对于本地资源，名称一般是节点名

* attributes：描述设备固有特性的键值对，用于标识和选择设备
  * 比如 GPU 架构、型号等
* capacity：设备可分配的资源量（如 GPU 显存），DRAConsumableCapacity 特性启用后可被多个 ResourceClaim 按容量共享消耗




## 3. DeviceClass：资源分类标准（Admin 视角）

DeviceClass 定义了"什么样的设备属于这一类"，一般由 DRA Driver 或者集群管理员创建。

一个完整的 DeviceClass 内容如下：

```yaml
root@GB200-POD2-F06-Node05:~# k get deviceclass gpu.nvidia.com -oyaml
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  annotations:
    meta.helm.sh/release-name: nvidia-dra-driver-gpu
    meta.helm.sh/release-namespace: nvidia-dra-driver-gpu
  creationTimestamp: "2026-04-22T02:08:27Z"
  generation: 1
  labels:
    app.kubernetes.io/managed-by: Helm
  name: gpu.nvidia.com
  resourceVersion: "52725555"
  uid: 2e700a15-2392-4c7f-9467-26bf6249b71a
spec:
  selectors:
  - cel:
      expression: device.driver == 'gpu.nvidia.com' && device.attributes['gpu.nvidia.com'].type
        == 'gpu'
```



### 3.1 CEL 选择器

DeviceClass 的关键是 CEL 选择器：

```yaml
spec:
  selectors:
  - cel:
      expression: device.driver == 'gpu.nvidia.com' && device.attributes['gpu.nvidia.com'].type == 'gpu'
```

这表示：**驱动为 `gpu.nvidia.com` 且类型为 `gpu` 的设备才属于这个 Class**。

CEL 表达式可以组合任意属性条件。比如管理员可以创建一个只包含高端 GPU 的 DeviceClass：

```yaml
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu.nvidia.com-a100
spec:
  selectors:
  - cel:
      expression: |
        device.driver == 'gpu.nvidia.com' &&
        device.attributes['gpu.nvidia.com'].type == 'gpu' &&
        device.attributes['gpu.nvidia.com'].productName == 'NVIDIA A100'
```

这样用户只需要在 ResourceClaim 中指定 `deviceClassName: gpu.nvidia.com-a100`，就能精确地申请到 A100，而无需关心 CEL 表达式的写法。

> DevicePlugin 只能通过 gpu.nvidia.com:1 指定数量，GPU 型号管不了，得靠 nodeSelector 之类的方式绕路。DRA 直接在 Claim 里选 Class 就行。

说白了，DeviceClass 就是把 CEL 的复杂性藏起来——管理员写规则，用户选 Class。



### 3.2 config：设备级配置

DeviceClass 还可以在 `config` 字段中定义传递给 DRA 驱动的配置参数：

```yaml
spec:
  selectors:
  - cel:
      expression: device.driver == 'gpu.nvidia.com' && device.attributes['gpu.nvidia.com'].type == 'gpu'
  config:
  - opaque:
      driver: gpu.nvidia.com
      parameters:
        apiVersion: v1
        kind: GPUConfig
        spec:
          enablePersistence: true
```

当 Kubelet 调用驱动的 `NodePrepareResources` 准备设备时，会将这些配置传递给驱动。管理员在 Class 级别统一设置设备参数，不用每个用户单独配。

> 目前 NVIDIA DRA 驱动对 config 的支持还在完善中，具体可配置项参考驱动文档。



### 3.3 extendedResourceName：兼容模式

**extendedResourceName** 是 DeviceClass 中的一个字段，开启后匹配该 Class 的设备可以通过 Pod 的扩展资源请求（resources.requests/limits）来使用，不用写 ResourceClaim。调度器会负责资源核算，确保不会超分配。

DeviceClass 中指定 `extendedResourceName`：

```yaml
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: nvidia-gpu-class
spec:
  extendedResourceName: nvidia.com/gpu
  selectors:
  - cel:
      expression: 'device.driver == "gpu.nvidia.com"'
```

Pod 按传统方式申请即可：

```yaml
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: container
    resources:
      requests:
        nvidia.com/gpu: 1
      limits:
        nvidia.com/gpu: 1
```

> `extendedResourceName` 是 Alpha 特性，需要启用 `DRAExtendedResource` feature gate 才能生效。该特性在 Kubernetes 1.34/1.35 默认关闭，1.36 开始默认开启。



## 4. ResourceClaim：资源需求方（User 视角）

ResourceClaim 是用户对资源的"采购订单"，描述需要什么设备、需要多少。

### 4.1 两种声明方式

#### 4.1.1 ResourceClaimTemplate：Pod 级别声明

上一篇我们用了 **ResourceClaimTemplate**：

```yaml
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: single-gpu
spec:
  spec:
    devices:
      requests:
      - name: gpu
        exactly:
          deviceClassName: gpu.nvidia.com
          allocationMode: ExactCount
          count: 1
```

随 Pod 创建而自动生成 ResourceClaim，Pod 删除时自动回收。适合"一个 Pod 用一份资源"的场景。



#### 4.1.2 ResourceClaim：独立声明（支持跨 Pod 共享）

单独创建的 ResourceClaim 有独立的生命周期，可以跨 Pod 共享。适合"多个 Pod 共用同一份资源"的场景（如 GPU 共享）。

是的，DRA 默认就支持 GPU 共享。多个 Pod 同时引用同一个 Claim，获得同一个设备的访问权，本质是共享同一块 GPU。效果和 DevicePlugin 的 TimeSlicing 方案类似。

> 关于 TimeSlicing 方案推荐阅读这篇文章：[一文搞懂 GPU 共享方案： NVIDIA Time Slicing](https://www.lixueduan.com/posts/kubernetes/25-gpu-share-time-slicing/)



例如：

```yaml
# 共享的 ResourceClaim
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: shared-gpu
spec:
  devices:
    requests:
    - name: gpu
      exactly:
        deviceClassName: gpu.nvidia.com
        allocationMode: ExactCount
        count: 1
---
# Pod A 引用共享 Claim
apiVersion: v1
kind: Pod
metadata:
  name: pod-a
spec:
  resourceClaims:
  - name: gpu
    resourceClaimName: shared-gpu   # 直接引用独立 Claim
  containers:
  - name: app
    image: nvidia/cuda:12.1.0-base-ubuntu22.04
    command: ["nvidia-smi", "-L"]
    resources:
      claims:
      - name: gpu
---
# Pod B 引用同一个 Claim
apiVersion: v1
kind: Pod
metadata:
  name: pod-b
spec:
  resourceClaims:
  - name: gpu
    resourceClaimName: shared-gpu   # 同一个 Claim
  containers:
  - name: app
    image: nvidia/cuda:12.1.0-base-ubuntu22.04
    command: ["nvidia-smi", "-L"]
    resources:
      claims:
      - name: gpu
```

Pod A 和 Pod B 共享同一个 GPU 设备，调度器会将它们调度到同一节点，分配同一个设备。

结果如下：

```bash
root@GB200-POD2-F06-Node05:~/lixd/dra/test# k logs -f pod-a
GPU 0: NVIDIA GB200 (UUID: GPU-f6c15dc5-4024-fb95-39e6-5089c0c100c8)
root@GB200-POD2-F06-Node05:~/lixd/dra/test# k logs -f pod-b
GPU 0: NVIDIA GB200 (UUID: GPU-f6c15dc5-4024-fb95-39e6-5089c0c100c8)
```



### 4.2 分配模式

ResourceClaim 支持两种分配模式：

```yaml
# 模式一：精确数量
exactly:
  deviceClassName: gpu.nvidia.com
  allocationMode: ExactCount
  count: 2              # 恰好需要 2 个 GPU

# 模式二：全部满足
all:
  deviceClassName: gpu.nvidia.com
  allocationMode: All   # 需要节点上该 Class 下的全部设备
```

`All` 模式本质是：**我要这个节点这个资源池里的所有设备。**

只有当该节点上该 Class 下的所有设备均未被占用时，分配才会成功；否则分配失败（除非设置了 `adminAccess`）。适合分布式训练这类需要独占节点全部 GPU 的场景。



### 4.3 额外选择条件

除了通过 `deviceClassName` 限定设备范围，ResourceClaim 还可以添加额外的 CEL 选择条件，进一步缩小匹配范围：

```yaml
spec:
  devices:
    requests:
    - name: training-gpu
      exactly:
        deviceClassName: gpu.nvidia.com
        selectors:
        - cel:
            expression: device.attributes['gpu.nvidia.com'].productName == 'NVIDIA GB200'
        allocationMode: ExactCount
        count: 2
```

这里虽然引用的是通用的 `gpu.nvidia.com` Class，但通过额外选择条件限定只匹配 GB200 GPU。



### 4.4 跨设备约束

当请求多个设备时，可以用 `constraints` 定义跨设备的约束条件：

```yaml
spec:
  devices:
    requests:
    - name: gpu
      exactly:
        deviceClassName: gpu.nvidia.com
        allocationMode: ExactCount
        count: 2
    constraints:
    - matchAttribute: productName
    - matchAttribute: cudaComputeCapability
```

`matchAttribute: productName` 表示：分配的 2 个 GPU 必须是相同型号。

`matchAttribute: cudaComputeCapability` 则是确保 CUDA 兼容性。

> matchAttribute 配置主要匹配 ResourceSlice 中的 devices.attributes 参数，保证所有设备都有同样的参数。



## 5. 三角协作：从设备发布到 Pod 启动

三个对象各自干什么搞清楚了，接下来看它们怎么配合完成一次资源分配。以下流程以上篇的 `gpu-test-pod` 为例。

![DRA 资源分配流程](https://img.lixueduan.com/kubernetes/dra/dra-p2-flowchart-dra-allocation.jpg)

### 5.1 阶段一：设备注册（DRA Driver → ResourceSlice）

```
DRA Driver 启动
    │
    ├─ 1. 扫描节点上的 GPU 设备
    ├─ 2. 收集设备属性（型号、显存、驱动版本等）
    └─ 3. 创建/更新 ResourceSlice
```

DRA 驱动以 DaemonSet 方式运行在每个节点上，持续 watch 设备状态。设备变化时（新增、移除、健康状态变化），驱动更新对应的 ResourceSlice 并递增 `pool.generation`。



### 5.2 阶段二：分类定义（Admin → DeviceClass）

```
管理员创建 DeviceClass
    │
    ├─ 1. 确定设备筛选规则（如"所有 NVIDIA GPU"）
    ├─ 2. 编写 CEL 表达式
    │     例如：expression: device.driver == 'gpu.nvidia.com'
    └─ 3. 创建 DeviceClass，供用户引用
```

DeviceClass 是管理员对 ResourceSlice 中设备的分类标准。驱动把原始设备信息发布到集群后，管理员通过 DeviceClass 告诉用户"有哪些设备类别可用"。

例如创建一个通用的 NVIDIA GPU 类别：

```yaml
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu.nvidia.com
spec:
  selectors:
  - cel:
      expression: device.driver == 'gpu.nvidia.com'
```

用户不需要理解 CEL 语法，只需知道 `deviceClassName: gpu.nvidia.com` 代表"NVIDIA GPU"。管理员写好规则，用户选 Class 就行——跟 CSI 里选 StorageClass 一个道理。



### 5.3 阶段三：用户声明需求（ResourceClaim / Template）

用户根据 DeviceClass 创建 ResourceClaim，声明需要什么设备、需要多少。上一篇我们用的是 **ResourceClaimTemplate**：

```yaml
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: single-gpu
spec:
  spec:
    devices:
      requests:
      - name: gpu
        exactly:
          deviceClassName: gpu.nvidia.com
          allocationMode: ExactCount
          count: 1
```

也可以直接创建独立的 ResourceClaim（适合跨 Pod 共享的场景）。

无论哪种方式，都是通过 `deviceClassName: gpu.nvidia.com` 引用 DeviceClass，告诉调度器"我需要这个类别的设备"。

> 与 DevicePlugin 只能指定数量不同，ResourceClaim `allocationMode` 除了 `ExactCount` 之外还支持 `All` 模式，不指定数量，直接占用该节点该 class 下的全部设备。适合分布式训练等需要独占所有 GPU 的场景。



### 5.4 阶段四：创建 Pod 并触发调度

用户创建 Pod，通过 `resourceClaims` 引用 Template：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-test-pod
spec:
  containers:
  - name: cuda-container
    image: nvidia/cuda:12.1.0-base-ubuntu22.04
    resources:
      claims:
      - name: gpu-claim
  resourceClaims:
  - name: gpu-claim
    resourceClaimTemplateName: single-gpu
```

Pod 提交后，调度流程如下：

```
Pod 提交到 API Server
    │
    ├─ 1. ResourceClaim Controller 根据 Template 创建 ResourceClaim
    │
    ├─ 2. 调度器 Filter
    │     → 遍历节点，基于 ResourceSlice 判断：
    │        - 先通过 DeviceClass CEL 筛选符合 Class 的设备子集
    │        - 再结合 Claim selector 进一步过滤
    │        - 容量是否满足
    │        - 约束是否满足
    │
    ├─ 3. 调度器 Reserve + Bind
    │     → 选定节点 + 选定具体设备
    │     → 写入 ResourceClaim.status.allocation（driver/pool/device）
    │     → 写入 ResourceClaim.status.reservedFor
    │     → 绑定 Pod 到节点
    │
    └─ 4. Kubelet 启动 Pod
```

调度器不仅选节点，还选定了具体设备。Reserve 阶段调度器在内部缓存中完成设备选择，Bind 阶段将分配结果（哪个节点、哪个设备）写入 `ResourceClaim.status.allocation`，后续 Driver 和 Kubelet 都基于这个结果工作。

三个组件的分工：

| 组件 | 职责 |
|---|---|
| **Scheduler** | 选节点 + 选具体设备，写入 allocation |
| **Driver** | 根据 allocation 结果准备设备（NodePrepareResources） |
| **Kubelet** | 调用 Driver + 将设备注入容器 |



### 5.5 阶段五：设备准备与 Pod 启动

```
Kubelet 收到绑定的 Pod
    │
    ├─ 1. 查找 Pod 引用的 ResourceClaim
    │     → gpu-test-pod-gpu-claim-7rqsj (已 allocated + reserved)
    │
    ├─ 2. 调用 DRA Plugin 的 NodePrepareResources
    │     ├─ 传入 ResourceClaim 的分配结果
    │     ├─ 驱动准备设备（配置 GPU、创建 CDI 描述文件）
    │     └─ 返回 CDI 设备 ID
    │
    ├─ 3. Kubelet 将 CDI ID 传递给容器运行时
    │
    └─ 4. 容器运行时根据 CDI 描述注入设备到容器
          → Pod 启动，nvidia-smi 可见 GPU
```

最终我们看到 P1 中的运行结果：

```bash
root@GB200-POD2-F06-Node05:~# kubectl logs gpu-test-pod
GPU 0: NVIDIA GB200 (UUID: GPU-137d7996-cb8e-7683-e47d-9c99e6f49eb5)
GPU allocation successful.
```



### 5.6 ResourceClaim 状态流转

![ResourceClaim 状态流转](https://img.lixueduan.com/kubernetes/dra/dra-p2-flowchart-claim-lifecycle.jpg)

整个过程中 ResourceClaim 的状态变化：

| 状态 | 触发时机 | 含义 |
|------|----------|------|
| (创建) | Pod 创建时由 Controller 生成 | 等待调度器处理 |
| Allocated | 调度器完成分配 | 已选定具体设备，写入 allocation 结果 |
| Reserved | Pod 绑定到节点 | 设备已预留给该 Pod，其他 Pod 不可使用 |

P1 中我们看到的 `allocated,reserved` 就是这两个状态的组合：

```bash
NAME                           STATE                AGE
gpu-test-pod-gpu-claim-7rqsj   allocated,reserved   37s
```

Pod 删除后，基于 Template 创建的 ResourceClaim 会随 Pod 一起被回收，设备回到可分配状态。若是独立创建的 ResourceClaim，Pod 删除后 allocation 被清除、Claim 回到 Pending，但 Claim 本身不会被删除，可被其他 Pod 重新引用。




## 6. 小结

| 概念 | 职责 |
|------|------|
| ResourceSlice | 设备目录，驱动发布设备信息 |
| DeviceClass | 分类标准，封装选择规则 |
| ResourceClaim | 资源订单，描述用户需求 |

协作流程：

1. 驱动发布 ResourceSlice（设备注册）
2. 管理员定义 DeviceClass（分类筛选）
3. 用户创建 ResourceClaim/Pod（资源申请）
4. 调度器综合三者的信息做分配决策
5. Kubelet 调用驱动准备设备，容器获得 GPU

下一篇来看 DRA 的完整工作流和源码实现——调度器怎么分配、Kubelet 怎么准备设备，以及 NVIDIA DRA Driver 的源码。



---

> 作者: [意琦行](https://github.com/lixd)  
> URL: https://www.lixueduan.com/posts/kubernetes/55-dra-p2-slice-class-claim/  

