这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
动态资源分配
特性状态:
GA since Kubernetes v1.35
More information about this feature
This is a stable feature in Kubernetes, and has been since version v1.35. It was first available in the v1.30 release. You can no longer disable or opt out of this feature or behavior (it is locked); if you explicitly set a value for the associated feature gate DynamicResourceAllocation, Kubernetes ignores it but does not report any error.
本节介绍 Kubernetes 中的动态资源分配(Dynamic Resource Allocation,DRA)。
DRA 是 kubernetes 提供的一项特性,允许你在多个 Pod 之间请求和共享资源。
这些资源通常是挂接的设备,例如硬件加速器。
借助 DRA,设备驱动和集群管理员可以定义设备的类别,这些类别可供工作负载中的 Pod 申领。
Kubernetes 会将匹配的设备分配给特定的申领,并将相应的 Pod 调度到能够访问这些已分配设备的节点上。
使用 DRA 分配资源提供了与动态卷制备类似的体验:
你使用 PersistentVolumeClaims 从存储类中申领存储容量,
并在 Pod 中请求使用该已申领的容量。
DRA 的优势
DRA 提供了一种灵活的方式来分类、请求和使用集群中的设备。
使用 DRA 具有以下优势:
- 灵活的设备过滤: 使用公共表达式语言(CEL)对特定设备属性进行细粒度过滤。
- 设备共享: 通过引用相应的资源申领,与多个容器或 Pod 共享同一资源。
- 设备配置: 将特定于供应商的设备配置附加到你的资源申领,
启用按工作负载的设备配置,而非如今的按节点设备配置。
- 集中式设备分类: 设备驱动和集群管理员可以使用 DeviceClass
为应用运维人员提供针对各种用例优化的硬件类别。
例如,你可以为通用工作负载创建成本优化的 DeviceClass,
为关键作业创建高性能的 DeviceClass。
- 简化的 Pod 请求: 通过 DRA,应用运维人员无需在 Pod 资源请求中指定设备数量。
相反,Pod 引用一个资源申领,该申领中的设备配置将应用于该 Pod。
与需要按容器请求设备、不支持设备共享且不支持基于表达式的设备过滤的
设备插件相比,
这些优势在设备分配工作流方面提供了显著改进。
DRA 用户类型
使用 DRA 分配设备的工作流涉及以下类型的用户:
限制
- Kubernetes 调度器不支持为 DRA 资源进行
抢占。
这意味着在节点上运行并使用 DRA 资源的现有 Pod,
不能被同样需要 DRA 资源的更高优先级 Pod 抢占。
高优先级 Pod 将保持挂起状态,直到设备可用 —— 这发生在冲突 Pod 终止或被手动删除时。
接下来
1 - DRA API 对象
本页介绍 Kubernetes API 种类,这些种类由动态资源分配(DRA)用于对设备进行分类、请求和分配。
DRA 术语
DRA 使用以下 Kubernetes API 种类来提供核心分配功能。
所有这些 API 种类都包含在 resource.k8s.io/v1
API 组中。
- DeviceClass
- 定义了可以被申领的设备类别,以及如何在申领中选择特定设备属性。
DeviceClass 参数可以匹配 ResourceSlice 中的零个或多个设备。
若要从 DeviceClass 中申领设备,ResourceClaim 需要选择特定的设备属性。
- ResourceClaim
- 描述了访问集群中已挂接资源(例如设备)的请求。
ResourceClaim 为 Pod 提供对特定资源的访问。
ResourceClaim 可以由工作负载运维人员创建,
也可以由 Kubernetes 基于 ResourceClaimTemplate 生成。
- ResourceClaimTemplate
- 定义一个模板,Kubernetes 使用该模板为工作负载创建每个 Pod 的 ResourceClaim。
ResourceClaimTemplate 为 Pod 提供对独立的、配置相似的资源的访问。
Kubernetes 从此模板生成的每个 ResourceClaim 都会绑定到一个特定的 Pod。
当 Pod 终止时,Kubernetes 删除对应的 ResourceClaim。
- ResourceSlice
- 代表挂接到节点上的一个或多个资源,例如设备。
驱动在集群中创建和管理 ResourceSlice。当 ResourceClaim 被创建并在 Pod 中使用时,
Kubernetes 使用 ResourceSlice 查找能够访问已申领资源的节点。
Kubernetes 将资源分配给 ResourceClaim,并将 Pod 调度到可以访问这些资源的节点上。
DeviceClass
DeviceClass 允许集群管理员或设备驱动在集群中定义设备的类别。
DeviceClass 告知运维人员他们可以请求哪些设备,以及如何请求这些设备。
你可以使用公共表达式语言(CEL)根据特定属性选择设备。
引用 DeviceClass 的 ResourceClaim 随后可以请求 DeviceClass 中的特定配置。
要创建 DeviceClass,
请参阅在集群中设置 DRA。
ResourceClaim 与 ResourceClaimTemplate
ResourceClaim 定义了工作负载所需的资源。每个 ResourceClaim 都包含
requests,它们引用一个 DeviceClass 并从该 DeviceClass 中选择设备。
ResourceClaim 还可以使用 selectors 过滤符合特定需求的设备,
并可以使用 constraints 限制能够满足请求的设备。
ResourceClaim 可以由工作负载运维人员创建,
也可以由 Kubernetes 基于 ResourceClaimTemplate 生成。
ResourceClaimTemplate 定义了一个模板,Kubernetes 可以使用该模板为 Pod
自动生成 ResourceClaim。
ResourceClaim 与 ResourceClaimTemplate 的使用场景
你使用的方法取决于你的需求,如下所述:
- ResourceClaim: 你希望多个 Pod 共享对特定设备的访问。
你手动管理你创建的 ResourceClaim 的生命周期。
- ResourceClaimTemplate: 你希望 Pod
能够独立访问独立的、配置相似的设备。Kubernetes 根据 ResourceClaimTemplate 中的规约生成
ResourceClaim。每个生成的 ResourceClaim 的生存期都绑定到对应 Pod 的生存期。
当你定义工作负载时,可以使用
公共表达式语言(CEL)
来过滤特定的设备属性或容量。可用于过滤的可用参数取决于设备和驱动。
如果你在 Pod 中直接引用特定的 ResourceClaim,该 ResourceClaim 必须已经存在于与
Pod 相同的名字空间中。如果 ResourceClaim 不存在于该名字空间中,
Pod 将无法调度。此行为类似于 PersistentVolumeClaim 必须存在于引用它的 Pod
所在的同一名字空间中。
你可以在 Pod 中引用自动生成的 ResourceClaim,但这并不推荐,
因为自动生成的 ResourceClaim 的生存期绑定到触发生成的 Pod 或 PodGroup 的生存期。
要了解如何使用这些方法之一申领资源,
请参阅使用 DRA 为工作负载分配设备。
优先级列表
特性状态:
GA since Kubernetes v1.36; (默认启用)
More information about this feature
This is a stable feature in Kubernetes, and has been since version 1.36. It was first available in the v1.33 release.
你可以为 ResourceClaim 或 ResourceClaimTemplate 中的请求提供子请求的优先级列表。
调度器会选择第一个可以被分配的子请求。
这允许用户指定在首选方案不可用时工作负载可以使用的备选设备。
在以下示例中,ResourceClaimTemplate 请求了一台颜色为黑色、尺寸为大号的设备。
如果没有具有这些属性的设备可用,Pod 将无法被调度。
借助优先级列表特性,可以指定第二个备选方案,即请求两台颜色为白色、尺寸为小号的设备。
如果大号黑色设备可用,将分配它;如果不可用,但有两台小号白色设备可用,Pod 仍然可以运行。
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: prioritized-list-claim-template
spec:
spec:
devices:
requests:
- name: req-0
firstAvailable:
- name: large-black
deviceClassName: resource.example.com
selectors:
- cel:
expression: |-
device.attributes["resource-driver.example.com"].color == "black" &&
device.attributes["resource-driver.example.com"].size == "large"
- name: small-white
deviceClassName: resource.example.com
selectors:
- cel:
expression: |-
device.attributes["resource-driver.example.com"].color == "white" &&
device.attributes["resource-driver.example.com"].size == "small"
count: 2
如果 Pod 适合在集群中的多个节点上运行,调度器在为每个节点打分时,
会将从任何优先级列表中选中的子请求的索引作为输入之一。
因此,能够分配更高级子请求中所请求设备的节点,
比只能分配较低级子请求设备的节点更有可能被选中。
该决策是按每个 Pod 单独作出的,因此如果 Pod 是 ReplicaSet 或类似分组的成员,
你不能依赖该组的所有成员都选用相同的子请求。你的工作负载必须能适应这一点。
工作负载 ResourceClaim
特性状态:
Beta since Kubernetes v1.37; (默认禁用)
当你使用
Workload API
来组织 Pod 时,你可以为整个
PodGroup
预留 ResourceClaim,而不是为每个 Pod 单独预留;
并为 PodGroup 而非单个 Pod 生成 ResourceClaimTemplate,
从而允许 PodGroup 内的 Pod 共享对生成的 ResourceClaim 所分配设备的访问权。
此特性针对两个问题:
- ResourceClaim API 的
status.reservedFor 列表只能包含 256 项。
由于 kube-scheduler 仅在该列表中记录单个 Pod,
因此只能有 256 个 Pod 共享一个 ResourceClaim。
通过允许将 PodGroup 记录在 status.reservedFor 中,
远多于 256 个 Pod 可以共享一个 ResourceClaim。
- 只有当 ResourceClaim 的确切名称已知时,Pod 才能共享它。
对于复制 Pod 组的复杂工作负载,当组的集合扩缩容时,
每组中 Pod 共享的 ResourceClaim 都需要显式创建和删除。
通过为每个 PodGroup 生成 ResourceClaim,
单个 ResourceClaimTemplate 可以作为既被自动复制、又能在
PodGroup 内的 Pod 之间共享的 ResourceClaim 的基础。
PodGroup API 定义了一个 spec.resourceClaims 字段,其结构与 Pod API 中的
spec.resourceClaims 字段相同,含义也类似:
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
name: training-group
namespace: some-ns
spec:
...
resourceClaims:
- name: pg-claim
resourceClaimName: my-pg-claim
- name: pg-claim-template
resourceClaimTemplateName: my-pg-template
与 Pod 提出的申领类似,定义了 resourceClaimName 的 PodGroup
申领按名称引用一个 ResourceClaim。定义了 resourceClaimTemplateName
的申领引用一个 ResourceClaimTemplate,该模板会复制为整个 PodGroup 的一个
ResourceClaim,供其 Pod 之间共享。
当 Pod 定义的申领的 name、resourceClaimName 和
resourceClaimTemplateName 全部与其 PodGroup 的某个
spec.resourceClaims 匹配时,kube-scheduler 会为 PodGroup
而非 Pod 预留该 ResourceClaim。如果 Pod 的申领与 PodGroup 的申领不匹配,
kube-scheduler 则为 Pod 预留 ResourceClaim。在这两种情况下,
预留都会记录在 ResourceClaim 的 status.reservedFor 中。
PodGroup 预留以及对应的资源分配会在 ResourceClaim 中持久存在,
直到 PodGroup 被删除,即使该组中不再有任何 Pod。
当匹配 PodGroup 申领的 Pod 申领定义了 resourceClaimTemplateName 时,
会为 PodGroup 生成一个 ResourceClaim。组中定义了相同申领的其他 Pod
将共享该生成的 ResourceClaim,而不会为每个 Pod 触发新的 ResourceClaim 生成。
无论 resourceClaimTemplateName 申领是否匹配 PodGroup 申领,
生成的 ResourceClaim 的名称都会记录在 Pod 的 status.resourceClaimStatuses 中。
只有当
DRAWorkloadResourceClaims
特性启用时,匹配的 PodGroup 申领才会触发 ResourceClaimTemplate 创建 ResourceClaim。
在该特性禁用时,不会创建 ResourceClaim,以避免在集群升级或 kube-apiserver 与
kube-controller-manager 之间进行特性滚动/回滚期间产生虚假的按 Pod 分配的
ResourceClaim。
从 ResourceClaimTemplate 为 PodGroup 生成的 ResourceClaim 遵循 PodGroup
的生命周期。当 PodGroup 及其 ResourceClaimTemplate 都存在时,才会首次创建
ResourceClaim。在 PodGroup 已被删除且 ResourceClaim 不再被预留后,
ResourceClaim 会被删除。
请看以下示例:
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
name: training-group
namespace: some-ns
spec:
...
resourceClaims:
- name: pg-claim
resourceClaimName: my-pg-claim
- name: pg-claim-template
resourceClaimTemplateName: my-pg-template
---
apiVersion: v1
kind: Pod
metadata:
name: training-group-pod-1
namespace: some-ns
spec:
...
schedulingGroup:
podGroupName: training-group
resourceClaims:
- name: pod-claim
resourceClaimName: my-pod-claim
- name: pod-claim-template
resourceClaimTemplateName: my-pod-template
- name: pg-claim
resourceClaimName: my-pg-claim
- name: pg-claim-template
resourceClaimTemplateName: my-pg-template
在此示例中,training-group PodGroup 有一个名为 training-group-pod-1 的 Pod。
Pod 的 pod-claim 和 pod-claim-template 申领不匹配 PodGroup 的任何申领,
因此这些申领不受 PodGroup 的影响:ResourceClaim my-pod-claim 变为为 Pod 预留,
并且从 ResourceClaimTemplate my-pod-template 生成一个 ResourceClaim,
同样变为为 Pod 预留。pg-claim 和 pg-claim-template 确实匹配 PodGroup
的申领。ResourceClaim my-pg-claim 变为为 PodGroup 预留,
并且从 ResourceClaimTemplate my-pg-template 生成一个 ResourceClaim,
同样变为为 PodGroup 预留。
将 ResourceClaim 与 Workload API 资源关联由
kube-apiserver、kube-controller-manager、kube-scheduler 和 kubelet 中的
DRAWorkloadResourceClaims 特性门控控制。
ResourceSlice
每个 ResourceSlice 代表一个池中一个或多个设备。
该池由设备驱动管理,驱动会创建和管理 ResourceSlice。
池中的资源可能由单个 ResourceSlice 表示,也可能跨越多个 ResourceSlice。
ResourceSlice 向设备使用者和调度器提供有用的信息,
并且对于动态资源分配至关重要。每个 ResourceSlice 必须包含以下信息:
- 资源池(Resource pool): 驱动管理的一个或多个资源的组。
池可以跨越多个 ResourceSlice。池中资源的变更必须在该池的所有
ResourceSlice 之间传播。管理该池的设备驱动负责确保此传播发生。
- 设备(Devices): 被管理的池中的设备。
ResourceSlice 可以列出池中的每个设备,也可以列出池中的设备子集。
ResourceSlice 定义设备信息,例如属性、版本和容量。
设备使用者可以通过在 ResourceClaim 或 DeviceClass 中过滤设备信息来选择要分配的设备。
- 节点(Nodes): 可以访问这些资源的节点。
驱动可以选择哪些节点可以访问资源,
是集群中的所有节点、单个指定名称的节点,还是具有特定节点标签的节点。
驱动使用控制器将集群中的
ResourceSlice 与驱动必须发布的信息进行协调。
此控制器会覆写任何手动更改,例如集群用户创建或修改 ResourceSlice 的操作。
请看以下 ResourceSlice 示例:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: cat-slice
spec:
driver: "resource-driver.example.com"
pool:
generation: 1
name: "black-cat-pool"
resourceSliceCount: 1
# allNodes 字段定义集群中的任何节点是否都可以访问该设备。
allNodes: true
devices:
- name: "large-black-cat"
attributes:
color:
string: "black"
size:
string: "large"
cat:
bool: true
该 ResourceSlice 由 resource-driver.example.com 驱动在 black-cat-pool 池中管理。
allNodes: true 字段表示集群中的任何节点都可以访问这些设备。
ResourceSlice 中有一台设备,名为 large-black-cat,具有以下属性:
color:blacksize:largecat:true
DeviceClass 可以使用这些属性选择此 ResourceSlice,
ResourceClaim 可以在该 DeviceClass 中过滤特定设备。
命名与优先级
Kubernetes 调度器评估设备分配顺序的依据是 ResourceSlice
和资源池名称的字典序排序。调度器使用最先适应(first-fit)策略,
即选择满足申领需求的第一台可用设备。
这允许通过为池和 ResourceSlice 分配的名称来影响资源分配的优先级。
请注意,没有绑定条件
的池总是比具有绑定条件的池先被评估,无论它们的名称如何。
对于使用 k8s.io/dynamic-resources/kubeletplugin Go 包构建的驱动,
或使用该模块中的 ResourceSlice 控制器构建的驱动,
这些组件会自动处理 ResourceSlice 命名,
确保按照驱动指定的顺序进行评估。
管理员访问
特性状态:
GA since Kubernetes v1.36; (默认启用)
More information about this feature
This is a stable feature in Kubernetes, and has been since version 1.36. It was first available in the v1.32 release.
你可以将 ResourceClaim 或 ResourceClaimTemplate 中的请求标记为具有特权特性,
用于维护和故障排查任务。具有管理员访问的请求可以授予对正在使用中设备的访问权限,
并可能在使设备在容器中可用时启用额外权限:
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: large-black-cat-claim-template
spec:
spec:
devices:
requests:
- name: req-0
exactly:
deviceClassName: resource.example.com
allocationMode: All
adminAccess: true
管理员访问是一种特权模式,在多租户集群中不应授予普通用户。
只有被授权在带有 resource.kubernetes.io/admin-access: "true"
(区分大小写)标签的名字空间中创建 ResourceClaim 或
ResourceClaimTemplate 对象的用户,才能使用 adminAccess 字段。
这确保了非管理员用户不会滥用此特性。
管理员访问由 kube-apiserver、kube-scheduler 和 kubelet 中的
DRAAdminAccess 特性门控控制。
列表类型属性
特性状态:
Alpha since Kubernetes v1.36; (默认禁用)
此特性改进了 ResourceSlice API,允许 DRA 驱动为设备属性指定列表值,
而不仅仅是标量。这对于建模更复杂的节点内部拓扑非常有用,
例如当 CPU 与多个 PCIe 根节点相邻时。
对于 ResourceClaim 的编写者(最终用户),
这意味着 matchAttribute 和 distinctAttribute 在此类场景下工作得更好。
matchAttribute — 两个属性必须具有非空的列表交集,
而非完全相同(标量值被视为单项列表)。这仅意味着如果一个驱动为例如
PCIe 根节点发布了单个值,而另一个驱动发布了一个列表,
只要该单个值出现在列表中的某个位置,约束就被满足。
distinctAttribute — 属性值必须两两互不相交(任意两台设备之间不共享任何值)。
为了帮助 ResourceClaim 的编写者在 CEL 表达式中使用可能为列表的属性,
此特性还引入了 includes() CEL 函数。
# 标量属性(向后兼容)
# 假设:device.attributes["dra.example.com"].model = "model-a"
device.attributes["dra.example.com"].model.includes("model-a") # true
device.attributes["dra.example.com"].model.includes("model-b") # false
# 列表类型属性(需要 DRAListTypeAttributes)
# 假设:device.attributes["dra.example.com"].supported-models = ["model-a", "model-b"]
device.attributes["dra.example.com"].supported-models.includes("model-a") # true
device.attributes["dra.example.com"].supported-models.includes("model-c") # false
DRA 驱动编写者的细节
默认情况下,每个 DeviceAttribute 恰好保存一个标量值:
布尔值、整数、字符串或语义版本字符串。
DRAListTypeAttributes 特性门控为 DeviceAttribute 扩展了四个列表类型字段,
允许设备为单个属性通告多个值:
bools — 布尔值列表ints — 64 位整数值列表strings — 字符串列表(每项最多 64 个字符)versions — 符合 semver.org 规范 2.0.0 的语义版本字符串列表
(每项最多 64 个字符)
每台设备的单个属性值总数(标量字段加上所有列表元素的总和)上限为 48。
当 ResourceSlice 中的任何设备使用此特性或其他高级特性(如污点)时,
ResourceSlice 最多被限制为 64 台设备。
以下是一台设备使用列表类型字符串属性通告多个支持型号的示例:
kind: ResourceSlice
apiVersion: resource.k8s.io/v1
metadata:
name: example-resourceslice
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 1
driver: dra.example.com
devices:
- name: gpu-0
attributes:
dra.example.com/supported-models:
strings:
- model-a
- model-b
列表类型属性由 kube-apiserver 和 kube-scheduler 中的
DRAListTypeAttributes 特性门控控制。
派生属性
特性状态:
Alpha since Kubernetes v1.37; (默认禁用)
通常,matchAttribute 和 distinctAttribute 约束要求设备
使用完全相同的名称来发布属性。如果 GPU 驱动发布 pcie_locality,
而 NIC 驱动发布 pcie_root(或将相同信息嵌入到诸如 numa0-pcie1 的字符串中),
调度器无法识别它们表示的是同一事物,
因此来自两个驱动的设备无法被共置,除非先就共享属性名称达成一致。
derivedAttributes 让你可以就地弥合这一差距,
而无需等待驱动在共享属性名称上实现标准化。
在请求的 .spec.devices.requests[].exactly 或
.spec.devices.requests[].firstAvailable[] 下添加一个或多个
derivedAttributes 条目。每个条目定义一个 CEL 表达式,
调度器会针对该请求的每个候选设备评估该表达式。
评估结果成为一个虚拟属性,可以与驱动提供的设备属性完全一样地被
matchAttribute 或 distinctAttribute 约束引用。
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
name: gpu-nic-numa-alignment
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.example.com
count: 1
derivedAttributes:
- name: derived/numa
expression: device.attributes["gpu.example.com"].numa
- name: nic
exactly:
deviceClassName: nic.example.com
count: 1
derivedAttributes:
- name: derived/numa
expression: device.attributes["nic.example.com"].numaNode
constraints:
- requests: ["gpu", "nic"]
matchAttribute: derived/numa
在此示例中,gpu 和 nic 驱动使用不同的属性名称(numa 和 numaNode)来发布拓扑信息。
每个请求都从自己的设备属性中计算出一个公共的 derived/numa 值,
而 matchAttribute 约束将两个请求在该虚拟属性上对齐,
即使底层驱动从未就共享属性名称达成一致。
关于 derivedAttributes 需要了解以下几点:
- 命名:
name 必须是一个 DNS 子域名,后跟 / 和一个 C 标识符,
格式与驱动提供的属性名称相同(例如 example.com/numaNode 或
derived/numaNode)。如果该名称与驱动已经发布的驱动提供属性相匹配,
则派生属性的值会在约束匹配时遮蔽驱动提供的属性。
如果你希望避免无意中发生遮蔽,请使用不会被任何驱动使用的域名前缀,
例如 derived/。每个请求最多可以定义 32 个派生属性。
- 必须被约束使用: 每个派生属性必须被至少一个应用于定义它的请求(或子请求)的
matchAttribute 或 distinctAttribute 约束引用。
否则 ResourceClaim 验证会失败。
- 评估范围和顺序:
expression 对每个候选设备评估一次,
评估发生在请求自己的 CEL 选择算符(.selectors[].cel)
已经过滤完该设备之后。因此,派生属性不能被选择算符表达式引用,
也不会通过 CEL 环境中的 device.attributes 暴露。
- 返回类型:
expression 必须评估为一个标量(string、int、bool
或语义版本),或者当同时启用了 DRAListTypeAttributes 特性门控时,
评估为上述标量类型之一的列表。
- 开销限制: 每个表达式都有最大长度和 CEL 评估开销估算上限。
除此之外,ResourceClaim 中所有
derivedAttributes
表达式的组合估算开销也有上限,以限制单次调度尝试的总附加开销。
如果超出上述任何限制,ResourceClaim 将被拒绝。
- 运行时错误会中止调度: 如果对某个候选设备的表达式评估失败(例如因为它引用了该设备没有的属性),
调度器会中止分配,且该 Pod 会调度失败,而不是静默跳过该设备。
请以防御方式编写表达式,例如在读取属性之前先检查它是否存在。
派生属性由 kube-apiserver 和 kube-scheduler 中的
DRADerivedAttributes 特性门控
控制。
有关 DRA 驱动可以发布的标准设备属性列表,
请参阅标准设备属性参考。
通过 DRA 的扩展资源分配
特性状态:
GA since Kubernetes v1.37; (默认启用)
More information about this feature
This is a stable feature in Kubernetes, and has been since version 1.37. It was first available in the v1.34 release.
你可以为 DeviceClass 提供一个扩展资源名称。调度器随后会为扩展资源请求选择匹配该类的设备。
这允许用户继续在 Pod 中使用扩展资源请求,
来请求由设备插件提供的扩展资源,或 DRA 设备。
在单个集群节点上,同一扩展资源可以由设备插件提供,也可以由 DRA 提供。
在同一集群中,某些节点上的同一扩展资源可以由设备插件提供,
而其他节点上可以由 DRA 提供。
在以下示例中,DeviceClass 被赋予了一个 example.com/gpu 的 extendedResourceName。
如果 Pod 请求扩展资源 example.com/gpu: 2,
它可以被调度到具有两台或更多匹配该 DeviceClass 设备的节点上。
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: gpu.example.com
spec:
selectors:
- cel:
expression: device.driver == 'gpu.example.com' && device.attributes['gpu.example.com'].type
== 'gpu'
extendedResourceName: example.com/gpu
此外,用户可以使用一种特殊的扩展资源来分配设备,而无需显式创建 ResourceClaim。
使用扩展资源名称前缀 deviceclass.resource.kubernetes.io/ 加上 DeviceClass 名称即可。
这对任何 DeviceClass 都有效,即使它没有指定扩展资源名称。
生成的 ResourceClaim 将包含一个请求,要求分配该 DeviceClass 的指定数量设备的
ExactCount。
通过 DRA 的扩展资源分配由
kube-apiserver、kube-scheduler、kube-controller-manager 和 kubelet 中的
DRAExtendedResource 特性门控控制。
有关请求扩展资源的实际演练,
请参阅将扩展资源分配给容器。
2 - DRA 的工作原理
本页介绍 Kubernetes 如何通过动态资源分配(DRA)为工作负载分配设备,
以及预先调度的 Pod 如何与该流程交互。
如何使用 DRA 进行资源分配
以下各节描述了各种
DRA 用户类型的工作流,
以及 Kubernetes 系统在动态资源分配过程中的工作流。
用户的工作流
- 驱动创建: 设备所有者或第三方实体创建驱动,
这些驱动能够在集群中创建和管理 ResourceSlice。
这些驱动还可以选择性地创建 DeviceClass,
用于定义设备的类别以及如何请求它们。
- 集群配置: 集群管理员创建集群、将设备附加到节点并安装 DRA 设备驱动。
集群管理员还可以选择性地创建 DeviceClass,
用于定义设备的类别以及如何请求它们。
- 资源申领: 工作负载运维人员创建 ResourceClaimTemplate 或 ResourceClaim,
在 DeviceClass 中请求特定设备配置。在同一阶段,
工作负载运维人员修改其 Kubernetes 清单以请求这些
ResourceClaimTemplate 或 ResourceClaim。
Kubernetes 的工作流
- ResourceSlice 创建: 集群中的驱动创建 ResourceSlice,
用于表示被管理的同类设备池中一个或多个设备。
工作负载创建: 集群控制平面检查新工作负载中是否引用了
ResourceClaimTemplate 或特定的 ResourceClaim。
- 如果工作负载使用 ResourceClaimTemplate,名为
resourceclaim-controller
的控制器会为该工作负载生成 ResourceClaim。 - 如果工作负载使用特定的 ResourceClaim,Kubernetes 会检查该
ResourceClaim 在集群中是否存在。如果 ResourceClaim 不存在,
Pod 将无法部署。
ResourceSlice 过滤: 对于每个 Pod,Kubernetes 检查集群中的 ResourceSlice,
以找到满足以下所有条件的设备:
- 可以访问这些资源的节点有资格运行该 Pod。
- ResourceSlice 具有与 Pod 的 ResourceClaim 需求匹配的未分配资源。
- 资源分配: 在为 Pod 的 ResourceClaim 找到合格的 ResourceSlice 后,
Kubernetes 调度器使用分配详情更新 ResourceClaim。
调度器使用最先适应(first-fit)策略,并按池和 ResourceSlice 的名称字典序对其进行评估。
驱动可以通过适当的命名为特定的 slice 或池设置优先级。详细信息请参阅
命名与优先级。
- Pod 调度: 资源分配完成后,调度器将 Pod 放置在可以访问所分配资源的节点上。
该节点上的设备驱动和
kubelet 通过 gRPC 协调以配置设备和 Pod
对设备的访问权限,除非驱动为不需要节点本地制备或清理的设备声明了
可选的节点操作。
预先调度的 Pod
当你(或另一个 API 客户端)创建 Pod 时,如果 spec.nodeName 已经被设置,
调度器将被绕开。如果该 Pod 所需的某个 ResourceClaim 尚不存在、尚未分配或未为该 Pod
保留,那么 kubelet 将无法运行该 Pod,并会周期性地重新检查,
因为这些需求可能稍后仍会被满足。
当 Pod 被调度时调度器中尚未启用动态资源分配支持时(版本偏差、配置、特性门控等),
也可能出现这种情况。
kube-controller-manager 会检测到此情况,并通过保留所需的 ResourceClaim
尝试使 Pod 变为可运行状态。但是,这仅在这些 ResourceClaim 已被调度器为其他某个 Pod 分配时才有效。
最好避免绕开调度器,因为被分配到节点的 Pod 在挂起期间会阻塞常规资源(RAM、CPU),
使其无法被其他 Pod 使用。若要让 Pod 在特定节点上运行,
同时仍经过正常的调度流程,请为 Pod 创建一个与目标节点精确匹配的节点选择算符:
apiVersion: v1
kind: Pod
metadata:
name: pod-with-cats
spec:
nodeSelector:
kubernetes.io/hostname: name-of-the-intended-node
...
你也可以在准入阶段变更进入系统的 Pod,取消 .spec.nodeName 字段的设置,
改为使用节点选择算符。
设备绑定条件
特性状态:
Beta since Kubernetes v1.36; (默认启用)
设备绑定条件(Device Binding Conditions)允许 Kubernetes 调度器延迟 Pod 绑定,
直到外部资源(例如通过交换架构连接的 GPU 或可重新编程的 FPGA)被确认就绪为止。
这种等待行为在调度框架的
PreBind 阶段中实现。
在此阶段,调度器在继续绑定之前会检查所有必需的设备条件是否均已满足。
这通过避免过早绑定提高了调度的可靠性,并支持与外部设备控制器的协调。
要使用此特性,设备驱动(通常由驱动所有者管理)必须在 ResourceSlice 的
Device 部分发布以下字段。集群管理员必须启用 DRADeviceBindingConditions 和
DRAResourceClaimDeviceStatus 特性门控,调度器才会遵循这些字段。
bindingConditions- 在 Pod 可以被绑定之前必须被设置为 True
(位于关联 ResourceClaim 的
.status.conditions 字段中)的条件类型列表。
这些条件通常表示就绪信号,例如 DeviceAttached(设备已挂接)或
DeviceInitialized(设备已初始化)。
bindingFailureConditions- 条件类型列表,若在关联 ResourceClaim 的
status.conditions 字段中被设置为 True,
则表示失败状态。如果其中任意条件为 True,调度器将中止绑定并重新调度该 Pod。
bindsToNode- 若设置为
true,调度器会在 ResourceClaim 的
status.allocation.nodeSelector 字段中记录所选中的节点名称。
这不会影响 Pod 的 spec.nodeSelector。相反,
它会在 ResourceClaim 内部设置一个节点选择算符,
外部控制器可以使用它来执行节点特定的操作,例如设备挂接或制备。
bindingConditions 和 bindingFailureConditions 中列出的所有条件类型
都根据 ResourceClaim 的 status.conditions 字段进行评估。
外部控制器负责使用标准 Kubernetes
条件语义(type、status、reason、message、lastTransitionTime)更新这些条件。
调度器最多等待 600 秒(默认值),以等待所有 bindingConditions 变为 True。
如果达到超时或任何 bindingFailureConditions 为 True,
调度器会清除分配并重新调度该 Pod。集群管理员可以通过编辑 kube-scheduler
配置文件来配置此超时时间。
以下给出了在 KubeSchedulerConfiguration 中配置此超时的示例:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: DynamicResources
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: DynamicResourcesArgs
bindingTimeout: 60s
示例
以下是你可能在集群中看到的一个 ResourceSlice 示例,
该集群中正在使用某个 DRA 驱动,并且该驱动支持绑定条件:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: gpu-slice-1
spec:
driver: dra.example.com
nodeSelector:
nodeSelectorTerms:
- matchExpressions:
- key: accelerator-type
operator: In
values:
- "high-performance"
pool:
name: gpu-pool
generation: 1
resourceSliceCount: 1
devices:
- name: gpu-1
attributes:
vendor:
string: "example"
model:
string: "example-gpu"
bindsToNode: true
bindingConditions:
- dra.example.com/is-prepared
bindingFailureConditions:
- dra.example.com/preparing-failed
该示例 ResourceSlice 具有以下属性:
- ResourceSlice 目标为带有
accelerator-type=high-performance 标签的节点,
因此调度器仅使用特定的一组合格节点。
- 调度器从所选组中选择一个节点(例如
node-3),并将 ResourceClaim 中的
status.allocation.nodeSelector 字段设置为该节点名称。
dra.example.com/is-prepared 绑定条件指示设备 gpu-1 必须被制备
(is-prepared 条件的状态为 True)后才能绑定。
- 如果
gpu-1 设备制备失败(preparing-failed 条件状态为 True),
调度器会中止绑定。
- 调度器最多等待 600 秒(默认值),等待设备准备就绪。
- 外部控制器可以使用 ResourceClaim 中的节点选择算符,在选定的节点上执行节点特定的设置。
设备绑定条件由 kube-apiserver 和 kube-scheduler 中的
DRADeviceBindingConditions 特性门控控制。
节点可分配资源
特性状态:
Alpha since Kubernetes v1.36; (默认禁用)
由 DRA 管理的设备可能具有由节点可分配资源(如 cpu、memory 或 hugepages)
构成的底层占用。此特性将这些基于 DRA 的请求与常规 Pod spec
中对这些资源的请求一起整合到调度器的标准核算中。
DRA 驱动使用两种不同的模型来定义设备如何消耗节点可分配资源:
- 直接资源映射(
mapping): DRA 设备直接提供标准节点资源
(例如自定义 CPU 核心池或内存块)。申领分配直接映射到节点上的标准 CPU 或内存容量。
- 辅助设备开销(
overhead): DRA 设备(例如 GPU 或加速器)在被分配给
Pod 或容器时,需要主机资源(例如主机 RAM)作为辅助开销才能运行。
Pod 编写者的注意事项
在使用申领为这些类型的设备编写 PodSpec 时,需要注意以下几点:
容器的总资源需求等于其容器级资源与其关联资源申领中的任何节点可分配资源之和。
申领共享限制: 使用直接资源映射(mapping)的申领不能在多个 Pod 之间共享。
带有 overhead 的设备申领支持设备共享,且开销按每个 Pod 或每个容器追踪。
带有 DRA 申领的 Pod 支持对 spec 中的标准 requests 进行就地调整大小。
调度器确保调整后的标准 requests 与静态 DRA 分配结合后仍然能适配节点。
DRA 驱动编写者的细节
DRA 驱动使用 ResourceSlice 中设备上的 nodeAllocatableResources 字段
来声明此节点可分配资源占用。
它定义了将请求的 DRA 设备或容量转换为在节点的 status.allocatable
中追踪的标准资源的映射(注意,此字段不支持扩展资源)。
这对于直接暴露原生资源的驱动(例如 CPU 或内存 DRA 驱动)
和需要辅助节点依赖的设备(例如需要主机内存的加速器)都非常有用。
nodeAllocatableResources 字段支持两种不同的使用场景:
- 映射(Mapping): 当 DRA 设备直接代表标准资源时使用
(例如 CPU 或内存 DRA 驱动)。调度器通过使用
capacityMultiplier 缩放容量,
或使用 deviceMultiplier 缩放设备数量来计算精确的数量。
- 开销(Overhead): 当设备需要辅助节点依赖时使用
(例如 GPU 消耗的主机内存)。这可以定义为固定的
perPod 成本,
或定义为随引用容器数量线性缩放的可变 perContainer 成本。
示例:CPU DRA 驱动(映射 Mapping)
以下示例中,CPU DRA 驱动使用 DRA 可消耗容量将一个
CPU 插槽作为 128 个 CPU 的池暴露出来。capacityKey 将消耗的
cpu.example.com/cpu 容量直接链接到节点的标准 cpu 可分配资源:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: my-node-cpus
spec:
driver: cpu.example.com
nodeName: my-node
pool:
name: socket-cpus
generation: 1
resourceSliceCount: 1
devices:
- name: socket0cpus
allowMultipleAllocations: true
capacity:
"cpu.example.com/cpu": "128"
nodeAllocatableResources:
mapping:
cpu:
capacityKey: "cpu.example.com/cpu"
- name: socket1cpus
allowMultipleAllocations: true
capacity:
"cpu.example.com/cpu": "128"
nodeAllocatableResources:
mapping:
cpu:
capacityKey: "cpu.example.com/cpu"
capacityMultiplier: 1
示例:带辅助资源的加速器(Overhead)
以下资源切片示例中,一台加速器每个 Pod 需要额外 8Gi 的内存才能运行:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: my-node-xpus
spec:
driver: xpu.example.com
nodeName: my-node
pool:
name: xpu-pool
generation: 1
resourceSliceCount: 1
devices:
- name: xpu-model-x-001
attributes:
example.com/model:
string: "model-x"
nodeAllocatableResources:
overhead:
memory:
perPod: "8Gi"
在 Pod 成功绑定到节点后,通过 DRA 分配的精确节点可分配资源数量会由
kube-scheduler 聚合,并直接嵌入到 Pod 的
status.nodeAllocatableResourceClaimStatuses 字段中。
这提供了从调度器到 kubelet 的清晰、持久的交接。
关键是,kubelet 原生地消费此 API 来完美对齐系统级边界:
- cgroups: Pod 和容器的 cgroups 现在将包含基于 DRA 的分配,
防止工作负载被内核人为地节流。
- OOM Scores:
kubelet 将容器的 DRA 内存 requests 计算到其有效内存请求中。
节点可分配资源是一个 Alpha 特性,当在 kube-apiserver、kube-scheduler 和
kubelet 中启用 DRANodeAllocatableResources 特性门控
时即启用。
3 - DRA 特性
本页面针对一些高级用例介绍可选的 DRA 特性。其中一些特性需要 DRA 驱动的支持。
每个特性都注明了其成熟度以及启用该特性的特性门控。
可分区设备
特性状态:
Beta since Kubernetes v1.36; (默认启用)
DRA 中表示的设备不一定必须是连接到单台机器的单个单元,也可以是由连接到多台机器的多个设备组成的逻辑设备。
这些设备可能会消耗底层物理设备的重叠资源,这意味着当分配一个逻辑设备时,其他设备将不再可用。
在 ResourceSlice API 中,这表示为命名 CounterSet 的列表,每个 CounterSet 包含一组命名计数器。
这些计数器表示物理设备上可用于通过 DRA 通告的逻辑设备的资源。
逻辑设备可以指定 ConsumesCounters 列表。
每个条目包含对一个 CounterSet 的引用,以及一组命名计数器及其将消耗的数量。
因此,要使设备可分配,被引用的计数器集必须具有足够的数量来满足设备引用的计数器需求。
CounterSet 必须在与设备不同的 ResourceSlice 中指定。
设备可以消耗与其位于同一资源池中的任何 CounterSet 定义的计数器。
以下是两个设备的示例,每个设备从一个具有 8Gi 内存的共享计数器中消耗 6Gi 内存。
因此,在任何时间点只能分配其中一个设备。
调度器会处理这种情况,并且对使用者是透明的,因为 ResourceClaim API 不受影响。
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: resourceslice-with-countersets
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 2
driver: dra.example.com
sharedCounters:
- name: gpu-1-counters
counters:
memory:
value: 8Gi
---
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: resourceslice-with-devices
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 2
driver: dra.example.com
devices:
- name: device-1
consumesCounters:
- counterSet: gpu-1-counters
counters:
memory:
value: 6Gi
- name: device-2
consumesCounters:
- counterSet: gpu-1-counters
counters:
memory:
value: 6Gi
可分区设备由 kube-apiserver 和 kube-scheduler 中的
DRAPartitionableDevices 特性门控控制。
设备兼容性组
特性状态:
Alpha since Kubernetes v1.37; (默认禁用)
设备兼容性组允许 DRA 驱动声明哪些分区设备可以在同一物理硬件上共同分配。
如果没有此特性,不兼容的设备组合只有在 kubelet 在节点上准备 Pod 时才会被检测到 —— 这会导致准备失败。
有了兼容性组,调度器会在调度时、任何节点端工作开始之前就拒绝不兼容的组合。
这对于支持互斥操作模式的硬件最为有用。例如,可以在 MIG 模式或 vGPU 模式下运行的 GPU:
处于 MIG 模式的设备和处于 vGPU 模式的设备不能共同分配,因为它们以不兼容的方式消耗重叠的物理资源。
通过声明 compatibilityGroups,驱动使此约束对调度器可见。
此特性建立在可分区设备的基础之上:
compatibilityGroups 字段位于 device.consumesCounters[] 条目上,而该条目仅存在于可分区设备中。
DRADeviceCompatibilityGroups 和 DRAPartitionableDevices 这两个特性门控都必须在
kube-apiserver 和 kube-scheduler 中启用。
工作原理
驱动为 ResourceSlice 中的每个 device.consumesCounters[] 条目定义一个 compatibilityGroups 列表。
该列表最多包含 2 个不透明的字符串名称,表示该设备在该特定计数器集上的操作模式或分区类型。
当调度器分配从同一计数器集获取资源的多个设备时,它会计算这些设备的 compatibilityGroups 的交集。
只有当该交集非空时 —— 即每个共同分配的设备至少共享一个共同的组名 —— 分配才会成功。
从不同计数器集获取资源的设备永远不会相互比较。
未声明任何组的设备(未设置、为 nil 或为空列表)被视为特殊情况:它只能与同一计数器集上其他没有组的设备共同分配。
它永远不能与声明了一个或多个组的设备共同分配。
此约束适用于在单个调度周期内分配的所有声明:
如果两个声明各自从同一计数器集分配一个设备,则跨声明的组交集也会被强制执行。
示例
考虑一个可以在 MIG 模式或 vGPU 模式下运行的 GPU。
驱动发布两个设备,每个设备从同一个 8 GiB 的共享内存计数器中消耗 4 GiB。
仅根据计数器容量,两个设备可以一起分配。每个设备将其操作模式声明为一个兼容性组,从而使两种模式互斥:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: gpu-counters
spec:
nodeName: worker-1
pool:
name: gpu-pool
generation: 1
resourceSliceCount: 2
driver: gpu.example.com
sharedCounters:
- name: gpu-0-memory
counters:
memory:
value: 8Gi
---
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: gpu-devices
spec:
nodeName: worker-1
pool:
name: gpu-pool
generation: 1
resourceSliceCount: 2
driver: gpu.example.com
devices:
- name: gpu-0-mig
consumesCounters:
- counterSet: gpu-0-memory
counters:
memory:
value: 4Gi
compatibilityGroups:
- mig
- name: gpu-0-vgpu
consumesCounters:
- counterSet: gpu-0-memory
counters:
memory:
value: 4Gi
compatibilityGroups:
- vgpu
在此示例中:
gpu-0-mig 属于 mig 组。gpu-0-vgpu 属于 vgpu 组。
如果一个 Pod 或 PodGroup 从该资源池请求两个设备,
调度器会检查所选的两个设备在 gpu-0-memory 计数器集上是否共享一个共同的兼容性组。
由于 {"mig"} ∩ {"vgpu"} = ∅,该设备对会被拒绝——即使计数器集有足够的内存同时满足两者。
两个请求只能通过来自存在此类设备对的资源池中的两个 MIG 设备(或两个 vGPU 设备)来满足。
约束条件
- 每个
consumesCounters[] 条目最多可以声明 2 个组名。 - 组名在单个条目内必须唯一。
- 组名对 Kubernetes 是不透明的;它们仅在发布驱动的资源池内有意义。
- 组按计数器集进行比较:一个计数器集上的组对另一个计数器集的共同分配决策没有影响。
版本偏差安全
当 DRADeviceCompatibilityGroups 特性门控被禁用时(Alpha 阶段的默认设置),
kube-apiserver 会从任何新的或更新的 ResourceSlice 中剥离 compatibilityGroups
字段——除非旧对象已经设置了该字段。
然后,调度器会将任何以前有分组设备的资源池中的设备视为属于不完整的资源池,并完全跳过它们。
只有非空列表才算作设置了该字段:compatibilityGroups: null 和 compatibilityGroups: []
被视为与省略该字段相同。
带有这些值的设备的行为与没有组的设备完全相同 —— 它们不会导致调度器将资源池视为不完整。
设备兼容性组由 kube-apiserver 和 kube-scheduler 中的
DRADeviceCompatibilityGroups 特性门控控制。
同时还必须启用
DRAPartitionableDevices 特性门控。
可消耗容量
特性状态:
Beta since Kubernetes v1.36; (默认启用)
可消耗容量特性允许多个独立的 ResourceClaim 消耗同一设备,由 Kubernetes 调度器管理每个声明消耗了多少设备容量。
这类似于 Pod 如何共享节点上的资源;ResourceClaim 可以共享设备上的资源。
设备驱动可以设置 ResourceSlice 的 .spec.devices 中新增的 allowMultipleAllocations 字段,
以允许将该设备分配给多个独立的 ResourceClaim 或一个 ResourceClaim 内的多个请求。
用户可以设置 ResourceClaim 的 spec.devices.requests 中新增的 capacity 字段,以指定每次分配的设备资源需求。
对于允许多次分配的设备,所请求的容量从其总容量中提取(即消耗),这一概念被称为可消耗容量。
然后,调度器确保所有声明的总消耗容量不超过设备的整体容量。
此外,驱动开发者可以对各个设备容量使用 requestPolicy 约束来控制这些容量的消耗方式。
例如,驱动开发者可以指定某个容量只能以 1Gi 的增量消耗。
下面是一个网络设备的示例,该设备允许多次分配并包含可消耗的带宽容量。
kind: ResourceSlice
apiVersion: resource.k8s.io/v1
metadata:
name: resourceslice
spec:
nodeName: worker-1
pool:
name: pool
generation: 1
resourceSliceCount: 1
driver: dra.example.com
devices:
- name: eth1
allowMultipleAllocations: true
attributes:
name:
string: "eth1"
capacity:
bandwidth:
requestPolicy:
default: "1M"
validRange:
min: "1M"
step: "8"
value: "10G"
可消耗容量的请求方式如下例所示。
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: bandwidth-claim-template
spec:
spec:
devices:
requests:
- name: req-0
exactly:
deviceClassName: resource.example.com
capacity:
requests:
bandwidth: 1G
分配结果将包含已消耗的容量和共享标识符。
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
...
status:
allocation:
devices:
results:
- consumedCapacity:
bandwidth: 1G
device: eth1
shareID: "a671734a-e8e5-11e4-8fde-42010af09327"
在本例中,选择了一个可多次分配的设备。
然而,任何至少具有所请求 1G 带宽的 resource.example.com 设备都可以满足该需求。
如果选择了不可多次分配的设备,则分配将占用整个设备。
要强制只使用可多次分配的设备,可以使用 CEL 条件 device.allowMultipleAllocations == true。
DistinctAttribute 约束
在一个 ResourceClaim 中请求多个设备时,你可以使用 DistinctAttribute
约束来确保每个已分配设备在指定属性上具有不同的值。此约束是随可消耗容量特性一起引入的。
DistinctAttribute 约束在处理可多次分配的设备时特别有用。
它可以防止调度器在单个 ResourceClaim 内多次分配同一设备,即使该设备允许多次分配也是如此。
除了防止重复分配外,此约束还有助于通过确保设备根据其属性分布来优化性能。
例如,你可以使用它在不同的 NUMA 节点间分布设备,以优化内存带宽并减少争用。
细粒度状态授权
特性状态:
Beta since Kubernetes v1.36; (默认启用)
从 Kubernetes v1.36 开始,DRA 通过使用合成子资源和节点感知动词,对 ResourceClaim 状态的更新实施细粒度的授权检查。
有关安全加固指南(包括调度器和 DRA 驱动的 RBAC 示例),
请参见加固指南 - 动态资源分配。
有关集群管理员的分步操作流程,请参见在集群中加固动态资源分配。
可选节点操作
特性状态:
Alpha since Kubernetes v1.37; (默认禁用)
在动态资源分配(DRA)中,kubelet 通过 gRPC 与节点本地驱动协作,
在容器启动前准备已分配的设备(NodePrepareResources),并在 Pod 终止时取消准备(NodeUnprepareResources)。
虽然这种设置对于 GPU 或 FPGA 等节点本地硬件至关重要,但某些资源完全在控制平面中管理,不需要节点本地设置。
可选节点操作特性允许资源驱动声明可以跳过特定的节点本地 gRPC 操作。
配置后,kubelet 会绕过这些设备的驱动查找和 gRPC 调用,从而无需在每个工作节点上部署和维护空的节点本地驱动。
驱动配置
驱动开发者可以在 ResourceSlice 的 .spec.skipNodeOperations 中指定 skipNodeOperations 字段。
该字段是一个唯一字符串列表,指定要为该切片中所有设备绕过的节点本地操作。
有效值包括:
"NodePrepareResources":跳过 NodePrepareResources gRPC 调用。
除非同时列出了 "NodeUnprepareResources"(或指定了 "*"),否则不能指定该值。
此限制避免了在缺少节点本地插件时 Pod 卡在 Terminating 状态的问题,因为当跳过准备操作时,
Pod 启动期间不会检查插件。
以下是一个用于控制平面资源的 ResourceSlice 示例,该资源跳过所有节点本地操作:
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
name: control-plane-resources
spec:
nodeName: worker-1
pool:
name: central-pool
generation: 1
resourceSliceCount: 1
driver: control-plane.example.com
skipNodeOperations:
- "*"
devices:
- name: virtual-device-1
分配结果与执行
当 Kubernetes 调度器将设备分配给 ResourceClaim 时,
它会将 skipNodeOperations 列表从 ResourceSlice 复制到分配结果中:
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
...
status:
allocation:
devices:
results:
- device: virtual-device-1
driver: control-plane.example.com
pool: central-pool
skipNodeOperations:
- "*"
当 Pod 在节点上运行时,kubelet 读取分配结果。
如果 ResourceClaim 中某个给定驱动的所有已分配设备都跳过某项特定操作,
则 kubelet 会完全绕过对该驱动的该 gRPC 钩子调用。
操作注意事项
原地驱动更新
由于 skipNodeOperations 设置是在分配时从 ResourceSlice 复制到 ResourceClaim 中的,
因此正在运行的 Pod 和活跃的分配会保留它们被调度时的设置。
如果驱动的节点操作要求被原地更新(例如,从需要节点操作变为跳过节点操作),现有的声明仍将使用之前的配置。
为避免出现问题 —— 例如终止中的 Pod 在等待已停用的节点插件时挂起 —— 集群管理员应在更改驱动的节点操作要求或移除节点本地驱动 DaemonSet
之前,确保该驱动没有活跃的声明存在。
节点声明特性集成
为了防止 Pod 被调度到 kubelet 不支持跳过 DRA 操作的节点上(这会导致 kubelet 在等待缺失的节点插件时失败),
该特性与节点声明特性集成。
当 Pod 使用配置了 skipNodeOperations 的 ResourceClaim 时,Kubernetes 调度器会在调度 Pod 之前,
验证目标节点是否在其 .status.declaredFeatures 中声明了对 DRAOptionalNodeOperations 特性的支持。
可选节点操作由 kube-apiserver、kube-scheduler 和 kubelet 中的
DRAOptionalNodeOperations
特性门控控制。
特性状态:
Alpha since Kubernetes v1.36
DRA 驱动可以将设备元数据(例如设备属性 —— PCI 总线地址或中介设备的 mdevUUID —— 或网络配置)作为 JSON 文件直接暴露给容器。
这使得容器内的应用无需查询 Kubernetes API 或构建自定义控制器即可发现已分配设备的信息。
KEP-5304 定义了驱动必须遵循的设备元数据协议,
以便容器内的应用在不同驱动和集群之间看到一致的布局。
DRA kubelet 插件库为你实现了此协议;
本节其余部分介绍如何使用它。
设备元数据遵循与设备访问相同的规则:仅当容器在其容器规约中请求该设备时,元数据才在容器内可用,否则不可用。
有关如何在 Pod 和容器中请求 DRA 设备,请参阅在工作负载中使用 DRA 请求设备。
该协议包含四条规则:
文件路径。 元数据文件位于容器内的 /var/run/kubernetes.io/dra-device-attributes 目录下。
对于直接引用的 ResourceClaim,路径为
resourceclaims/<claimName>/<requestName>/<driverName>-metadata.json;
对于从 ResourceClaimTemplate 创建的声明,路径为
resourceclaimtemplates/<podClaimName>/<requestName>/<driverName>-metadata.json
(其中 podClaimName 是 pod.spec.resourceClaims[].name)。
当 ResourceClaim 请求使用优先级列表特性时,
文件路径中的 <requestName> 段仅使用顶层请求名称(即,/<subrequest> 部分被丢弃)。
在 JSON 文件内部, requests[].name 字段携带完整的 <request>/<subrequest>
引用(例如 gpu/high-memory),以便消费者能够识别分配的是哪个备选项。
路径常量定义在 k8s.io/dynamic-resource-allocation/api/metadata 中。
- JSON API。 每个文件是一个或多个
DeviceMetadata
对象的流,这些对象按照 Kubernetes API 约定,序列化为带有 apiVersion 和 kind 的版本化 JSON。
相同的元数据针对每个支持的 API 版本编码一次(最新版本优先)。
流中的所有对象在语义上是等价的;消费者应使用他们能够解码的第一个对象。
- 世代号。 当驱动更新元数据文件时,嵌入的
metadata.generation 字段必须递增,以便消费者能够检测到变化。
- 容器暴露。 文件通常通过 CDI 绑定挂载暴露,
但也允许使用其他机制,只要文件出现在正确的路径上并且在容器内是只读的。
设备元数据是一个驱动端的特性,不需要任何 Kubernetes API 更改或特性门控。
使用 DRA kubelet 插件库是实现驱动的常用方式,但驱动也可以通过其他方式构建。
使用 kubelet 插件的驱动通过在启动插件时传递 EnableDeviceMetadata 和 MetadataVersions
选项来启用此特性。
MetadataVersions 指定哪些 API 版本被序列化到元数据文件中,并且必须由驱动显式设置。
请查看你的 DRA 驱动的文档,了解是否支持设备元数据以及如何启用它。
启用设备元数据后,驱动在为 Pod 准备已分配设备时、在消费容器启动之前,生成元数据文件和 CDI 绑定挂载规约。
元数据按照上文定义的知名路径出现在容器内部。
当单个请求从多个 DRA 驱动分配设备时,每个驱动写入自己的元数据文件。
容器枚举请求目录中的 *-metadata.json 文件以发现所有设备。
Go 包 k8s.io/dynamic-resource-allocation/devicemetadata
提供了供容器内应用读取和解码这些元数据文件的工具。
每个元数据文件都符合
DeviceMetadata API
(metadata.resource.k8s.io/v1alpha1)。以下示例显示了通过 ResourceClaimTemplate 分配的 GPU 设备的元数据文件:
{
"kind": "DeviceMetadata",
"apiVersion": "metadata.resource.k8s.io/v1alpha1",
"metadata": {
"name": "pod0-gpu-2kqrd",
"namespace": "gpu-test1",
"uid": "c7e7b22e-239b-4498-b27c-7f1344481e14",
"generation": 1
},
"podClaimName": "gpu",
"requests": [
{
"name": "gpu",
"devices": [
{
"driver": "gpu.example.com",
"pool": "worker-0",
"name": "gpu-0",
"attributes": {
"driverVersion": {
"version": "1.0.0"
},
"index": {
"int": 0
},
"model": {
"string": "LATEST-GPU-MODEL"
},
"uuid": {
"string": "gpu-18db0e85-99e9-c746-8531-ffeb86328b39"
}
}
}
]
}
]
}
驱动以以下两种方式之一提供元数据:
- 即时
- 驱动在节点上准备声明时填充元数据,并在容器启动前写入元数据文件。
这是 GPU 驱动的典型情况,设备信息在准备时已知。
- 延迟
- 在某些情况下,例如网络驱动,设备信息在设备分配时不可用,但在 Pod 沙箱创建后变为可用。
在这些情况下,驱动使用空的元数据文件创建 CDI 挂载,然后通过在容器启动前运行的 NRI
钩子稍后写入实际元数据。这确保应用永远不会看到缺失或部分写入的文件。
每次更新都必须递增
metadata.generation,以便消费者能够检测到变化。
DRA kubelet 插件库中的 MetadataUpdaterAPI 为驱动开发者自动处理世代号的簿记工作。
在两种情况下,元数据在每个消费容器的生命周期内都保持可用。
元数据文件在 Pod 中的所有容器终止后被清理。
要了解如何在工作负载中使用设备元数据,
参阅访问 DRA 设备元数据。
不使用 DRA kubelet 插件库的自定义手工驱动必须自己实现设备元数据协议。
这意味着在正确的文件路径写入 DeviceMetadata JSON,每次更新时递增 metadata.generation,
并通过 CDI 或等效机制将文件以只读方式暴露在容器内部。
4 - 设备污点与容忍度
本页介绍 DRA 中的设备污点与容忍度,它们允许驱动和管理员阻止 Pod
使用特定设备,或驱逐已经在使用这些设备的 Pod。
设备污点与容忍度
特性状态:
GA since Kubernetes v1.37
More information about this feature
This is a stable feature in Kubernetes, and has been since version v1.37. It was first available in the v1.33 release. You can no longer disable or opt out of this feature or behavior (it is locked); if you explicitly set a value for the associated feature gate DRADeviceTaints, Kubernetes ignores it but does not report any error.
设备污点与节点污点类似:一个污点包含字符串键、字符串值以及效果。
该效果会应用到正在使用被污染设备的 ResourceClaim,以及引用该 ResourceClaim 的所有 Pod。
NoSchedule 效果会阻止这些 Pod 的调度。
在尝试分配 ResourceClaim 时,被污染的设备会被忽略,
因为使用它们会阻止 Pod 的调度。
NoExecute 效果暗含 NoSchedule,并且会额外驱逐所有已被调度的 Pod。
该驱逐由 kube-controller-manager 中的设备污点驱逐控制器通过删除受影响的 Pod 来实现。
调度器和驱逐控制器会忽略 None 效果。
DRA 驱动可以用它向管理员或其他控制器传达异常情况,
例如设备的健康状况下降。管理员也可以用它在 DeviceTaintRule
中执行 Pod 驱逐的试运行(更多见下文)。
ResourceClaim 可以容忍污点。如果某个污点被容忍,则其效果不会生效。
空的容忍度匹配所有污点。容忍度可以被限制为仅对某些效果生效,
和/或匹配某些键值对。容忍度可以检查某个键是否存在(无论其值是什么),
也可以检查键的特定值。有关这种匹配的更多信息,
请参阅节点污点概念。
通过在一段时间内容忍污点可以延迟驱逐。
该延迟从污点被添加到设备上的时间开始计算,
该时间记录在污点的一个字段中。
如上所述,污点同样适用于分配节点上"所有"设备的 ResourceClaim。
必须所有设备都未被打上污点,或者它们的所有污点都被容忍。
使用管理员访问(见上文的描述)分配设备同样不能豁免。
使用该模式的管理员必须显式容忍所有污点才能访问被污染的设备。
你可以通过使用 DeviceTaintRule API 种类,以以下方式向设备添加污点。
由驱动设置的污点
DRA 驱动可以向其在 ResourceSlice 中发布的设备信息添加污点。
请查阅 DRA 驱动的文档,了解该驱动是否使用污点以及其键和值是什么。
由管理员设置的污点
特性状态:
GA since Kubernetes v1.37
More information about this feature
This is a stable feature in Kubernetes, and has been since version v1.37. It was first available in the v1.35 release. You can no longer disable or opt out of this feature or behavior (it is locked); if you explicitly set a value for the associated feature gate DRADeviceTaintRules, Kubernetes ignores it but does not report any error.
管理员或控制平面组件可以直接为设备设置污点,
而无需告知 DRA 驱动在其 ResourceSlice 中的设备信息里包含污点。
他们通过创建 DeviceTaintRule 来实现。
每个 DeviceTaintRule 向匹配设备选择算符的设备添加一个污点。
如果没有选择算符,则不会有设备被污染。
这降低了因误操作遗漏选择算符而意外驱逐所有使用 ResourceClaim 的 Pod 的风险。
可以通过指定 DeviceClass、驱动、池和/或设备的名称来选择设备。
DeviceClass 会选择所有被该 DeviceClass 中的选择算符选中的设备。
仅使用驱动名称,管理员可以污染由该驱动管理的所有设备,
例如在整个集群中对该驱动进行某种维护时。
如果驱动管理节点本地的设备,添加池名称可以将污点限制到单个节点。
最后,添加设备名称可以选择一个特定设备。
如果需要,设备名称和池名称也可以单独使用。
例如,建议节点本地设备的驱动使用节点名称作为其池名称。
这样,使用该池名称设置污点会自动污染节点上的所有设备。
驱动可能使用像 "gpu-0" 这样的稳定名称,
隐藏了当前分配给该名称的是哪一个具体设备。
为了支持污染特定的硬件实例,如果驱动为其硬件提供了
特定于供应商的唯一 ID 属性,可以在 DeviceTaintRule 中使用 CEL
选择算符来匹配该属性。
只要 DeviceTaintRule 存在,污点就会生效。
它可以随时被修改和删除。
以下是一个针对虚构 DRA 驱动的 DeviceTaintRule 示例:
apiVersion: resource.k8s.io/v1
kind: DeviceTaintRule
metadata:
name: example
spec:
# 该特定驱动的整个硬件安装已损坏。
# 驱逐所有 Pod,且不调度新的 Pod。
deviceSelector:
driver: dra.example.com
taint:
key: dra.example.com/unhealthy
value: Broken
effect: NoExecute
kube-apiserver 通过设置 spec 中的 timeAdded 字段,
自动追踪该污点的创建时间。容忍期从该时间戳开始计算。
在变更效果的更新过程中(见下文的模拟驱逐流程),
kube-apiserver 会自动更新时间戳。用户可以在创建 DeviceTaintRule
时显式设置该字段,或在更新时改为其他值,从而显式控制时间戳。
status 中包含由驱逐控制器添加的一个条件:
kubectl describe devicetaintrules
Name: example
...
Spec:
Device Selector:
Driver: dra.example.com
Taint:
Effect: NoExecute
Key: dra.example.com/unhealthy
Time Added: 2025-11-05T18:15:37Z
Value: Broken
Status:
Conditions:
Last Transition Time: 2025-11-05T18:15:37Z
Message: 1 pod evicted since starting the controller.
Observed Generation: 1
Reason: Completed
Status: False
Type: EvictionInProgress
Events: <none>
Pod 通过被删除来驱逐。通常这会很快发生,
除非对污点的容忍度将其延迟一段时间,或者有非常多的 Pod 需要驱逐。
如果驱逐花费的时间较长,message 会提供关于当前状态的信息:
```
2 pods need to be evicted in 2 different namespaces. 1 pod evicted since starting the controller.
```
该状况可用于检查驱逐当前是否正在进行:
```
kubectl wait --for=condition=EvictionInProgress=false DeviceTaintRule/example
```
请注意调度器和控制器在不同时间观察到新污点可能引发的竞态条件:
这可能导致在控制器认为没有需要驱逐的 Pod 从而将此条件设置为
False 时,仍有 Pod 被调度。实际中,通过刻意延迟几秒钟才更新状态,
这种竞态条件出现的可能性非常低。
对于 effect: None,message 会提供关于受影响设备数量、
其中已分配的设备数量,以及如果效果为 NoExecute 将会被驱逐的 Pod
数量的信息。这可以在实际触发驱逐之前用作试运行:
- 使用所需的选择算符和
effect: None 创建一个 DeviceTaintRule。
3 published devices selected. 1 allocated device selected.
1 pod would be evicted in 1 namespace if the effect was NoExecute.
This information will not be updated again. Recreate the DeviceTaintRule to trigger an update.
已发布的设备是 ResourceSlice 中列出的设备。为它们设置污点会阻止为新 Pod 分配。
只有已分配的设备才会导致使用它们的 Pod 被驱逐。
- 编辑 DeviceTaintRule,将效果更改为
NoExecute。