[云原生] Paper-Reading-Google Borg
Google Borg
论文:Large-scale cluster management at Google with Borg
发表时间:2015
论文链接:论文链接
Borg是Google内部的大规模集群管理系统,已经延续十余年的时间,大体上与MapReduce、GFS、BigTable、Chubby是同时代的产物,但一直被雪藏,直至2015年才发表论文,披露其实现细节。
Kubernetes被认为是Borg的开源版本(研发团队部分重合、功能简化聚焦、架构类似)。许多研发Borg的工程师现在正投身于Kubernetes系统,并从Borg中汲取经验,不断打磨Kubernetes。
1. Abstract & Introduction
Borg系统为Google进行集群管理,其上运行着十万级别的作业(Job)、数千级别的应用(App)、管理着数万级别的机器(Machine)。
Borg的三大优势:
(1)隐藏资源管理和错误处理的细节,使得用户可以聚焦应用开发;
(2)高可靠性和高可用性,同时支持应用的高可靠和高可用;
(3)在数万节点上有效运行工作负载。
这三条优势说的已经比较明确了,如果再总结一下,可以归结为(1)易用性(解决分布式共性问题,屏蔽细节);(2)高可用性;(3)可扩展性(数万节点)和高利用率(有效运行)。这些都是云平台需要解决的核心问题。
Borg不是第一个关注这些问题的系统,却是仅有的几个如此大规模的系统之一。
2. 用户视角 The user perspective
Borg的用户是Google的开发人员和系统管理员。用户以作业(Job)的形式向Borg提交工作,其中每个作业(Job)包含着一个或者多个运行相同程序的任务(task)。每个作业(Job)运行在同一个Borg单元(Cell)中,单元(Cell)是一组机器的集合。
2.1 工作负载
Borg Cell中运行着异构的工作负载,主要包括两个部分:服务 和 批处理作业。服务长时间运行,几乎不会停止,处理短期的、延迟敏感的请求,服务通常是面向终端用户的产品(如Gmail、Google Docs、web search)和一些内部基础设施服务(BigTable,即HBase的Google版本)。批处理作业运行时间在几秒到几天不等;它们对短期性能波动并不敏感。工作负载会在Cell上混合部署。
大部分应用框架已经运行在Borg上多年,包括内部的MapReduce、FlumeJava、Millwhell、Pregel。Google的分布式存储系统也都运行在Borg之上,包括GFS、BigTable、Megastore等。
论文将高优先级的作业成为“生产型作业”(prod),将其他称为“非生产型作业”(non-prod)。大部分长时间运行的服务都是生产型作业,生产型作业被分配了大约70%的CPU资源并占用了大约60%的利用率;同时被分配了大约55%的内存资源并占用了大约85%的内存利用率。
可以发现,在Google内部,无论是服务型还是批处理型,绝大多数作业都运行在Borg之上,包括其核心产品和非常重要的基础设施。
2.2 集群和单元Cluster and cells
一个集群Cluster通常包括一个大规模的单元Cell和若干个小规模的用于测试和特殊目的的单元Cell。Cell的规模大约是10k节点,且机器是异构的。
集群Cluster包含若干个单元Cell,单元Cell又包含大量的机器Machine。三者关系示意图如下图所示。相比于Cluster和Machine,Cell的概念较为少见。但其实Cell的概念在工程实践中比较常见,很多项目在上线之初将Cluster作为一个整体使用,后来出于某种目的(绝大多数时候是就是为了测试)又将集群分为两部分使用,这样集群就被划分成为一个主体Cell和一个用于测试的小Cell,与论文描述一致。可以说,Cell的概念脱胎于实践,而不是Google拍脑袋想出来的。
2.3 作业和任务Jobs and tasks
一个Borg作业Job具有属性,包括name、owner、task数量等。Job具有约束(constraint)来强制其任务运行在具有特定属性的机器上,例如处理器架构、OS版本、IP地址等。约束可以分为硬约束和软约束,前者是需求,必须满足;后者是偏好,尽量满足。
文中提到了约束constraint的概念,在集群调度领域提出约束主要是为了解决任务和集群的异构性挑战。如果要进一步了解约束的概念以及软硬约束的区别,可以研读下面两篇论文:Modelingand Synthesizing Task Placement Constraints in Google Compute Cluster 和 Alsched: Algebraic Scheduling of Mixed Workloads in Heterogeneous Clouds。
每个任务,可以映射为一个container中的一组Linux程序。Borg绝大部分工作负载不运行在虚拟机中,这主要是由于虚拟机性能开销较大,此外一些硬件不支持虚拟化。
一个任务也具有属性,例如资源需求和task index。Borg程序采用静态链接库,以减少对运行环境的依赖,软件包中主要包括二进制程序和数据文件。
在云平台工程实践中,打包和部署是一个难以忽视的重要挑战。Docker基本上就是凭借解决部署问题而风靡开源界的。在Docker出现之前,传统虚拟机(例如KVM、Xen)虽然支持镜像部署,但是性能开销较大;轻量级虚拟化(例如LXC和更底层的cgroups)虽然性能开销小,但是自身不具备镜像,打包部署琐碎繁杂。Docker兼具二者优势,解决了这个问题。
用户向Borg发送RPC用以操作作业。用户可以提交作业、查询作业,也可以改变作业、任务属性。作业Job和任务task的状态图如下图所示。用户可以触发submit、kill和update事务。
作业Job和任务task的生命周期管理要基于这个状态图来实现,特别是错误处理,要在状态图的基础上基于事件触发来实现。例如,当任务finish、fail、kill、lost时,系统会根据特定事件进行专门处理。
任务Task在被抢占之前,会收到通知,二者都是通过Unix信号,抢占通过SIGKILL信号,程序直接退出;通知通过SIGTERM信号,可以被处理。这样做主要是为了让程序关闭前清理环境、保存状态、完成当前请求等。
云平台需要支持任务“优雅”的退出,Borg不是个例,LXC和Docker也有类似功能。例如,docker stop命令就是优雅退出,其机制与Borg类似;而docker kill则是直接退出。
2.4 Allocs
一个Borg alloc是一台machine上预留资源的集合,可以用于一个或多个任务运行。Alloc被用于为未来的task预留资源,用来在停止和再次启动任务之间保持资源,或者将不同作业Job的任务task聚合在一起运行(例如一个web服务器实例和一个相关的logsaver)。
一个Alloc集合类似一个job:它是在多个机器上预留资源的一组alloc。一旦一个alloc集合被创建,一个或者多个作业Job就可以被提交在其中运行。简而言之,一个task与一个alloc对应,一个job与一个alloc集合对应。
Alloc是allocation的简称,应该可以理解为资源分配对应的逻辑单元,或者直接理解为资源单元。初读觉得alloc类似container,但是由于一个alloc可以支持一个或多个task运行,因此alloc实际上更类似Kubernetes中的pod(一个pod中包含若干个container)。另外,Kubernetes中没有Job和Task的概念,我个人感觉Kubernetes中的Service类似于Borg中Job(仅限于服务型Job)的概念,Kubernetes中container类似于Borg中task的概念。当然,仅是类似,说完全相同是不准确的。
2.5 优先级,配额和准入控制 Priority, quota, and admission control
每个作业都有一个优先级,具体形式是一个小的正整数。Borg定义非重叠优先级,包括:monitoring, production, batch, and best effort (also known as testing or free),生产型作业(prod job)包含前两种优先级。
为了避免“抢占洪流”,Borg不允许一个生产型作业区抢占另一个,只允许生产型作业抢占非生产型作业。
资源配额Quota是一组资源量化表达式(CPU、RAM、Disk等),它与一个优先级和一个时间段对应。如果Quota不足,作业提交会被拒绝。
在Borg中如果Quota不足会立即拒绝作业提交。
2.6 名字服务和监控 Naming and monitoring
仅仅是创建和放置任务是不够的,一个服务的客户端或者其他系统需要找到服务,当服务被重新定位到新机器之后也是如此。为此,Borg创建了一个稳定的Borg名字服务(BNS),用于为每个任务命名。任务名字包括cell name、job name和task number这个三元组。Borg将任务的主机名和端口号写入Chubby中的一致性、高可用文件。Borg也会将作业规模和任务健康信息写入Chubby,负载均衡器LoadBalancer据此来进行路由。
名字服务也被称为服务发现,通常使用高可用存储系统(例如Chubby,可以视为Google内部的Zookeeper,当然现在开源界etcd似乎更火)存储任务名字与访问方式endpoint的映射关系。在Borg中,任务名字包括cell name、job name和task number三元组;访问方式endpoint则是hostname和port二元组。客户端或其他系统需要访问服务时,就访问BNS根据任务名字查询访问方式endpoint,以此来“发现”服务。
每个任务会构建HTTP server来发布其健康信息和性能指标,Borg根据URL来监控任务,并重启异常任务。
3 Borg架构 Borg architecture
一个Borg cell包含一组机器集合、一个逻辑中央控制器Borgmaster、每台机器上都运行的代理进程Borglet。Borg的所有组件都使用C++开发。
3.1 Borgmaster
Borgmaster包含两类进程:主Borgmaster进程和分离的调度器进程。主Borgmaster进程处理客户端RPC;管理系统中所有对象Object的状态机,包括machines、tasks、allocs;与Borglet通信;提供webUI。
Borgmaster逻辑上一个进程,但是拥有5个副本。每个Borgmaster副本维护cell状态的一份内存副本,cell状态同时在高可用、分布式、基于Paxos的存储系统中做本地磁盘持久化存储。一个单一的被选举的master既是Paxos leader,也是状态管理者。当cell启动或者被选举master挂掉时,系统会选举Borgmaster,选举机制按照Paxos算法流程进行。
Borgmaster基于高可用、分布式、基于Paxos的存储系统进行元数据持久化和热Borgmaster备份,以此实现Borg系统的高可用。关于这个基于Paxos的存储系统,在Google内部应该就是Chubby,不过不知道为何这里没有提及,难道还有新的系统?开源界etcd最近比较火,但是etcd没有采用Paxos算法,而是使用更为简单且易于理解的raft。这里还是采用Paxos算法。
Borgmaster的状态会定时设置checkpoint,具体形式就是在Paxos store中存储周期性的镜像snapshot和增量更改日志。
3.2 调度Scheduling
当作业被提交,Borgmaster将其记录到Paxos store中,并将作业的任务增加到等待队列中。调度器异步浏览该队列,并将任务分配给机器。调度算法包括两个部分:可行性检查和打分。
可行性检查,用于找到满足任务约束、具备足够可用资源的一组机器;打分,则是在“可行机器”中根据用户偏好,为机器打分。用户偏好主要是系统内置的标准,例如挑选具有任务软件包的机器、分散任务到不同的失败域中(出于容错考虑)。
Borg使用不同的策略进行打分。
实践中,E-PVN(worst fit)会将任务分散到不同的机器上;best fit,会尽量“紧凑”的使用机器,以减少资源碎片。Borg目前使用一种混合模型,尽量减少“受困资源”。
3.3 Borglet
Borglet是运行在每台machine上的本地Borg代理,管理本地的任务和资源。Borgmaster会周期性地向每一个Borglet拉取当前状态。这样做更易于Borgmaster控制通信速度,避免“恢复风暴”。
为了性能可扩展性,每个Borgmaster副本会运行一个无状态的link shard去处理与部分Borglet通信。当Borgmaster重新选举时,会重新分区。Borgmaster副本会聚合和压缩信息,仅仅向被选举master报告状态机的不同部分,以此减少更新负载。
如果Borglet多轮没有响应资源查询,则会被标记为down。运行其上的任务会被重新调度到其他机器。如果恢复通信,则Borgmaster会通知Borglet杀死已经重新调度的任务,以此保证一致性。
3.4 规模可扩展性Scalability
我们不确定Borg中心化架构的可扩展性极限在哪里;不过到目前为止,每次接近极限,我们都能设法消除它。一个Borgmaster可以管理数千台机器,任务到达率约为每分钟10K个任务。一个忙碌的Borgmaster使用10-14个CPUcore和50GB内存。
为了提升可扩展性,我们将调度器分割为独立进程,这样它可以与Borgmaster并行进行操作。调度器具备多个副本,每个调度器在cell状态的副本上进行操作。它重复以下操作:从被选举master中检索状态变更;更新本地状态副本;进行调度、分配任务;将分配通知被选举master。Master会检查调度器的调度操作,如果发生调度冲突则再下一轮重新调度,否则接受并应用该调度结果。这套机制与Omega一文中的乐观并发控制高度相似。此外,Borg最近支持为不同负载类型使用不同调度器。
Omega的核心思想就是采用乐观并发控制,使得调度并行化,解决调度器可能成为集群系统性能瓶颈的问题,提高系统的可扩展性。根据上述描述,Borg多调度器并发调度采用了这种机制。
为了提高系统可扩展性,Borg调度器还作了几种优化,分别是得分缓存(可以将可行性检查和打分结果缓存下来)、等价类(同一job中的task通常具有类似的约束,因此可以将多个任务视为一个等价类)、轻松随机化(在大规模cell中计算所有机器的可行性和得分代价太高,可以随机遍历直到找到一个“足够好”的机器)。
4 可用性 Availability
失败在大规模系统中非常常见。本文列举了Borg提高可用性的例子:
(1)自动重新调度器被驱逐的任务;
(2)为了降低相关失败,将任务分散到不同的失败域中;
(3)限制一个作业中任务的个数和中断率;
(4)限制任务重新调度的速率,因为不能区分大规模机器故障和网络分区;
(5)避免引发错误的任务-机器匹配对;
(6)关键数据持久化,写入磁盘。
5 利用率 Utilization
本节首先通过实验证明,混合部署(prod负载和non-prod负载)比独立部署具有更高的利用率。实验结果下图。
随后,结合实验说明,几种方法可以提高集群利用率,具体包括Cell sharing、Large cell、Fine-grained resource requests和Resource reclamation。前几种方法都比较直观,不做展开。Resource reclamation比较有意思,重点阐述。
一个作业(job)可以定义一个资源上限(resource limit),资源上限用于Borg决定用户是否具有足够的资源配额(quota)来提交作业(job),并且用于决定是否具有足够的空闲资源来调度任务。因为Borg会kill掉一个尝试使用更多RAM和disk空间资源(相比于其申请的资源)的task,或者节流CPU资源(相比于其要求的),所以用户总是申请更多的资源(相比其实际所有的)。此外,一些任务偶尔会使用其所有资源,但大部分时间没有。
用户总是会处于“心理安全”和负载高峰波动等原因,申请较多的资源,但大部分时候,任务不会真正使用如此之多的资源。这就造成了资源浪费。
对于可以容忍低质量资源的工作(例如批处理作业),Borg会评估任务将使用的资源,并回收空闲资源。这个整个过程称为资源回收(resource reclamation)。评估过程称为任务预留(task reservation)。最初的预留值与其资源请求一致,然后300秒之后,会慢慢降低到实际使用率外加一个安全边缘。如果利用率超过资源预留值,预留值会快速增长。
这里直观的表明实际使用资源、预留资源、资源上限的关系。
6 隔离 Isolation
由于集群资源共享,所以每个机器不可避免会运行不同的任务,这就需要有一套较好的机制来确保这些任务之间的影响最小。
安全隔离,使用Linux chroot来进行安全隔离;
性能隔离,每个task都运行在基于cgroup的container中,并且在实际的资源使用中,prod类型的任务可以独占某些资源而non-prod的任务则不行。
Borg按照两种方法来控制overload和overcommitment,overload通过将任务分为Latency-sensitive(LS)和batch两种来控制资源;overcommitment则通过任务对资源的占用划分为compressible(例如cpu、I/O带宽)和non-compressible(例如内存、磁盘空间)。
如果机器上non-compressible的资源不够用了,那么Borglet会立刻按照优先级杀掉本机上优先级低的任务释放出资源;如compressible的资源不够用,那么Borglet会先尝试控制batch任务对cpu的使用来度过高峰,如果还不行,就会由borgmaster拿掉机器上的一个或更多的低优先级任务。
Borglet里面还有一个用户态的程序来控制分配给prod和non-prod任务的内存,如内存超过了task的限制,或本机的内存不够用了,内核中处理OOM事件的部分将按照优先级来kill task。
在CPU方面,在资源的分配上,对于LS tasks允许占用整个cpu核,而batch tasks则可以在任何核上运行,但batch tasks会在较低优先级上,Borg修改了内核的CPU调度器,允许根据每个container的load状况来动态决定是否要kill batch tasks,同时避免多个LS tasks在一个cpu上争抢,目前Borg仍然在尝试的是在cpu调度时更好的考虑线程亲和、NUMA亲和之类的。
7 相关工作 Related work
资源调度是热点研究问题,具有许多相关研究。论文介绍了许多相关系统,这里找几个重点系统进行简要分析。
- Mesos
Mesos通过resourceoffer机制,将资源管理和放置功能分开。Mesos的中央资源管理器(Mesos master)负责资源管理,多个框架(例如Hadoop、Spark)负责具体的任务放置(即任务调度)。Borg采用基于请求(request-offer)的机制,而非基于offer的机制,更易于系统扩展。 - YARN
YARN是Hadoop中心化的集群管理器。每个应用都有一个管理器(AM)用于向中央资源管理器(RM)协商请求资源。这种机制类似于2008年以前Borg为Google MapReduce分配资源的机制。 - 阿里巴巴Fuxi
Fuxi不是像Borg那样把task调度到合适的machine上,而是“反向调度”,为最近可用的machine匹配任务task。 - Omega
Omega支持并发调度,其机制在前文3.4节已经介绍. - Kubernetes
建设Borg的许多工程师正在研发Kubernetes,并将Borg的经验应用到Kubernetes之中。
8 经验以及未来工作 Lessons and futurework
该篇论文阐述了borg系统的一些设计思想,某些设计思想有很强大引导作用。
borg系统其实可以看做是集中式的调度器,因为其调度的对象是task,master负责调度和通信工作.为了提高并发度,有多个调度器采用状态共享无锁乐观并发的方式进行,这可以说是开创性的,现在很多商用的云平台都采用这种方式来提高并发度;可以同时调度在线和离线任务,这就是混合调度的起源。不同类型的任务采用不同的调度器;资源超售机制的提出,这种机制的提出能够显著提高系统的资源利用率,其实对于目前来说,调度系统内部我们已经可以做的比较优了,唯一的问题就是对于一个任务我们无法预知到该任务真正需要多少资源,这就导致了在资源分配的时候造成大量资源闲置和浪费。而资源超售可以很好的解决这个问题。细粒度的任务约束性描述,包括操作系统版本、处理器架构等。
K8S也从borg中吸取到经验和教训,并继续改进,如
- 在Borg中,Job是用来对task进行分组的唯一方式,这种方式相当不灵活,导致用户自己发展出很多方式来实现更灵活对分组。 Kuberntes吸取了Borg的这个教训,引入了Labels,使Pod可以被灵活地分组。
- 在Borg中,一个node上的所有task共享node的ip,这直接导致端口也成为一种资源,在调度时候需要被考虑。Kubernetes为每个Pod分配独立的IP。
- Kubernetes借鉴了Alloc(node上的一块可以被多个task共享的资源),设计了Pod(多个容器的封装)。这种设计,可以将一个pod中的任务分拆成不同 的容器,由不同的团队开发,特别是一些辅助性的任务,例如日志采集等。
- Borg中task、job的命名机制被kubernetes借鉴,提供了service等特性。
- Borg将系统内部的事件、task的日志暴露给用户,提供了不同级别的UI和调试工具,使几千个用户能够自助地使用Borg。 Kubernetes吸收了Borg的这些特性,引入cAdvisor、Elasticsearch/Kibana、Fluentd等组件。
- Borg采用中心式设计,管理功能集中于Master,这种设计方便了后续更多特性引入以及规模的扩展。 Kubernetes更进一步,apiserver只负责处理请求和状态维护,集群的管理逻辑被拆分到多个更精悍、内聚的controller中。