关于我们

更多关于云原生的案例和知识,可关注同名【腾讯云原生】公众号~

①公众号后台回复【手册】,可获得《腾讯云原生路线图手册》&《腾讯云原生最佳实践》~

②公众号后台回复【系列】,可获得《15个系列100+篇超实用云原生原创干货合集》,包含Kubernetes 降本增效、K8s 性能优化实践、最佳实践等系列。

③公众号后台回复【白皮书】,可获得《腾讯云容器安全白皮书》&《降本之源-云原生成本管理白皮书v1.0》

④公众号后台回复【光速入门】,可获得腾讯云专家5万字精华教程,光速入门Prometheus和Grafana。

作者

徐蓓,腾讯云容器技术专家,腾讯云异构计算容器负责人,多年云计算一线架构设计与研发经验,长期深耕 Kubernetes、在离线混部与 GPU 容器化领域,Kubernetes KEP Memory QoS 作者,Kubernetes 积极贡献者。

当前存在问题

GPU 具备大量核心和高速内存,擅长并行计算,非常适合训练和运行机器学习模型。由于近几年 AI 技术愈发成熟,落地场景越来越多,对 GPU 的需求呈井喷趋势。而在资源管理调度平台上,Kubernetes 已成为事实标准。所以很多客户选择在 Kubernetes 中使用 GPU 运行 AI 计算任务。

Kubernetes 提供 device plugin 机制,可以让节点发现和上报设备资源,供 Pod 使用。GPU 资源也是通过该方式提供。以 nvidia GPU 为例,用户在 Kubernetes 节点部署 nvidia-device-plugin,插件扫描节点 GPU 卡,会以 extended resource 机制将 GPU 资源以类似​​nvidia.com/gpu: 8​​的形式注册到节点中。用户创建 Pod 时指定该资源名,经过调度器调度后,Pod 绑定到节点,最终通过 nvidia docker 提供的一系列工具,将所需 GPU 设备挂载到容器里。

Kubernetes device plugin 提供了一种便捷的方式用于集成第三方设备。但应用在 GPU 场景,还是存在以下不足:

  • 集群 GPU 资源缺少全局视角。没有直观方式可获取集群层面 GPU 信息,比如 Pod / 容器与 GPU 卡绑定关系、已使用 GPU 卡数 等
  • 不能很好支持多 GPU 后端。各种 GPU 技术(nvidia docker、qGPU、vCUDA、gpu share、GPU 池化)均需独立部署组件,无法统一调度和管理

问题一:缺少 GPU 资源全局视角

现有 Kubernetes 对 GPU 资源的分配调度是通过 extended resource 实现的,它是基于节点上卡数量的加减调度。用户如果想知道集群中 GPU 卡的分配情况,需要遍历节点,拿到并计算这些信息。并且由于这个资源是标量的,所以并无法拿到 Pod / 容器 与卡的绑定关系。这些问题在整卡模式下不是那么凸显,但在细粒度共享模式下,就尤为严重了。

由于 GPU 卡相对昂贵,并且某些 AI 负载吃不满单张 GPU 算力,GPU Sharing 技术应运而生。在 Kubernetes 中,我们会将一些 AI 负载共享同一张 GPU 卡,通过增加业务部署密度,提升 GPU 利用率,从而节省成本。以 TKE qGPU 为例,在 GPU Sharing 方式下,扩展资源从 GPU 卡数量变为百分比的 qGPU Core 与 MB 的 qGPU Memory。也就是说,用户可通过 qGPU 容器虚拟化技术,申请小于一张卡的 qGPU 虚拟设备。这些设备是在单张物理卡上虚拟出来的,资源之间也是强隔离的。除了 qGPU,vCUDA、gpu share 等技术都支持多个 Pod / 容器 共享同一张 GPU 卡。基于现有 Kubernetes 架构,是无法知道 GPU 卡所包含切片资源(我将之定义为 GPU Core 与 Memory 的组合)的分布情况的。集群资源分布对管理员与用户都是黑盒。管理员无法知道整个集群 GPU 切片资源的分配情况,用户也不知道新部署业务有无资源可用。

问题二:无法支持多 GPU 后端

除分配挂载整卡的方式外,TKE qGPU、vCUDA、gpu share、GPU 池化 等 GPU 共享技术越来越被用户采用。每种方案都有自己独立的一套 Kubernetes 集成实现方式。比如在 TKE qGPU 中,我们自研了 tke-qgpu-scheduler 用于 GPU 细粒度算力与显存分配调度,配套的 tke-qgpu-manager,用于节点初始化、注册上报 qGPU 资源及 qGPU 容器虚拟化。vCUDA、gpu share 也是类似架构,同样是由调度器 + device plugin 组成。这些方案相互独立,没有统一标准,无法共通。这导致用户在单个集群中很难同时使用多种 GPU 后端技术。比如用户集群有些业务是在线推理,吃不满整卡,想申请 TKE qGPU 切片资源。另一部分业务是训练,需要分配单卡。有些仿真和模型调试业务,为了成本和弹性,想要动态从远端 GPU 池申请资源。现有方案很难同时满足以上诉求,这为基于 Kubernetes 构建统一 AI 基础设施平台增加了很多难度。

以上问题均是 TKE 在基于 Kubernetes 帮助客户构建 AI 计算平台时遇到的真实困扰。随着 AI 业务的不断精进,客户已不再仅满足于“能使用 Kubernetes GPU 资源”。对 GPU 成本的关注,对 GPU 资源的整体把控,对 GPU 不同后端的精准使用,都成为了客户能用好 GPU 算力的前提条件。既然现有体系无法满足,那我们就需要另辟蹊径,重新思考 GPU 在 Kubernetes 中的位置。

一种全新的 Kubernetes GPU 方案

PV / PVC 带来的启示

在 Kubernetes 中,资源一般是围绕 Pod 设计和定义。从重要程度上讲,集群可用资源包含两种类型:核心资源和外部资源。核心资源指维持 Pod 正常运行的必不可少的资源,包括 CPU、内存、临时存储、网卡 等。这些会通过 kubelet 扫描节点并上报到集群中。另一部分是外部资源,多指外-挂存储和其他设备等,比如数据盘、GPU、FPGA 等。这些设备可能是本地设备挂载、也可能是远端设备挂载。这类资源的存在可以使得 Pod 更好的运行。比如数据盘增加了 Pod 的存储容量、GPU / FPGA 加速了 Pod 的计算能力。从这个角度看,存储与 GPU 有相似之处。

Kubernetes 在存储上抽象出了一组资源,如 PV / PVC / StorageClass,并为之提供了一组 API 和交互方式,将存储的供给管理和使用标准化和分离了出来。

  • PV:PV 是集群中的一块实际存储资源,可以由管理员手动创建,或者通过 StorageClass 方式动态创建。PV 类似节点上的 CPU、内存、网卡 等资源。PV 可以有多种后端供给,如前文描述的公有云存储服务、自建共享存储或本地存储。
  • PVC:PVC 是用户对 PV 存储资源的申领。它类似于集群中的 Pod。Pod 申请节点上的 CPU、内存、网络 等资源,PVC 申请存储资源,也就是 PV。
  • StorageClass:StorageClass 为管理员提供了描述存储“类”的方法。比如 PV 后端创建的方法、挂载的方法 等。

用户通过 PV 在指定后端创建实际存储,用户通过 PVC 申请已创建的 PV 存储资源,或者指定 StorageClass 动态从后端创建 PV。

参照 Kubernetes 存储的设计方式,我们认为 GPU 也可定义和实现类似抽象。

Elastic GPU CRD

我们定义了三种全新的 Kubernetes CRD,用于代表 GPU 资源的不同抽象:

  • ElasticGPU:ElasticGPU 是集群中一个实际可使用的 GPU 资源,可以是一块本地 GPU 物理卡、一个 GPU 切片资源( GPU 算力 / 显存 的组合)、一个远端 GPU 设备。
  • ElasticGPUClaim:ElasticGPUClaim 是用户对 ElasticGPU 资源的申领,可以申请整卡数量,申请 GPU 核数 / 显存,或者申请 TFLOPS 算力。
  • EGPUClass:EGPUClass 提供了生产和挂载 ElasticGPU 的方式,可以使用 qGPU 虚拟化、vCUDA、或是 GPU 远端池化的技术。
type ElasticGPU struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec ElasticGPUSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status ElasticGPUStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

type ElasticGPUSpec struct {
Capacity v1.ResourceList `json:"capacity,omitempty" protobuf:"bytes,1,rep,name=capacity,casttype=ResourceList,castkey=ResourceName"`
ElasticGPUSource `json:",inline" protobuf:"bytes,2,opt,name=elasticGPUSource"`
ClaimRef v1.ObjectReference `json:"claimRef,omitempty" protobuf:"bytes,3,opt,name=claimRef"`
NodeAffinity GPUNodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,4,opt,name=nodeAffinity"`
NodeName string `json:"nodeName,omitempty" protobuf:"bytes,5,opt,name=nodeName"`
}

type ElasticGPUSource struct {
QGPU *QGPUElasticGPUSource `json:"qGPU,omitempty" protobuf:"bytes,1,opt,name=qGPU"`
PhysicalGPU *PhysicalGPUElasticGPUSource `json:"physicalGPU,omitempty" protobuf:"bytes,2,opt,name=physicalGPU"`
GPUShare *GPUShareElasticGPUSource `json:"gpuShare,omitempty" protobuf:"bytes,3,opt,name=gpuShare"`
}

type ElasticGPUClaim struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec ElasticGPUClaimSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status ElasticGPUClaimStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

type ElasticGPUClass struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Provisioner string `json:"provisioner" protobuf:"bytes,2,opt,name=provisioner"`
Parameters map[string]string `json:"parameters,omitempty" protobuf:"bytes,3,rep,name=parameters"`
}

下面以 TKE qGPU 为例,描述结合 Elastic GPU 方案的整个资源调度分配流程。

qGPU 资源申请

用户在集群中创建 ElasticGPUClass,指定 qGPU 作为 GPU 后端。

apiVersion: elasticgpu.io/v1alpha
kind: ElasticGPUClass
metadata:
name: qgpu-class
provisioner: elasticgpu.io/qgpu
reclaimPolicy: Retain
eGPUBindingMode: Immediate

创建 ElasticGPUClaim 描述对 qGPU 资源的申领,​​tke.cloud.tencent.com/qgpu-core​​​代表申请 10% 的 GPU 算力,​​tke.cloud.tencent.com/qgpu-memory​​代表申请 4GB 显存。


apiVersion: elasticgpu.io/v1alpha
kind: ElasticGPUClaim
metadata:
name: qgpu-egpuc
spec:
storageClassName: qgpu-class
resources:
requests:
tke.cloud.tencent.com/qgpu-core: 10
tke.cloud.tencent.com/qgpu-memory: 4GB

用户在创建 Pod 时指定 ElasticGPUClaim 完成 qGPU 资源申领。

apiVersion: v1
kind: Pod
metadata:
name: qgpu-pod
annotations:
elasticgpu.io/egpuc-<container-name>: qgpu-egpuc
spec:
containers:
- name: test

qGPU 资源调度

考虑到 out-tree 的设计,qGPU 资源发现、上报和调度,还是依赖原有 device plugin 与 extended resource 机制。

我们通过 elastic-gpu-admission-hook 在 Pod 创建时识别 annotations ​​elasticgpu.io/egpuc-<container-name>​​,将申请资源正确设置到 containers 中。


apiVersion: v1
kind: Pod
metadata:
name: qgpu-pod
annotations:
elasticgpu.io/egpuc-test: qgpu-egpuc
spec:
containers:
- name: test
resources:
requests:
tke.cloud.tencent.com/qgpu-core: 10
tke.cloud.tencent.com/qgpu-memory: 4GB
limits:
tke.cloud.tencent.com/qgpu-core: 10
tke.cloud.tencent.com/qgpu-memory: 4GB

qgpu-scheduler 扩展调度器用于 qGPU 资源调度,返回符合要求的节点。当 Pod 绑定到节点上后,qgpu-provisioner 会更新​​ElasticGPU​​ CRD 中节点、GPU 卡索引 等信息,实现 qGPU 设备的绑定。

使用 Elastic GPU 管理 Kubernetes GPU 资源_腾讯云

qGPU 资源创建

qgpu-manager 会 watch ​​ElastciGPU​​ CRD 变化,在绑定节点成功后,会执行创建 qGPU 设备的操作。qgpu-manager 会根据 CRD 中包含的申请算力与显存信息以及调度到的 GPU 卡索引,在底层创建 qGPU 设备。

使用 Elastic GPU 管理 Kubernetes GPU 资源_json_02

qGPU 设备挂载

qgpu-manager 是一个 device plugin 插件,kubelet 会在分配 device 时通过标准接口调用插件。在接口 ​​Allocate​​​ 和 ​​PreStartContainer​​ 中,我们会挂载必要的 qGPU、nvidia 设备以及设置环境变量。最后,我们依赖 qgpu-container-runtime 进行 qGPU 设备与容器的绑定工作。

使用 Elastic GPU 管理 Kubernetes GPU 资源_elastic_03

下一步发展

随着 AI 业务的大规模落地,越来越多的用户在 Kubernetes 中使用 GPU 进行 AI 计算。现有的 extended resource 与 device plugin 机制很难满足客户对 GPU 资源的精细控制和分配,新的技术框架势在必行。Elastic GPU 在 Kubernetes 集群中抽象了一种 native GPU 资源,围绕三种自定义 CRD,在标准化定义了与其他 GPU 技术交互的前提下,同时提供了集群层面全局 GPU 资源视角,让用户可以更好的观察和管理 GPU 资源。

Elastic GPU 第一步会聚焦在 CRD 定义以及交互流程标准化,并首先适配 TKE qGPU。在这个阶段,我们希望参照 PV / PVC / CSI 的设计理念,以 Kubernetes native 的方式提供对 GPU 资源的抽象,标准化资源分配、调度、挂载等流程,并提供灵活的接口,供其他 GPU 技术集成。通过首先在生产环境支持 TKE qGPU,我们会持续打磨框架,发布第一个 alpha 版本。接下来,我们会推动社区实现对主流 GPU 技术的集成支持,包括 nvidia docker、gpu share 以及 vCUDA,横向扩展框架的适用场景。通过标准框架,统一接口和流程,降低客户管理成本。通过 GPU Sharing、Remote GPU 等技术提升灵活性、增加利用率,降低客户用卡成本。我们希望依赖 Elastic GPU 框架,最终可以为客户提供 Kubernetes 开箱即用使用 GPU 资源的能力。

TKE qGPU:https://cloud.tencent.com/document/product/457/61448