Large-scale cluster management at Google with Borg
Posted 琦彦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Large-scale cluster management at Google with Borg相关的知识,希望对你有一定的参考价值。
众所周知,Borg是Kubernetes的前身,了解Borg的设计思路及其对Kubernetes架构的影响,对我们更好地使用K8S,或在K8S的基础上进行二次开发都是大有裨益的,甚至在设计类似的分布式系统时也能有所启发。本文由Google在2015年正式发表,拜读后把它翻译了给大家分享,有不恰当的地方还望大家积极指正。
Borg论文全文地址在这里: https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/43438.pdf,论文原名Large-scale cluster management at Google with Borg,中文翻译过来就是Google使用Borg进行大规模集群管理。
目录
2.5 Priority, quota, and admission control(优先级,配额和准入控制)
2.6 Naming and monitoring(命名和监控)
5.1 Evaluation methodology(评估方法)
5.4 细粒度的资源申请(Fine-grained resouce requests)
5.5 资源回收(Resource reclamation)
6.2 性能隔离(Performance isolation)
8. Lessons and future work(经验和未来的工作)
摘要
Google Borg
是一个横跨多个集群(单个集群的机器数可达数万台),运行着成百上千不同应用任务的集群管理系统。 它通过进程级别的性能隔离,将准入控制,高效的任务打包,超卖以及机器资源共享,糅合在一起以达到高利用率。它还具有最小化故障恢复时间,降低相关故障概率的调度策略等运行时特性,来支持应用的高可用。Borg
提供了声明式的作业规格语言,命名(域名)服务集成,实时作业监控等系统行为分析与仿真工具来简化用户的工作。 我们将对Borg
的系统架构,特性,重要的设计决定,定量的决策分析和定性的测试分析,及过去十余年间的实践经验进行一个总结。
1. 介绍
我们内部称之为Borg
的集群管理系统,管理,调度,启动,重启并监视了所有的Google
应用。本文将解释它如何实现。 Borg
提供了三大好处:(1)隐藏了资源管理和故障处理的细节,因此它的用户可以专注应用开发。(2)本身即高可靠/高可用,在其之上运行的应用亦是。(3)将我们的工作负载高效地运行在成千上万的机器上。Borg
并不是第一个解决此类问题的系统,但它是极少数能够以如此高弹性和高完成度处理这种规模的系统之一。本文将围绕这些主题,通过我们十余年的经验得出一些定性的结论(观察)。
2. 用户角度
Borg
的用户是Google
应用及服务的开发者或SRE。用户以作业 (jobs) 的形式提交他们的任务 (task) ,每个job由一个或多个的二进制程序组成。每个job运行在单个Borg
cell 上:作为最小单元被管理的一些机器的集合。本章节将描述Borg
暴露给用户视角的一些主要特性。
2.1 Workload(工作负载)
Borg cell
主要运行着两大异构的工作负载。第一部分是长时运行的服务(在线任务),主要处理短时且延迟敏感的请求(从几微秒到几百毫秒不等)。这些服务面向终端用户,例如Gmail,Google Docs,Google search
,也用于内部的一些基础设施服务,例如BigTable
。第二部分是处理时长从几秒到几天不等的批处理任务(离线任务),这些任务对短期的性能波动并不敏感。不同cells之间运行的工作负载差异很大,主要依赖于它们的租户(某些cell的批处理任务高度密集),并且在时间维度上这些任务的差异也非常大:批处理任务来来去去,而很多面向终端用户服务的调用高峰在日间。Borg
需要平等地处理这些场景。
一个代表性的Borg
中工作负载的例子是一个公开的2011年5月整月的记录数据集,它已被充分分析。
过去几年已经有很多应用框架在Borg
之上被建立起来了,包括我们内部的MapReduce,FlumeJava,MillWheel,Pregel
等。这些中的大多数都有一个控制器来提交主job和一些工作job,其中MapReduce
和FlumeJava
扮演了与YARN的应用管理器相似的角色。我们的分布式存储系统GFS
,以及它的后继CFS
,还有BigTable,Megastore
都跑在Borg
上。
在此文中,我们把高优先级的Borg job
划分为"production (prod) job
",其余为"non-production (non-prod) job
"。在一个具有代表性的cell中,prod jobs
占据了大约70%的CPU资源以及大约60%的CPU利用率,同时占据了55%的总内存和85%的内存利用率。
2.2 Clusters and cells(集群和单元)
同一个cell里的机器都属于单个 集群 ,通过数据中心级的高性能网络光缆连接在一起。一个集群存活在单个数据中心的建筑中,多个建筑构成了一个站点。一个集群通常只有一个cell,但是可能会有一些小型的用于测试的cell和其他有特殊用途的cell。我们竭尽所能地避免任何的单点故障。
我们除去测试cell之后的中心cell规模大约有1万台机器,甚至有些更大。一个cell中的机器在多个维度都是异构的:大小(CPU,RAM,硬盘,网络),处理器类型,性能和IP地址的容量或闪存介质。Borg
通过这些维度的差异将不同用户隔离开来并决定task被分配到哪个cell上运行,获取资源,安装程序及其依赖,监视它们的健康状况,并在他们异常退出时重启。
2.3 Jobs and tasks(作业和任务)
一个Borg job
的属性包括名字,拥有者和它所包含的task数目。Jobs可以通过 约束 去强制它的tasks运行在包含特定 属性 的机器上,比如处理器架构,OS版本或是一个外网IP地址。约束可以是硬性的也可以是弹性的。弹性的指可以作为偏好设置而不是硬性需求。一个job的可以延迟到前一个job结束才启动,一个job只能运行在一个cell中。
每个task会映射到运行着容器的Linux进程集合中。绝大部分的Borg
工作负载不运行在虚拟机上,因为我们不想承担硬件虚拟化的开销。并且,这个系统是在我们很多处理器没有支持虚拟化的时候设计的。
Task
也有自己的属性,比如资源需求和它在job中的索引。同一个job中的大多数task有着相同的属性,但也可以被新值覆盖,例如特定任务的命令行参数。各个资源维度(CPU核数,RAM,硬盘空间,硬盘读写速率,TCP端口等)都被独立地指定。Borg
程序为了减少对运行时环境的依赖,都采用静态链接,并且被组织成由二进制程序和数据文件组成的 包 ,它们的安装都由Borg
负责。
用户通过调用RPC来操作jobs,RPC通常通过命令行工具,由其他Borg
job或是监控系统来发出。大多数job的描述通过声明式配置语言BCI完成。BCI是GCL的变种,它会生成protobuf
文件并通过Borg
专有的关键字来拓展。GCL提供lambda函数进行计算,可以被应用用来调整配置以适应环境。有数万的BCL文件超过1k行,累计已经超过1千万行的BCL。Borg
job的配置和Aurora
配置文件有很多相同之处。 图2展示了jobs和tasks在其生命周期内的状态转移。
用户可以通过向Borg
推送一份新的job配置文件来更改一个正在运行job中的部分或所有task,并且命令Borg
将task 更新 到最新。这些是一种轻量级的,非原子性的事务操作,那么它们在被关闭(提交)之前当然也可以轻易撤销。更新通常是滚动执行的,而且可以限制由更新导致的任务中断(被重新调度或抢占)的数量;超过限值后,任何将导致中断的改动都会被跳过。
一些task的更新操作(例如推送一个新的二进制文件)总是会触发重启,一些(例如对资源的需求数增长或改变了约束)会使得task不再适应于当前的机器,然后导致它被停止然后重新调度,还有一些(例如更改优先级)可以无需重启或移动。
Task可以请求在被kill之前通过Unix SIGTERM
信号机制得到通知,从而有时间完成扫尾,保存状态,结束当前正在执行的请求并拒绝新请求等操作。如果抢占者设置了延迟上限那么有些通知将来不及发出。实践中,80%的情况下能发出通知。
2.4 Allocs(分配)
Borg
上的 Alloc 是为一个或多个task运行的预留资源。不管有没有被使用,这些资源都被分配出去了。Alloc
能为未来的task预留资源,在停止task与重启task之间保留资源,并能将不同job的task聚集到同一台机器上 —— 举个例子,一个web服务实例,以及一个与之关联的写log任务(将本地服务器的URL日志从本地磁盘复制到分布式文件系统中)。Alloc
像机器一样对待其管理的资源,多个运行在同一Alloc
内的task共享其资源。如果一个Alloc
需要被迁移到其他机器,那么从属于它的task都会被重新调度。
一个 Alloc集合 就像一个job:它是在一组在多台机器上预留了资源的allocs。一旦一个Alloc
集合被创建,一个或多个job就可以被提交并运行。为了简洁起见,我们通常用"task"指代一个Alloc
或一个顶级task(独立于alloc),用"job"指代一个job或是Alloc
集合。
2.5 Priority, quota, and admission control(优先级,配额和准入控制)
当更多可被容纳的工作出现时(注:即超过了系统的负载能力)会发生什么?我们的解决方案是引入优先级和配额。
每个job都有 优先级 ,一个正整数。一个高优先级的task可以通过牺牲低优先级的task来获取资源,甚至可以抢占(杀死)后者。Borg
为不同的用途定义了不重叠的 优先级区间 ,包括(降序): 监控(monitoring),生产(production),批处理(batch),尽力而为(best effort)(测试或免费程序) 。在本文中,prod jobs
指的就是监控和生产区间。
尽管一个被抢占的task通常会被重新调度到cell的某个机器上,级联抢占还是可能发生:例如当一个高优先级task抢占了一个稍低优先级的task,而这个task又抢占了更低优先级的另一个task。为了避免这样的情况,我们禁止production
区间内的task之间进行抢占。细粒度的优先级划分在别的情况下也同样有用。例如MapReduce
的master
上的task比worker
节点上的task优先级稍高一些,为的就是提高master
的可靠性。
优先级反映了cell中正在运行或者正在等待的job的重要程度,而配额(Quota) 被用于调度哪个job 准入 。在一段时间内(通常是数月),配额用一个资源向量(CPU,RAM,硬盘等)来表示某时用户的job可以请求的最大资源(例如,在cell xx,prod
优先级下,20TB的RAM,直到7月底)。配额检查是准入控制的一部分,而非调度时:当job的配额不足时会在提交时被立刻拒绝。
高优先级的配额比低优先级的配额开销更大。Production
级别的配额会被cell中的实际可用资源所限制,所以当一个用户提交了production
优先级的job且此job满足配额时,它一定可以运行。尽管我们鼓励用户按需购买配额,很多用户还是会超买以防未来应用基数增长时将要面临的资源短缺。我们对低优先级的配额进行超卖来应对这样的情况,这意味着当优先级为0时用户有无限的配额,尽管这实际上几乎无法实现。一个低优先级的job可以准入,但是因为资源不足会保持等待。
配额是Borg
之外的系统处理的,并且和我们的物理容量规划密切相关,在不同的数据中心,容量规划反映出的价格和可用性都会不尽相同。用户的job只有在请求的优先级上有足够配额时才会被接收。配额的使用使得DRF这样的公平性策略变得不再必要。
Borg
有一个可以为某些用户提供特殊权限的容量系统,例如,允许管理员删除或更改cell中的任意job,或者允许用户访问被限制的内核特性和禁用job资源预估这样的Borg
行为。
2.6 Naming and monitoring(命名和监控)
仅仅创建和部署tasks还不够:即使tasks被迁移到了新机器上,服务的客户端和其他系统也应该能找到他们。为了实现这个特性,Borg
建立了一个稳定的“Borg
域名服务”为每个task命名,包括cell名,job名和task编号。Borg
把一个task的主机名和端口号写入一个持久化存储且高可用的Chubby
文件中,这被RPC
系统用来找到task的终端(真实所在的位置)。BNS
域名也构成了task的DNS
域名的基础,例如,cell cc中urban用户的名为jfoo的job下的第50个task会被命名为50.jfoo.ubar.cc.borg.google.com。当job的大小和task的健康状况改变时,Borg
会写入Chubby
,因此负载均衡方可以决策将请求路由到哪里。
几乎所有运行在Borg
之下的task都会包含一个内置的HTTP服务,它会发布task的健康信息以及数千的性能指标(例如,RPC延迟)。Borg
通过健康检查URL监控task并对那些没有及时相应或返回了HTTP错误码的task进行重启。其他数据会被监控工具跟踪并展示在仪表盘上,并对未达SLO的服务进行报警。
一个被称为Sigma
的服务提供了基于web的用户界面(UI),用户可以检测他们所有job的状态,或是一个特定的cell,或者深入研究独立jobs和tasks的资源状况,日志细节,执行历史和最终结果。我们的应用会产生大量的日志,它们会自动的轮转以防用尽磁盘空间,并且在退出后会保留一段时间,为了辅助调试。如果一个job没有处于运行中,Borg
会提供一个"why pending?(为什么挂起)"的注解,以及一个如何修改job的资源需求以更好适配cell的指引。我们发布了一个“符合”资源形态使得调度更加容易的指南。
Borg
会把所有的job提交记录和task事件,以及每个task尽可能详细的资源使用信息记录在Infrastore
上,Infrastore
是一个可扩展的只读数据存储,通过Dremel
提供了类SQL的交互式接口。这些数据被用来支持基于使用量的收费,调试job和系统错误以及长期的容量规划。它也为Google
集群的工作负载追踪提供了数据。 所有的这些特性帮助用户理解Borg
的行为,调试job,协助SRE可以独立管理成千上万的机器。
Borg architecture(Borg架构)
一个Borg cell
由一些机器,一个被称为Borgmaster
的逻辑上的中心控制器,以及一个被称为Borglet
的运行在cell的每台机器上的一个代理进程组成。Borg
的组件都是用C++写的。
3.1 Borgmaster
每个cell的Borgmaster
都由两个进程构成:一个主进程和一个独立的调度进程。Borgmaster
的主进程处理来自客户端的RPC请求,如修改状态(例如创建job),提供只读的数据访问(例如查询job)。它还管理了系统中所有对象(机器,tasks,allocs等)的状态机,通过Borglets
的通信,并提供一个web UI作为Sigma
的备份。
Borgmaster
逻辑上是一个单进程,但实际上有5个副本。每个副本在内存中维护了一份cell中大多数状态的拷贝,并且这个状态会被记录在一个高可用,分布式且基于Paxos
的存储中(副本的本地硬盘上)。每个cell上仅有一个 选举出的master ,同时作为Paxos
集群的leader和状态的修改者,处理所有改变cell状态的操作,例如提交一个job或终止一台机器上的某个task。当一个cell启动或之前选举出的master宕机了,都会触发一次master节点的选举(利用Paxos
协议),新的master会获取一个Chubby
锁,这样别的系统就能找到它。选举一个新master并把故障转移通常需要10s,但是在一个大型cell中耗时也能高达1分钟,因为内存中的状态需要被重新构建。当一个副本从宕机中恢复过来时,它会动态地从其他最新的Paxos
副本中重新同步它的状态。
Borgmaster
某个时间点的状态被称为检查点(checkpoint),以定期的快照(snapshot)加上改动日志的形式持久化在Paxos
存储中。检查点有很多用途,包括恢复过去任意一个时间点的Borgmaster
的状态(例如,把时间点设为接受某个触发软件缺陷的请求之前,这样可以用于调试); 极端情况 下可以手动修复它;构建事件的持久化日志用于未来的查询;还有离线仿真。
一个叫作Fauxmaster
的高保真Borgmaster
仿真器被用来读取检查点文件,并且包含了一份完整的线上Borgmaster
代码,还有Borglets
的存根接口。它接收RPC
请求去改动状态机,并执行类似“调度所有挂起的task”这样的操作。我们还用Fauxmaster
来调试故障,把它当做一个真实的Borgmaster
来与它交互,用模拟的Borglets
从检查点文件中重放真实的交互操作。用户可以进入系统一步一步观察过去发生的状态改变。Fauxmaster
对于容量规划也很有帮助(多少同样类型的新job会合适?),还有在对cell的配置改动之前进行可行性检查(这样的改动会导致任何重要的job不可用吗?)
3.2 Scheduling(调度)
当一个job被提交时,Borgmaster
会将它持久化在Paxos
的存储中,并且将job里的tasks加入 挂起(等待)队列 中。 调度器 会异步地扫描队列,如果有足够的资源且符合job的约束,它会将tasks赋给机器。(调度器主要操作tasks而不是jobs)。扫描会遵循从高到低的优先级,同一级别优先级下的会采用轮转法来保证不同用户之间的公平性,以避免队首大型任务阻塞。调度算法包括两个部分: 可行性检查(feasibility checking) ,找到task可以运行的机器集合,和 评分(scoring) ,从可行的机器中选择一台。
在可行性检查中,调度器找到满足tasks约束并且有足够多可用资源的机器集合(包括那些可以从低优先级任务中抢夺过来的资源)。在评分阶段,调度器会判断每个可行机器的质量。评分考虑了用户指定的一些偏好,但是主要还是由内置的标准驱动,例如最小化被抢占任务的优先级和数量,选择已经有任务安装包的机器,使任务分散在不同能耗和故障域之间,以及整合质量(在单台机器上混合高低优先级的tasks使得高优先级任务可以在负载突刺时扩容)等。
Borg
之前使用E-PVM
算法用来评分,E-PVM
对异构资源生成一个成本值,最小化部署一个任务的成本。在实践中,E-PVM
将负载传播到了所有的机器上,为负载突刺留出了资源——但代价是增加了资源碎片,尤其是对需要占据一台机器大部分资源的大型task来说。有时候我们称之为“最差匹配(worst fit)”。
与之相反的是“最佳匹配(best fit)”。“最佳匹配”尽可能的将任务填满机器。这使得某些机器没有在运行用户的job(但仍然运行着存储服务),因此部署大型任务就会非常直接,但是如果Borg
或用户误判了资源的需求,这样紧实的任务整合策略会导致性能受损,当有突发的高负载时会损害应用,尤其是对指定了低CPU需求的批处理job有负面影响,它们本应能轻易被调度并且抓住机会使用空闲的资源:20%的non-prod
优先级的tasks只请求了少于0.1核的CPU。
我们当前的评分模型是一个混合模型,力图减少搁浅(stranded)资源的数量——搁浅资源指的是某些因为其他类型的资源被饱和分配了而不能使用的资源。它比最佳匹配模型高出了大约3-5%的整合效率。
如果在评分阶段被选中的机器没有足够的可用资源去匹配新task,Borg
会 抢占(杀死) 更低优先级的tasks,优先级从低到高直到有足够的可用资源。我们将被抢占的tasks加入调度器的挂起队列中,而不是将它迁移或休眠。
Task的启动延迟(从job提交到task运行所经历的时间)是我们持续关注的重点。它是一个变化很大的值,中位数大约为25s。包的安装大约占据了总时间的80%:一个已知的性能瓶颈是不同的包在写入本地硬盘时会产生竞争。为了减少task的启动时间,调度器更倾向于把task赋给已经有必要安装包(程序和数据)的机器:大多数的安装包是只读的因此可以被共享和缓存。(这是Borg
调度器唯一支持的一种数据局部化形式。)除此之外,Borg
利用树形结构和类似torrent的协议并行地把包分发到不同机器上。 此外,调度器使用了一些技术使得它可以扩展到包含数万台机器的cell中。
3.3 Borglet
Borglet
是存在于cell的每台机器上的本地Borg
代理。它被用来启动和停止tasks,重启失败的task,通过内核调用来管理本地资源,滚动调试日志,并且向Borgmaster
和其他监控系统报告机器的状态。
Borgmaster
每隔几秒会轮询每个Borglet
来获取机器的当前状态并发送请求。这使得Borgmaster
可以控制通信的速率,避免了复杂的流量控制机制,且防止了恢复风暴。
选举出的master节点负责准备发往Borglet
的消息,并根据他们的响应更新cell的状态。为了性能的可拓展性,每个Borgmaster
的副本运行着一个无状态的 链接分片(link shard) 去处理和某些Borglet
的通信;只要新的选举发生,这样的分区就会被重新计算。为了弹性,Borglet
会一直报告它的完整状态,链接分片会把这些信息聚合并压缩,只向状态机报告与之前的不同之处,以此减少master
节点的更新负载。
如果一个Borglet
对多次的轮询信息都没有作出响应,这台机器就会被标记为宕机,其上的所有task都会被重新调度。如果通信恢复,Borgmaster
为避免复制的开销,会通知Borglet
杀死那些已经被重新调度task。即使和Borgmaster
失去联系,Borglet
也会继续执行普通的操作,因此当前正在运行的task和服务会被保留就算Borgsmaster
复制失败了。
3.4 Scalability(可拓展性)
我们还不知道Borg
这种集中式架构的最终拓展上限在哪;至今为止,每次我们接近限制的时候都可以顺利的突破它。单个Borgmaster
可以在一个cell中管理数千台机器,有些cell每分钟有超过10000的task到达。一个忙碌的Borgmaster
占用10-14个CPU核数以及高达50GB的内存。我们使用了几种技术来达到这样的扩展性。
早期的Borgmaster
用一个简单的同步的循环来接收请求,调度任务并和Borglets
通信。为了处理更大型的cells,我们把调度器分割为独立的进程,这样它就可以和其他Borgmaster
的功能并行了,并且这些功能为了容错会有副本。调度器对一份缓存的cell状态拷贝进行操作。它重复以下操作:从选举出的master
节点中取回状态变更(包括被分配的和正在等待的task);更新本地拷贝;调度并分配任务;把分配的结果通知master
节点。master
节点会接受并且应用这些分配,除非它们不再合适(例如基于过期的状态的分配),这将导致它们等待下一轮被重新调度。这和Omega
采用的乐观并发控制思路十分相似,实际上我们最近对Borg
新增了根据不同工作负载类型选择不同调度器的能力。
为了改进响应时间,我们增加了线程来与Borglets
通信,并且对只读的RPC调用进行响应。为了更好的性能,我们将5个Borgmaster
副本对这些功能进行分片。这些举措使得99%分位数的UI响应时间低于1秒,95%分位数的Borglet
轮询间隔低于10秒。 以下几方面是Borg
调度器强拓展性的关键。
缓存评分 :可行性评估并对一台机器进行评分的开销是很大的,所以Borg
会将评分缓存起来直到机器或task的属性改变——例如,一台机器上的一个task终止了,或是一个属性被改变了,或是一个task的需求发生变更。忽略一些少量的资源变化可以减少缓存失效。
等效类别 :Borg
job中的tasks通常有相同的需求和约束,所以Borg
只对 等效类别(equivalence) 的一个task进行可行性分析和评分,而不是对每一台机器上的每一个等待中的task进行可行性分析,对每一台可行的机器进行评分。(等效类别是指一组相同需求的tasks)
宽松随机化 :对一个大型cell中的每台机器计算可行性及评分是一种资源的浪费,因此调度器会乱序地检查机器知道找到足够多可行的机器去评分,并且在这个集合中选择最优的一台机器。这减少了评分的数量以及task进入/离开系统时的缓存失效,并且加速了task分配给对应机器的过程。宽松随机化类似Sparrow
中的批量采样技术,但它还处理了优先级,抢占,异质性以及安装包的成本。 在我们的实验中,从零开始调度整个cell的工作负载通常会花费几百秒,但当以上技术被关闭的时候,调度3天多还没有结束。通常情况下,一个等待队列中的调度任务会在少于半秒的时间内结束。
4. Availability(可用性)
故障在大规模系统中是十分常见的。图3展示了15个cell样本中task异常退出的原因。运行在Borg
上的程序应该使用诸如多副本,状态持续存储至分布式文件系统,(如果合适的话)执行快照(checkpoint)等技术来处理这些事件。即使如此,我们还是尽量地减轻这些事件带来的影响。例如,Borg
会:
- 自动对退出的任务重新调度,如果有必要的话将调度到新机器上。
- 通过把一个job中的task传播至不同的可能失败域,例如不同机器,机架和供能域,来减少关联故障。
- 操作系统/机器更新期间,限制允许的任务中断的速率以及job中同时失败的task数量。
- 使用声明式预期状态表示和幂等的修改操作,这样故障的客户端就可以无损的重新提交遗漏的请求。
- 当机器不可用的时候限制tasks的重新调度速率,因为
Borg
不能区分大规模的机器故障和网络分区。 - 避免造成机器::task崩溃的组合再次出现。
- 通过不断地重新执行日志保存task,把写入本地硬盘的 关键 中间数据恢复,即使与日志关联的
Alloc
已经终止或转移到其他机器上了。用户可以指定系统重试的时长,通常设置为几天。
Borg
的一个重要设计特性是:就算Borgmaster
或task从属的Borglet
退出了,正在运行的task还会继续运行下去。但是保持master
节点正常运行还是十分重要,因为当它退出,新的task不能被提交,现有的task也不能被更新,故障机器上的tasks也不能被重新调度。 Borgmaster
使用了一系列技术使得它在实践中的可用性高达99.99%:多副本以防机器故障,准入控制以防过负载,使用简单且贴近底层的工具来部署实例使得外部依赖最小化。每个cell都是独立于其它cell的,这样可以降低关联操作错误和故障传播的几率。同时这也是我们不愿扩大cell规模的主要原因,而不是拓展性的限制。
5. Utilization(利用率)
Borg
最初的目标之一就是高效的利用好Google的大规模机器,这同时也是一笔很有意义的经济投资:提高几个百分点的利用率就能省下几百万美元。这一节将讨论Borg
使用的一些策略和技术。
5.1 Evaluation methodology(评估方法)
Jobs有部署的约束,且需要处理少见的负载突刺,我们的机器是异构的,我们从服务型job回收资源来运行批处理job。因此为了评估我们的策略选择,我们需要一个比“平均利用率”更复杂的指标。在大量实验之后我们选择了 cell压缩量 :给定工作负载,我们通过不断移除机器直到当前的资源总量不足以支持工作负载,来确定一个cell需要的最小规模,从零开始重复部署此工作负载,确保我们不会被某个意料之外的配置所干扰。这提供了明确的终止条件,且促进了自动化程度,避免了生成和建模合成负载的陷阱。评估技术的定量比较可以在[78]查看:我们会发现细节令人惊讶地微秒。
利用线上的cells来进行试验是不可能的,但我们可以用线上cells的数据和工作负载,包括约束,限制,预留,数据的使用量等,通过Fauxmaster
来获得高保真的仿真结果。以下的数据来自2014年10月1日14:00周三的一份Borg
快照(其他快照也会提供类似的结果)。我们首先排除了用于特殊目的的,用于测试的以及小型(机器数小于5000)的cells,然后从剩下选择了15个Brog
cells,抽样的结果关于cell的大小大致均匀分布。
为了在紧凑的cell中保持机器异质性,我们随机地移除机器。为了保持工作负载的异质性,我们全量保留,除了那些与特定机器关联的服务型或存储tasks(如Borglet
)。对于比原cell一半规模还大的job,我们把硬性约束变为弹性约束,并且允许最多0.2%的tasks进入等待状态,这是针对那些十分“挑剔”且只能部署在少数机器上的tasks而言的;大量的实验证明结果是可复现的,并且波动很小。如果我们需要一个规模更大的cell,就在合并(压缩)之前的把原cell多克隆几倍;如果需要更多的cell,就只要克隆原cell。
每个实验都借助不同的随机数种子在每个cell上重复了11次。在图中,我们用误差条来展示所需机器的最小值和最大值,选择90%分位数作为结果 —— 平均值和中位数并不能合理反映系统管理员期望把握的工作负载。我们相信cell压缩率是比较调度策略的一个公平且一致的指标,它能被直接翻译成开销/收益;更优的策略只需更少的机器就能支撑起同样的工作负载。 我们的实验关注即时的调度(装箱),而不是重放一段长时的工作负载追踪记录。原因一是避免复制开放和闭合队列模型的复杂度,二是因为传统的“完成时间”指标并不适用于我们的环境,很多服务都是长时运行的,三是为提供清晰的比较结果,四是我们相信结果并不会差异巨大,最后是一个现实问题:我们发现单次实验消耗了200000个Borg
CPU核 —— 即使以Google的规模来看,这也是一笔不小的投资。
在线上,我们特意为工作负载的增长,偶然的“黑天鹅事件”,负载突刺,机器故障,硬件升级和大规模局部故障(如供电短路)保留了足够的裕度(headroom)。图4展示了如果我们执行cell合并(压缩),我们的实际cell可以有多小。图中使用这些压缩后的大小作为基准线。
5.2 Cell共享(Cell sharing)
几乎所有的机器同时运行着prod task
和non-prod task
:共享的Borg cells
中大约有98%的机器是这样,被Borg
管理的整个机器集合中大约有83%(一些cell有特殊用途)。
很多其他组织在独立的集群里运行面向用户的job和批处理job,我们审视了一下如果我们也这么做会发生什么。图5显示,在一个中等规模的cell上,prod job
和non-prod job
隔离运行的话将多需要20%-30%的机器来支撑起工作负载。因为prod jobs
通常会预留一些资源来应对罕见的负载突刺,但大多数时间并不要这些资源。Borg
会将这些资源回收来运行non-prod jobs
,因此总体上我们并不需要那么多机器。
大多数的Borg cells
都会被数千用户所共享。图六展示了原因。在这个测试中,如果用户的工作负载消耗了至少10TiB(或100TiB)的内存,我们就会把它切分入一个新cell。目前的共享策略看起来效果不错:即使选择更大的阈值(100TiB),我们需要2-16倍的cells和20%-150%的多余机器。池化资源能够有效节约成本。
但把无关的用户和job类型打包到同一台机器上可能会导致CPU干扰,是否会导致我们需要更多的机器进行补偿?为了弄明白,我们来看一下同种机器类型,同样的时钟速度下,CPI(cycles per instruction)
是如何改变不同环境中的tasks的。在这些条件下,CPI的值是可比较的,且可以被用作性能干扰的参考量,因为成倍的CPI值对于一个计算密集型程序来说意味着双倍的运行时间。这些数据是过去一周,使用了[83]中描述的硬件分析工具,以5分钟为间隔对时钟数和指令数进行统计,从大约12000个随机选择的prod tasks
收集来的,最后我们对样本进行加权平均。也许结果并不那么直观。
(1) 我们发现在同样的时间间隔下,CPI和两种测量方法正相关:CPU的总使用率和(基本独立的)tasks的数量;机器每新增一个task,其它任务的CPI就会增长0.3%(使用线性模型拟合数据的结果);机器的CPU使用率提高10%,CPI会增加不到2%。尽管这样的相关性很有统计意义,但它只能解释CPI测量中5%的变化;其他因素取决于,例如应用间的固有差异,某种特定的干扰等。
(2) 通过我们从共享cells和某些 专用 cells的多样化应用中采样到的CPI数据来看,共享cells的平均CPI为1.58(σ=0.35),专用cells的平均CPI为1.53(σ=0.32)- 即在共享cells中,CPU性能会低3%。
(3) 为了解决不同cell上的应用可能有不同工作负载,甚至是选择偏好(也许某些对冲突敏感的程序已经被迁移到专用cell上)的问题,我们会观察两种类型cell中所有运行着的Borglet
的CPI。我们发现专用cells的CPI为1.20(σ=0.29),共享cells的CPI为1.43(σ=0.45),意味着专用cells的性能是共享cells的1.19倍,尽管这高估了对轻负载机器的影响,结果轻微偏向专用cells。 这些实验确认了仓库规模的性能比较是十分困难的,强化了[51]中的观测,也说明了共享的策略并没有大幅增加运行程序的开销。 尽管对我们的结果作了最保守的假设,共享依然是有益的:与显著减少几种不同分区方案中的机器数相比,CPU降速显得不足为奇,并且共享的优势还在其他的资源中有所体现,包括内存和硬盘,而不仅仅是CPU。
5.3 大型cells(Large cells)
Google
为了满足运行大型计算任务和减少资源碎片的需求,建造了大型cells。我们通过对一个cell上的工作负载分区至多个更小的cells上,测试了对资源碎片的影响——对jobs随机排列,然后将它们轮转地分配给各个分区。图7显示使用更小的cells将显著地需要更多机器。
5.4 细粒度的资源申请(Fine-grained resouce requests)
Borg
的用户以毫核
为单位申请CPU资源,以字节
为单位申请硬盘空间和内存(一个核是一个处理器超线程(HT),为不同类型机器的性能进行了标准化)。图8显示用户充分利用了细粒度:对请求的内存量和CPU核数没有明显的“偏好点”,资源之间也没有明显的相关性。这些分布和[68]中展示的十分相似,除了我们在90%分位数上的内存请求稍微大一些。
在Iaas(infrastructure-as-a-service)
中普遍提供的固定尺寸的容器或虚拟机并没有很好地符合我们的需求。为了说明这一点,我们对prod jobs
和Alloc
的CPU核数和内存申请向上取整,取最接近的2的幂次数,最低为0.5核/1G RAM。图9显示这样的做法通常需要30-50%更多的资源。上界来自于即使将压缩前的原cell的尺寸扩大四倍仍无法满足资源需求的大型tasks,只得为它们分配一整台机器;下界来自于允许tasks进入挂起等待状态(这比[37]中将近100%的额外开销要少一些,因为我们支持不仅4种buckets,且允许CPU和RAM容量独立缩放)。
5.5 资源回收(Resource reclamation)
Job可以指定资源申请的 上限(Limit) 。Borg
利用资源上限来确定用户是否有足够配额准许job运行,并确定是否有某台特定的机器有足够的空闲资源来调度此task。就像有些用户会购买超出需求的配额一样,有些用户会请求超出他们的tasks使用的资源,因为Borg
通常会杀死一个实际的RAM或硬盘空间使用量超出它请求量的task,或压制CPU以不超过它的申请值。除此之外,有些tasks只是偶尔需要使用它们的资源(例如在一天中的负载峰值或遭受了拒绝服务攻击),但大多数时候并不用。
与其浪费那些已申请但当前未被消费的资源,我们会对一个task的资源用量进行评估,并回收剩余的资源,供那些对资源质量要求并不那么高的任务使用,例如批处理jobs。这整个过程叫做 资源回收(resource reclamation) 。评估的结果叫 预留(reservation) ,Borgmaster
每隔几秒便会利用从Borglet
捕获的细粒度 使用率(资源消费),对预留值进行计算。最初的预留值被设为资源的请求量(即资源上限),300s的启动阶段之后,预留值会缓慢下降到实际用量加上安全余量。当实际用量超过它时,预留值会迅速上升。
Borg
调度器使用资源上限来计算prod tasks
(高优先级,时延敏感)的可行性,所以prod tasks
从来不依赖资源回收,也和资源超卖无关;对于non-prod tasks
,它们使用现有运行tasks的预留值,因此新task可以利用这些被回收的资源进行调度。
如果预留值(预测值)出错,机器可能在运行时耗尽资源——即使所有tasks的使用量都低于它们的上限。如果有这样的情况发生,我们会杀死或者压制non-prod tasks
,但永远不会是prod tasks
。
图10显示,如果没有资源预留,会需要很多额外的机器。在一个中等规模的cell中,大约有20%的工作负载运行在被回收的资源中。
图11可以看到更多的细节,展示了预留值与使用上限的比。一个超出内存限制的task,如果它的资源被其他task需要,不管优先级有多高它都会首先被抢占,所以极少有task会超过它的内存限制。另一方面,CPU很容易被压制,因此短期的突刺会让使用量超出预留值,却不会有什么损害。 图11表明资源回收可能不必要地保守了:预留值和使用量之间有足够的浮动空间。为了测试这点,我们选择一台线上的cell,并调整了它的资源评估算法,在一周的时间内以减少安全余量的方式使其更 激进 ,接着下一周在基准线和 激进 设置之间选择一个 适度 的设置,最后一周恢复到基准线。
图12显示发生了什么。在第二周,预留值明显和真实使用量更接近,比第三周稍低一些,最显著的差距在第一周和第四周。如预期,内存溢出(OOM)事件的发生速率在第二和第三周略微增长。回顾这些结果之后,我们认为利大于弊,并把同样的适度策略的资源回收参数应用到了其他cell上。
6. Isolation(隔离)
50%的机器都运行着9个及以上tasks:一个处于90%分位数的机器大约有25个tasks,且运行着大约4500个线程[83]。尽管在应用之间共享机器提高了利用率,但它需要一种合适的机制来防止tasks(实质是进程)之间的干扰。这适用于安全性和性能两个方面。
6.1 安全性隔离(Security isolation)
我们使用Linux的 chroot 作为同一机器上多个tasks之间主要的安全性隔离机制。过去为了支持远程调试,当机器在为某个用户运行task时,我们会通过自动分发(及废除)ssh密钥的方式给予用户访问对应机器的权限。对于大多数用户,现在这种方式已经被borgssh命令取代了,borgssh会与Borglet
一起向目标task所在的chroot和cgroup,用shell构建起一个ssh连接,更严格地锁定访问权限。 虚拟机(VM)和安全沙箱技术被用来在Google的AppEngine(GAE)
和ComputeEngine(GCE)
运行外部软件。宿主机上的虚拟机以KVM进程的形式运行一个Borg
task。
6.2 性能隔离(Performance isolation)
早期版本的Borglet
采用了相对原始的资源隔离手段:事后检查内存,硬盘空间和CPU使用量,对那些过量使用内存或硬盘的tasks进行统一的终止,并降低过量占用CPU使用的tasks的Linux CPU优先级。不过一些粗暴的tasks还是很容易影响到同一机器上其他tasks的性能,因此有些用户夸大了他们的资源请求量以使Borg
减少与其同寸的tasks数目,这间接减少了利用率。资源回收可以夺回剩余的一些资源,但毕竟有限,因为涉及到安全裕度。在极端场景下,用户还会申请使用专用的机器或cells。
现在,所有的Borg tasks
都运行在基于Linux cgroup的容器中,Borglet
直接操作容器的设置,有了OS内核的帮助,极大地提升了控制能力。即使如此,低阶的资源冲突还是会发生(例如内存带宽或L3缓存污染),就像[60,83]。 为了解决过载和过售,Borg tasks
有一个应用类别 (appclass) 属性。最重要的区别在于延时敏感任务 (latency-sensitive, LS) 类型和其他被称为批处理 (batch) 的任务。LS tasks指面向用户的应用以及需要快速响应请求的共享基础设施服务。高优先级的LS tasks最先被优待,为此可以暂时挂起批处理tasks几秒。
第二个区别在于: 可压缩的 资源(例如CPU使用量,硬盘I/O带宽)是基于速率的,可以通过降低task的服务质量而不杀死它来回收资源; 不可压缩的 资源(例如内存,硬盘空间)只能杀死task来回收。如果某台机器用尽了不可压缩资源,Borglet
会依据优先级从低到高终止tasks,直到剩余的预留量满足需求。如果某台机器用尽了可压缩资源,Borglet
会限制资源使用量(偏好LS tasks),这样不用杀死任何tasks就可以应付短时的负载尖峰。如果情况没有改善,Borgmaster
会从机器上移走一个或多个task。
Borglet
有一个用户态的控制循环,它负责以下工作:为容器分配内存,prod tasks
基于未来使用量的预测值,non-prod tasks
则基于内存压力;处理来自内核的内存溢出 (OOM) 事件;当task尝试分配超出它们内存限制的内存时,或某台超售的机器确实耗尽了内存,则杀死这些任务。Linux激进的文件缓存机制加大了我们实现的复杂度,因为需要对内存精确计算。 为了提高性能隔离的效果,LS tasks可以完全占有一个物理核,并阻止其他LS tasks使用。批处理tasks可以在任意一个核上运行,但相对于LS tasks,它们只有很少的调度额度。Borglet
动态地调整贪婪LS tasks的资源上限,以保证它们不会长久(数分钟)挂起批处理tasks,必要时会有选择性的应用CFS带宽控制[75];份额总是不够的,因为我们有多种优先级级别。
像Leverich
,我们发现标准Linux CPU调度器(CFS)需要实质性的调整来同时支持低延迟和高利用率。为了减少调度延迟,我们的CFS版本对cgroup作了拓展,使得每个cgroup都有自己的加载历史[16];允许LS tasks抢占批处理tasks;当多个LS tasks在一个CPU上都可运行时减少了调度量。幸运的是,我们的多数任务使用了“为每个请求创建一个处理线程”(thread-per-request)的模型,这减轻了持续的负载不均衡。我们保守地使用 cpusets 为有特定延迟要求的应用分配CPU核。以上工作的结果展示在了图13。这方面的工作还在继续,我们正在加入支持NUMA超线程,感知功耗的线程管理和CPU管理,并提升Borglet
的控制精确度。 Tasks可以消费其规定上限内的资源。大多数任务还被允许消费超出上限的可压缩资源
以上是关于Large-scale cluster management at Google with Borg的主要内容,如果未能解决你的问题,请参考以下文章
Spark运行模式_spark自带cluster manager的standalone cluster模式(集群)
Open Cluster Management 部署应用实践
Enable Kerberos secured Hadoop cluster with Cloudera Manager
如何使用 Google Cluster Manager 自定义默认引脚标记?