Kubernetes v1.26 [稳定]Kubernetes 提供了一个设备插件框架,您可以使用它向 Kubelet 通告系统硬件资源。
厂商无需自定义 Kubernetes 本身的代码,只需实现一个可以手动部署或作为 DaemonSet 部署的设备插件即可。目标设备包括 GPU、高性能 NIC、FPGA、InfiniBand 适配器以及其他可能需要厂商特定初始化和设置的类似计算资源。
Kubelet 导出一个 Registration gRPC 服务
service Registration {
rpc Register(RegisterRequest) returns (Empty) {}
}
设备插件可以通过此 gRPC 服务向 Kubelet 注册自身。在注册期间,设备插件需要发送:
ResourceName。这里的 ResourceName 需要遵循 扩展资源命名方案,格式为 vendor-domain/resourcetype。(例如,NVIDIA GPU 通告为 nvidia.com/gpu。)注册成功后,设备插件会向 Kubelet 发送它所管理的设备列表,然后 Kubelet 负责将这些资源作为 Kubelet 节点状态更新的一部分通告给 API 服务器。例如,在设备插件向 Kubelet 注册 hardware-vendor.example/foo 并报告节点上有两个健康设备后,节点状态将更新,以通告该节点已安装并可用 2 个“Foo”设备。
然后,用户可以在 Pod 规范中请求设备(参见 container)。请求扩展资源类似于管理其他资源的请求和限制,但有以下区别:
假设一个 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 套接字,并实现以下接口:
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 套接字向 Kubelet 注册自身。
成功注册后,设备插件以服务模式运行,在此期间它会持续监控设备健康状况,并在任何设备状态发生变化时向 Kubelet 报告。它还负责处理 Allocate gRPC 请求。在 Allocate 期间,设备插件可能会执行特定于设备的准备工作;例如,GPU 清理或 QRNG 初始化。如果操作成功,设备插件会返回一个 AllocateResponse,其中包含用于访问已分配设备的容器运行时配置。Kubelet 将此信息传递给容器运行时。
AllocateResponse 包含零个或多个 ContainerAllocateResponse 对象。在这些对象中,设备插件定义了必须对容器定义进行的修改,以提供对设备的访问。这些修改包括:
DevicePluginCDIDevices 特性门控。此功能在 Kubernetes v1.28 中作为 Alpha 特性添加,在 v1.29 中升级为 Beta,在 v1.31 中转为 GA。设备插件应该能够检测到 Kubelet 的重启,并向新的 Kubelet 实例重新注册自身。新的 Kubelet 实例在启动时会删除 /var/lib/kubelet/device-plugins 下所有现有的 Unix 套接字。设备插件可以监控其 Unix 套接字的删除,并在发生此类事件时重新注册自身。
在某些情况下,设备会发生故障或被关闭。在这种情况下,设备插件的责任是使用 ListAndWatchResponse API 将情况通知 Kubelet。
一旦设备被标记为不健康,Kubelet 将减少该节点上此资源的可分配计数,以反映可用于调度新 Pod 的设备数量。该资源的容量计数不会改变。
分配给已故障设备的 Pod 将继续分配给该设备。依赖该设备的代码通常会开始失败,如果 Pod 的 restartPolicy 不是 Always,则 Pod 可能会进入 Failed 阶段,否则将进入崩溃循环。
在 Kubernetes v1.31 之前,了解 Pod 是否与故障设备关联的方法是使用 PodResources API。
Kubernetes v1.36 [beta](默认启用)当启用特性门控 ResourceHealthStatus(自 v1.36 起为 Beta 并默认启用)时,字段 allocatedResourcesStatus 会被添加到每个 Pod 的 .status 中的每个容器状态内。allocatedResourcesStatus 字段报告分配给容器的每个设备的健康信息。每个资源健康条目可以包含一个可选的 message 字段,提供有关健康状态的额外人类可读上下文,例如错误详细信息或故障原因。
对于失败的 Pod,或者当您怀疑存在故障时,可以使用此状态来了解 Pod 的行为是否可能与设备故障有关。例如,如果加速器报告过温事件,allocatedResourcesStatus 字段可能会报告此信息。
您可以将设备插件部署为 DaemonSet、节点操作系统的软件包或手动部署。
规范目录 /var/lib/kubelet/device-plugins 需要特权访问,因此设备插件必须在特权安全上下文中运行。如果您将设备插件作为 DaemonSet 部署,则必须将 /var/lib/kubelet/device-plugins 挂载为插件 PodSpec 中的 Volume。
如果您选择 DaemonSet 方法,您可以依赖 Kubernetes 来:将设备插件的 Pod 放置在节点上、在发生故障后重启守护进程 Pod,以及帮助自动化升级。
以前,版本控制方案要求设备插件的 API 版本必须与 Kubelet 的版本完全匹配。自该特性在 v1.12 升级到 Beta 版以来,这不再是硬性要求。API 已进行版本控制,并且自该特性升级到 Beta 以来一直保持稳定。因此,Kubelet 升级应该是无缝的,但在稳定之前 API 可能仍会发生变化,导致升级无法保证是破坏性兼容的。
作为项目,Kubernetes 建议设备插件开发者:
要在需要升级到具有较新设备插件 API 版本的 Kubernetes 版本的节点上运行设备插件,请在升级这些节点之前升级您的设备插件以支持两个版本。采取这种方法将确保在升级过程中设备分配能够持续运行。
Kubernetes v1.28 [稳定]为了监控设备插件提供的资源,监控代理需要能够发现节点上正在使用的设备集,并获取元数据来描述指标应关联到哪个容器。由设备监控代理公开的 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 的机器,它还包含有关为容器保留的内存和巨页(hugepages)的信息。
从 Kubernetes v1.27 开始,List 端点可以提供有关通过 DynamicResourceAllocation API 在 ResourceClaims 中分配的正在运行的 Pod 的资源信息。从 Kubernetes v1.34 开始,此功能默认启用。
// 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 的 GetCpuIdsGetAllocatableResources 调用中减去所有 GetCpuIds 调用得出的 CPUGetAllocatableResources gRPC 端点Kubernetes v1.28 [稳定]GetAllocatableResources 提供有关工作节点上最初可用资源的信息。它比 Kubelet 导出给 APIServer 的信息更多。
GetAllocatableResources 应仅用于评估节点上的 可分配 资源。如果目标是评估空闲/未分配的资源,则应将其与 List() 端点结合使用。除非公开给 Kubelet 的底层资源发生变化,否则通过 GetAllocatableResources 获得的结果将保持不变。这种情况很少发生,但当它发生时(例如:热插/热拔、设备健康状态变化),客户端应调用 GetAllocatableResources 端点。
但是,如果 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 套接字提供。设备插件资源的监控代理可以作为守护进程或 DaemonSet 部署。规范目录 /var/lib/kubelet/pod-resources 需要特权访问,因此监控代理必须在特权安全上下文中运行。如果设备监控代理作为 DaemonSet 运行,则必须将 /var/lib/kubelet/pod-resources 挂载为设备监控代理 PodSpec 中的 Volume。
当从 DaemonSet 或任何其他作为容器部署在主机上的应用程序访问 /var/lib/kubelet/pod-resources/kubelet.sock 时(将套接字挂载为卷),最佳实践是挂载目录 /var/lib/kubelet/pod-resources/ 而不是 /var/lib/kubelet/pod-resources/kubelet.sock。这将确保在 Kubelet 重启后,容器将能够重新连接到此套接字。
容器挂载由引用套接字或目录的 inode 管理,具体取决于挂载的内容。当 Kubelet 重启时,套接字被删除并创建一个新套接字,而目录保持不变。因此,套接字的原始 inode 变得不可用。指向目录的 inode 将继续工作。
Get gRPC 端点Kubernetes v1.34 [beta]Get 端点提供有关正在运行的 Pod 的资源信息。它公开的信息类似于 List 端点中描述的信息。Get 端点需要正在运行的 Pod 的 PodName 和 PodNamespace。
// GetPodResourcesRequest contains information about the pod
message GetPodResourcesRequest {
string pod_name = 1;
string pod_namespace = 2;
}
Get 端点可以提供与动态资源分配 API 分配的动态资源相关的 Pod 信息。从 Kubernetes v1.34 开始,此功能默认启用。
Kubernetes v1.27 [稳定]拓扑管理器是一个 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,},}}}
以下是一些设备插件实现的示例: