本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 1.26:用于动态资源分配的 Alpha API
动态资源分配是用于请求资源的新 API。它是用于通用资源的持久卷 API 的泛化,使得以下操作成为可能:
- 在不同的 Pod 和容器中访问相同的资源实例,
- 为资源请求附加任意约束,以获得你所需要的确切资源,
- 根据用户提供的参数初始化资源。
第三方资源驱动程序负责解释这些参数,以及在请求进入时跟踪和分配资源。
动态资源分配是一个 alpha 特性,只有在启用 DynamicResourceAllocation
特性门控和 resource.k8s.io/v1alpha1
API 组时才会启用。有关详细信息,请参阅 --feature-gates
和 --runtime-config
kube-apiserver 参数。kube-scheduler、kube-controller-manager 和 kubelet 组件也都需要启用该特性门控。
kube-scheduler 的默认配置当且仅当该特性门控被启用时才启用 DynamicResources
插件。自定义配置可能需要修改以包含它。
一旦启用了动态资源分配,就可以安装资源驱动程序来管理某些类型的硬件。Kubernetes 有一个用于端到端测试的测试驱动程序,但也可以手动运行。有关分步说明,请参见下文。
API
新的 resource.k8s.io/v1alpha1
API 组提供了四种新类型
- ResourceClass
- 定义哪个资源驱动程序处理某种类型的资源,并为其提供通用参数。ResourceClass 由集群管理员在安装资源驱动程序时创建。
- ResourceClaim
- 定义工作负载所需的特定资源实例。由用户创建(生命周期手动管理,可以在不同 Pod 之间共享),或由控制平面基于 ResourceClaimTemplate 为单个 Pod 创建(自动生命周期,通常仅由一个 Pod 使用)。
- ResourceClaimTemplate
- 定义用于创建 ResourceClaim 的规约和一些元数据。由用户在部署工作负载时创建。
- PodScheduling
- 当需要为 Pod 分配 ResourceClaim 时,由控制平面和资源驱动程序在内部使用以协调 Pod 调度。
ResourceClass 和 ResourceClaim 的参数存储在单独的对象中,通常使用安装资源驱动程序时创建的 CRD 所定义的类型。
启用此 alpha 特性后,Pod 的 spec
定义了 Pod 运行所需的 ResourceClaim:此信息进入一个新的 resourceClaims
字段。该列表中的条目引用 ResourceClaim 或 ResourceClaimTemplate。当引用 ResourceClaim 时,所有使用此 .spec
的 Pod(例如,在 Deployment 或 StatefulSet 中)共享相同的 ResourceClaim 实例。当引用 ResourceClaimTemplate 时,每个 Pod 都会获得自己的 ResourceClaim 实例。
对于在 Pod 中定义的容器,resources.claims
列表定义该容器是否可以访问这些资源实例,这使得在同一 Pod 内的一个或多个容器之间共享资源成为可能。例如,一个 init 容器可以在应用程序使用资源之前对其进行设置。
这是一个虚构的资源驱动程序的示例。将为此 Pod 创建两个 ResourceClaim 对象,每个容器都可以访问其中一个。
假设已安装名为 resource-driver.example.com
的资源驱动程序以及以下资源类
apiVersion: resource.k8s.io/v1alpha1
kind: ResourceClass
name: resource.example.com
driverName: resource-driver.example.com
然后,最终用户可以如下分配两个类型为 resource.example.com
的特定资源
---
apiVersion: cats.resource.example.com/v1
kind: ClaimParameters
name: large-black-cats
spec:
color: black
size: large
---
apiVersion: resource.k8s.io/v1alpha1
kind: ResourceClaimTemplate
metadata:
name: large-black-cats
spec:
spec:
resourceClassName: resource.example.com
parametersRef:
apiGroup: cats.resource.example.com
kind: ClaimParameters
name: large-black-cats
–--
apiVersion: v1
kind: Pod
metadata:
name: pod-with-cats
spec:
containers: # two example containers; each container claims one cat resource
- name: first-example
image: ubuntu:22.04
command: ["sleep", "9999"]
resources:
claims:
- name: cat-0
- name: second-example
image: ubuntu:22.04
command: ["sleep", "9999"]
resources:
claims:
- name: cat-1
resourceClaims:
- name: cat-0
source:
resourceClaimTemplateName: large-black-cats
- name: cat-1
source:
resourceClaimTemplateName: large-black-cats
调度
与原生资源(如 CPU 或 RAM)和扩展资源(由设备插件管理,由 kubelet 公布)不同,调度器不知道集群中有哪些动态资源可用,或者如何将它们拆分以满足特定 ResourceClaim 的要求。资源驱动程序负责这些。驱动程序一旦为 ResourceClaim 保留了资源,就会将其标记为**已分配(allocated)**。这也会告诉调度器所声明的资源在集群中的实际可用位置。
ResourceClaim 可以在创建后立即获得资源分配(**立即分配**),而不考虑哪些 Pod 将使用该资源。默认行为(**等待第一个消费者**)是延迟分配,直到依赖于该 ResourceClaim 的 Pod 符合调度条件。这种具有两种分配选项的设计类似于 Kubernetes 处理存储供应的方式,即使用 PersistentVolumes 和 PersistentVolumeClaims。
在“等待第一个消费者”模式下,调度器会检查 Pod 所需的所有 ResourceClaim。如果 Pod 有任何 ResourceClaim,调度器会创建一个 PodScheduling(一个代表 Pod 请求调度细节的特殊对象)。PodScheduling 与 Pod 具有相同的名称和命名空间,并以该 Pod 为其所有者。调度器使用其 PodScheduling 通知负责这些 ResourceClaim 的资源驱动程序,告知其调度器认为适合该 Pod 的节点。资源驱动程序通过排除那些没有足够驱动程序资源的节点来响应。
一旦调度器获得该资源信息,它就会选择一个节点并将该选择存储在 PodScheduling 对象中。然后,资源驱动程序会根据相关的 ResourceClaim 分配资源,以确保资源在该选定的节点上可用。一旦资源分配完成,调度器就会尝试将 Pod 调度到合适的节点。此时调度仍然可能失败;例如,在此期间可能有另一个 Pod 被调度到同一节点。如果发生这种情况,已经分配的 ResourceClaim 可能会被取消分配,以便能够调度到不同的节点上。
作为此过程的一部分,ResourceClaim 也会为该 Pod 保留。目前,ResourceClaim 要么只能由单个 Pod 独占使用,要么可以由无限数量的 Pod 使用。
一个关键特性是,除非 Pod 的所有资源都已分配和保留,否则不会将其调度到节点。这避免了 Pod 被调度到一个节点然后无法在那里运行的情况,这是很糟糕的,因为这样一个处于 pending 状态的 Pod 也会阻塞为它预留的所有其他资源,如 RAM 或 CPU。
限制
调度器插件必须参与调度使用 ResourceClaim 的 Pod。通过设置 nodeName
字段绕过调度器会导致 kubelet 拒绝启动 Pod,因为 ResourceClaim 未被保留,甚至未被分配。未来可能会消除此限制。
编写资源驱动程序
动态资源分配驱动程序通常由两个独立但相互协调的组件组成:一个中心化的控制器,以及一个由节点本地 kubelet 插件组成的 DaemonSet。中心化控制器与调度器协调所需的大部分工作可以由样板代码处理。只需要定制实际分配插件拥有的 ResourceClass 的 ResourceClaim 所需的业务逻辑。因此,Kubernetes 提供了以下包,包括用于调用此样板代码的 API 以及一个您可以实现的 Driver
接口,以提供其自定义业务逻辑
同样,可以使用样板代码将节点本地插件注册到 kubelet,并启动 gRPC 服务器以实现 kubelet 插件 API。对于用 Go 编写的驱动程序,建议使用以下包
由驱动程序开发人员决定这两个组件如何通信。KEP 概述了一种使用 CRD 的方法。
在 SIG Node 内部,我们还计划提供一个完整的示例驱动程序,可以作为其他驱动程序的模板。
运行测试驱动程序
以下步骤直接从 Kubernetes 源代码启动一个本地的单节点集群。作为先决条件,你的集群必须有支持容器设备接口(CDI)的容器运行时的节点。例如,你可以运行 CRI-O v1.23.2 或更高版本。一旦 containerd v1.7.0 发布,我们预计你可以运行该版本或任何更高版本。在下面的示例中,我们使用 CRI-O。
首先,克隆 Kubernetes 源代码。在该目录中,运行
$ hack/install-etcd.sh
...
$ RUNTIME_CONFIG=resource.k8s.io/v1alpha1 \
FEATURE_GATES=DynamicResourceAllocation=true \
DNS_ADDON="coredns" \
CGROUP_DRIVER=systemd \
CONTAINER_RUNTIME_ENDPOINT=unix:///var/run/crio/crio.sock \
LOG_LEVEL=6 \
ENABLE_CSI_SNAPSHOTTER=false \
API_SECURE_PORT=6444 \
ALLOW_PRIVILEGED=1 \
PATH=$(pwd)/third_party/etcd:$PATH \
./hack/local-up-cluster.sh -O
...
要开始使用您的集群,您可以打开另一个终端/选项卡并运行
$ export KUBECONFIG=/var/run/kubernetes/admin.kubeconfig
集群启动后,在另一个终端运行测试驱动程序控制器。必须为以下所有命令设置 KUBECONFIG
。
$ go run ./test/e2e/dra/test-driver --feature-gates ContextualLogging=true -v=5 controller
在另一个终端,运行 kubelet 插件
$ sudo mkdir -p /var/run/cdi && \
sudo chmod a+rwx /var/run/cdi /var/lib/kubelet/plugins_registry /var/lib/kubelet/plugins/
$ go run ./test/e2e/dra/test-driver --feature-gates ContextualLogging=true -v=6 kubelet-plugin
更改目录的权限使得普通用户可以运行和(当使用 delve 时)调试 kubelet 插件,这很方便,因为它使用了已经填充的 Go 缓存。完成后,记得用 sudo chmod go-w
恢复权限。或者,你也可以构建二进制文件并以 root 身份运行。
现在集群已准备好创建对象
$ kubectl create -f test/e2e/dra/test-driver/deploy/example/resourceclass.yaml
resourceclass.resource.k8s.io/example created
$ kubectl create -f test/e2e/dra/test-driver/deploy/example/pod-inline.yaml
configmap/test-inline-claim-parameters created
resourceclaimtemplate.resource.k8s.io/test-inline-claim-template created
pod/test-inline-claim created
$ kubectl get resourceclaims
NAME RESOURCECLASSNAME ALLOCATIONMODE STATE AGE
test-inline-claim-resource example WaitForFirstConsumer allocated,reserved 8s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
test-inline-claim 0/2 Completed 0 21s
测试驱动程序做的不多,它只设置在 ConfigMap 中定义的环境变量。测试 Pod 会转储环境,因此可以检查日志以验证一切是否正常工作
$ kubectl logs test-inline-claim with-resource | grep user_a
user_a='b'
后续步骤
- 有关设计的更多信息,请参阅动态资源分配 KEP。
- 请在 Kubernetes 官方文档中阅读动态资源分配。
- 你可以参与 SIG Node 和/或 CNCF 容器编排设备工作组。
- 您可以查看或评论动态资源分配的项目看板。
- 为了将此功能推向 beta 阶段,我们需要硬件供应商的反馈,因此我们在此呼吁:试用此功能,考虑它如何帮助解决您的用户遇到的问题,并编写资源驱动程序……