当前位置: 首页 > 科技观察

字节跳动YARN云原生进化实践

时间:2023-03-14 19:44:38 科技观察

一、进化背景字节跳动(以下简称字节跳动)内部线下业务规模巨大,每天有数十万节点在线运行,日任务量达百万级。每天的资源使用量达到千万核级别。在如此庞大的计算规模下,为了高效处理任务,提高资源流动效率,调度系统扮演着非常重要的角色。如上图所示,我们可以清楚的看到字节内部的调度架构分为两大部分——离线调度系统和在线调度系统。离线调度系统主要负责离线资源管理和离线任务调度,在线调度系统主要负责在线资源管理和在线任务调度。离线调度系统基于YARN实现,主要包括资源管理器(RM)和节点管理器(NM)两个组件,分别负责资源调度和容器运行时管理。字节跳动在YARN的基础上做了大量的特性丰富和优化工作,针对不同的场景实现了不同的调度器,例如:BatchScheduler、GangScheduler等。基于Kubernetes生态,优化了在线调度系统alot以支持Byte内的各种在线服务。为了提高字节内部的整体资源利用率,我们还探索了混合部分技术。主要思路是在在线节点上同时部署Kubelet和NM服务。当在线节点相对空闲时,可以及时将空闲资源转移到离线服务中,从而可以大大提高整个数据中心的资源利用率。但随着公司业务规模的不断发展,这套系统也暴露出一些不足:一是线下属于两套系统,一些重大事件场景需要通过运维进行线下资源转换。重量大,转换周期长;其次,目前的混合部门架构只是在部分节点上同时部署了NM和Kubeletagents,在资源利用率上还有很大的提升空间;最后,离线有两个独立的系统。配额平台、机器运维等无法复用,大数据运营无法享受到云原生的各种好处,比如:资源池化、更好的单机隔离特性等。总结起来,核心诉求有3个字节内部:重大事件场景(春节/双11等),线下资源需要能够高效灵活的相互转换;整个数据中心的利用率需要更全面、更充分的提升,进一步降本增效;在离线资源池中,统一进行Quota控制、调度、运维、机器运维。为了实现上述诉求,我们进行了一些思考和探索。其中一个解决方案是:离线作业是否可以直接迁移到Kubernetes上?即:大数据生态中的各个计算引擎(包括:Spark、Flink等)都进行了深度改造以适应Kubernetes。在探索过程中,发现该方法存在比较大的缺陷,主要有以下三点:对计算引擎的侵入较深,需要在计算引擎侧进行大量修改以支持系统中的各种特性。原始纱线;生产环境的运行(100)如何从YARN顺利迁移到Kubernetes也是一个比较大的问题;特别是一些比较老的计算引擎,比如MapReduce,目前处于维护状态,无法通过大改造迁移。基于以上考虑,我们提出了一个全新的解决方案——Yodel,Yodel的全称是YARNonG?del(G?del是公司内部Kubernetes的增强版,增强了APIServer、G?delscheduler和底层runtime),是字节跳动提出的HadoopYARN云原生演化实践方案,通过Yodel将公司大数据业务(Spark、Flink等)和训练业务(Primus)平滑迁移到Kubernetes,实现了线下资源的统一2.解决方案下面将从Yodel整体架构RemoteGodel的六个方面详细介绍我们的解决方案Scheduler(RGS)服务,RemoteKubeleteService(RKS)服务,持久化服务,平滑迁移,重要优化。2.1Yodel整体架构Yodel基于YARN实现,增加了ZK/ETCD/KVStateStore、RemoteGodelScheduler、RemoteKubeletService服务。ZK/ETCD/KVStateStore主要用于持久化存储,RemoteGodelScheduler维护资源请求并与APIServer交互,将调度能力统一到GodelScheduler;RemoteKubeletService实现了YARNNM的所有接口,在对用户和作业透明的前提下,将NM的容器管理能力平滑下放到Kubelet。Yodel整体架构图从上面的架构图可以清楚的看到Yodel的整体架构。图中蓝色组件是经过适配改造的组件,蓝色标红的组件是新组件,黄色组件是G?del生态系统组件,关于新组件:ZK/ETCD/KVStateStore:支持持久化将元数据信息集群到ZK、ETCD、KV等持久化存储中,并可以方便地通过APIServer查询和更新相关数据;RemoteGodelScheduler:维护集群中所有任务的资源请求。通过该服务将任务的资源请求转化为Pod写入APIServer。同时与APIServer交互,获取被调度的Pod,最终将调度能力下沉到底层的GodelScheduler;RemoteKubeletService:实现了原有YARN中NM的所有接口,如启动容器、停止容器、获取容器状态等接口。通过这个服务容器启动从NM切换到Kubelet,最终将容器运行时的管理下沉到底层的Kubelet。下面介绍Yodel架构下提交并运行离线任务的过程:用户从开发机或任务托管平台向集群提交任务;当任务验证完成后,YodelRM会创建一个新的App对象并持久化到APIServer;YodelRM创建AMPod并写入APIServer,等待底层调度器调度;YodelRM接收到调度好的AMPod,并进行相关的改造操作;YodelRM将相关启动信息丰富到AMPod中,通过KubeletPatchtoAPIServer拉起相关流程;AM启动成功后,主动心跳向YodelRM申请资源;YodelRM收到任务的资源请求后,通过RGS服务将资源请求转化为Pod对象或PodGroup对象,写入APIServer;底层调度器观察到相关对象后,按照一定的策略进行调度。同时,YodelRM也会及时收看预定的Pod;YodelRM会将调度好的Pod转化为Container,并随心跳返回给对应的AM;AM收到调度好的Container后,会和YodelRM交互,启动相应的容器;YodelRM收到容器启动请求后,会通过RKS服务和Patch到APIServer,将容器启动所需的信息丰富到Pod对象中。KubeletWatch检测到要启动的Pod后,会启动Pod。Yodel架构Pod生命周期上面提到了Yodel架构下任务的启动过程。让我们看一下Pod的生命周期是什么样的。核心流程如上图所示:首先,AM会通过心跳申请资源;YodelRM收到资源请求后,会根据资源请求的资源数量和优先级创建Pod对象,写入APIServer。创建完成后,Pod对象处于Pending-Unscheduled状态,等待底层调度器调度;底层调度器监听新创建的Pod,按照一定的策略进行调度,调度完成后将调度结果写入APIServer。写入完成后,Pod对象的状态会变为Pending-Scheduled状态;YodelRMWatch会将调度好的Pod转换为Container,Pod对象的状态会变为Allocated状态;新分配的Container会按照心跳返回给AM。Container被对应的AM拿走后,Pod对象的状态会变为Acquired状态;AM获取容器后,会与YodelRM交互,开始运行;YodelRM收到容器拉取请求后,将容器启动所需的信息填充到Pod对象中,并打补丁到APIServer;KubeletWatch找到需要启动的Pod后,会启动相关进程,容器运行时由Kubelet维护。2.2RemoteGodelScheduler以下是调度模块的一个重要服务——RGS服务。从上图可以看出,RGS服务主要分为三部分:最上层是QuotaManager,负责Quota管理:QuotaManager部分是在YARN的基础上增强的。YARN中有队列的概念,但是队列只支持一种资源类型。Yodel对此进行了扩展,一个队列可以同时支持两种类型的资源——GuaranteedResource和Best-effortResource。单个队列支持两种资源类型后,可以显着简化用户的队列管理成本,更加人性化。保证资源:稳定的资源。使用GuaranteedResource的容器在正常情况下不会被抢占或驱逐;Best-effortResource:混合资源,由在线节点分配的暂时空闲的资源。资源会随着节点负载的增加而动态波动,使用Best-effortResource的容器可能会被抢占或驱逐;中间层是AllocateService,负责请求转换和状态维护:主要包括四个子服务,ConvertRequesteustToPodService负责将任务资源请求转换成Pod对象写入APIServer;ConvertPodToContainerService负责将调度好的Pod转换成Container返回给AM;UpdatePodStatusService负责及时更新Pod状态并持久化到APIServer;当任务结束时,及时删除APIServer中的相关对象。最底层是RemoteScheduler,负责调度和关键信息的持久化。通过上述服务的协调配合,Yodel可以做到:100%兼容Hadoop协议,用户无需做任何改动,即可将Yodel作为原有的YARN使用;支持GT和BE两种资源类型,方便上层用户使用平台使用。2.3RemoteKubeletService接下来介绍RKS服务。RKS部署在YodelRM内部,实现了YARNNM的所有接口,将NM的Container管理能力平滑下沉到Kubelet。主要由两部分组成:PatchPodService:主要负责在收到AMpullrequest后,将容器启动所需的信息丰富到Pod对象中,这些信息包括:容器ENV、HDFS自研列表、启动命令、ETC。;PodStatusUpdateService:该服务会及时从APIServer上监听到Pod的最新状态,并将状态返回给对应的AM。此外,为了补充NM上的运行体验,在底层以daemonset方式部署了一些其他服务。这些daemonsets补充了NM的能力,使得离线作业只需要升级hadoop依赖,容器不需要做太多改动就可以运行在Kubelet上。这些服务包括:LocalizationService:用于下载Pod需要的HDFS资源;LogServing:用于方便用户查看Pod日志;ShuffleService:主要是SparkShuffleService和MRShuffleService,这些ShuffleServices与NM进程解耦,单独部署,提供计算框架的Shuffle服务;MetricsCollector:用于收集离线Pod运行时各个维度的监控信息;webshel??l:方便用户通过web终端进入容器的shell,方便排查问题。看看一个容器是如何运行在Kubelet上的:修改了NMClientSDK,让AM在调用startContainer时可以直接连接RKS;RKS收到启动请求后,会将containerLaunchcontext等信息写入Pod和Patch到APIServer;KubeletWatch到离线的Pod后,会通过本机的LocalizationService下载Pod对应的HDFS资源;下载完成后,Kubelet会通过Containerd将对应的HDFS资源挂载到容器的Pod中,然后通过Containerd启动Pod;启动完成后,Kubelet会将Pod的状态更新回APIServer;RKSwatch改变Pod的状态后,会同步更新内存中Container的状态,然后等待AM心跳同步Container的最新状态。2.4持久化服务YARN架构通过ZKRMStateStore将元数据信息持久化到ZooKeeper,而Yodel架构实现了一个KVStateStore将元数据存储到APIServer。存储的元数据包括MetaData、Queue、Application、Appattempt和Pod。现在一个在线APIServer可以支持存储300个队列,2万个应用,10万次app尝试,支持30万个离线Pod同时运行。MetaData:集群元数据信息,集群默认配置等;Queue(~300/cluster):队列Quota、ACL信息等;Application(~2W/cluster):Name,User,State等;AppAttempt(~10W/集群):Name、User、State等;Pod(~30W/cluster):State、Annotation、ENV、HDFS自研列表、启动命令等。2.5平滑迁移每天有百万个任务在Bytes中运行。如何将作业从YARN架构平滑迁移到Yodel架构是一个很大的挑战。整体来说,我们使用ResLake来完成。首先介绍几个重点组件:WorkFlowHosting:作业托管平台,负责作业提交;ResLake:RMProxy,可以根据一定的策略将作业路由到不同的集群;配额平台:用于同步队列的配额信息;AutoMigration:负责从YARN集群下线的节点迁移到Yodel集群。ResLake在YARN集群和Yodel集群上都有同名队列,比如上图中的root.queueA和root.queueB。这两个集群上的队列具有相同的元数据信息。AutoMigration服务会不断将节点从YARN迁移到Yodel集群,迁移信息会同步到QuotaPlatform,QuotaPlatform会进一步同步queueQuota信息到Reslake。当作业托管平台将作业提交给ResLake时,ResLake会根据YARN/Yodel上队列的Quota信息来决定是将作业提交给YARN集群还是Yodel集群。随着机器不断向Yodel迁移,最终作业顺利迁移到Yodel集群。2.6重要优化在升级上线Yodel架构的过程中,我们也遇到了很多问题,我们也做了很多优化,主要包括性能优化和运行优化。2.6.1性能优化Recover阶段异步恢复Pod状态,减少mastercut时间(秒级):最初为了保证mastercut后cluster、queue、task各维度统计信息的准确性,Pod是同步恢复的,但是上线的时候发现恢复过程非常耗时。为此,通过优化,在保证各维度信息统计准确的前提下,异步恢复Pod状态,将切换到master的时间缩短到秒级;异步多线程运行Pod提高调度吞吐量(~2K/s):通过异步多线程方式将调度的Pod转换为Container后,调度吞吐量得到了显着提升。目前调度吞吐量可达每秒2000个Pod;PodName哈希优化有助于将底层存储写入延迟降低到1/100(百分之一):因为APIServer底层使用基于范围的KV存储。如果PodName是有序的,分区裂变会频繁发生,导致APIServer的相关处理延迟明显增加。通过优化PodName的hash,将Pod分散存储在不同的分区中,底层存储的写入延迟降低100倍;增强与APIServer的交互,优化JavaFabric8KubernetesClient:支持指数退避重试,增强APIServer的容错能力;List操作默认增加ResourceVersion参数,避免底层存储崩溃;将InformerResync设置为0,避免内存频繁拷贝导致OOM。2.6.2运行优化AM容器运行在单独的资源池中,独立的优先级不能被抢占:使用BE资源的容器存在抢占或驱逐的风险,AM作为任务的master失效将导致整个任务失败。为了避免这个问题,将AM容器运行在单独的资源池中,保证AM能够稳定运行,避免任务失败;支持镜像本地化约束,平均拉取速度提升1000倍左右:部分离线任务的镜像比较大,容器启动时不时拉取镜像耗时较长,会导致到更长的启动时间。为了解决这个问题,支持镜像本地化约束,让容器尽可能的调度到有镜像的节点上。功能上线后,容器平均速度提升1000倍;支持双栈节点和v6only节点运行在一个集群中:通过在同一个集群中混合运行双栈节点和v6only节点,显着降低运维成本,也有利于提高资源利用率;Shuffle数据远程写入,避免炸毁本地磁盘:Shuffle数据通常比较大,容易将本地磁盘填满,然后将shuffle数据写入远程,可以避免由于本地磁盘占满导致任务运行异常;引入大量的SSD和Nvme磁盘来加速作业的运行。3、上线收益字节内部上线了Yodel架构,带来了以下收益:高效的资源切换:在2022年元旦/春节期间实现了线下资源约50万核的分钟级传输,显着提升了线下资源的转化效率。为重大赛事场景的资源切换提供坚实的技术支持;利用率提升:NM和外围单机组件下线,降低Overhead,单机利用率提升2%;线下统一:线下资源充分池化,Quota管控、调度、运营、机器运维统一。4.未来规划RGS&RKS部署云原生接入服务发现支持容器化部署弹性扩展开源Yodel回馈社区