本文已超过一年。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 1.26:动态资源分配的 Alpha API
动态资源分配是一种用于请求资源的新 API。它是 Persistent Volumes API 的通用化,适用于通用资源,使得可以
- 在不同的 Pod 和容器中访问同一个资源实例,
- 为资源请求附加任意约束,以获得你所需的精确资源,
- 根据用户提供的参数初始化资源。
第三方资源驱动负责解释这些参数以及跟踪和分配接收到的请求所对应的资源。
动态资源分配是一项 Alpha 特性,仅当启用了 DynamicResourceAllocation
Feature Gate 和 resource.k8s.io/v1alpha1
API Group 时才启用。有关详细信息,请参阅 kube-apiserver 参数的 --feature-gates
和 --runtime-config
选项。kube-scheduler、kube-controller-manager 和 kubelet 组件都需要启用 Feature Gate。
kube-scheduler 的默认配置仅当 Feature Gate 启用时才会启用 DynamicResources
插件。自定义配置可能需要修改以包含它。
动态资源分配启用后,可以安装资源驱动来管理某些类型的硬件。Kubernetes 有一个测试驱动,用于端到端测试,也可以手动运行。请参阅下方的分步说明。
API
新的 resource.k8s.io/v1alpha1
API Group 提供了四种新类型:
- ResourceClass
- 定义哪个资源驱动处理特定类型的资源,并为其提供通用参数。ResourceClass 由集群管理员在安装资源驱动时创建。
- ResourceClaim
- 定义工作负载所需的特定资源实例。由用户创建(生命周期手动管理,可在不同 Pod 之间共享)或由控制平面基于 ResourceClaimTemplate 为单个 Pod 创建(生命周期自动管理,通常仅由一个 Pod 使用)。
- ResourceClaimTemplate
- 定义用于创建 ResourceClaim 的 spec 和一些元数据。由用户在部署工作负载时创建。
- 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 标记为已分配。这也会告诉调度器已申领的资源在集群中的实际可用位置。
ResourceClaim 一旦创建就可以获得资源分配(立即分配),而无需考虑哪些 Pod 将使用资源。默认情况(等待第一个消费者)是延迟分配,直到依赖 ResourceClaim 的 Pod 成为可调度的对象为止。这种具有两种分配选项的设计类似于 Kubernetes 如何使用 PersistentVolume 和 PersistentVolumeClaim 处理存储供应。
在“等待第一个消费者”模式下,调度器会检查 Pod 所需的所有 ResourceClaim。如果 Pod 有任何 ResourceClaim,调度器会创建一个 PodScheduling(一个代表 Pod 请求调度详细信息的特殊对象)。PodScheduling 的名称和 namespace 与 Pod 相同,并将 Pod 作为其 Owner。调度器使用其 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 源代码启动本地单节点集群。前提条件是你的集群节点具有支持 Container Device Interface (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'
下一步
- 有关设计详情,请参阅动态资源分配 (Dynamic Resource Allocation) KEP。
- 在官方 Kubernetes 文档中阅读动态资源分配 (Dynamic Resource Allocation)。
- 你可以参与SIG Node 和/或 CNCF 容器编排设备工作组 (Container Orchestrated Device Working Group)。
- 你可以在动态资源分配项目看板上查看或评论。
- 为了将此特性推进到 Beta 阶段,我们需要硬件厂商的反馈,因此在此呼吁大家:试用此特性,考虑它如何帮助解决你的用户遇到的问题,并编写资源驱动程序……