云端驾驭硬件:Device Plugin 2.0 时代的来临
背景
K8s 在 1.8 版本中引入了 Device Plugin 机制,支持以插件化的方式将第三方的设备接入到 kubernetes 体系中,类似 CPU、MEM 方式进行调度、使用,例如 GPU 设备。设备厂商只需要开发一个配套的 Device Plugin 组件,并且以 DaemonSet 的方式将其运行在集群当中,Kubelet 将会感知并注册到 K8s 节点信息中。应用通过 resource request、limit 显示声明使用即可,如同 CPU、MEM 一样。但是随着云原生以及 AI 的不断发展下,现有 Device Plugin 机制在一些特殊的场景下已经没有办法完全覆盖:
设备初始化:在启动使用加速器(如 FPGA)的工作负载时,希望在工作负载本身启动之前,能够重新配置或重新编程加速器以适应该工作负载。出于安全原因,工作负载不应能够直接重新配置设备。
设备清理:当工作负载完成使命后,希望有一种清理设备的机制,以确保设备不包含来自先前工作负载的痕迹/参数/数据,并保持适当的电源状态/关闭。例如,可能需要重置 FPGA,因为其用于工作负载的配置是保密的。
部分分配:在部署容器时,希望能够在容器内使用可共享设备的一部分,而其他容器应该能够使用同一设备上的其他空闲资源。
动态分配:例如,在最新一代 NVIDIA GPU 的高端显卡中具有一种称为 MIG 的操作模式,允许将它们划分为一组小型 GPU(称为 MIG 设备),每个设备提供不同数量的内存和计算资源。从硬件角度来看,配置 GPU 为一组 MIG 设备是非常动态的,并且根据特定应用程序的资源需求创建与之匹配的 MIG 设备得到很好支持。然而,使用当前的设备插件 API,利用此功能的唯一方式是将 GPU 预划分为一组 MIG 设备,并以与全/静态 GPU 相同的方式将它们通告给 kubelet。然后,用户必须从这组预定义的 MIG 设备中选择,而不是根据其特定的资源约束动态创建其中之一。无法在请求时动态创建 MIG 设备(即在请求时创建它们)的能力使得必须仔细调整预定义的 MIG 设备集,以确保 GPU 资源不会因为某些预先分配的设备需求较低而未被使用。这还让用户选择特定的 MIG 设备类型,而不是更抽象地声明资源约束。
可选分配:在部署工作负载时,希望能够指定软(可选)设备需求。如果设备存在并且可分配,它将被分配。如果没有 - 工作负载将在没有设备的节点上运行。GPU 和加密卸载引擎是此类设备的示例。如果它们不可用,工作负载仍然可以通过回退到仅使用 CPU 来运行相同的任务。
在这些特殊场景的催化下,Nvidia、Intel 等头部厂商联合推出了 DRA(Dynamic Resource Allocation)技术(号称 Device Plugin 2.0)。此次分享的作者顶级大佬 Kevin Klues 也是核心人员之一,接下来将按照 Kevin Klues 的分享思路,从使用者以及开发者的角度去剖析 DRA 的用法以及原理。
02
DRA 核心概念介绍
架构图:
核心组件
DRA 会有两个组件运行在集群当中,如果某个设备厂商需要利用 DRA 机制将其设备接入 Kubernetes 集群中使用,那些需要完成如下两个组件的开发:
Resource Controller:一个中心化的组件,通过监听 ResourceClaims 并在分配完成后更新 ResourceClaim 状态来处理资源的分配,简单理解就是维护整个 Kubernetes 集群中设备资源的账本以及进行资源分配。
Resource Kubelet Plugin:与 Kubelet 配合使用,与 Device Plugin 插件类似,每个节点部署一个,为 Pod 准备节点资源的工作,例如:挂载宿主机设备到容器中,设置某些环境变量。
核心资源
DRA 借鉴了 StorageClass 的设计,提供了资源管理的全新视角,增强了 Kubernetes 现有的资源分配能力。为此 kubernetes 新增了一些基础资源:
ResourceClass:该对象由集群管理员定义,描述哪个资源驱动程序负责处理特定类型的资源。ResourceClass 还保存资源的公共参数,以确保一致性。
ResourceClaim:ResourceClaim 表示用户对特定资源实例的需求。它可以由用户手动创建,也可以由 controller 基于 ResourceClaimTemplate 为各个 Pod 创建。
ResourceClaimTemplate:该对象由用户在部署工作负载时创建,定义创建 ResourceClaims 的规范。
PodSchedulingContext:用于协调 pod 调度,PodSchedulingContext 会作为调度器与 DRA 组件沟通的桥梁。
除此之外,kubernetes 为了适配不同厂商对语义化的需求,在 ResourceClaim 资源中新增了 ParametersRef 的字段,他的 value 是一个CRD Object 对象,这个 Object 对象中的内容由不同厂商自定义,以 NVIDIA GPU 举例,他现阶段存在两个 Parameters CRD 资源:
GpuCliamParameters:其中定义了许多设备的约束,例如想使用的 GPU 的 index、uuid、显存大小等等参数。
MigDeviceCliamParameters:其中定义了想使用 Mig 设备的规格、是否开启共享模式,以及共享模式的选择。
后续篇幅将以 Nvidia GPU为案例,来演示如何去使用 DRA 相关的能力。
03
快速开始
接下来会以 k8s-dra-driver 项目为核心,来展示如何使用 Nvidia DRA的能力,该项目也是由 Kevin Klues 主导设计开发的 Nvidia DRA Driver 的实现。我们会从环境安装切入,同时以两个最小的案例帮助我们快速的了解 DRA 的能力。
前置条件
集群大于1.26 并且开启 DRA Feature Gate 安装 Nvidia Driver
安装 Nvidia container runtime 并且设置为默认runtime
安装 k8s-dra-driver
开启 DRA Feature Gate
kubernetes 在 1.26 版本引入了 DRA 的新特性,所以我们要确保 kubernetes 的版本大于等于 1.26,同时需要手动开启 DRA 的 Feature Gate ,涉及到需要修改配置的组件主要有四个, kube-apiserver、 kube-scheduler、 kube-controller-manager 和 kubelet。以下是详细的操作步骤,这里以 kube-scheduler 配置为例:
进入 Kubernetes 配置文件目录,编辑 kube-scheduler.yaml 文件:
cd /etc/kubernetes/manifests && vim kube-scheduler.yaml
在启动参数中添加下参数:
--feature-gates=DynamicResourceAllocation=true \
其他组件开启 Feature Gate 的方式相似。但是,请注意 kube-apiserver 组件除了需要开启 DRA 的 Feature Gate 以外还需要额外开启 API 的支持,如下参数:
--runtime-config=resource.k8s.io/v1alpha1=true \
注意:因为 kubernetes 在 1.27 版本对 api 版本进行了升级,所以如果你的 kubernetes 版本大于 1.27,请将 v1alpha1 修改成 v1alpha2。
当以上组件重启成功后,我们可以通过判断 resourceclasses 资源是否存在判断是否开启 DRA 特性:
kubectl get resourceclasses
同时 k8s-dra-driver 也为我们提供了一键准备集群环境的脚本,但是因为网络问题运行会有些缓慢,如果需要请参考下述命令:
# clone repo
git clone https://github.com/NVIDIA/k8s-dra-driver.git && cd k8s-dra-driver
#build cluster
./demo/clusters/kind/create-cluster.sh
安装 Nvidia GPU Driver 与 NVIDIA Container Toolkit
安装方式通常有两种
宿主机安装方式
通过 gpu-operator 安装
宿主机安装:
通过 gpu-operator 安装:
这并不是本文介绍的重点,请参考官方文档(https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html)进行安装。
注意:如果使用 gpu-operator 的方式安装,需要手动关闭 Device Plugin 的能力,参考下述命令:
helm install --wait --generate-name \
-n gpu-operator --create-namespace \
nvidia/gpu-operator --set devicePlugin.enable=false
k8s-dra-driver 安装
项目地址: https://github.com/NVIDIA/k8s-dra-driver
镜像准备:
# clone repo
git clone https://github.com/NVIDIA/k8s-dra-driver.git && cd k8s-dra-driver
# 镜像获取方式二选一
# 1. 镜像构建脚本
./demo/clusters/kind/build-dra-driver.sh
# 2. 使用官方开发版本镜像
docker pull ghcr.io/nvidia/k8s-dra-driver:a5f2b34c-ubuntu20.04
docker tag ghcr.io/nvidia/k8s-dra-driver:a5f2b34c-ubuntu20.04 nvcr.io/nvidia/cloud-native/k8s-dra-driver:v0.1.0
# 获取镜像
root@ubuntu:~# docker image ls nvcr.io/nvidia/cloud-native/k8s-dra-driver:v0.1.0
REPOSITORY TAG IMAGE ID CREATED SIZE
nvcr.io/nvidia/cloud-native/k8s-dra-driver v0.1.0 87084540e307 9 days ago 361MB
安装:
# 运行安装脚本
./demo/clusters/kind/install-dra-driver.sh
# 查看部署后的组件
root@ubuntu:~/k8s-dra-driver# kubectl get pod -n nvidia-dra-driver
NAME READY STATUS RESTARTS AGE
nvidia-k8s-dra-driver-controller-6d5869d478-495vg 1/1 Running 0 89s
nvidia-k8s-dra-driver-kubelet-plugin-zztgb 1/1 Running 0 89s
快速使用
环境展示
本次环境信息:
kubernetes版本:1.28
系统版本:Ubuntu22
GPU:两张 Tesla T4
当我们 k8s-dra-driver 部署成功后,它会为每个存在 GPU 设备的节点额外创建一个名为 NAS(NodeAllocationState) 的资源,其中会将节点能够分配使用的 GPU 注册进去,我们可以获取他的 yaml 查看当前环境设备的情况:
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver# kubectl get nas -n nvidia-dra-driver
NAME AGE
k8s-dra-driver-cluster-worker 18m
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver# kubectl describe nas -n nvidia-dra-driver k8s-dra-driver-cluster-worker
.......................................
Spec:
Allocatable Devices:
Gpu:
Architecture: Turing
Brand: Nvidia
Cuda Compute Capability: 7.5
Index: 1
Memory Bytes: 16106127360
Mig Enabled: false
Product Name: Tesla T4
Uuid: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a
Gpu:
Architecture: Turing
Brand: Nvidia
Cuda Compute Capability: 7.5
Index: 0
Memory Bytes: 16106127360
Mig Enabled: false
Product Name: Tesla T4
Uuid: GPU-07416406-52e9-b9ee-ba58-b370252e155c
......................................
案例一
案例目标:使用 DRA 的能力分别为两个 POD 分配一张 Tesla T4 GPU 并且独占。
案例需要使用到的 yaml 文件如下:
---
apiVersion: v1
kind: Namespace
metadata:
name: gpu-test1
---
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaimTemplate
metadata:
namespace: gpu-test1
name: gpu.nvidia.com
spec:
spec:
resourceClassName: gpu.nvidia.com
---
apiVersion: v1
kind: Pod
metadata:
namespace: gpu-test1
name: pod1
labels:
app: pod
spec:
containers:
- name: ctr
image: ubuntu:22.04
command: ["bash", "-c"]
args: ["nvidia-smi -L; sleep 9999"]
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
source:
resourceClaimTemplateName: gpu.nvidia.com
---
apiVersion: v1
kind: Pod
metadata:
namespace: gpu-test1
name: pod2
labels:
app: pod
spec:
containers:
- name: ctr
image: ubuntu:22.04
command: ["bash", "-c"]
args: ["nvidia-smi -L; sleep 9999"]
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
source:
resourceClaimTemplateName: gpu.nvidia.com
我们主要做了以下几件事:
1. 声明了一个 ResourceCliamTemplate 资源并且指定到名为 gpu.nvidia.com 的 ResourceClass 资源。
2. 创建了两个 Pod,在 Spec 声明了 resourceClaims 资源,并且指向我们刚创建的 ResourceCliamTemplate 资源。
3. 在两个 Pod 的 containers.resources 中 使用这个 ResourceClaims 资源。
4. 两个 Pod 会分别打印容器中能够使用的 GPU 卡信息。
最终我们创建的两个 pod,会被各分配一张 T4 GPU。我们执行下述代码运行 Yaml:
root@iZ0xij0ehad4c74g1foyrjZ:~# kubectl apply -f gpu-test1.yaml
namespace/gpu-test1 created
resourceclaimtemplate.resource.k8s.io/gpu.nvidia.com created
pod/pod1 created
pod/pod2 created
查看 Pod,等待状态 Running 后分别查看两个 Pod 的日志:
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart# kubectl get pod -n gpu-test1
NAME READY STATUS RESTARTS AGE
pod1 1/1 Running 0 89s
pod2 1/1 Running 0 89s
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart# kubectl logs pod1 -n gpu-test1
GPU 0: Tesla T4 (UUID: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a)
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart# kubectl logs pod2 -n gpu-test1
GPU 0: Tesla T4 (UUID: GPU-07416406-52e9-b9ee-ba58-b370252e155c)
通过日志,我们能看到两个 pod 分别被分配了一张 GPU 并且独占,这时候我们可以通过获取 NAS 资源查看 GPU 分配的情况:
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart# kubectl describe nas -n nvidia-dra-driver k8s-dra-driver-cluster-worker
.......................
Spec:
Allocatable Devices:
Gpu:
Architecture: Turing
Brand: Nvidia
Cuda Compute Capability: 7.5
Index: 0
Memory Bytes: 16106127360
Mig Enabled: false
Product Name: Tesla T4
Uuid: GPU-07416406-52e9-b9ee-ba58-b370252e155c
Gpu:
Architecture: Turing
Brand: Nvidia
Cuda Compute Capability: 7.5
Index: 1
Memory Bytes: 16106127360
Mig Enabled: false
Product Name: Tesla T4
Uuid: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a
Allocated Claims:
c2acde2c-4fc5-48f1-9a58-397449c9c65d:
Claim Info:
Name: pod2-gpu
Namespace: gpu-test1
UID: c2acde2c-4fc5-48f1-9a58-397449c9c65d
Gpu:
Devices:
Uuid: GPU-07416406-52e9-b9ee-ba58-b370252e155c
fb22f085-4925-4367-84c8-5bdf47c591fe:
Claim Info:
Name: pod1-gpu
Namespace: gpu-test1
UID: fb22f085-4925-4367-84c8-5bdf47c591fe
Gpu:
Devices:
Uuid: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a
Prepared Claims:
c2acde2c-4fc5-48f1-9a58-397449c9c65d:
Gpu:
Devices:
Uuid: GPU-07416406-52e9-b9ee-ba58-b370252e155c
fb22f085-4925-4367-84c8-5bdf47c591fe:
Gpu:
Devices:
Uuid: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a
...............
相比于最初查看的结果,NAS 中新增了一个 Allocated 字段,其中描述了 GPU 设备被分配的情况,例如 GPU-07416406-52e9-b9ee-ba58-b370252e155c 这张卡被分配给了 gpu-test1 命名空间下 名为 pod2-gpu 的 ResourceClaim,我们可以通过命令查看该 ResourceClaim 的情况:
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart# kubectl get resourceclaims -n gpu-test1 pod2-gpu -o yaml
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaim
metadata:
creationTimestamp: "2023-12-09T06:52:30Z"
finalizers:
- gpu.resource.nvidia.com/deletion-protection
name: pod2-gpu
namespace: gpu-test1
ownerReferences:
- apiVersion: v1
blockOwnerDeletion: true
controller: true
kind: Pod
name: pod2
uid: 27ddd657-b811-4b41-9b42-f79ef9983a0e
resourceVersion: "8146"
uid: c2acde2c-4fc5-48f1-9a58-397449c9c65d
spec:
allocationMode: WaitForFirstConsumer
resourceClassName: gpu.nvidia.com
status:
allocation:
availableOnNodes:
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: In
values:
- k8s-dra-driver-cluster-worker
shareable: true
driverName: gpu.resource.nvidia.com
reservedFor:
- name: pod2
resource: pods
uid: 27ddd657-b811-4b41-9b42-f79ef9983a0e
我们通过这个案例快速的体验了 DRA 的能力,相比与 Device Plugin 的方式分配从使用上来说会更加复杂,但是同样也会给我们带来更加灵活的体验。
案例二
案例目标:两个 Pod 共享使用 GPU,并且指定共享模式。
案例 yaml 文件如下:
---
apiVersion: v1
kind: Namespace
metadata:
name: gpu-test3
---
apiVersion: gpu.resource.nvidia.com/v1alpha1
kind: GpuClaimParameters
metadata:
namespace: gpu-test3
name: t4
spec:
count: 1
sharing:
strategy: TimeSlicing
timeSlicingConfig:
timeSlice: Long
---
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaim
metadata:
namespace: gpu-test3
name: shared-gpu
spec:
resourceClassName: gpu.nvidia.com
parametersRef:
apiGroup: gpu.resource.nvidia.com
kind: GpuClaimParameters
name: t4
---
apiVersion: v1
kind: Pod
metadata:
namespace: gpu-test3
name: pod1
labels:
app: pod
spec:
containers:
- name: ctr
image: ubuntu:22.04
command: ["bash", "-c"]
args: ["nvidia-smi -L; sleep 9999"]
resources:
claims:
- name: shared-gpu
resourceClaims:
- name: shared-gpu
source:
resourceClaimName: shared-gpu
---
apiVersion: v1
kind: Pod
metadata:
namespace: gpu-test3
name: pod2
labels:
app: pod
spec:
containers:
- name: ctr
image: ubuntu:22.04
command: ["bash", "-c"]
args: ["nvidia-smi -L; sleep 9999"]
resources:
claims:
- name: shared-gpu
resourceClaims:
- name: shared-gpu
source:
resourceClaimName: shared-gpu
apiVersion: gpu.resource.nvidia.com/v1alpha1
kind: GpuClaimParameters
metadata:
namespace: gpu-test3
name: t4
spec:
count: 1
sharing:
strategy: TimeSlicing
timeSlicingConfig:
timeSlice: Long
1. count 指定我们需要使用一张卡。
2. 开启了共享能力,并且设置共享模式为时间片共享。
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart/test# kubectl apply -f gpu-test3.yaml
namespace/gpu-test3 created
gpuclaimparameters.gpu.resource.nvidia.com/t4 created
resourceclaim.resource.k8s.io/shared-gpu created
pod/pod1 created
pod/pod2 created
查看两个 pod 对应的日志,我们能够发现他们实际使用的是同一个 GPU:
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart/test# kubectl logs pod1 -n gpu-test3
GPU 0: Tesla T4 (UUID: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a)
root@iZ0xij0ehad4c74g1foyrjZ:~/k8s-dra-driver/demo/specs/quickstart/test# kubectl logs pod2 -n gpu-test3
GPU 0: Tesla T4 (UUID: GPU-7ee7be03-7ebe-a704-dcf3-2f7d2852257a)
而 Device Plugin 是通过声明式数值的方式将设备进行注册使用,例如 CPU、MEM 和 GPU,当分配设备时数量就会减少,从而导致应用无法直接共享设备。但是通过动态资源分配(DRA)的能力,我们能够轻松实现应用共享设备的功能。
04
总结
本篇以 Kevin Klues 北美 KubeCon 的分享为基础,介绍了 DRA 的前世今生,同时以两个基础的案例快速带领大家了解 DRA 的能力。总的来说 DRA的出现给设备接入提供了一个新的选择,但是在我看来它的出现并不是为了替代原有的 Device Plugin 的方式,而是对原有的机制进行补充,相对于原有的 Device Plugin 机制,它的优势很明显:
更丰富的语义化支持,让应用对硬件设备的需求脱离于传统单一的 resources 数值化声明方式,更加灵活,在一些复杂场景下尤其 AI 场景优势非常明显。
提供插件化的方式接入 kubernetes ,这种方式使得我们现有的大多数设备都能够以 DRA 的方式接入到 kubernetes 中。
当然,设计上带来的便利性,同时也带来了很高的复杂性,缺点也很明显:
为了实现多样语义化的支持,同时给用户使用带来了更高的负担。
不同于 Device Plugin 提前上报资源做法,DRA 会在调度时协商最终调度的节点,从调度效率上来说,他的效率肯定会更低。
所以实际的使用还需要结合业务场景来进行选择,不过现在 无论是 DRA 还是我们演示的 k8s-dra-driver 项目,都处于一个 alpha 的阶段,他的能力还在演进,需要对其持续关注。在下一篇中我们将会对 DRA 更高级的能力进行讲解与演示,同时也会对 DRA 核心的原理进行讲解,敬请期待。
05
参考链接
https://developer.aliyun.com/article/1272914
https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/3063-dynamic-resource-allocation/README.md
本文作者
「DaoCloud 道客」云原生研发工程师
热门推荐
访问以下网址,或点击文末【阅读原文】直接下载
DaoCloud 公司简介
「DaoCloud 道客」云原生领域的创新领导者,成立于 2014 年底,拥有自主知识产权的核心技术,致力于打造开放的云操作系统为企业数字化转型赋能。产品能力覆盖云原生应用的开发、交付、运维全生命周期,并提供公有云、私有云和混合云等多种交付方式。成立迄今,公司已在金融科技、先进制造、智能汽车、零售网点、城市大脑等多个领域深耕,标杆客户包括交通银行、浦发银行、上汽集团、东风汽车、海尔集团、屈臣氏、金拱门(麦当劳)等。目前,公司已完成了 D 轮超亿元融资,被誉为科技领域准独角兽企业。公司在北京、南京、武汉、深圳、成都设立多家分公司及合资公司,总员工人数超过 350 人,是国家级“专精特新”小巨人企业、上海市高新技术企业、上海市“科技小巨人”企业和上海市“专精特新”企业,并入选了科创板培育企业名单。网址:www.daocloud.io
邮件:info@daocloud.io
电话:400 002 6898