作者:来自 vivo 互联网服务器团队

本文依据甘青、黄荣杰老师在“2023 vivo开发者大会"现场演讲内容整顿而成。

随同 vivo 互联网业务的高速倒退,数据中心的规模不断扩大,老本问题日益突出。在离线混部技术能够在保障服务质量的同时,极大的晋升数据中心资源利用率,降低成本。混部技术波及任务调度、资源隔离、运维观测等一系列技术难题,本文将介绍 vivo 在混部技术方面的实际和摸索,为读者提供借鉴和参考

一、在离线混部技术背景

1.1 为什么混部

数据中心运行的服务能够分为在线服务和离线工作两大类,它们具备不同的资源应用特色。

在线服务是指那些长时间运行、对时延十分敏感的服务,如电商、游戏等,在线服务的资源利用率存在显著的波峰波谷景象,均匀利用率较低。离线工作是指那些运行周期短,有容错性,对实时性要求低的服务,如数据转换、模型训练等,离线工作在执行过程中资源利用率很高。

在混部之前,在线和离线都是离开独立部署,机器不共享,无奈造成无效的资源互补,这导致数据中心整体资源利用率不高,却要一直购买新机器,造成了资源节约。

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 弹性调度实际

3.1 计划选型

在大方向的技术选型上,咱们抉择了 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数量。

第二阶段,咱们次要面临三个方面的问题和挑战

首先是咱们须要为Spark抉择一个内部的shuffle服务,通过技术选型和比拟,咱们最终抉择开源的celeborn作为咱们的remote shuffle service组件。咱们通过对机型和参数的测试调优,使celeborn的性能达到咱们的预期需要。在大规模的利用场景中,咱们还发现了会存在大工作会阻塞小工作,导致shufle read变慢的问题,咱们对这种状况做了参数和代码上的优化,以后社区也针对shuffle read的问题有一些待优化的改良。另外celeborn进行了容器化部署,在晋升自动化运维能力的同时,也能够为混部集群提供额定的计算资源。

其次,在工作稳定性方面,咱们也解决了一系列的问题。

  1. 在双跑的时候,咱们发现有不少工作在on K8s模式下很容易OOM,这是因为在on YARN模式下申请的container内存大小,不止是由Spark工作自身的内存参数决定,还会被YARN的资源粒度参数所影响。所以这块要做一些适配对标工作。
  2. 在任务量比拟大的状况下,Spark operator的吞吐能力会遇到瓶颈,须要咱们将并发worker数量、队列的相干参数调大。
  3. CoreDNS因为Spark工作频繁的域名解释申请,导致压力增大,甚至可能影响在线服务。这个能够通过拜访ip而不是域名的形式来躲避,比方namenode节点、driver和executor。
  4. 横向扩大namespace,这样能够防止单namespace的瓶颈,也避免etcd呈现问题。
  5. 咱们K8s apiserver的压力随着任务量增长压力也会逐步增大,这会影响整个集群的稳定性。咱们次要通过优化Spark driver list pod接口、应用hostnetwork形式两个优化伎俩,无效升高了apiserver的压力。

最初要说的是数据一致性,关键点是要做到行级记录的MD5校验,发现有不统一的Case,咱们做到100%的剖析笼罩。排除了因为工夫戳随机函数等一些预期内的不统一,咱们发现并修复两种case会偶发导致不统一的问题:

  • celeborn Bug导致不统一,具体可参考CELEBORN-383解决
  • Java版本不统一导致的问题

3.2.3 工作跑得智能

第三阶段,咱们须要解决的问题是让工作跑得智能,怎么定义智能,我想用三个词来概括弹性、强壮、业务需要。这是咱们弹性调度的架构图,细节我就不讲了,这里我介绍下咱们的调度零碎重点反对的性能。

在弹性方面,咱们须要做到实时依据混部集群资源闲忙,智能提交至混部集群或者Hadoop集群。在后期咱们K8s集群的资源绝对Hadoop是小头,通过正当的水位线管制,避免大量工作同时调度到K8s导致饿死。

强壮,就是要保障工作的高可用。

咱们建设的能力包含:

  • 工作双跑胜利后再混部
  • 反对离线工作失败主动回滚到Hadoop集群执行
  • 反对用户自主决定工作是否可调度至K8s集群
  • 初期剔除重要外围工作、剔除不可重试工作

目标是在用户工作迁徙时做到让用户无感。

在满足业务需要方面,咱们反对优先调度本业务的离线工作, 优先满足业务部门的离线工作资源需要;反对只在指定时间段里调度离线工作,反对在出现异常状况下一键终止调度K8s。这些是为了确保在线服务的高可用性,罢黜在线业务的后顾之忧。

3.3 混部成果

克服了三步走过程中的磕磕碰碰,咱们终于能够将离线工作大规模混布到K8s混部集群了。然而咱们很快发现,混部集群的整体利用率并没有达到咱们的预期,次要有三方面的起因

  1. 初期的Spark工作有余,这个咱们通过放慢双跑,迁徙低版本的Spark工作,迁徙Hive SQL工作来解决。
  2. 在混部的时候,咱们也察看到,离线工作的pod cpu利用率其实没那么高。比方咱们申请一个核,通常只能利用0.6个核,存在节约的状况。咱们上线了CPU资源超分的能力,目前是动态的固定比例超分,通过这个措施,咱们能将pod的理论cpu利用率打到80%以上。
  3. 另外就是混部集群中的机器配置不均,局部机器cpu资源短缺,然而内存不够。咱们通过在调度侧管制可调度工作的资源粒度,尽量调度对内存资源需要较小的工作。

通过咱们在任务调度侧,以及之前甘青提到过的其余措施。混部集群利用率失去了进一步的晋升。

最初,我向大家同步下,以后咱们达成的混部成果

咱们目前可供调度的工作靠近2万个,这些工作每天调度的次数曾经超过了4万次。在凌晨的高峰期,咱们通过混部,能为离线工作额定减少2万核、50TB内存的计算资源。这个收益是相当可观的,咱们也心愿在将来的2到3年,将可调度的工作规模晋升到6万个,弹性资源可能为离线计算总资源奉献20%的份额。

通过持续深度推动在离线混部技术,咱们冀望可能为vivo增效降本工作继续地贡献力量。

以上就是本次分享的全部内容。