摘要:
Borg是Google的集群管理系统,管理着多个由成千上万台机器组成集群,上面运行着10万+的任务和上千种不同类型的应用。
它组合了诸如提交控制,高效的任务包装,过量提交,进程级性能隔离的机器共享等这些方法来获取高性能。它的一些运行时功能比如最小化错误恢复时间,降低关联失败可能性的调度策略等可支持高可用的应用。Borg通过给用户提供一种声明式的job描述语言(名叫服务集成),实时的job管理以及一些分析和模拟系统行为的工具来简化了用户的工作
本文介绍了Borg系统的架构和特点和重要的设计决策,一些策略选择的定量分析和运行十几年来的经验教训总结。
简介
Google的集群管理系统内部叫Borg,它负责提交,调度,开始,重启,管理全部运行在google上的应用。本文介绍了它是如何做的。
Borg提供了三个好处:(1)它隐藏了资源管理和错误处理的细节使得用户可以关注到应用开发本身上来;(2)支持高可用性和可靠性 (3)在成千上万机器上高效运行工作负载。Borg不是第一个类似的系统,但是他是少数几个可以在这么大的规模上运行且具有高弹性和完整性的。本文围绕这些主题,总结了我们运行borg十多年的一些定性的结论。用户视角
Borg的用户是google的开发者和跑google应用和服务的系统管理者。用户以job的形式提交borg工作,每个job由一个或者多个运行相同二进制程序的task组成。每个job跑在一个cell里,cell是管理一群机器集合的单元。下面描述了用户视角下的borg的主要功能。
2.1 工作类型
Borg上运行的主要有两种工作类型。一个是长时间运行绝不能挂掉,主要处理在线的用户请求,延迟在us或者ms级别,比如Gmail,Google docs和网页搜索和bigTable这样的内部基础设施。 另一种是需要从几秒钟到几天时间完成的批量任务,这类任务对短时间的性能波动并不敏感。工作负载混合不同的cell,这些cell根据租户的需求不同其上运行的主要的应用或者时间段也不同。批量任务不断的提交并完成,许多面向终端用户的服务都是按天运行的形式。Borg需要对这些情况都能处理。
过去的几年里,基于borg上面建立了许多应用框架,包括我们内部的MapReduce系统,FlumeJava,Millwheel和Pregel. 其中大部分使用一个controller来提交一个master job和一到多个worker jobs. 前两个和YARN的manager扮演的角色类似。我们的分布式存储系统GFS和后续的CFS,Bigtable和Megastore都是运行在Borg上。
本文把高优先级的job叫做生产类型(prod), 剩下的叫非生产类(non-prod).大多数长时间运行的服务是生成类的,大多数批量任务是非生产类。在一个典型cell里,prod类型分配了70%的CPU资源,实际占用了60%的CUP使用率;分配了55%的内存,实际用了85%的使用率。在分配和使用之间的差异会在5.5节介绍。
2.2 集群(cluster)和cell
Cell中的机器属于一个单一的集群,集群是通过光纤连接的高性能的数据中心。集群在一个单一的数据机房里,多个机房构成一个site. 一个集群通常有一个大的cell和多个用于测试和特殊用途的小cell。我们努力避免任何单点故障。
中等的cell大约1万台机器,有些更多。一个cell中的机器在许多纬度都是不同的:大小(CPU,RAM,硬盘,网卡), 处理器类型,性能和外部ip地址和flash存储。Borg在确定那个歌cell跑哪个job,分配资源,安装程序和依赖,维护健康状态和失败重启的时候,对用户屏蔽了这些差别。
2.3 Job和task
Job的属性包括名称,所有者和它拥有task的数量。Job可以指定它的task运行所需要的特定特征,比如处理器架构,OS版本和外部IP地址等。这些限制可以是硬件也可以是软件,软件限制更倾向于是偏爱而非要求。一个job的启动可以被推迟到之前的一个完成之后。一个job只跑在一个cell中。
每个task映射到一台机器的一个container的一组linux进程。绝大多数的borg工作都不在虚拟机(VM)上运行,因为我们不想付出虚拟化的代价。同时,系统在设计的时候我们在无虚拟化硬件支持的处理器上做了大量投入。
Task的属性包括其资源需求和它在一个job里面的索引。多数task的属性在一个job中都是相同的,但是也可以重载,比如提供特定task的命令行标记。每个资源(CPU核,RAM,硬盘空间,硬盘存取速度,TCP端口等)都是在细粒度独立指定的,我们不强加固定大小的桶或槽位。Borg程序都是静态链接的,并打成二进制包和数据文件,这样可以减少运行时环境依赖,其安装是由Borg组织完成的。
用户通过RPC来操作jobs,通常是命令行工具或者我们的管理系统。多数job的描述都是通过声明式配置语言(BCL)写成.这是GCL的变体,它生成protobuf文件,并扩展了一些borg专用的关键字。 GCL提供lambda函数用于应用调整他们的环境配置,上万的BCL文件常长度有1千行以上,我们已经积累了千万行BCL语句。Borg job配置文件和Aurora配置文件类似。
下图描述了job和task的生命周期。
用户可以通过给Borg推送新的job配置来改变正在运行的job的task的属性,并更新task到新的配置。这是轻量级的非原子的事务操作,可以在commit之前很容易的回滚。更新以一种滚动给的方式进行,对于任务中断(重新调度或者抢占)的数量可以做限制,任何可能引起更多中断的变更都会被跳过。
一些task更新(比如二进制文件更新)总是需要重新启动;一些(比如增加资源需求或者变化约束)可能导致当前的机器不适应task的需求,由此引发任务停止和重调度;还有一些(比如改变优先级)总是可以在不重启或移动任务的情况下执行。
Task在被SIGKILL信号抢占之前可以通过Unix SIGTERM信号通知,这样他们有时间做一些清理工作,保存状态,结束当前在执行的请求拒绝新的请求。如果抢占者设置了延迟限制,那么通知会变少。实际上,80%的时间会发通知。
2.4 分配器(Allocs)
alloc是机器上一组预留的资源集合,一个或多个task可以在其中运行;无论他们是否运行,资源都会被保留。alloc可以用来:设置未来的任务所需要的资源;在停止一个任务和启动一个任务之间依然保留资源;汇集一个机器上不同job的task,比如一个web server实例和一个把url请求从本地复制到分布式文件系统的logsaver任务。alloc的资源处理方式和机器的资源处理方式类似;多个任务运行在一个共享的资源当中。如果一个alloc必须重新放到另外的机器上,,它上面跑的任务也会跟着它重新调度。
一个alloc set类似一个job,是一组在多个机器上预留资源的alloc集合。一旦一个alloc set被创建,一或者多个job就可以提交给它运行。我们用task来表示alloc或者高层的task(alloc之外的),job来表示一个job或者alloc set。
2.5 优先级,预算和准入控制
当超过容量的task出现后会发生什么?我们的解决方案是优先级和预算。
每个job有一个优先级,正整数。高优先级的会比低的获得更多的资源,甚至包括kill后者。Borg定义了不重叠的优先级区间给不同的用户,优先级(从高到低)包括:管理级,生产级,批量级,测试级。本文中,prod jobs处于管理级和生产级。
尽管一个被抢占的任务可以被重新调度到cell的别处,但是高优先级的抢了稍低的,稍低的强了更低的,导致连锁抢占的情况发生。为了消除这种情况,我们不允许生产级的task相互抢占。细粒度的优先级在另一个场景下也有用,比如Mapreduce的master比worker拥有稍高的优先级,这样增加了可靠性。
优先级表达了job的重要性.预算用于决定应该调度哪个job。预算表示为一段时间内(典型是一个月)在给定优先级下的资源量的向量(CPU,RAM,disk等)。这个量说明了一个job在一段时间内能请求的最大的资源(比如从现在到7月在cellxx里面以prod优先级需要20Tib的RAM)。预算检查是准入控制,而非调度的一部分,预算不够的job会在提交之前马上拒绝。
高优先级的预算的费用要高于比低优先级的预算。生产级的预算受限于cell内的资源总量,所以用户提交了符合预算的生产级job就可以预期它运行。尽管我们鼓励用户按实际需要购买预算,但是很多人考虑到未来流量的增加还是会多买一些。我们通过多卖出低优先级的预算来应对这种情况:每个用户都拥有0级优先级的无限的预算,当然实际上由于资源的超额认购这种假设很难实际操作。低优先级的job可以被提交但是会由于资源不足处于pending(未被调度 unscheduled)状态.
预算分配是在borg之外进行的,和我们的物理容量规划密切关联,规划的结果反映了不同数据中心的预算的价格和可用性。用户的job只在本优先级有足够的资源的时候才会被提交,这减少了主导资源公平(Dominant Resource Fairness)策略的需要。
Borg可以给一部分特殊用户特别的权利,比如允许管理员删除或者变更cell的任何job,运行用户使用受限的内核功能比如禁用资源评估。
2.6 命名和管理
只生成和部署tasks是不够的,服务的客户端和其他系统需要能找到它们,即使是被重新部署到新的机器的时候。Borg使用稳定BNS(Borg name service)服务来实现这一点,其为每个task命名,其中包括了cell的名字,job的名字和task的序号。Borg把task的主机和端口信息写到高一致性可靠性的Chubby文件里面,用于我们的RPC系统寻找task. BNS名称构成了task的DNS名的基础,比如在cell:cc中的用户ubar的一个job:jfoo的第五十个task,可以通过域名50.jfoo.ubar.cc.borg.google.com来访问。
Borg也把job的大小和task的健康信息的变动写入Chubby,这样负载均衡可以考虑请求的路由。
Borg上运行的每个task都带一个内置的HTTP server,可以查看task的健康情况和上千个性能指标(比如PRC时间). Borg管理着健康检查的url,如果task没有响应或者返回了错误的http状态码,就会重启task. 其他数据是通过报表和服务告警来跟踪管理。
Sigma服务提供了一个基于web的用户界面,用户可以检查他们job的状态,特定的cell,一直到特定的job,task的资源情况,日志,执行历史和最终结果。我们的系统产生大量的日志,这些日志会保存一段时间方便问题追查,为了防止写满硬盘会定期清理。如果一个job停止了,Borg会提供一个“why pending?”的注解,指导如何修改job的资源要求来更好的适配cell.我们发布符合要求的资源模版来帮助更容易的调度。
Borg把所有的job提交和task事件,包括每个task的资源使用情况都记录在分布式只读数据库Infrastore里,可以通过Dremel用类SQL接口进行查询。这些数据用于用户记账,debug系统失败和长期的容量规划。也提供给Google集群负载的跟踪。
所有上述功能帮助用户理解和debug Borg和他们的jobs,也使我们一个人就可以运维成千上万的机器。
- Borg架构
Borg cell由多台机器组成,其逻辑的中心叫做borgmaster, 每台机器上都有一个agent 叫做Borglet. 所有的组件都是用C++编写。
3.1 Borgmaster
Borgmaster由两个进程组成,一个borgmaster主进程,一个是独立的schduler进程。主进程处理客户端的RPC请求,执行状态更新(比如创建一个job)或者查询(比如查找一个job).它管理系统中所有实体的状态(比如机器,task,alloc等),和Borglet通讯并提供给Sigma一个web界面。
Borgmaster逻辑上是一个进程但实际上有5个备份。每个备份都在内存中存有cell的状态,并且本地保存,属于分布式的Paxos-based高可靠性的存储。每个cell中有一个被选出来的master,既作为Paxos的leader,也左右状态的控制者,处理所有改变cell状态的操作,比如提交一个job,中止一个task. Cell创建的时候或者一个master挂了的时候,一个新master就基于paxos被选出来;它获取chubby锁,所以其他系统能找到它。选一个新master通常需要10秒,但是在一个大cell中会用1分钟,因为有些内存状态需要重建。当一个备份从灾难中恢复之后,会从其他备份中动态同步最新状态。
Borgmaster有checkpoint信息存在Paxos store里。Checkpoint用于恢复过去任意时间点的状态;极端条件下的人工恢复;为查询事件建立日志和线下模拟等。
有一个高仿真master模拟器叫做Fauxmaster,包含全部的master代码,带有borglet的stub接口,可以读取checkpoint文件,可以接受PRC请求。我们用它来debug错误,和模拟的borglets交互。用户可以真实的回放过去的系统状态变化。Fauxmaster也有助于容量规划和检查变更之前的配置。
3.2 调度(Scheduling)
当一个job被提交,borgmaster把job持久化存储到Paxos store中,然后把job的tasks放到等待队列中。队列会被scheduler异步的扫描,如果有足够的资源符合job的需求,就会把tasks分发到机器上。(scheduler主要处理task,而非job).扫描按照优先级从高到底,同时在同一个优先级内混合round-robin算法来保证公平性,防止一个大job后面造成阻塞。调度算法包含两部分:可行性检查和评分。 前者找到task可以运行的机器集合,后者选择一个可行的。
在可行性检查中,scheduler找到一批满足task限制同时还有足够的“可用”资源的机器,这里的可用是指上面有低优先级的task可以被抢占。通过评分来选择“好”机器。评分会考虑用户设定的偏好,但主要是根据内置的标准,比如减少数量和优先级的抢占,选择已经有task copy的机器,扩展任务的电源和故障域,高低优先级的混合部署在一台机器以此来保证高优先级的在负载高峰可扩展。
borg原来使用e-pvm的一个变种来评分,它跨越异构资源产生一个单一的成本值,在分配任务时保证成本值的变化最小。在实践中,e-pvm防止了负载蔓延到所有机器,给负载高峰留下了余量。但是代价是增加了碎片,特别是对于需要大量机器的task.我们有时称之为“最差匹配”。
另外一种是“最好匹配”,它试图尽量紧密的填满机器。这使得一些机器空出来可以直接分配大的task,但是紧密的填充会为错误的资源需求估计付出代价。这伤害了一些有突发流量的应用,对那些低CPU功耗且很容易被调度和的批量任务也特别不好:有20%的非生产任务只需要少于0.1的CUP核。
我们目前的评分模型是一个混合式的,它试图减少滞留资源的量,滞留资源是指机器上由于其他资源被完全分配了而不能使用的资源。这比最好匹配提高了3-5%的打包效率。
如果被评分系统选中的机器没有足够的资源给新任务,borg就会从低到高逐步杀掉低优先级的任务,直到资源满足需求。我们把被杀掉的任务放到scheduler的等待队列中,而不是迁移或者休眠他们。
任务启动时间(从任务提交到任务运行的时间)是一个普遍关注的领域。这个时间很宽泛,平均大约25秒。安装包的时间花了80%,一个已知的瓶颈是争夺本地磁盘写入。为了减少启动时间,scheduler倾向于选择已经安装好程序和数据的机器;大部分安装包都是不变的可以共享和缓存。此外,borg通过并行方式分发安装包。还采用了一些可扩展的技术(见3.4)
3.3 Borglet
Borglet是运行在每个机器的一个agent.它启停任务,重启,管理本地资源;清理日志;给Borgmaster上报机器状态等。
Borgmaster每隔几秒就轮询每个borglet获取机器状态和发送请求。这给了borgmaster对通讯速率的控制,避免了流量控制机制和重启时候的通讯风暴。
Master负责准备发送给borglet的消息,根据borglet的返回更新cell状态。处于可扩展的性能需要,每个borgmaster备份都和一部分borglets保持一个无状态的连接分区;每次重新选举,这个分区就重新计算。出于弹性考虑,borglet总是上报其全部的状态,连接分区会汇总和压缩这些信息,只发送状态变更信息,以此来减少master的负载。
如果Borglet几次不响应轮询的消息就会被认为“挂了”,它上面的任务就会被重新调度的其他机器上。如果通讯恢复,master就告诉borglet杀掉这些任务来避免重复。
如果borglet联系不上master,它上面在跑的任务和服务还会继续跑,即使所有的master备份都挂了。
3.4 扩展性
我们不能确定Borg架构的扩展性极限在哪里,到目前为止,我们遇到的限制都被克服了。一个borgmaster能够管理一个cell中的上千台机器,cell集群每分钟可以处理1万多个task.一个繁忙的borgmaster使用10-14核CPU和50G RAM.我们使用以下一些技术来达到这种规模。
早期的Borgmaster版本有一个单一的同步的循环用来接受请求,调度任务,和borglet通讯。为了应对cell规模的增加,我们把shceduler独立为单独的进程,可以并行的和其他master备份通讯。一个shcedler副本操作cell状态的一份缓存拷贝,不断的检查master的状态,更新拷贝;调度任务,通知master这些改变。Master接受并应用这些变化除非这些变化是不合适的(比如过期了),这引起scheduler在下一次调度中重新考虑。这很像Omega的乐观并发控制,事实上我们最近为Borg增加了这种为不同的工作类型设置不同调度器的能力。
为了改善响应时间,我们分配了单独的线程同borglet通讯并响应只读的RPC调用。为了更好的性能,我们把功能在5个master备份中做了划分(partition),这保证了99%的UI请求在1秒之内,95%的borglet轮询在10秒之内。
还有几件事增加了borg调度的扩展性:
评分缓存:可行性评估和评分都比较耗时,所以Borg缓存了这些评分直到机器或task发生变化,比如机器上的task中止;熟悉变化;task的需求改变等情况。同时忽略小的资源变化来减少cache失效。
等价类:job中的task通常拥有相同的需求和约束,所以borg只会对每个task等价类(一组拥有相同需求的task集)进行可行性评估和评分,而不会针对每一个task在每一台机器上评估可行性和评分。
轻松随机化:在一个大cell中计算所有机器的可行性和评分是一种浪费,scheduler以一种随机的方式检查机器直到找到足够的机器来评分,然后选择其中最好的。这减少了在task换入换出时造成的评分量和缓存失效,加速了task到机器的分配。轻松随机化类似Sparrow的批量取样,同时也处理优先级,抢占,异质性和包的安装成本。
在我们的实验中,从零开始调度整个cell的工作需要几百秒,但是如果把上面的技术禁用就可能3天都完不成。通常,scheduler在半秒内就能处理完等待队列。
- 可用性
大规模系统中失败是很常见的,下图给出了15个样本cell的系统失败导致的任务逐出情况。
Borg在处理这些情况时,使用了下面一些技术诸如复制,分布式文件持久化存储,不定期的checkpoints等.即使如此,我们还想尽量减少这些问题带来的冲击,比如Borg会采用下面一些方法:
自动把被抢占的任务重新调度到另外的机器。
任务部署考虑跨机器,跨机架,跨机房的情况,来减少关联性的失败。
在系统维护的时候,例如os和机器升级时限制任务的中断速度和个数。
使用声明性的理想状态表述和幂等操作,失败的client可以重新提交过去的请求。
在分配task时如果机器不可达,会有速率限制,因为不知道是大范围的机器错误还是网络中断
避免重复的task::machine匹配那会引起task或机器死机。
通过重跑logsaver任务来恢复中间状态数据,即使alloc已经结束或者分给别的机器。用户可以设置重试的时间,一般是几天。
Borg一个核心设计是就算borgmaster或者borglet挂了,那些在跑的任务还要继续跑下去。但是保证master的存活依然是重要的,因为它挂了时候新的job就无法提交,已经在跑的也无法更新,那些失败机器上的task也无法重新调度。
Borgmaster使用了一组技术使它在实际中达到的99.99%的可用性:机器失败备份;准入控制避免过载;使用简单的底层的工具部署实例以减少外部依赖。每个cell都和其他的独立,用以较少相关操作错误和故障传播。这些目标,没有可扩展性的局限性,是大cell的主要论点。
- 应用
Borg的一个主要目标是有效地利用google庞大机器群,这是一个有意义的投资:提高利用率的一个百分点可以节省数百万美元。本节讨论borg这样做的一些策略和技术。
5.1 评价方法
我们的工作有部署约束,需要应对流量的尖峰,机器是异构的,我们使用从service job回收下来的资源跑批量job.所以需要一个比“平均使用”更复杂的指标。经过大量的实验,我们选择了cell压缩度:给出一个工作,我们通过不断去掉机器来找到能承担工作的最小cell, 不断的从零开始分配工作来确认我们没有被错误的配置所干扰。这提供了明确的中止条件,促进了自动化比较,避免了综合性的工作产生和建模的陷阱。评价技术的定量比较可以参考[78]:细节是非常微妙的。
在生产环境做实验是不可能的,我们应用真实的线上数据,包括其限制,约束,余量和使用量等数据,通过Fauxmaster获取了高度仿真的数据。这里的数据来自2014-10-01星期三 14:00的checkpoint. 我们选择了15个cell(去掉了测试的,小的(<5000机器)的cell),从中取样获取了基于规模的较公平的结果。
为了保证机器的异构,我们从压缩后的cell中随机的去掉一些机器。为了保证工作类型的异构,我们保留了所有的工作量。对于需要一半以上的cell的大job,我们把硬约束改成软的,如果job过于挑剔只能放到少数机器上,就允许最多0.2%的task停下来。实验给出了低方差的可重复的结果。如果我们需要一个更大的cell,就克隆原来那个好几遍;如果需要更多的cell,就直接克隆原来的。
每个实验都用不同的随机种子重复了11遍。图中我们用错误栏来表示所需机器的最大最小数量,选择90%的值作为结果—中指或者均值不能反映一个系统管理员为了确定一个合适的工作量所做的事情。我们相信cell紧密度给出了一个公平一致的方式来比较调度策略,可以直接转换为投入/产出:更好的策略导致运行相同的工作时使用更少的机器。
实验关注于一个时间点的工作调度,而不是长期的工作轨迹的重放。这么做是由于下述原因:复制开放式和封闭式队列模型的困难;传统的完成时间指标不适合我们这种长期的服务;提供可比较的干净的信号;不相信结果会是明显不同;另一个实际的问题,使用20万的cpu core即使对google来说也不是个小投入。
生产中,我们为工作量的增长,偶尔的”黑天鹅“事件,负载尖峰,机器故障,硬件升级以及大规模的故障特意留下了一部分余量。下图展示了使用cell压缩后真实的cell的缩小程度。
5.2 Cell共享
几乎所有的机器都同时运行生产和非生产任务:占共享cell的机器的98%,全部机器的83%。
很多其他的公司把面向用户的服务和批量任务分开在不同的集群,来看看我们如果这么做会发生什么:下图说明了如果分开生产和非生产任务,我们就多需要20-30%的机器。因为生产型的job会留出一部分余量应对压力的尖峰,但是这部分资源大部分时间都用不上。Borg会回收这部分资源去跑非生产任务,所以需要更少的机器。
大部分cell被上千的用户共享。下图展示了原因,在这个测试中,如果一个用户使用超过10T(或100T)内存,我们就把他放到一个新cell中。目前的策略看起来不错:即使在更大的阈值上,我们也需要2-16个cell,20-150%的机器。资源共享再次降低了成本。
但是把不用用户和类型的job放到同一个机器上可能导致CPU的干扰,我们是不是需要更多的机器来解决?为了评估这个,我们来看看在相同机器上相同时钟速率下,指令周期(CPI)是如何根据不同环境的tasks变化的。这种环境下,CPI可以看作是评价性能干扰的一个代理指标,因为对一个CPU型的程序来说,CPI翻倍就会导致运行时间翻倍。我们从一个星期内的12000个随机挑选的生产task中收集数据,使用[83]描述的一种硬件分析设施获取5分钟内的指令周期,并对数据加权保证公平。但是结果并不明确。
CPI和两个指标正相关:机器上总的CPU使用率和机器上task的数量;增加一个task导致其他task的CPI增加0.3%。增加CPU使用率10%导致CPI增加不到2%。即使这种相关性在统计上是显著的,但是只能解释我们测量到的CPI变化的5%。其他的因素占主导地位,比如应用程序固有的差异,具体的干扰模式等。
对比共享和不共享的cell的CPI,我们发现共享cell的CPI均值是1.58,不共享的是1.53,也就是说在共享型cell导致3%的CPU性能损失。
对于borglet的CPI,在共享cell下是1.43,非共享是1.20,说明在非共享情况下快了1.19倍,虽然影响不大,但是这个结果偏向于非共享。
这些实验说明大范围的性能比较是很微妙的,也说明共享并不会大幅增加程序运行的成本。
总的来说,共享取得了胜利:CPU的下降通过减少机器来弥补了,而且共享不止是CPU,还有内存和硬盘。
5.3 大cell
Google使用大cell来进行大规模计算并较少碎片。我们测试了在多个小cell情况下划分工作的效果,随机置换工作用round-robin方式放到小cell中。下图说明这种方式需要更多的机器。
5.4 细粒度的资源划分
Borg对CPU的划分细到一个核的千分之一,对内存和硬盘细分到byte.下图说明这种细粒度是有好处的:很少有分配内存和cup的”蜜罐“,他们之间也没有明显关联。这种分布和[68]提到的很像,除了我们发现90%以上的申请都是稍大的内存。
提供固定大小的container或者vm,在Iaas提供商来说是比较常见的,但这不能满足我们的需求。为了证明这一点,我们把0.5核CPU和1G RAM作为基本单位,按需求进行整数倍的分配。下图说明这样做会多需要30-50%的资源。上限是分配一个整机的大任务,这些大任务在压缩之前的cell翻两番都处理不了;下限运行这些任务等待。
5.5 资源回收
Job可以设置资源限制-就是每个task可以分配的资源上限。Borg用这个限制来决定用户是否有足够的quota来提交job, 并确定是否有特定的机器足够的资源来调度task.正如有用户购买比他们需要的更多的quota,有用户要求比他们的任务更多的资源,因为borg通常会杀掉哪些试图获取更多的RAM和硬盘资源的task,或者对CPU节流。另外,有些task偶尔会使用所有的资源(比如在一天的高峰时段或者应对拒绝服务攻击时,但大部分时间不会。
我们评估task需要的资源,回收剩余的资源给那些可能忍受低质量资源的任务,比如批量任务,而不是浪费掉那些当前不跑的资源。整个过程叫做 资源回收。评估的过程称为任务预定(task’s reservation), master每几秒钟就会根据从borglet获取的细粒度的消耗信息计算一遍。初始的预定和资源的请求是相等的,300s之后,允许启动瞬变,它在加上一个安全边际后朝着实际的资源使用值逐步衰减。如果被使用值超过,预定值会快速增加。
Borg计算生产型task的可行性有一些限制,所以他们不会用回收来的资源,也不会面对资源超卖的情况;对于非生产性task,他们使用现有任务的预订量,这样新的任务可以被调度到回收来的资源上。
如果预订量算错了,机器可能在运行时无资源可用,即使所有task的用量都小于其限制。这时候我们就杀掉非生产性任务,但绝对不会动生产性的。
下图(10)说明如果没有资源回收,会需要更多的机器。在一个中等规模的cell中,20%的工作跑在回收来的资源上。
图11展示了更多的细节,他展示了预订量和实际使用限额的比例。一个超过内存限制的不论优先级大小都task会被抢占,所以内存超限是比较少见的。另外,CPU很容易被节流,所以对于短时间的高峰可以使用比限额多一点。
图11说明资源回收可能有点过于保守,在预定和使用直接有一块明显的区域。为了验证这一点,我们选了一个生产cell,把参数调正到很有竞争性的水平用以减少安全边际,再下一周再把参数调整到上面两者的中间,再下一周,恢复回基线。
图12展示了这些变化,第二周预订量和使用量非常接近,第三周稍微少一点,第一和第四周的差距是最大的。如预期的那样,第二周和第三周的OOM略有增加。根据这些结果,我们认为收益大于缺点,所以在cell中配置了的中等的回收参数。
- 隔离
我们的机器50%运行9个或更多的任务;90%约有25的任务,运行4500个线程。虽然共享应用程序的机器增加了可用性,这也需要好的机制来防止任务相互干扰。这适用于安全和性能。
6.1 安全隔离
在单机上我们使用Linux chroot jail作为多任务的安全隔离机制。我们自动分发SSH keys给在在跑任务的用户,这样他们可以远程调试。对大多数用户来说,这已经被borgssh命令取代,它和borglet通过SSH建立一个连接shell,运行在相同的chroot和cgroup里面,锁定访问更加紧密。
Google’s AppEngine (GAE)和Google Compute Engine (GCE)使用VM和安全沙箱技术运行外部的软件。我们在一个KVM进程中运行一个托管的VM,这个进程看作一个borg task.
6.2 性能隔离
早期的borglet使用比较原始的性能隔离:事后检查内存,硬盘和CPU,结合一些措施比如终止使用过多内存和硬盘的task, 对使用过多CPU的调整优先级。但是这对于一些影响其他task的流氓任务太easy了,所以有的用户夸大他们的资源要求来降低其任务的混合调度数量,这减少了可用性。资源回收可以收回一些多余的,但不是全部,因为存在安全边际。在极端情况下,用户请求使用专有的机器或者cell.
现在所有task运行在基于cgroup的容器里面,borglet控制容器的设置,因为系统内核的支持改善了控制。即使如此,偶尔的底层资源的干扰(例如,内存带宽或L3缓存污染)仍然发生,如[60,83]
对于超载和超负荷,borg有一种应用程序appclass. 最重要的区别在于延迟敏感类latency-sensitive (LS)和批量类型。LS的任务是用于面向应用和共享的基础设施服务,需要快速响应用户的请求。高优先级的任务受到最优待的处理,并能够暂时停止批处理任务几秒的时间。
另外一个区别是可压缩的和不可压缩的,可压缩的是基于百分率的,比如CPU,IO带宽,这些可以减少而不必杀死task; 不可压缩的是硬盘,内存,这些如果不杀死task就无法回收。 如果一个机器运行不可压缩的资源,borglet立即终止任务,从最低到最高优先级,直到预订可以满足。如果机器可压缩资源不够了,borglet就限制使用,所以短的尖峰不用杀死任务。如果情况不改善,borgmaster将从机器中删除或者移动一个或多个任务。
Borglet对于生产型任务,会根据预测的使用量给container分配内存;对于非生产任务,则根据内存压力;处理内核的OOM事件;从那些试图分配超卖资源或者内存用光的机器上杀掉task。Linux的文件缓存把这个过程复杂化了,因为需要准确的内存统计。
为了改善性能隔离,LS task可以预定整个物理的CPU核,这阻止了其他的task使用。批量任务可以在任何核上运行,相对LS,批量任务给出了很少的调度共享。Borglet动态的调整贪婪的LS任务的资源空间来保证批量任务不至于饥饿好几分钟,必要的时候会选择CFS带宽控制[75],因为有很多优先级所以共享是不够的。
类似Leverish[56], 我们发现标准LinuxCPU调度程序(CFS)需要大量的调整来支持低延迟和高利用率。为了减少调度延迟,我们的CFS版本使用了扩展的cgroup负载记录[16],运行LS task抢占批量task,减少了多LS任务运行在一个CPU上的调度总量。幸运的是,多数应用是一个请求一个线程的模型,这混合的负载不均的影响。我们有节制的使用cpuset来分配CPU给那些有特别严格的延迟要求的应用。这些努力的效果见图13. 这一领域的工作依然在继续,包括增加线程配置,NUMA(非统一内存访问),超线程,功率感知,提高borglet的控制能力等等。
Task运行使用资源直到达到其limit。 对于可压缩的资源(如CPU),大部分task都可以通过利用那些未使用的资源来超越限制;只有5%的LS task不能这么做,目的是可能是获得更好的资源可预测性,少于1%的批量任务可以超越限制。使用空闲内存是被禁止的,因为这增加了task被杀的机会. 即使如此,有10%的LS task还是override了,79%的批量任务也可以这么做,因为mapreduce的默认配置就是这样的。这补充了资源回收的效果。批量任务都愿意使用闲置的或者回收来的资源,尽管偶尔会被一个匆忙的LS task杀死。
其他相似的系统
资源调度已经被研究了几十年了。从大范围的HCP超级网格计算,网络工作站到大范围的服务器集群,这里只讨论后者。
最近的一些研究分析了雅虎,google,facebook等公司的工作,说明了现代数据中心的规模和异构性带来的挑战。 [69]对机器管理架构做了个分类。
Apache Mosos把资源管理和部署分离为一个中央resource manager和多个基于出价的(offer-based) framework(例如hadoop,spark).Borg是基于请求的分配(request-based),更容易扩展。DRF(资源公平控制)开始是为Mesos开发的,borg反而使用了优先级和quota准入。Mesos开发者已经宣布会扩展Mesos支持可预测的资源分配和回收,并修复一些确定的问题。
YARN是一个以hadoop为中心的机器管理器。每个应用都有一个manager和中央manager协商请求资源;这和2008年以来Google MapReduce从Borg获取资源的机制是一样的。YARN的资源管理器最近才刚刚成为可容错的(fault tolerant). 一个开源上的努力是Hadoop容量调度器(Hadoop Capacity Scheduler),它提供了多组合支持,容量保证,层级队列,弹性共享和公平机制。YARN最近也支持了多资源类型,优先级,抢占和高级准入控制。俄罗斯方块原型(Tetris research prototype)支持基于完成时间感知(makespan-aware)的工作打包。
Facebook’s Tupperware是类似Borg的cgroup容器调度系统,资料很少,但是看上去使用了资源回收。Twitter有开源的Aurora, 基于Mesos的长时间运行的服务的调度系统,类似Borg有专门的配置语言和状态机器。
微软的Autopilot系统提供对微软集群的“自动化的软件准备和部署,资源管理,对软件和硬件失败有修复机制”。Borg生态也提供了类似的功能,这里不讨论。isaard [ 48 ]概述了许多很好的实践。
Quincy使用一个网络流模型(network flow model)来处理一个有数百个节点的集群的数据处理DAG,提供了公平性和数据感知的调度。Borg使用quota和优先级来共享用户资源。Quincy是在borg之上独立构建的,直接处理图。
Cosmos专注于批处理,强调用户获得调度资源和提供给集群的是公平的。它使用一个job一个的manger来获取资源,公开的资料比较少。
微软的Aollo系统为短时间的批量任务使用一个job一个的调度器来获取集群的高效运行,他们的规模看起来和Borg类似。Apollo让低优先级的work找机会执行,以此来提升高优先级任务的可用性,代价是有时候等待队列会延迟好几天。Apollo node提供了一个基于两个纬度的的启动时间预测模型,调度器综合启动代价和远程数据获取的代价来做决定,并加上随机的延迟来减少冲突。Borg使用了基于之前的分配状态的中央调度器来做部署决定,可以处理更多的资源纬度,专注于高可用性,长时间运行的应用;Appollo可能有更高的任务到达率。
阿里巴巴的伏羲支持数据分析类型的工作;它从2009年开始使用。像Borgmaster一样,Fuximaster收集节点的资源可用信息,接受应用的请求并分配资源。伏羲的调度策略和Borg是相反的:不是为每个task寻找匹配的机器,而是为新来的资源从等待队列中寻找一个工作。像Mosos,伏羲允许定义虚拟资源。只公开了综合的工作结果。
Omega支持平行的,专用的”切面“(verticals),约等于Borgmaster减去其持久化存储和分片之后的功能。Omege调度使用乐观并发控制来管理期望的共享资源,观察从borlet的一个独立链路传输的被中心持久化的cell状态信息。Omega的架构被设计成支持多种不同的工作类型,这些工作支持特定的RPC接口,状态机和调度策略(例如长时间任务,不同framework下的批量任务,类似集群存储系统的基础服务,Google云平台的虚拟机等). Borg提供了一个一刀切的RPC接口,状态机表示和调度策略,这些随着时间的推移,需要支持不同的工作类型,增加了规模和复杂性,扩展性还没有成为问题。
Google开源的Kubernets系统在多个节点放置docker容器。他即运行在裸机(类似borg),也运行的多种云主机,比如Google计算引擎(Google Compute Engine).它被许多开发Borg的工程师积极开发着,Google提供了一个宿主叫做Google容器引擎(Google Container Engine)[39].下一节我们讨论Borg的经验是如何加入Kubernets的。
高性能计算社区在这一领域有长时间的研究,但是规模,工作类型和容错的的需求和Google cell的不同。一般来说,这样实现高可用率的系统有大量的积压未决的工作。
虚拟化供应商,如VMware [ 77 ]和数据中心解决方案供应商如惠普和IBM [ 46 ]提供集群管理解决方案,通常规模为O(1000)。此外,一些研究小组也有一些在某些方面提高了调度决策质量的原型系统。
最后,正如我们指出的,管理大型集群的另一个重要组成部分是自动化和”运维规模“。[43]介绍了规划失败,多租户,健康检查,准入控制,重启这些方面对于一个运维管理多个机器是非常必要的。Borg的设计理念是相似的,让我们支持一个运维支持上万机器。(SRE,网站可靠性工程)经验和下一步的工作
本节我们讲述十多年来运行Borg的一些定性的经验教训,并描述这些是如何在设计Kubernets时被利用的。
8.1 教训
我们先讲一些Borg的特征作为告诫,在设计Kubernets时采用了另外的方式。
Job被限制为task唯一的分组机制。
Borg没有高级的方式来管理整个多任务服务作为一个单一的实体,或者查询服务的实例(比如生产轨迹)。作为折中,用户需要在job的名字里面编码服务拓扑并开发一些高级管理工具来分析这些名字。另外,想指定任意子集的job是不可能的,这导致了在分级更新和job调整尺寸的时候不灵活的语义问题。
为了避免这种困难,Kubernets抛弃了job的概念,使用labels来组织其调度单元Pod. Labels是任意的k-v键值对,可以连接系统中的任意实体。一个Borg的job可以等价的表示为一个job:jobname lebel连接一组pod,其他有用的分组也可以这样表示,比如服务,层次,发布类型(生产,工作台,测试)。Kubernets通过label查询来识别目标,这种方式比单一的job分组提供了更大的灵活性。
一个机器一个IP使问题复杂化
Borg中一个机器上的所有task使用同一个ip地址,共享主机的端口空间。这带来了很多困难:Borg必须把端口当作一种资源来调度;task必须先声明他们需要多少端口,而且在启动的时候要被告知要使用哪个;Borglet必须保证端口隔离;命名和RCP系统必须同时处理Ip和端口。
感谢Linux namespaces,VM,ipv6和软件定义网络的到来,Kubernets可以使用一种更友好的方式消除这种复杂性;每个pod和服务都有他自己的ip地址,允许开发者选择端口,而不用修改软件去适配底层给他们选择的端口,而且去掉了底层管理端口的复杂性。
为大用户做优化,影响了普通用户
Borg为大用户提供了很多功能,目的是想让大用户能够更好的调整他们的程序(BCL说明中列出了230个参数)。 开始的想法是支持Google的最大的资源使用者,因为他们的收益是首要的。不幸的是,API的丰富影响了普通用户,并限制了它的进化。我们的解决方案是在borg上层建立自动化工具,并从实验来确定具体的设置。这些受益于支持容错应用的自由实验,如果自动化除了问题,也不会是一场灾难。
8.2 好的经验
另一方面,Borg的许多设计是很优秀的,经得起时间的考验。
Allocs是有用的
Borg Alloc的抽象引起了广泛使用的logsaver模式,另外一个受欢迎的是一个简单的数据加载task周期性的更新web server使用的数据。Alloc和package允许这些辅助服务由单独的团队开发。Kubernets对应于alloc的是pod,它是一到多个被部署到同一台机器并可以共享资源container的资源封装。Kubernets在pod中使用help container,类似alloc当中的task.
集群管理比task管理更重要
虽然borg的主要任务是管理task和机器的生命周期,应用也从很多集群服务从受益,比如命名服务和负载均衡。Kubernets使用服务抽象来支持命名和负载均衡:一个服务有一个name和一组通过label selector定义的动态pod.集群中的任何服务都可以通过名字连接。所以Kubernets可以通过匹配label selector的pods中自动负载均衡连接服务,并一直跟踪pod的运行位置,pod可能会因为失败而重新调度。
内省(Introspection)是至关重要的
Borg工作中,有些地方出错了,定位到错误的根源是有挑战性的。Borg一个重要的设计决策是把debug信息呈现给用户而不是隐藏起来:Borg有几千个用户,所以”自救”是debug的第一步。尽管这会让我们废弃或修改用户在使用的策略变的更加困难,但是这依然是一个胜利,找不到其他现实的选择。为了处理巨大数量的数据,我们提供多层次的UI和调试工具,用户可以快速识别有关他们的job的异常事件,并钻到应用和底层基础设施的细节和日志中去。
Kubernets复制了许多borg的自省技术。比如它发布了基于Elasticsearch/Kibana and Fluentd的工具cAdvisor用于资源管理和日志汇总。 Master可以查询实体状态的快照。Kubernets所有组件记录事件(比如pod重调度,容器失败)有统一的机制,这些记录客户都可以得到。
Master是分布式系统的核心
Borgmaster最初被设计成一个整体的系统,但是随着时间推移,它成为了协同管理用户job的生态系统的核心。例如,我们把调度器和主要的UI分成独立的进程,添加了准入控制服务,垂直和水平自动扩展,task重新打包,周期性job提交(cron),工作流管理,离线查询的归档系统等。这些加在一起,让我们可以扩展工作规模和功能而不牺牲性能或可维护性。
Kubernets的架构更进一步:其核心有一个API服务,只负责处理请求和管理底层状态对象。集群管理逻辑是一组小的,组合式的微服务,作为API服务的客户,例如复制控制器,会在失败时保持pod需要的复制品的数量;还有节点控制器,用于管理机器的生命周期。
8.3 结论
事实上,所有的google集群工作在过去的十年里都迁移到了Borg上。我们依然在发展它,并把其中的经验应用到Kubernets。
致谢和引用(略)