设备插件
Kubernetes v1.26 [stable]
Kubernetes 提供了一个设备插件框架,你可以使用它向 Kubelet 通告系统硬件资源。
供应商无需自定义 Kubernetes 本身的代码,而是可以实现设备插件,你可以手动部署或将其部署为 DaemonSet。目标设备包括 GPU、高性能网卡、FPGA、InfiniBand 适配器以及其他类似的可能需要特定于供应商初始化和设置的计算资源。
设备插件注册
Kubelet 暴露一个 Registration
gRPC 服务
service Registration {
rpc Register(RegisterRequest) returns (Empty) {}
}
设备插件可以通过此 gRPC 服务向 Kubelet 注册。注册期间,设备插件需要发送
- 其 Unix socket 的名称。
- 构建时所依据的设备插件 API 版本。
- 它想要通告的
ResourceName
。这里的ResourceName
需要遵循扩展资源命名方案,格式为vendor-domain/resourcetype
。(例如,NVIDIA GPU 通告为nvidia.com/gpu
。)
成功注册后,设备插件将向 Kubelet 发送其管理的设备列表,然后 Kubelet 负责将这些资源作为 Kubelet 节点状态更新的一部分通告给 API 服务器。例如,在设备插件向 Kubelet 注册 hardware-vendor.example/foo
并报告节点上有两个健康的设备后,节点状态会更新,通告该节点已安装并可用 2 个“Foo”设备。
然后,用户可以请求设备作为 Pod 规约的一部分(参阅 container
)。请求扩展资源类似于你管理其他资源的请求和限制的方式,但有以下区别
- 扩展资源仅支持整数类型资源,并且不能超量分配(overcommit)。
- 设备不能在容器之间共享。
示例
假设一个 Kubernetes 集群运行了一个设备插件,并在某些节点上通告资源 hardware-vendor.example/foo
。下面是一个 Pod 请求此资源以运行演示工作负载的示例
---
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
spec:
containers:
- name: demo-container-1
image: registry.k8s.io/pause:3.8
resources:
limits:
hardware-vendor.example/foo: 2
#
# This Pod needs 2 of the hardware-vendor.example/foo devices
# and can only schedule onto a Node that's able to satisfy
# that need.
#
# If the Node has more than 2 of those devices available, the
# remainder would be available for other Pods to use.
设备插件实现
设备插件的一般工作流程包括以下步骤
初始化。在此阶段,设备插件执行特定于供应商的初始化和设置,以确保设备处于就绪状态。
插件启动一个 gRPC 服务,使用主机路径
/var/lib/kubelet/device-plugins/
下的 Unix socket,并实现以下接口service DevicePlugin { // GetDevicePluginOptions returns options to be communicated with Device Manager. rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {} // ListAndWatch returns a stream of List of Devices // Whenever a Device state change or a Device disappears, ListAndWatch // returns the new list rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {} // Allocate is called during container creation so that the Device // Plugin can run device specific operations and instruct Kubelet // of the steps to make the Device available in the container rpc Allocate(AllocateRequest) returns (AllocateResponse) {} // GetPreferredAllocation returns a preferred set of devices to allocate // from a list of available ones. The resulting preferred allocation is not // guaranteed to be the allocation ultimately performed by the // devicemanager. It is only designed to help the devicemanager make a more // informed allocation decision when possible. rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {} // PreStartContainer is called, if indicated by Device Plugin during registration phase, // before each container start. Device plugin can run device specific operations // such as resetting the device before making devices available to the container. rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {} }
注意
插件无需为GetPreferredAllocation()
或PreStartContainer()
提供有用的实现。如果在调用GetDevicePluginOptions()
返回的DevicePluginOptions
消息中设置了指示这些调用是否可用的标志,则 Kubelet 将始终先调用GetDevicePluginOptions()
以查看哪些可选函数可用,然后才会直接调用其中的任何一个。插件通过主机路径
/var/lib/kubelet/device-plugins/kubelet.sock
处的 Unix socket 向 Kubelet 注册。注意
工作流程的顺序很重要。插件必须在向 Kubelet 注册之前启动 gRPC 服务,才能成功注册。成功注册后,设备插件会运行在服务模式下,期间它会持续监控设备健康状况并在设备状态发生任何变化时报告给 Kubelet。它还负责处理
Allocate
gRPC 请求。在Allocate
期间,设备插件可能会进行设备特定的准备;例如,GPU 清理或 QRNG 初始化。如果操作成功,设备插件会返回一个AllocateResponse
,其中包含用于访问已分配设备的容器运行时配置。Kubelet 将此信息传递给容器运行时。一个
AllocateResponse
包含零个或多个ContainerAllocateResponse
对象。在这些对象中,设备插件定义了为提供对设备的访问必须对容器定义进行的修改。这些修改包括- 注解
- 设备节点
- 环境变量
- 挂载点
- 完全限定的 CDI 设备名称
注意
设备管理器处理完全限定的 CDI 设备名称,要求在 Kubelet 和 kube-apiserver 上都启用了DevicePluginCDIDevices
特性门控。这在 Kubernetes v1.28 中作为 Alpha 特性添加,在 v1.29 中升级到 Beta,并在 v1.31 中升级到 GA。
处理 Kubelet 重启
设备插件应能检测到 Kubelet 重启,并向新的 Kubelet 实例重新注册。新的 Kubelet 实例在启动时会删除 /var/lib/kubelet/device-plugins
下所有现有的 Unix socket。设备插件可以监控其 Unix socket 的删除,并在发生此类事件时重新注册。
设备插件和不健康的设备
有些情况下设备会发生故障或关机。此时设备插件的职责是使用 ListAndWatchResponse
API 将情况通知 Kubelet。
一旦设备被标记为不健康,Kubelet 将减少节点上此资源的 allocatable(可分配)数量,以反映有多少设备可用于调度新 Pod。资源的 Capacity(容量)数量不会改变。
分配给发生故障设备的 Pod 将继续分配到该设备。通常,依赖于设备的代码将开始失败,如果 Pod 的 restartPolicy
不是 Always
,则 Pod 可能会进入 Failed 阶段,否则会进入崩溃循环。
在 Kubernetes v1.31 之前,判断 Pod 是否与发生故障设备关联的方式是使用 PodResources API。
Kubernetes v1.31 [alpha]
(默认为禁用)通过启用特性门控 ResourceHealthStatus
,字段 allocatedResourcesStatus
将被添加到每个容器的状态中,位于每个 Pod 的 .status
内。allocatedResourcesStatus
字段报告分配给容器的每个设备的健康信息。
对于失败的 Pod,或你怀疑有故障的情况,你可以使用此状态来了解 Pod 的行为是否可能与设备故障有关。例如,如果加速器报告超温事件,allocatedResourcesStatus
字段可能能够报告此信息。
设备插件部署
你可以将设备插件部署为 DaemonSet、你的节点操作系统的软件包,或者手动部署。
标准目录 /var/lib/kubelet/device-plugins
需要特权访问,因此设备插件必须在特权安全上下文中运行。如果你将设备插件部署为 DaemonSet,则 /var/lib/kubelet/device-plugins
必须在其插件的 PodSpec 中挂载为一个 Volume。
如果你选择 DaemonSet 方式,你可以依靠 Kubernetes 来:将设备插件的 Pod 放置到节点上,在失败后重启守护进程 Pod,并帮助自动化升级。
API 兼容性
以前,版本控制方案要求设备插件的 API 版本与 Kubelet 的版本完全匹配。自此特性在 v1.12 晋升到 Beta 版本以来,这不再是硬性要求。该 API 是版本化的,并且自从 Beta 晋升以来一直稳定。因此,Kubelet 升级应该是无缝的,但在稳定之前 API 可能仍然会有变化,这使得升级不一定能保证不中断。
注意
尽管 Kubernetes 的设备管理器组件是一个通用可用特性,但 设备插件 API 并不稳定。有关设备插件 API 和版本兼容性的信息,请阅读设备插件 API 版本。作为项目,Kubernetes 建议设备插件开发人员
- 关注未来版本中设备插件 API 的变化。
- 支持设备插件 API 的多个版本,以实现向后/向前兼容。
要在一个需要升级到具有更新设备插件 API 版本的 Kubernetes 发行版的节点上运行设备插件,请在升级这些节点之前,将你的设备插件升级到同时支持两个版本。采用这种方法将确保升级期间设备分配的持续功能。
监控设备插件资源
Kubernetes v1.28 [stable]
为了监控设备插件提供的资源,监控代理需要能够发现节点上正在使用的设备集合,并获取元数据以描述该指标应与哪个容器关联。Prometheus 指标由设备监控代理公开,应遵循Kubernetes 仪表指南,使用 pod
、namespace
和 container
Prometheus 标签来标识容器。
Kubelet 提供一个 gRPC 服务来启用对正在使用的设备的发现,并提供这些设备的元数据
// PodResourcesLister is a service provided by the kubelet that provides information about the
// node resources consumed by pods and containers on the node
service PodResourcesLister {
rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
rpc Get(GetPodResourcesRequest) returns (GetPodResourcesResponse) {}
}
List
gRPC 端点
List
端点提供有关运行中 Pod 资源的信息,包括独占分配的 CPU ID、设备插件报告的设备 ID 以及这些设备分配到的 NUMA 节点 ID 等详细信息。此外,对于基于 NUMA 的机器,它还包含为容器预留的内存和巨页信息。
从 Kubernetes v1.27 开始,List
端点可以提供通过 DynamicResourceAllocation
API 在 ResourceClaims
中分配的运行中 Pod 资源的信息。要启用此特性,必须使用以下标志启动 kubelet
--feature-gates=DynamicResourceAllocation=true,KubeletPodResourcesDynamicResources=true
// ListPodResourcesResponse is the response returned by List function
message ListPodResourcesResponse {
repeated PodResources pod_resources = 1;
}
// PodResources contains information about the node resources assigned to a pod
message PodResources {
string name = 1;
string namespace = 2;
repeated ContainerResources containers = 3;
}
// ContainerResources contains information about the resources assigned to a container
message ContainerResources {
string name = 1;
repeated ContainerDevices devices = 2;
repeated int64 cpu_ids = 3;
repeated ContainerMemory memory = 4;
repeated DynamicResource dynamic_resources = 5;
}
// ContainerMemory contains information about memory and hugepages assigned to a container
message ContainerMemory {
string memory_type = 1;
uint64 size = 2;
TopologyInfo topology = 3;
}
// Topology describes hardware topology of the resource
message TopologyInfo {
repeated NUMANode nodes = 1;
}
// NUMA representation of NUMA node
message NUMANode {
int64 ID = 1;
}
// ContainerDevices contains information about the devices assigned to a container
message ContainerDevices {
string resource_name = 1;
repeated string device_ids = 2;
TopologyInfo topology = 3;
}
// DynamicResource contains information about the devices assigned to a container by Dynamic Resource Allocation
message DynamicResource {
string class_name = 1;
string claim_name = 2;
string claim_namespace = 3;
repeated ClaimResource claim_resources = 4;
}
// ClaimResource contains per-plugin resource information
message ClaimResource {
repeated CDIDevice cdi_devices = 1 [(gogoproto.customname) = "CDIDevices"];
}
// CDIDevice specifies a CDI device information
message CDIDevice {
// Fully qualified CDI device name
// for example: vendor.com/gpu=gpudevice1
// see more details in the CDI specification:
// https://github.com/container-orchestrated-devices/container-device-interface/blob/main/SPEC.md
string name = 1;
}
注意
List
端点中的 ContainerResources
中的 cpu_ids 对应于分配给特定容器的独占 CPU。如果目标是评估属于共享池的 CPU,则需要将 List
端点与 GetAllocatableResources
端点结合使用,如下所述
- 调用
GetAllocatableResources
以获取所有可分配 CPU 的列表 - 对系统中所有
ContainerResources
调用GetCpuIds
- 从
GetAllocatableResources
调用中减去所有GetCpuIds
调用中的 CPU
GetAllocatableResources
gRPC 端点
Kubernetes v1.28 [stable]
GetAllocatableResources 提供工作节点上初始可用资源的信息。它比 Kubelet 导出到 APIServer 的信息更多。
注意
GetAllocatableResources
仅应用于评估节点上的可分配资源。如果目标是评估空闲/未分配资源,应与 List() 端点结合使用。除非暴露给 Kubelet 的底层资源发生变化,否则 GetAllocatableResources
获得的结果将保持不变。这种情况很少发生,但当发生时(例如:热插拔、设备健康变化),客户端应调用 GetAlloctableResources
端点。
但是,在 CPU 和/或内存更新的情况下,仅调用 GetAllocatableResources
端点是不够的,需要重启 Kubelet 才能反映正确的资源容量和可分配量。
// AllocatableResourcesResponses contains information about all the devices known by the kubelet
message AllocatableResourcesResponse {
repeated ContainerDevices devices = 1;
repeated int64 cpu_ids = 2;
repeated ContainerMemory memory = 3;
}
ContainerDevices
确实暴露了拓扑信息,声明设备与哪个 NUMA 单元关联。NUMA 单元使用不透明的整数 ID 进行标识,其值与设备插件在向 Kubelet 注册时报告的值一致。
gRPC 服务通过 /var/lib/kubelet/pod-resources/kubelet.sock
处的 Unix socket 提供。设备插件资源的监控代理可以作为守护进程或 DaemonSet 进行部署。标准目录 /var/lib/kubelet/pod-resources
需要特权访问,因此监控代理必须在特权安全上下文中运行。如果设备监控代理作为 DaemonSet 运行,则 /var/lib/kubelet/pod-resources
必须作为 Volume 挂载到设备监控代理的 PodSpec 中。
注意
当从 DaemonSet 或部署为容器在主机上运行的任何其他应用访问 /var/lib/kubelet/pod-resources/kubelet.sock
时(将 socket 作为卷挂载),最佳实践是挂载目录 /var/lib/kubelet/pod-resources/
而不是 /var/lib/kubelet/pod-resources/kubelet.sock
。这样可以确保在 Kubelet 重启后,容器能够重新连接到该 socket。
容器挂载由引用 socket 或目录的 inode 管理,具体取决于挂载的内容。当 Kubelet 重启时,socket 被删除并创建一个新的 socket,而目录保持不变。因此,socket 的原始 inode 变得不可用。指向目录的 inode 将继续工作。
Get
gRPC 端点
Kubernetes v1.27 [alpha]
Get
端点提供运行中 Pod 资源的信息。它公开的信息类似于 List
端点中描述的信息。Get
端点需要运行中 Pod 的 PodName
和 PodNamespace
。
// GetPodResourcesRequest contains information about the pod
message GetPodResourcesRequest {
string pod_name = 1;
string pod_namespace = 2;
}
要启用此特性,必须使用以下标志启动 Kubelet 服务
--feature-gates=KubeletPodResourcesGet=true
Get
端点可以提供与通过动态资源分配 API 分配的动态资源相关的 Pod 信息。要启用此特性,必须确保使用以下标志启动 Kubelet 服务
--feature-gates=KubeletPodResourcesGet=true,DynamicResourceAllocation=true,KubeletPodResourcesDynamicResources=true
设备插件与拓扑管理器集成
Kubernetes v1.27 [stable]
拓扑管理器是 Kubelet 的一个组件,它允许资源以拓扑对齐的方式进行协调。为此,设备插件 API 进行了扩展,以包含一个 TopologyInfo
结构体。
message TopologyInfo {
repeated NUMANode nodes = 1;
}
message NUMANode {
int64 ID = 1;
}
希望利用拓扑管理器的设备插件可以在设备注册时返回一个填充了数据的 TopologyInfo 结构体,以及设备 ID 和设备健康状况。设备管理器随后将使用此信息咨询拓扑管理器并做出资源分配决策。
TopologyInfo
支持将 nodes
字段设置为 nil
或 NUMA 节点列表。这允许设备插件通告一个跨越多个 NUMA 节点的设备。
将 TopologyInfo
设置为 nil
或为给定设备提供空的 NUMA 节点列表,表示设备插件对该设备没有 NUMA 亲和性偏好。
设备插件为设备填充 TopologyInfo
结构体的示例
pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}
设备插件示例
以下是一些设备插件实现的示例
- Akri,它让你轻松暴露异构的叶设备(如 IP 摄像头和 USB 设备)。
- AMD GPU 设备插件
- 用于通用 Linux 设备和 USB 设备的通用设备插件
- HAMi 用于异构 AI 计算虚拟化中间件(例如 NVIDIA、寒武纪、海光、依图、沐曦、昇腾、摩尔线程)
- 用于 Intel GPU、FPGA、QAT、VPU、SGX、DSA、DLB 和 IAA 设备的Intel 设备插件
- 用于硬件辅助虚拟化的KubeVirt 设备插件
- NVIDIA GPU 设备插件,NVIDIA 官方设备插件,用于暴露 NVIDIA GPU 并监控 GPU 健康状况
- 用于 Container-Optimized OS 的NVIDIA GPU 设备插件
- RDMA 设备插件
- SocketCAN 设备插件
- Solarflare 设备插件
- SR-IOV 网络设备插件
- 用于 Xilinx FPGA 设备的Xilinx FPGA 设备插件
下一步
- 了解如何使用设备插件调度 GPU 资源
- 了解如何在节点上通告扩展资源
- 了解拓扑管理器
- 阅读有关如何在 Kubernetes 中使用硬件加速实现 TLS Ingress 的文章
本页面上的项目指的是提供 Kubernetes 所需功能的第三方产品或项目。Kubernetes 项目的作者不对这些第三方产品或项目负责。有关更多详细信息,请参阅 CNCF 网站指南。
在提议添加额外的第三方链接的更改之前,你应该阅读内容指南。