百度混部实践系列 | 如何提升 K8S 集群资源利用率?

2021年09月15日 阅读数:3
这篇文章主要向大家介绍百度混部实践系列 | 如何提升 K8S 集群资源利用率?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

【导读】随着Kubernetes(如下简称『K8S』)被业界愈来愈普遍地使用,单个集群规模也逐渐增大,不少人都会发现本身维护的 K8S 集群广泛存在一个问题:分配率较高,而利用率偏低。web

好比,一个有1000+节点的集群,在分配率达到80%后,经常会由于集群碎片的缘由,不少大规格的 Pod 就没法再被建立出来。而与此同时,整个集群的日均 CPU 利用率却不足15%,常态使用率偏低。那么,如何才能把剩余的闲置资源尽量利用到极致呢?安全

今天这篇文章,是百度云原生团队分享云原生混部实战的第一弹,咱们一块儿探索 K8S 原理,用 K8S 原生的方式来解决这个问题。并发

 

1. 原生 K8S 可否解决资源利用率问题?

 

首先,咱们来看下图这个示例。图中黄色框表明2个 Node,这两个 Node 的 CPU 总数都是40核,而且都已经分配了38核。其中 NodeA 的真实用量是35核,NodeB 的真实用量是10核。而蓝色框则表明如今咱们还有3个待调度的 Pod。app

 

 

若想调度3个 Pod:性能

 

  • PodA 的 Request 和 Limit 都是5c,此时它没法被调度,即便Node B上还有空闲资源;测试

 

  • PodB 的 Request 为1c,Limit 为10c,该 Pod 超发比较严重,它能够被调度到 NodeA 或 NodeB,可是调度到 NodeA 时可能被驱逐;字体

 

  • PodC 的 Request 和 Limit 都没有填写,此时它能够被调度,可是当调度到 NodeA 时可能被驱逐大数据

 

基于以上场景,能够尝试总结一下为何原生 K8S 没办法直接解决资源利用率的问题:优化

 

第一,资源使用是动态的,而配额是静态限制。在线业务会根据其使用的峰值去预估Quota(Request和Limit),配额申请以后就不能再修改,但资源用量倒是动态的,白天和晚上的用量可能都不同。ui


第二,原生调度器并不感知真实资源的使用状况。因此对于 PodB,PodC 这种想要超发的业务来讲,没法作到合理的配置。


基于这些缘由,团队进行了下一阶段的方案设计:引入动态资源视图

 

2. 引入动态资源视图

 

经过添加一个 Agent 去收集单机的资源用量状况,而且汇总计算获得动态的资源视图(机器真实的用量状况),将其上报到调度器,在调度器中配置相关策略,能够将上文中提到的 PodB 和 PodC 准确的调度到 NodeB 上。

 

也就是说经过构建资源视图,能够将 Limit 大于 Request 的 Pod 或者没填 Request 和Limit 的 Pod,调用到真实用量更少的 Node。这个方法能够解决因为不知道机器到底用了多少资源,因此调度失败被驱逐的问题,最终达到提高 CPU 利用率的效果。

 

 

可是这样作须要付出什么代价?若是要将 PodB 和 PodC 调度到 NodeB 上,NodeB 的配额只多分配了1核,可是用量却上升了20核,这就形成了超量使用。(申请量很是少,可是用量很是多)

 

对于 Node B,多用的20核实际上是已经分配出去的 Pod 没有用到,暂时让出的。

 

为了方便理解举个例子:由于工做须要,我申请了三台电脑,平时大部分状况只用一台,剩下的两台能够借给其余同事暂时用一用;可是我申请3台也是有缘由的,有时候是真的要用到的,那当有用到的时候,同事就必需要还给我,因此这就涉及到一个『如何借』和『如何还』的问题。

 

2.1 借用的代价:不稳定的生命周期

 

在上述的调度状况下,若是 PodC 用量持续增加,总体的负载可能超过了对单机设备的驱逐上限,会触发单机 kubelet 的驱逐行为。

 

对应到上述的例子中,若是我共有三台电脑,平时只用一台,外借了两台。如今因为工做须要我要多用一台(总体用量上涨),在这种状况下我并不须要一次性把两台电脑都收回(驱逐 Pod),而是仅收回我须要的那一台便可。

 

好比在 CPU 层面,能够尝试先下降 PodC 的 CPU Quota;在内存层面,能够先尝试进行内存回收。换句话说,能够优先考虑下降资源使用状况,而不是直接驱逐掉 Pod。

 

所以有两个核心问题要解决:第一,动态资源视图要如何作;第二个单机资源的调配如何保证供给。

 

2.2 单机引擎:隔离与退避

 

名词解释:Guaranteed-Pod、Burstable、BestEffort

 

K8S中的QoS是根据request 和limit动态算而来:

  • request等于limit,会被放到Guaranteed-Pod之中

  • request不等于limit,会被放在这个Burstable(突发型)目录下

  • request和limit都没有填,会被放在BestEffort目录下

 

 

上图灰色框的部分是 kubelet 为每一个 Pod 设置 Cgroup 的目录结构:首先有一个一级目录叫 kubepod,全部 Pod 的 Cgroup 都会被挂到它下面,图中有两个红色字体的 Guaranteed-Pod 是直接被挂载到 kubepod 目录下。


图中红色字体部分(Guaranteed-Pod, Burstable-Pod)的目录由 kubelet 给它们设置 Quota。以CPU为例,好比 Limit 填 5,Quota 就会设置为 5。白色字体部分是没有 Quota 限制的( kubepod 和 BestEffort-Pod),能够看到的是 Burstable,BestEffort 这两种 Pod 没有直接挂在 kubepod 目录下,而是本身有一个本来是空白的没有值的二级目录。

在树形结构下 Cgroup 有以下特色:单个目录下进程使用的资源限制并不只仅受本身所在节点的限制,还要受父节点的限制。好比在 Burstable 的框下边有两个Pod(图无关),在 Burstable 那个框设置了一个 Quota 是 10c,那这两个 Burstable-Pod 的 CPU 用量总和不能超过 10c。

 

基于这个原理团队设计了对应的压制策略,单机引擎会根据 Guaranteed-Pod 的真实用量去给 Burstable 目录总体设置了一个值,这个值经过动态计算而来。简单来讲,会先计算一下 Guaranteed-Pod 如今用多少,还剩多少资源能够给到 Burstable。BestEffort也是相似的,会先计算 Guaranteed 和 Burstable 如今的 Pod 的用量是多少,而后给框总体设定一个值。

 

若是单机的用量起来了,即申请的 Pod 如今要把本身借出去的这部分资源拿回来了,如何处理?此时会经过动态计算缩小 Burstable 和 BestEffort 的这两个框的值,达到一个压制的效果。


在不考虑整机 Quota 超发的状况下,若是整机 Quota 都分完了, 整机 Pod 资源用量又在持续上涨,这种状况要如何处理?

 

当资源用量持续上涨时,若是 BestEffort 框总体 CPU 用量小于 1c ,单机引擎会把 BestEffort Pod 所有驱逐掉。K8S 自己在单机发生资源紧张的时候,也是会按照这种顺序去驱逐相应的Pod。当Guaranteed-Pod的用量还在持续上涨的时候,就会持续的压低 Burstable 整框 CPU 的Quota,从而达到压制的效果。

 

Burstable 类型的 Pod 的特征是  Request 不等于 Limit , 该类型的 Pod 申请了相应资源的 Quota, 在这个前提下,Burstable 框内的Pod,最低会压到原本申请的资源量。好比 Burstable 框下只有一个Pod,Request是1c,Limit是10c,那么单机引擎最低会将 Burstable 整框压制到 1c。

 

换言之,对于 Request,就是说那些用户真实申请了 Quota 的资源,必定会获得获得供给;对于 Limit - Request 这部分资源,单机引擎和调度器会让它尽可能可以获得供给;对于 BestEffort,也就是 No Limit 这部分资源,只要单机的波动存在,就存在被优先驱逐的风险.

 

可是对于一些长尾延迟来讲,仅仅经过上述 K8S 的手段,保证不了服务质量。发生争抢时,系统 load 就会比较高。因此团队引入了内部的一些内核功能,而且对它进行一些扩展和支持,基本包括如下几类:

 

 

  2.3 单机引擎:构建资源视图   单机 Agent 须要收集两部分资源:Pod 的资源使用状况以及整机的资源使用状况(包括机器内核指标),实时计算实时发生行为同时实时计算上报一份已经算好的数据,能够减轻调度器的计算压力。  

 
  • 中等质量容器可用量 = 单机最大 CPU 用量 - 高质量容器用量 - Safety-Margin

  • 低等质量容器可用量 = 单机最大 CPU 用量 - 高质量容器用量 - 中等质量容器用量 - Safety-Margin

 

这里的 Safety-Margin 是安全水位线,做为预留buffer可以避免用量忽然的上涨致使整机忽然被打满。

 

这样就构造出了一个单机的资源视图,将其上报到调度器,调度器将此视图来做为调度依据进行优选和预选的策略。


同时单机上还有一些可定制化的策略。经过给这些策略设计了一个这个 CRD ,单机引擎经过对 APIServer 发起 List-watch,实时的 Watch CR 的变动,实时调整参数和相关策略。

  2.4 超发的结果:质量分级  

以上,团队完成了在 K8S 上混部探索的第一阶段,这个方案基于 K8S 没有作侵入式改动。

 

可是在对接用户时常常要面对两个问题:一是用户不知道 Request 和 Limit 要怎么填?

 

须要先对相关概念作科普:

 

  • Request 部分,表明是稳定的,安全的,只要申请了就必定能够用到的资源.。

     

  • Limit 减去 Request 部分,好比说申请的 Request 是5,Limit 是10,中间的 5核 的差距是相对来讲不稳定,但大几率可以获得供给的资源。若是说有须要的时候,系统会尝试把该 Pod 超用的资源压缩回去。

     

  • No Limit,就是既不写 Request 也不写 Limit 的部分会尽可能保证,可是资源供给 sla 会是一个比较低的数字,用这部分资源就要有随时被杀掉的准备。


第二个问题是若是可能,用户是否都倾向于用第一种 Request 级别的资源?

 

答案是否认的。事实证实,若是能把成本降下,一些鲁棒性高的业务是愿意接受低质量资源的。


好比一些不敏感的离线业务,若是被 kill,代价就是过一会再跑或者从新算一遍,它是乐于接受这种这个低质量资源的,前提条件是系统要给一个很低的成本。

 

所以团队构造了下图的成本模型,对于不一样质量的资源,有不一样的订价。做为平台方给用户结算计费的时候,会根据实际的用量和质量的乘积进行结算。

 

 

好比 K8S 集群中托管的机器没开混部,订价多是D(假设D为1);在开了混部的集群当中,用户去用这些 Request,价格可能就是0.85;在开了混部的不少集群当中,用户用 Limit 部分的话,价格可能就是0.5;若是用 Besteffort 部分,那可能它的价格是0.1。

 

经过这种方式,找到了第一批的这个种子用户。无论是在线业务、离线业务、测试业务,还有一些内部 DevOps 业务,都很是愿意根据这个成本模型再从新审视本身的业务模型, 选择对应的资源等级来运行本身的业务。

 

3.落地以后遇到了哪些问题

 

热点问题 : 保证了用量, 如何保证质量?

 

热点问题并不只仅是混部带来的,在线跟在线业务部署在同一台机器上也有这个问题。好比资源的争抢、内核关键路径的争抢均可能致使延迟的上升。在百度内部若是搜索的一个接口,因为混部质量致使延迟超过一百毫秒的话,影响面就会很是大,由于业务的链路一般都比较长,延迟累计最终给用户响应的时间可能达到秒级,这种状况是不可接受的。

 

除了内核提供的隔离技术外,如何给出热点问题的兜底方案?

 

答案是热点迁移,出现热点,系统自动迁移容器。具体而言,单机引擎会经过收集应用的一些指标来判断这个应用是否发生热点。若是发生热点的话,就会给一个机器上打一个 Annotation,而后当调度器 Watch 到这个 Annotation 时,它就会认为台机器上发生了热点,就要迁移热点容器。
 

Pending Pod : 低质量需求激增带来的调度性能需求

 

以某用户跑数的场景为例,假设他预计须要1000核资源,要在明天早上9点完成,所以他就申请了1000核高质量的资源,同时他又申请了1万核 Besteffort 的资源一块跑,经过了1万核的低质量的资源加速,可能不用明早9点,半夜1点就跑完了,剩下的这部分红本就了省下来。

 

可是用户这种使用方法会给调度器的性能带来很大影响,在集群负载较高时,用户建立的 BestEffort Pod 因为资源不足致使所有 Pending,而这些 Pending Pod 会反复的出如今调度队列中,针对这种场景,咱们尝试对离线调度器进行功能和性能上的优化 : 

 

  • 副本数托管 : 基于集群负载对应用进行动态扩缩容。社区叫 HCPA,用户经过建立一个 CR 来描述任务的最低的副本数是多少,最高的副本数是多少,当集群负载发生变化的时候,Controller 能够动态的扩缩用户的任务副本数,而再也不是静态的建立出海量的 Pending Pod, 阻塞在队列中。

 

  • 多调度器 :  基于质量的多调度器。对 Besteffort 的容器来讲, 在调度时调度器并不关心静态资源视图,也就是 Node Allocatable Resource。调度器只关心这台机器上如今还剩多少可用资源,因此在资源视图的角度,自然就和其余两种质量的 Pod 不冲突. 基于这个前提,咱们将 Guaranteed 和 Burstable 两种类型的 Pod 归并到一个调度器内进行调度, 而 BestEffort 类型的 Pod 则在另一个离线调度器内调度。

 

  • 等价类合并 :  在一个调度周期内, 使用相同 Pod Template 生成的 Pod, 在进行调度计算时必定会获得相同的 Node 列表。基于这个前提, 在调度上咱们构造了等价类的概念,在调度时单位从 Pod 变成了 PodEquivalenceGroup. 在优选结束后, 会按照 Node 分数的顺序来依次调度 PodEquivalenceGroup 中的全部 Pod。这样处理至关于将 O(n) 的调度计算降为了 O(1)。

 

  • 乐观并发调度 :  目前开源的 Kubernetes 调度器, 不管是默认调度器仍是社区的 kube-batch, volcano 都是依次进行调度的,队头阻塞的现象较为严重。在优先级相同的状况下, 最后一个 Pod 的调度延迟约等于队列内全部 Pod 的调度延迟之和。并发的关键在于如何解决冲突 : 在调度器内存中为每个 Node 维护了一个版本号, 当 Pod 与 Node 进行 Bind 操做时, 会尝试对 Node 版本号进行 +1 的 CAS 操做, 若是失败,则说明该 Node 已经发生过 Bind 操做, 此时会将该 Node 重算并从新尝试调度。基于 CAS + Version 的机制, 咱们实现了同优先级状况下, Pod 并发调度的方案。该方案能够带来 4 ~ 8 倍的调度性能提高。

 

4.总结

 

  • K8S 本来的资源模型存在局限性。咱们能够基于原生的 QOS 体系作一些不修改本来语义的扩展行为,而且基于质量创建相应的订价体系,经过给出不一样质量的资源供给 SLA,来对资源进行差别化订价,从而引导用户更加合理地使用资源。目前咱们在作的进一步探索是,如何根据业务特征,来划分出更适合业务的,更细粒度的资源模型。

 

  • 创建云原生可观测体系,根据质量去作单机资源的隔离/压制以及驱逐行为。由于在常见混部的状况下热点问题常常发生,因为热点对延迟敏感型业务会形成较大的影响,因此热点问题事实上限制了整机最大的资源利用率。而热点问题根因一般在内核层面,内核的可观测性又比较差,所以目前百度云原生团队在探索基于 ebpf 来创建更细粒度的热点探测/分析体系。

 

  • 大规模混部落地后,须要对热点问题、调度性能等问题给出解决方案。后续持续迭代相应的调度功能,在调度性能和支撑大数据业务容器化上作出更进一步的探索。

 

- End -

点击进入了解更多技术资讯~~