版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第第页vivoAI计算平台的K8s分级配额管理实践2023年底,vivo(AI)研究院为了解决统一高性能训练环境、大规模分布式训练、计算资源的高效利用调度等痛点,着手建设AI计算平台。经过四年多的持续迭代,平台建设和落地取得了很大进展,成为vivoAI领域的核心基础平台。平台从当初服务(深度学习)训练为主,到现在演进成包含VTraining、VServing、VContainer三大模块,对外提供模型训练、模型推理和容器化能力的基础设施。平台的容器集群有数千个节点,拥有超过数百PFLOPS的(GPU)算力。集群里同时运行着数千个训练任务和数百个在线服务。本文是vivoAI计算平台实战系列文章之一,主要分享了平台在资源配额管理方面的实践。
背景
K8s提供了原生的ResourceQuota资源配额管理功能,基于命名空间进行配额管理,简单易用。但是随着平台资源使用场景变得越来越复杂,例如多层级业务组织配额、针对具体(CPU)核和GPU卡的型号配额、资源使用时长配额等,ResourceQuota变得难以应对,平台面临业务资源争抢、配额管理成本增加、定位问题效率变低等问题。
本文主要介绍平台在K8s集群资源配额管理过程中遇到的问题,以及如何实现符合需求的配额管理组件:BizGroupResourceQuota——业务组资源配额(简称bizrq),用于支撑平台对复杂资源使用场景的配额管控。
ResourceQuota资源配额管理遇到的问题
在使用ResourceQuota做资源配额管理时,有以下4个问题比较突出:
1、无法满足有层级的业务组织架构的资源配额管理
ResourceQuota不能很好地应用于树状的业务组织架构场景,因为命名空间是扁平的,在实际场景中,我们希望将资源配额由父业务组到子业务组进行逐级下发分配。
2、以pod对象的粒度限额可能导致只有部分pod创建成功
ResourceQuota是以pod对象的粒度来进行资源限额的,正常情况下在线服务或离线任务的部署,例如deployment、argorollout、tfjob等,都需要批量创建pod,可能会造成一部分pod由于额度不足而创建失败的情形,导致部署无法完成甚至失败,我们希望要么全部pod都创建成功,要么直接拒绝部署并提示资源额度不足,提升部署体验。
3、无法针对具体CPU核和GPU卡的型号进行配额管理
ResourceQuota管理配额的资源粒度太粗,无法针对具体CPU核和GPU卡的型号进行配额管理,在实际场景中,不同的CPU、GPU型号的性能、成本差异很大,需要分开进行限额。例如我们会将CPU机器划分为A1、A2、A3、A4等机型,GPU机器也有T4、V100、A30、A100等机型,他们的性能和成本都是有差异的。
4、无法限制资源使用时长
ResourceQuota仅能限制当前时刻资源的已使用额度不能超过配额,但是并不能限制对资源的使用时长。在某些离线的深度模型训练场景,业务对CPU、GPU资源争抢比较激烈,某些业务组希望能按CPU核时或GPU卡时的方式,给团队成员发放资源配额,比如每人每周发放1000GPU卡时,表示可以用10张卡跑100小时,也可用20张卡跑50小时,以此类推。
BizGroupResourceQuota分级配额管理方案
针对ResourceQuota配额管理所面临的4个问题,我们设计了BizGroupResourceQuota配额管理——业务组资源配额管理方案,后文简称为bizrq。接下来,我们介绍一下bizrq方案。
我们通过K8scrd(CustomResourceDefine)来自定义bizrq资源对象(如下图bizrq配额示例),从而定义bizrq的实现方案:
如上图配额示例所示,下面分别解释一下bizrq配额方案的特点:
①bizrqname
bizrqname在clusterscope全局唯一,bizrq配额对象是集群范围的,不跟命名空间相关联。在实际业务场景中,bizrqname可以跟业务组ID对应起来,便于实现基于树状的业务组织架构的配额管理。
②父bizrqname
父bizrqname表示当前bizrq的父级业务组的bizrq配额对象名称,假如父bizrqname值设置了空字符串"",则表示当前bizrq是root节点。当创建非root的bizrq配额对象时,子bizrq的资源配额要从父bizrq的剩余额度中申请,并需要满足相关约束条件才能创建成功,后面也会介绍实现原理。这样就可以按常见的业务组织架构来管理配额,构成一颗“bizrq树”:
③默认配额示例
默认配额示例跟ResourceQuota的资源额度配置和限额效果是一致的,bizrq借鉴了ResourceQuota的实现,保持了一致的配置风格和使用体验。
④CPU核型号配额示例
bizrq支持将CPU核配额限制到具体型号,具体型号资源的已使用额度,也会累加到前缀相同的通用资源配额的已使用额度里,它们是可以结合使用的,如果都配置了则限额会同时生效。这样即保留了原生ResourceQuota的限额功能,又新增了不同型号资源的限额。
举例说明,比如将limits.cpu配额设置为10核,limits.cpu.A4配额设置为4核,它们一开始已使用额度都是0核,当我们的部署对象申请了4核的A4后,那么limits.cpu和limits.cpu.A4的已使用额度都会累加上这4核,因为bizrq会判断limits.cpu是limits.cpu.A4的前缀资源,属于通用资源类型,所以要一并计算。另外,此时业务组不能再申请A4型号的cpu资源了,因为limits.cpu.A4的剩余额度为0,不过limits.cpu剩余额度还有6核,所以还可以申请非A4型号的CPU资源。
那么我们能否将limits.cpu配额设置为10核,将limits.cpu.A4配额设置为100核呢(limits.cpu
如上图所示,A(PI)Server接收到资源对象请求后,由访问控制链路中的处理器按顺序进行处理,请求顺利通过访问控制链路的处理后,资源对象的变更才允许被持久化到etcd,它们依次是(认证)(authen(ti)cation)→鉴权(authorization)→变更准入控制(mutating(ad)mission)→对象Schema校验(objectschemavalidation)→验证准入控制(validatingadmission)→etcd持久化,我们需要重点关注“准入控制”环节:
•变更准入控制(mutatingadmission):对请求的资源对象进行变更。例如内置的ServiceAccountadmissioncontroller,会将pod的默认ServiceAccount设为default,并为每个容器添加volumeMounts,挂载至/var/run/secrets/kuberne(te)s.io/serviceaccount,以便在pod内部可以读取到身份信息来访问当前集群的apiserver。变更准入控制是可扩展的,我们可以通过配置mutatingadmissionwebhooks将自定义的逻辑加入到变更准入控制链路中;
•验证准入控制(validatingadmission):对请求的资源对象进行各种验证。此环节不会去变更资源对象,而是做一些逻辑校验,例如接下来要分析的QuotaAdmissionController,会拦截pod的创建请求,计算pod里容器申请的资源增量是否会导致超额。验证准入控制也是可扩展的,我们可以通过配置validatingadmissionwebhooks将自定义的逻辑加入到验证准入控制链路中。
如上图所示,ResourceQuota限额机制主要由两个组件组成:
•ResourceQuotaController:ResourceQuotaController是内置在controllermanager众多Controlle(rs)中的一个,主要负责监听资源对象,如pod、service等变更事件,也包括ResourceQuota对象的变更事件,以便及时刷新关联的ResourceQuota的status状态;
•QuotaAdmissionController:QuotaAdmission是内置在apiserver请求访问控制链路验证准入控制环节的控制器,QuotaAdmission主要负责拦截资源对象的创建、更新请求,对关联ResourceQuota的额度进行校验。
下面以Pod对象的限额为例,分析ResourceQuota限额机制原理。其他资源,比如service、pvc等对象限额的实现思路基本一致,只不过不同对象有不同的资源计算逻辑实现(Evaluator)。
ResourceQuotaController
当集群的controllermanager进程启动后,选主成功的那个controllermanagerleader就会将包括ResourceQuotaController在内的所有内置的Controller跑起来,ResourceQuotaController会以生产者-消费者的模式,不断刷新集群命名空间的ResourceQuota对象的资源使用状态:
作为任务生产者,为了及时把需要刷新状态的ResourceQuota放到任务队列,ResourceQuotaController主要做了以下3件事情:
·监听pod对象的事件,当监听到pod由占用资源状态变更为不占资源状态时,比如status由terminating变为Failed、Succeeded状态,或者pod被删除时,会将pod所在命名空间下关联的所有ResourceQuota放入任务队列;
·监听ResourceQuota对象的创建、spec.Hard更新(注意会忽略status更新事件,主要是为了将spec.Hard刷新到status.Hard)、delete等事件,将对应的ResourceQuota放入任务队列;
·定时(默认5m,可配置)把集群所有的ResourceQuota放入任务队列,确保ResourceQuota的状态最终是跟命名空间实际资源使用情况一致的,不会因为各种异常情况而出现长期不一致的状态。
作为任务消费者,ResourceQuotaController会为任务队列启动若干worker协程(默认5个,可配置),不断从任务队列取出ResourceQuota,计算ResourceQuota所在命名空间所有pod的容器配置的资源量来刷新ResourceQuota的状态信息。
QuotaAdmissionController
对于pod的限额,QuotaAdmission只会拦截Podcreate操作,不会拦截update操作,因为当部署对象的pod容器资源申请被变更后,原pod是会被删除并且创建新pod的,pod的删除操作也不用拦截,因为删除操作肯定不会导致超额。
当QuotaAdmission拦截到Pod的创建操作后,会找出对应命名空间所有相关联的ResourceQuota,并分析创建pod会不会造成资源使用超额,只要有一个ResourceQuota会超额,那么就拒绝创建操作。如果所有的ResourceQuota都不会超额,那么先尝试更新ResourceQuota状态,即是将此个pod的容器配置的资源量累加到ResourceQuotastatus.Used里,更新成功后,放行此次pod操作。
问题分析
总的来说,ResourceQuotaController负责监听各类对象的变更事件,以便能及时刷新对应的ResourceQuotastatus状态,而QuotaAdmission则负责拦截对象操作,做资源额度的校验。有几个比较关键的问题我们需要分析一下,这几个问题也是bizrq实现的关键点:
1、ResourceQuotastatus的并发安全问题
ResourceQuotaController会不断刷新ResourceQuotastatus里各类资源使用量,所有apiserver进程的QuotaAdmission也会根据ResourceQuotastatus校验是否超额,并将校验通过的pod资源增量累加到ResourceQuotastatus.Used,所以status的更新是非常频繁的,这就会导致并发安全问题:
假设某个业务组CPU配额有10核,已使用量为1核,业务A和业务B同时请求申请5核,此时对剩余额度的校验都是足够的(剩余CPU核数为10-1=9),两个请求都会将CPU已使用量更新为1+5=6核,并且都申请成功,这将导致限额失效,因为实际的资源申请已经超额了(1+5+5=11核)。
解决办法通常有以下3种方式:
·方式1:将所有对status的访问逻辑打包成任务放入队列,并通过单点保证全局按顺序进行处理;
·方式2:将所有对status的访问逻辑加锁,获取到锁才可以进行处理,保证任何时刻都不会有并发的访问;
·方式3:通过乐观锁来确保对status的访问是安全的。
ResourceQuota采用的是第三种方式,方式1、2都要引入额外的辅助手段,比如分布式队列、分布式锁,并且同一时刻只能处理一个请求,效率比较低下,容易造成处理延时,此外还要考虑ResourceQuotaController跟QuotaAdmissionController的协同(都会更新ResourceQuotastatus.Used),在限额场景实现起来不是那么简洁高效,而方式3可以直接基于K8s的乐观锁来达到目的,是最简洁高效的方式。
K8s乐观锁实现原理
K8s乐观锁的实现,有两个前提:
·一是K8s资源对象的resourceVersion是基于etcd的revision,revision的值是全局单调递增的,对任何key的修改都会使其自增;
·二是apiserver需要通过etcd的事务API,即clientv3.KV.Txn(ctx).If(cmp1,cmp2,).Then(op1,op2,).Else(op1,op2,),来实现事务性的比较更新操作。
具体流程示例如下:
·第1步:client端从apiserver查询出对象obj的值d0,对应的resourceVersion为v0;
·第2步:client端本地处理业务逻辑后,调用apiserver更新接口,尝试将对象obj的值d0更新为dx;
·第3步:apiserver接受到client端请求后,通过etcd的if...then...else事务接口,判断在etcd里对象obj的resourceVersion值是否还是v0,如果还是v0,表示对象obj没被修改过,则将对象obj的值更新为dx,并为对象obj生成(最新)的resourceVersion值v1;如果etcd发现对象obj的resourceVersion值已经不是v0,那么表示对象obj已经被修改过,此时apiserver将返回更新失败;
·第4步:对于client端,apiserver返回更新成功时,对象obj已经被成功更新,可以继续处理别的业务逻辑;如果apiserver返回更新失败,那么可以选择重试,即重复1~4步的操作,直到对象obj被成功。
apiserver的更新操作实现函数:
/kubernetes/kubernetes/blob/v1.20.5/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go
相对于方式2的锁(悲观锁),乐观锁不用去锁整个请求操作,所有请求可以并行处理,更新数据的操作可以同时进行,但是只有一个请求能更新成功,所以一般会对失败的操作进行重试,比如QuotaAdmission拦截的用户请求,加了重试机制,重试多次不成功才返回失败,在并发量不是非常大,或者读多写少的场景都可以大大提升并发处理效率。
QuotaAdmission中乐观锁重试机制实现函数是checkQuotas,感兴趣可自行阅读源码:
/kubernetes/kubernetes/blob/v1.20.5/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/controller.go
另外,更新冲突在所难免,客户端可以借助client-go的util函数RetryOnConflict来实现失败重试:
/kubernetes/client-go/blob/kubernetes-1.20.5/util/retry/util.go
2、超额问题
问题:QuotaAdmission校验pod创建是否超额时,查询出来的ResourceQuota的status.Used状态能否反映命名空间下资源最新实际使用量,会不会造成超额情形?
问题分析:
由于ResourceQuotaController和QuotaAdmission都会不断刷新ResourceQuota的status.Used状态,并且QuotaAdmission基本是通过informercache来获取ResourceQuota的,informer监听会有延迟,所以校验额度时查询的ResourceQuotastatus状态可能并不是准确反映命名空间下资源最新实际使用量,概括起来有以下3种不一致的情况:
·(1)informer事件监听延迟,查询到的可能不是etcd最新的ResourceQuota;
·(2)即使查询到的是etcd最新的ResourceQuota,在额度校验过程中,ResourceQuota也有可能先被别的请求或被ResourceQuotacontroller修改掉,那么ResourceQuota数据也是“过期”的;
·(3)即使查询到的是etcd最新的ResourceQuota,并且在额度校验过程中,ResourceQuota数据没有被修改,这个查询出来的etcd最新的ResourceQuota,也有可能不是最新的数据,因为ResourceQuotacontroller刷新ResourceQuota可能不是那么及时。
对于(1)、(2)这两种情形,由于QuotaAdmission基于K8s乐观锁做ResourceQuota资源额度的校验及状态更新,如果ResourceQuota不是etcd最新的,那么更新ResourceQuota状态时会失败,QuotaAdmission将进行重试,这样就能保证只有用最新的ResourceQuota来做额度校验,资源申请请求才能被通过;
对于(3)情形,ResourceQuotacontroller刷新ResourceQuota可能不及时,但也不会造成超额,因为资源使用量的增加(例如pod创建操作)都是要通过QuotaAdmission拦截校验并通过乐观锁机制将资源增量更新到ResourceQuota,ResourceQuotacontroller刷新ResourceQuota不及时,只会导致当前etcd最新ResourceQuota的剩余额度比实际“偏小”(例如pod由terminating变为Failed、Succeeded状态,或者pod被删除等情形),所以符合我们限额的目的。
当然,如果手动把配额调小,那可能会人为造成超额现象,比如原先CPU配额10核,已使用9核,此时手动把配额改成8核,那么QuotaAdmission对于之后的pod创建的额度校验肯定因为已经超额,不会通过操作了,不过这个是预期可理解的,在实际业务场景也有用处。
3、全局刷新问题
问题:为什么要定时全量刷新集群所有的ResourceQuota状态?
问题分析:
·定时全量刷新可以防止某些异常导致的ResourceQuotastatus跟实际的资源使用状态不一致的情形,保证状态回归一致性。例如QuotaAdmission更新了ResourceQuota后(额度校验通过后),实际的资源操作由于遇到异常而没有成功的情形(如pod创建操作在后续环节失败的场景);
·不过由于ResourceQuotaController会不断更新ResourceQuota,在额度校验通过后到实际资源操作被持久化前这段时间内(这段时间很短),ResourceQuota可能会被ResourceQuotaController更新回旧值,如果后续有新的资源申请操作,就可能造成超额情形,不过出现这个问题的几率应该很小,通过业务层补偿处理(比如超额告警、回收资源)即可。
关于ResourceQuotaController和QuotaAdmission的代码细节,感兴趣可以自行阅读:
/kubernetes/kubernetes/blob/v1.20.5/cmd/kube-controller-manager/app/core.go
/kubernetes/kubernetes/blob/v1.20.5/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go
bizrq实现
在分析了ResourceQuota的实现原理后,我们再看看如何实现bizrq的限额方案。
整体架构
参考ResourceQuota的实现,我们可以将bizrq按功能职责划分成3个模块:
bizrqcontroller
负责监听相关事件,及时刷新bizrq状态以及定时(默认5m)刷新整个集群的bizrq状态。
·监听bizrq的创建、更新(Spec.Hard变更),及时刷新bizrqstatus;
·监听部署对象,例如deployment、argorollout、tfjob等对象的更新(这里仅监听biz.group.resource.quotalabel配置的更新)、删除事件,及时刷新关联的bizrqstatus。
bizrqvalidatingadmissionwebhook(server)
基于apiserver的验证准入控制webhook,负责拦截bizrq的增删改事件,做bizrq本身以及bizrqparent相关的约束性校验,从而实现分级配额。
·拦截创建事件:对bizrqname做重名校验,确保clusterscope全局唯一;然后进行父bizrq校验;
·拦截更新事件:确保父parentBizGroup字段不能变更,不能更改父bizrq;然后进行父bizrq校验;
·拦截删除事件:确保只能删除叶子节点的bizrq,防止不小心将一棵树上所有的bizrq删除;然后进行父bizrq校验;
·父bizrq校验:校验非rootbizrq(Spec.ParentBizGroup字段不为"")的父bizrq的相关约束条件是否满足:
父bizrq必须已存在;
子bizrq必须包含父bizrq配额配置的所有资源类型名称key(Spec.Hard的key集合),父bizrq的配额配置是子bizrq配额配置的子集,例如父bizrq包含limits.cpu.A4,则创建子bizrq时也必须包含limits.cpu.A4;
父bizrq的剩余额度必须足够,子bizrq申请的额度是从父bizrq里扣取的额度。如果父bizrq不会超额,则先更新父bizrq的status,更新成功才表示从父bizrq申请到了额度。
objectvalidatingadmissionwebhooks(server)
基于apiserver的验证准入控制webhook,负责拦截需要限额的资源对象的创建和更新事件,例如deployment、argorollout、tfjob等对象的拦截,做额度校验,从而实现部署对象的限额校验,而不是pod对象粒度的校验。
拦截到部署对象的请求时,从部署对象提取出以下信息,就能计算各类资源的增量:
·资源型号(如果有指定具体型号的话),从labelbiz.group.resource.quota/cpu-type或biz.group.resource.quota/gpu-type提取;
·部署对象的副本数(obj.Spec.Replicas);
·PodTemplateSpec,用来计算容器配置的资源量。
然后再判断部署对象的请求是否会导致关联的bizrq超额(从labelbiz.group.resource.quota提取关联的bizrqname),如果bizrq不会超额,那么先尝试更新bizrq资源使用状态,更新成功后,放行此次请求操作。
关于核时、卡时的限额实现
controller会不断刷新bizrq的status,bizrq的status跟ResourceQuota的status有点不一样,bizrq的status添加了一些辅助信息,用来计算核时、卡时的使用状态(cash额度使用状态):
对比ResourceQuota的status:
·bizrqstatus包含了ResourceQuota也有的hard、used字段;
·增加selfUsed字段来记录bizrq本身(业务组本身)已使用资源量,selfUsed不包含子bizrq所申请的配额,并满足关系:selfUsed+子bizrq申请的配额=used;
·增加lastReconcileTime字段来记录controller最后一次刷新bizrqstatus的时间,因为计算核时、卡时是要基于资源量乘于使用时间来计算的,而且是要基于selfUsed累加的。计算cash时,controller会将部署对象包含的所有pod查询出来,然后结合lastReconcileTime、pod开始占用资源的时间点(成功调度到节点上)、pod释放资源的时间点(比如Succeeded、Failed),再结合pod容器配置的资源量,从而计算出整个bizrq业务组较准确的cash使用量。注意cash的计算不是百分百精确的,有些情形,例如pod被删除了,那么下个计算周期这个pod的cash就不会累加到status了,不过由于controller更新status的频率很高(最迟每5m更新一次),所以少量误差并不会影响大部分业务场景下的cash限额的需求;
·objectvalidatingadmissionwebhooks拦截到请求后进行核时、卡时的额度校验时,判断bizrq.status.used里相关资源类型的cash(如cash/limits.cpu)是否已经大于等于bizrq.status.hard里配置的配额,是则表示cash已经超额,直接拒绝拦截的请求即可;
·另外,当cash已经超额时,并不能强制把相应业务组正在运行的业务停掉,而是可以通过监控告警的等手段通知到业务方,由业务方自行决定如何处置。
开发工具简介
kubebuilder
Kubebuilder是一个基于CRD搭建controller、admissionwebhookserver脚手架的工具,可以按K8s社区(推荐)的方式来扩展实现业务组件,让用户聚焦业务层逻辑,底层通用的逻辑已经直接集成到脚手架里。
bizrq主要用到了kubebuilder的以下核心组件/功能:
·manager:负责管理controller、webhookserver的生命周期,初始化clients、caches,当我们要集成不同资源对象的client、cache时,只需写一行代码将资源对象的schema注册一下就可以了;
·caches:根据注册的scheme(sch
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度地下停车场开发与经营权转让合同3篇
- 二零二五年度环保熟石灰粉体加工销售合同3篇
- 2025至2031年中国氧化芳樟醇行业投资前景及策略咨询研究报告
- 2025至2031年中国料理食品行业投资前景及策略咨询研究报告
- 2025至2030年中国商业夹数据监测研究报告
- 二零二五年度废钢销售与环保技术开发合同3篇
- 二零二五年个人合伙清算协议书(清算后续税收筹划)4篇
- 二零二五年度轨道交通新风系统更新合同3篇
- 个人二零二五年度土地承包权入股合作协议书4篇
- 二零二五年度教育机构校车驾驶员职责与服务聘用合同3篇
- 淋巴瘤患者的护理
- 移动商务内容运营(吴洪贵)任务三 APP的品牌建立与价值提供
- 电子竞技范文10篇
- 人美版初中美术知识点汇总九年级全册
- 食堂服务质量控制方案与保障措施
- VI设计辅助图形设计(2022版)
- 眼科学常考简答题
- 物料分类帐的应用
- 乳房整形知情同意书
- 2022-2023年人教版九年级物理上册期末考试(真题)
- 根因分析(huangyan)课件
评论
0/150
提交评论