本文发布时间已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

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 可以在创建 ResourceClaim 后立即分配资源(_立即分配_),而不考虑哪个 Pod 将使用该资源。默认值(_等待第一个使用者_)是将分配延迟到依赖 ResourceClaim 的 Pod 符合调度条件时。这种具有两种分配选项的设计类似于 Kubernetes 如何使用 PersistentVolume 和 PersistentVolumeClaim 处理存储配置。

在等待第一个使用者模式下,调度程序会检查 Pod 所需的所有 ResourceClaim。如果 Pod 具有任何 ResourceClaim,则调度程序会创建一个 PodScheduling(一个特殊对象,代表 Pod 请求调度详细信息)。PodScheduling 具有与 Pod 相同的名称和命名空间,并且 Pod 作为其所有者。调度程序使用其 PodScheduling,通知负责这些 ResourceClaim 的资源驱动程序调度程序认为适合该 Pod 的节点。资源驱动程序通过排除没有足够驱动程序资源剩余的节点来响应。

一旦调度程序获得该资源信息,它就会选择一个节点并将该选择存储在 PodScheduling 对象中。然后,资源驱动程序根据相关的 ResourceClaim 分配资源,以便资源在该选定节点上可用。一旦资源分配完成,调度程序会尝试将 Pod 调度到合适的节点。此时调度仍然可能失败;例如,同时可以将不同的 Pod 调度到同一节点。如果发生这种情况,则可能会取消分配已分配的 ResourceClaim,以便能够在不同的节点上进行调度。

作为此过程的一部分,也会为 Pod 预留 ResourceClaim。当前,ResourceClaim 只能由单个 Pod 独占使用,也可以由无限数量的 Pod 使用。

一个关键特性是,除非已分配和预留 Pod 的所有资源,否则 Pod 不会被调度到节点。这避免了 Pod 被调度到一个节点,然后无法在该节点上运行的情况,这是不好的,因为这样的挂起 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 版,我们需要硬件供应商的反馈,因此这里发出行动号召:试用此功能,考虑它如何帮助解决用户遇到的问题,并编写资源驱动程序……