

数据中心运行的混部服务可以分为在线服务和离线任务两大类,它们具有不同的探索资源使用特征。
在线服务是实践指那些长时间运行、对时延非常敏感的离线服务 ,如电商、混部游戏等,探索在线服务的实践资源利用率存在明显的波峰波谷现象 ,平均利用率较低 。离线离线任务是混部指那些运行周期短,源码库有容错性 ,探索对实时性要求低的实践服务,如数据转换、离线模型训练等 ,混部离线任务在执行过程中资源利用率很高。探索
在混部之前,在线和离线都是分开独立部署 ,机器不共享 ,无法形成有效的资源互补,这导致数据中心整体资源利用率不高,却要不断购买新机器 ,造成了资源浪费 。
1.2 混部技术定义
通过混部技术 ,高防服务器我们可以将在线和离线部署到同一台物理机上,形成资源互补,提升物理机的资源利用率,降低成本 。混部技术最早由谷歌在2015年提出,经过多年的发展 ,混部技术已经趋于成熟 ,目前业内利用混部技术可以将数据中心的CPU利用率提升至40%左右 。
vivo在2020年开始调研混部技术 ,2023年混部平台投入生产,目前我们已经将部分混部集群的CPU利用率提升至25%(最新已达30%)左右。相较业界标杆这还有一定的服务器租用差距 ,但随着混部规模的扩大,我们将挑战更高的目标。
二 、在离线混部平台实践2.1 混部平台产品能力
混部平台必须具备两个产品能力:
第一 、强大的调度 、隔离能力第二 、完善的监控、运维能力强大的调度能力解决了,我们如何将离线任务高效、合理的香港云服务器调度到在线服务所在的物理机上 。而强大的隔离能力保障了在线服务的质量不受离线任务干扰。完善的监控和运维能力则可以让我们洞悉整个混部平台的运行情况,及时发现潜在风险,帮助运维人员更高效的完成系统和业务的运维工作,保障集群的高稳定性。
2.2 混部差异化资源视图
混部首先要解决的一个问题是亿华云离线使用哪一部分资源 。
在vivo混部系统中在线和离线看到的资源视图是不同的 :
在线可用资源为 整机资源离线可用资源为 整机资源减去 在线实际使用的资源同时为了避免整机负载太高影响系统的稳定性 ,我们设置一个安全水位线,用于调节离线可用资源大小。
2.3 混部QoS等级
为了保障混部系统的slo,我们将服务分为三个等级:高、中,低 。
不同等级的服务对物理资源如:CPU、建站模板内存 使用时有不同的优先级 。高优先级服务支持绑定CPU模式,适用对延时非常敏感的在线服务。一般的在线服务可设置为中优先级 。离线任务设置为低优先级 ,通过这样的等级划分,我们很好的实现在线对离线的资源压制和隔离,保障了在线服务质量。
2.4 混部核心组件架构
我们所有的混部组件都是以插件方式独立运行 ,对原生K8s无侵入。我们实现了一个混部调度器,在线和离线统一使用这个调度器 ,避免了多调度器资源账本冲突的问题。
每台物理机上都会部署一个混部agent ,它可以实时采集容器资源使用数据,并根据安全水位线对离线任务进行压制、驱逐等操作 。
内核层面我们使用了龙蜥OS,它具备强大的资源隔离能力,可以帮助我们更好的隔离在线、离线资源使用,保障在线服务质量。
2.5 混部组件功能
我们把混部组件分为管控组件和单机组件两大类。
管控组件主要负责调度和控制,根据vivo业务使用场景,我们对调度器做了一些增强 ,提供了numa感知、负载感知,热点打散 ,批量调度等能力 。
混部控制器主要提供了一些配置管理能力 :如资源画像统计、node slo配置、node扩展资源变更等。
2.6 混部可视化监控
我们为混部建立一套完整的可视化监控体系。
针对在线服务我们提供了:容器资源使用指标 ,受离线干扰指标、业务混部收益指标等监控能力 。
针对离线任务,我们提供了离线可用资源 、任务异常状态等监控能力。
在平台层面上我们提供了节点、内核,核心组件的监控 ,通过这些监控可及时发现平台潜在的风险。
2.7 混部平台运维
为了简化运维操作 ,提升运维效率 ,我们对混部集群搭建和节点扩缩容操作进行了白屏化改造,开发了资源池管理功能,简化了物理机接入流程,运维效率大幅提升。
在运维平台上运维人员可以快速调整混部隔离、水位线等参数,如果发现在线服务受到干扰,运维人员可以一键关闭混部,驱逐离线任务,保障在线服务质量。
2.8 问题与挑战2.8.1 apiServer拆分

通过混部产品能力的建设 ,我们很好的实现了容器混部能力,但是在实践中我们还是遇到一些新的挑战:相对于普通K8s集群,混部集群中运行着更多的容器,而且离线任务由于生命周期短,容器创建销毁更为频繁,这对K8s apiServer 产生了很大的压力 。
所以我们拆分了apiServer ,离线任务使用独立的apiServer ,保障了集群apiServer 负载一直处于一个安全水平。
2.8.2 监控架构优化

同样混部之后由于采集了更多的监控指标 ,导致Prometheus内存消耗过多 ,无法满足平台指标 采集需求。针对这个问题,我们优化了监控架构,将在线和离线监控组件分开部署,离线改用性能更好的vmagent,通过这个优化,监控组件内存消耗减少到原来的十分之一。
2.9 利用率提升
混部初期虽然集群CPU利用率有所提升,但是还是没有达到我们的预期,主要原因有:
一、部分低配置机器资源本身较少。二、Java 类应用堆会固定占用大量内存,导致可提供给离线使用内存资源不足。针对这些问题,我们开发了定时调整安全水位线功能 ,在业务低峰期上调安全水位线,释放更多的资源给离线使用 。通过一系列的优化手段 ,我们将其中一个混部集群的CPU利用率由13%提升到了25%左右,几乎翻倍,混部效果得到了有效的验证 。

在大方向的技术选型上,我们选择了 Spark on K8s,在业内,也有一些公司采用了 YARN on K8s的方案。我们也对这两种方案进行过对比 。
从业务适用性来说,YARN on K8s 是通用的,可以兼容Hive、Spark、Flink这些引擎,它不需要频繁创建Nodemanager pod,对K8s的压力比较小 。这些都是它的优点 ,但另一方面 ,Nodemanager ESS服务是对磁盘有容量和读写性能要求的 ,混部机器磁盘一般难以满足 。所以我们要支持不同引擎的remote shuffle service。
如果计算引擎有不同的版本 ,那么RSS也要支持不同版本 ,比如Spark2,Spark3 。如果你有不同的引擎,不同的版本 ,很可能一种RSS还满足不了需求 。另外Nodemanager需要根据K8s混部节点的剩余资源,动态调整可用的vcore和内存 ,所以还需要一个额外的组件来做这个事情,这需要较高的改造成本。在资源利用上 ,NM的资源粒度相对大,自身也会占用一些资源,存在一定的浪费。在资源紧张的情况下 ,Nodemanager作为整体被驱逐,会影响多个任务。这些是YARN on K8s的劣势 。
作为对比,Spark on K8s 劣势有哪些?首先这个特性在Spark 3.1以上版本才正式可用 。Spark on K8s由于会频繁的创建、查询、销毁大量的executor pod ,对K8s的调度能力以及master节点会造成比较大的压力。另一方面,它的优势在于只需要能支持spark3.X的RSS,这有较多的开源产品可选择 。而且改造成本比较低 ,不需要额外的组件 。资源粒度小,更有利于充分利用集群资源 。在资源紧张时,会逐个pod进行驱逐 ,任务的稳定性会更高。
两方案各有优劣势,为什么我们选择了Spark on K8s?一方面因为Spark3.X是vivo当前及未来2~3年的主流离线引擎 ,另一方面vivo内部对K8s研发比较深入 ,能有力支持我们 。基于以上原因 ,我们最终决定使用spark on K8s
3.2 三步走战略
确定了方案选型,那在vivo我们是如何推进spark on K8s大规模的应用落地呢 ?回顾总结我们走过的路,可以大致归纳为三步走的战略。
第一 ,是任务跑通跑顺的初期阶段第二,是任务跑稳、跑稳的中期阶段最后 ,是任务跑得智能的成熟阶段接下来的内容 ,我们将对每个阶段展开细说 。
3.2.1 任务跑通跑顺

在任务跑通、跑顺的第一阶段 ,我们要解决的是怎么将任务提交K8s集群,同时要求易用性 、便利性方面能够达到与on YARN 一致的用户体验。将我们最后采用的方案架构简化一下 ,就如同这张图所示。
首先 ,为了降低任务提交的复杂性、避免用户改造任务的成本。我们在任务调度管理平台做到了对原有Spark任务的兼容,通过vivo内部的容器开放API-这个代理层 ,我们不需要维护额外的K8s client环境,就可以轻松实现任务提交 ,提交后也能近实时获取任务的状态和日志信息。
另外一个关键点是,我们选用了Spark Operator作为Spark任务容器化的方案。Spark Operator是谷歌基于K8s Operator模式开发的一款的工具,用于通过声明式的方式向K8s集群提交Spark作业。
Spark Operator的方式还有其他优点 :
Operator方式对K8s更友好 ,支持更灵活 、更全面的配置项使用上更简单易用内置Metrics,有利于我们做集中管理要达到阶段一的目标,让任务跑通、跑顺。我们主要克服了哪些关键问题和挑战 ?

第一个是日志查看,因为Spark Operator方式并没有提供已结束作业的日志查看方式,包括driver和executor日志 。在Driver侧,我们通过定期请求容器开放API ,能准实时地获取Driver Pod状态与日志 。在Executor侧,我们参考了on yarn的方式,Executor Pod结束后,日志上传HDFS,与YARN日志聚合类似。
另一方面 ,我们也在Spark HistoryServer做了二次开发工作,增加了on K8s方式的日志查看接口。用户查看已完成的Executor日志时 ,不再请求JobHistory Server,而是请求Spark HistoryServer接口。在体验上做到了基本与yarn一致。
在混部K8s集群,我们也做了三方面能力的加强 。
一是,确保分配能力能支持离线任务频繁建删pod的需求 ,在优化后我们离线Pod分配能力达到数百pod/秒。二是,在K8s侧提升了spark内部的Driver优先级,确保了在驱逐时Driver稳定性高于Executor。最后一个是,发现并修复了spark-operator的一个bug ,这个bu是Operator在多副本部署时,slave副本webhook处理有一点概率出现pod 找不到的问题 。3.2.2 任务跑稳跑准

在第二阶段 ,我们要保障的是任务跑稳,数据跑准,因此,我们有两个关键的举措:
大规模双跑,目的是确保Spark任务迁移到K8s集群后是兼容的,任务成功率有保障;任务执行时长是稳定的,不会明显变慢;数据是准确的,跟on YARN保持一致性。为此,我们需要对任务进行on YARN和on K8s两种模式下的双跑测试,我们分批次总共进行了7轮双跑 ,覆盖了2万+的线上正式任务 。最终也取得了我们想要的结果 :我们双跑最终达成的任务成功率超过了99.5% ,绝大部分的任务在两种模式下的时长波动在25%以内 ,数据一致性是100%。混部集群的压力联调 ,目的是确保混部集群的承载容量能够支撑大规模的离线任务调度,通过模拟未来1年的任务量来给混部集群做压力测试,充分发现和检测K8s集群可能存在的性能问题。最终 ,通过我们多轮压测和问题解决 ,我们在单个K8s集群能够支撑150+同时运行的Spark任务,1万+同时在运行的Pod数量。
在第二阶段 ,我们主要面临三个方面的问题和挑战