导读
本文将分享 B 站大数据任务基于 Volcano 的云原生调度实践,主要从四个方面展开:首先介绍迁移背景,即为什么要从大数据的离线集群迁移到云原生 K8s 集群;接着介绍选取了什么样的云原生调度框架,在迁移过程中做了哪些针对大数据的适配工作;然后介绍在云原生场景下,大数据集群超配框架的搭建经验;最后是对未来工作的展望。
1.
迁移背景
2.
调度框架的适配
3.
超配框架的搭建
4.
未来展望
分享嘉宾|
卜凡
bilibili
资深开发工程师
编辑整理|
余安琪
内容校对|李瑶
出品社区|
DataFun
迁移背景
1. 云原生的优势
节约成本:统一管理模式,方便离线资源与其他资源合池,更充分的利用资源。
环境适配:对于有特殊依赖的任务,容器技术确保在异构环境中一致的运行表现。
提升效率:基于
K8s
实现更自动更准确的运维管理,提升日常运维效率。
2. 迁移的挑战
迁移面临的挑战主要包括两大部分,第一部分是计算组件的适配,这个在前期就已完成。其中计算引擎改造部分包括了 Spark Client 对
K8s 的提交
适配、任务状态和日志在
K8s
的环境中的收集适配、自研 WebUI 在
K8s
环境中跳转适配、Remote Shuffle Service(RSS 服务)的适配等;任务迁移改造部分,主要涉及的改造任务有任务路由策略、任务反压策略、任务监控告警,任务驱逐与超时管控等。
第二部分是调度引擎的适配,B 站在离线和混部集群中主要采用 Yarn 作为调度引擎,
K8s
集群中最终选用了 Volcano 作为调度引擎。
选取 Volcano 作为调度引擎的主要原因有以下三点:
(1)调度性能方面:Volcano 是 CNFC 下首个基于
K8s
的容器化批量计算平台,在
K8s
下针对大规模并行计算进行了专门优化。
(2)迁移难度方面:Spark 于 v3.3.0 版本已支持 Volcano 作为自定义调度器调度;Volcano 本身的 Queue、PodGroup 等概念与大数据场景有较高的适配度。
(3)可扩展性方面:Volcano 采用可扩展性的架构设计,支持用户自定义 Plugin 和 Action 以支持更多调度算法。
02
调度框架的适配
1.
Volcano 简介
Volcano 与 K8s 可以天然兼容,包括 Spark 在内的一些上层组件都可以较流畅的被适配。Volcano 的框架分为三个主要部分:
(1)Scheduler 是 Volcano 主要的调度模块,通过一系列 Actions 和 Plugins 来调度 Volcano
Job 与 PodGroup,并为其找到一个最合适的节点。与 K8s 的 Default Scheduler 相比,Volcano Scheduler 与众不同的地方是它支持了针对 Job 的多种调度算法。
(2)ControllerManger 负责管理 Volcano CRD 资源的生命周期,它主要由 VCJob、PodGroup 与 Queue 等 CRD 的 ControllerManager 构成。
(3)Admission 是 Volcano 的资源校验的模块,负责对 CRD API 涉及的资源进行拦截与校验。
2. 第一版层级队列的改造
Volcano 的引入带来了一些问题,例如在 Yarn 中的队列都是层级队列,层级队列包含了 ParentQueue 和 LeafQueue。由于 Volcano 当初没有层级队列的概念,所以快速进行了第一版本的层级队列的改造:
首先,Volcano Scheduler 会监听各个资源的状态更新事件,其中就包括 Queue 的 add、update 和 delete 事件。在 Queue add 触发的事件中,添加了当前是否处于初始化场景中的判断。若为初始化场景,则需要将当前 Queue 加入缓存的 map 中,等待所有队列全部缓存完毕,开始从 RootQueue 至 LeafQueue 构建并记录所有队列的层级关系;若不为初始化场景,或在 queue update 或 delete 事件中时,会直接去更新已存在的层级关系。
该解决方案的优点是快速简单,能够快速获取当前层级队列的信息,适用于 queue 的数量格式都比较稳定,且提交相对可控的场景;缺点是在初始化场景中,Queue 的构建会依赖 ConfigMap 配置,在非初始化场景的队列增减时也需要同时更新 ConfigMap。另外由于该方案没有使用 Volcano 已提供的组件能力,导致缺乏诸如“ParentQueue 禁止提交任务”的队列管控能力。
3. 第二版层级队列的改造
在内部层级队列改造后,社区也提出了支持层级队列的框架,社区版本的改造使用了 Volcano 各组件的能力,基本支持了层级队列的各类需求,使用体验与可维护性更好。
社区对 Volcano 的三大组件:ControllerManager、Admission 与 Scheduler 都进行了队列层级的适配改造。
(1)在 ControllerManager 中,open
queue 与 close queue 时都新增了层级队列的验证逻辑。如果 open queue 是在 ParentQueue 已经 close 的状态下,会拒绝当前队列的 open;当前队列执行了 open 后,当前队列下所属的所有 LeafQueue 都会递归的执行 open 操作。Close queue 也类似,RootQueue 禁止 close 操作;当前的队列如果 close 的话,下面的 LeafQueue 也会递归进行 close 操作。
(2)在 Admission 中,新增了对 Queue 与 Job 的合规性检查。在 Job 层面,与 Yarn 的规则类似,禁止向 ParentQueue 提交任务,所有的任务只能提交到 LeafQueue 上。在 Queue 层面,create 的时候检查 ParentQueue 是否已经没有任务执行了,只有当 ParentQueue 没有任务执行时,才可以对下属的 Queue 操作,delete queue 也需要检查,确保不能直接 delete ParentQueue。
(3)首先介绍一下 Volcano Scheduler 的主要运行流程。Scheduler 中的 event handler 会监听事件并把相关数据存入缓存中。另有一个 Goroutine 每隔固定时间执行 OpenSession 方法,OpenSession 时会依次调用在 ConfigMap 中声明的 Plugins 的 OnSessionOpen 方法,用来更新 Plugin 本身的缓存信息,并将 Plugin 中的方法注册至 Actions 中。所有 Plugins 注册完成后,会依次执行 ConfigMap 中申明的 Actions。每个 Action 都能够执行一类动作,包括进行调度流程正常分配的 Allocate Action、进行队列内抢占和 Job 内抢占的 Permit Action、进行队列间资源再平衡的 Reclaim Action 等。将申明的 Actions 都执行完毕后,会通过 CloseSession 方法依次调用各 Plugins 中的 OnSessionClose 方法,进行该轮 Session 的收尾操作。CloseSession 执行完毕后,Scheduler 的一轮 Session 就成功完成了。
层级队列在 Scheduler 中的改造主要是集中在 Capacity Plugin 中。Capacity 是社区最新推出的调度插件,允许用户设置 Deserved、Capability、Guarantee 等参数,实现了多租户场景下的弹性资源调度策略。
Capacity 会在 OpenSession 阶段判断当前是否开启了层级队列,若开启则进入层级队列的处理逻辑。Capacity对层级队列的处理主要流程如下:
首先,获取到当前队列的快照信息后,进行一次 Capacity 内层级队列相关 cache 的递归更新。
接着,Capacity 会对当前 Session 快照中所有的 Jobs 进行资源使用与需求分析,并将资源信息累加至各自 Job 所属的队列,完成对 LeafQueue 的资源情况更新,LeafQueue 的资源情况会向它所属的所有 ParentQueue 进行更新。
然后,待所有 Jobs 状态更新完成后,会从 RootQueue 向 LeafQueue 进行一次层级资源情况的检查。检查无误后会更新当前的队列的指标并更新 QueueMetrics。
最后,Capacity 向 Actions 进行方法注册,
在
队列间资源回收 Reclaim、队列内 Jobs 与 Jobs 内 Task 抢占 Preempt、是否能够分配的 Allocatable、筛选待调度任务进入待调度队列的 enqueue 等 Actions 中,Capacity 会提供层级队列、非层级队列两套不同的 Plugin 方法。
在 Session
Close 时,Capacity 会清除当前 cache 中的 totalResource、QueueOptions 等状态。
4. 其它改造
除上述改造外,我们还做了一系列大数据迁移云原生场景的适配:
(1)
由于在离线场景中没有需要完全预留资源的场景,我们删除了 Capacity
Plugin 中对 Guarantee 的定义。另外,为了与 Yarn 的语义保持一致,我们将 Capacity 中的 Deserve、Capability 等概念替换成了 MinCapacity 和 MaxCapacity。主要目的是为了统一离线集群与K8s集群在调度层面的语义。
(2)
大数据亲和/反亲和是为了解决大数据任务一批 Pod 打在同一台节点导致资源热点的问题。我们在社区 Task-topology Plugin 的基础上,沿用 bucket 与 Node 映射的逻辑,新增了对大数据任务场景特殊优化的 Bigdata-topology Plugin。
(3)新增了队列优先级的概念,影响队列中任务的分配与驱逐的顺序;新增了对队列资源百分比配置的支持,添加队列的时候支持用百分比表示,维护队列资源时更方便。
(4)
对 App Summary 进行了一些改造,针对 Spark 与 Flink 任务,添加了 Job 和 Pod 级别的信息统计,并接入元仓汇总成报表进行展示。
03
超配框架的搭建
1. 超配背景
离线主集群经过各类优化,以及离线超配组件 Amiya 的超配能力,资源利用率已经常年稳定在较高水位。而大数据离线集群任务刚迁移到 K8s 时,300+ 台节点 7 日平均内存利用率从离线集群的 62.9% 降低到 27.6%,7 日 CPU 利用率从离线集群的 87.8% 降低到了 28.3%。
允许大规模节点迁移的前提,是迁移前后的资源利用率处于相近水平,因此我们对超配框架进行了云原生版本的适配,形成了 Amiyad 组件。
2. 超配框架
Amiyad Master/Amyad/Volcano 共同组成了 B 站的离线集群云原生超配框架。新增组件主要分为两个模块,采用 Master/Worker 架构。
-
Master 为 Amiyad
Master,每个集群部署一个,主要负责 Amiyad 的健康检查与资源动态超配,以及整体超配比例的最终决策。
-
Worker 为 Amiyad,通过 DaemonSet 每台节点部署一个,主要负责资源运行状态收集、资源管控、容器运行时管控。
我们还为集群新增了一种自定义的扩展资源类型,名为 AmiyadExtendedResource,我们所有的资源超配操作都针对该资源进行,Volcano 的资源调度也会依据这个资源视图。
3. 新增自定义资源
上图左侧是 NodeMetrics
CRD,这是 Amiyad Master 与 Amiyad 信息交互的媒介。它会近实时的收集节点中 CPU、Memory、磁盘等资源的信息,通过 Amiyad 更新至自己节点的 NodeMetrics 中,Amiyad Master 能感知并获取到当前节点最新的资源信息。
上图右侧是 AmiyadExtendedResource 的 Node Yaml,能够看到在提供 91Core 的机器上,通过 Amiyad 超配,Volcano 可以对该 Node 进行共 105Core 的 Pod 调度,Memory 同理。
4. Amiyad
(1)StateStore Manager,它负责从多渠道获取节点的当前资源情况,并把这些信息汇总到 Amiyad 的缓存中,供其他 managers 使用。
(2)
Resource Manager,该组件主要有两个功能:一是超配功能,负责根据 ResourceInfo 计算出节点当前时刻的超配的提案(Proposal),通过 NodeMetrics CRD 上报给 Amiyad Master,供 Master 进行最终的超配决策;二是驱逐功能,根据 ResourceInfo 计算出当前节点各种资源处于什么状态,把当前状态映射成不同的资源等级,采取不同的处理策略。
(3)
RuntimeHook Manager,该组件主要实现了 Runtime-proxy 和 NRI 两种接口的 Hook 处理。由于采用 AmiyadExtendedResource 作为最终的分配资源,资源下发时需要通过 Hook 将 Cgroup 中的某些资源信息修改为 AmiyadExtendedResource 中的值。
真实物理资源的使用量超出预期的情况下,可能导致节点层级的不稳定,从而导致节点上所有任务出现非预期问题,所以 Amiyad 的重要功能之一就是通过各种方式保证节点的稳定使用。
Amiyad 驱逐模块考虑的资源包含 CPU、Memory 以及 Disk,考虑的因素包括当前资源的使用率、使用趋势、变化程度、资源上次状态等,通过这些因素推断各资源现在处于正常、预警、严重或是紧急状态。这些状态又对应了不同的处理方式。比如,在预警状态时,给节点发送 Taint,使节点不继续新增预警资源为主资源的任务;在 Memory 处于严重状态时,Amiyad 会对低优任务进行驱逐;甚至在紧急状态中,必要时驱逐高优任务来保证节点稳定性。
5. Amiyad Master
Amiyad Master 主要分为 2 个模块:
(1)巡检模块,会根据 Amiyad 心跳开启/关闭 Volcano 在当前节点的调度,维护 Amiyad 黑白名单调度状态。
(2)Resource Reconciler 模块,由 NodeUpdate 与 Amiyad 设置的 Annotation 共同唤醒该 Reconciler,唤醒后拉取该节点对应的 NodeMetrics CRD,读取当前节点的资源状态、当前超配 Proposal 以及申明的超配 Policy。
其中超配 Policy 采用可插拔的设计,每台 Amiyad 在 NodeMetrics CRD 声明自己当前的超配 policy,经过超配 policy 的计算后,获得最终的超配比值。
以动态超配 policy 为例,它会在 Reconciler 中根据当前集群全局压力信息再次调整 Amiyad 上报的超配比 Proposal,作为最终的超配比并计算当前最终的超配资源量。最终的超配资源量会通过 Node
Update 将 AmiyadExtendedResource 更新至 Node Yaml 的 Resource 中。
6. Extended Resource
Amiyad 采用 AmiyadExtendedResource 进行超配,主要涉及到对 Pod、PodGroup 以及 Node 的资源修改:
(1)Pod 采用 Volcano
Admission 的 Pod Mutate 机制,Pod
Mutate 会拦截所有 Volcano 调度器管控下的 Pod
Create 行为,并将原生资源的 value 赋值给 Extended
Resource。这样做的好处是对计算引擎透明,不需要计算引擎侧对 Extended Resource 进行适配改造。
(2)PodGroup 由 B 站自研的 Spark 管理组件 Slate 进行统一管控,通过修改 PodGroup 模版文件即可完成 ExtendedResource 的资源替换。
(3)Node 中的 ExtendedResource 是 Amiyad 与 Amiyad Master 根据节点资源与集群资源的使用情况综合考虑后确定,并最终由 Amiyad Master 更新的。
这些 ExtendedResource 在 Volcano Scheduler 中记录时,都会通过 Volcano 记录资源信息的 New 方法(resourceInfo.newResource)方法转换为 cache 中的 ResourceInfo。在 Volcano Scheduler 的整体视图中,Node 的资源信息已经是超配后的资源量,Pod 和 PodGroup 的资源量跟之前还是一致的,这就达到了资源超配的目的。
7. 超配的收益
与 Amiyad 在 K8s 集群上线前相比,静态超配 1.1 倍后,K8s 集群的 7 日平均内存利用率由 27.6% 提升到了 37.7%,7 日平均 CPU 利用率由 28.3% 提升到了 33.3%,效果显著,任务7日平均驱逐率仅为 0.5%。
04
未来展望
后续需要持续优化 Volcano 适配大数据场景的适配工作,
完善 Yarn 与 Volcano 在大数据场景下语义的一致性。更进一步,需要
持续优化 Volcano 批量调度的性能。
其次是需要持续优化 Amiyad 超配能力。与离线相匹配的资源率用率、稳定的驱逐率是比较重要的,
这也是支持不断往云原生上迁移的基础。同时提供
更全面的大数据任务 QoS 支持,
目前只支持高优任务与低优任务的驱逐判断,后续不同的 QoS 影响会扩展到例如 Cgroup 等其它地方,最终实现 K8s 集群调度全链路的 QoS 支持。