http://dockone.io/article/1153前端
【编者的话】从2000年以来,谷歌基于容器研发三个容器管理系统,分别是Borg、Omega和Kubernetes。这篇论文由这三个容器集群管理系统终年开发维护的谷歌工程师Brendan Burns、Brian Grant、David Oppenheimer、Eric Brewer和John Wilkes于近日发表,阐述了谷歌从Borg到Kubernetes这个旅程中所得到知识和经验教训。
@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、惟品会、eBay、道富银行、麻袋理财、土豆网、阿里百川、腾讯游戏、点融网等公司的技术负责人将带来实践经验分享,3月21日以前购票只需238元,欢迎感兴趣的同窗抢购。
尽管对软件容器普遍传播的兴趣是最近的现象,但在谷歌咱们大规模使用Linux容器已经有10多年了,并且期间咱们建了三种不一样的容器管理系统。每个系统都受以前的系统影响颇深,尽管它们的诞生是出于不一样缘由。这篇文章描述了咱们在研发和使用它们的过程当中获得的经验教训。
第一个在谷歌被开发出来的统一的容器管理系统,在咱们内部称之为“Borg”,它管理着长时间运行的生产服务和批处理服务。这两类任务以前是由两个分离开的系统来管理的:Babysitter和Global Work Queue。Global Work Queue的构架极大地影响了Borg,但却只是针对批量服务的,且二者都在Linux control groups诞生以前。Borg将这两种应用所用的机器统一成一个池子,这样得以提升资源利用率,进而下降成本。之因此能够实现这样的机器资源共享,是由于能够拿到Linux内核的容器支持(确实,Google对Linux内核的容器代码贡献了不少),这使得在对时限敏感的、且面对用户的服务和占用不少CPU资源的批处理进程提供了更好的隔离。
因为愈来愈多的应用被开发并运行在Borg上,咱们的应用和底层团队开发了一个普遍的工具和服务的生态系统。这些系统提供了配置和更新job的机制,可以预测资源需求,动态地对在运行中的程序推送配置文件、服务发现、负载均衡、自动扩容、机器生命周期的管理、额度管理以及更多。这个生态系统的发展源自谷歌内部不一样团队的需求,发展的结果成为了异构的、ad-hoc系统的集合,Borg的使用者可以用几种不一样的配置语言和进程来配置和沟通。因为Borg的规模、功能的普遍性和超高的稳定性,Borg在谷歌内部依然是主要的容器管理系统。
Omega,做为Borg的延伸,它的出现是出于提高Borg生态系统软件工程的愿望。Omega应用到了不少在Borg内已经被认证的成功的模式,可是是从头开始来搭建以期更为一致的构架。Omega存储了基于Paxos、围绕transaction的集群的状态,可以被集群的控制面板(好比调度器)接触到,使用了优化的进程控制来解决偶尔发生的冲突。这种分离容许Borgmaster的功能被区分红几个并列的组建,而不是把全部变化都放到一个单独的、巨石型的master里。许多Omega的创新(包括多个调度器)都被收录进了Borg。
谷歌研发的第三个容器管理系统是Kubernetes。Kubernetes的研发和认知背景,是针对在谷歌外部的对Linux容器感兴趣的开发者以及谷歌在公有云底层商业增加的考虑。和Borg、Omega彻底是谷歌内部系统相比,Kubernetes是开源的。像Omega同样,Kubernetes在其核心有一个被分享的持久存储,有组件来检测相关ojbect的变化。跟Omega不一样的是,Omega把存储直接暴露给信任的控制面板的组件,而在Kubernete中,是要彻底由domain-specific的提供更高一层的版本控制认证、语义、政策的REST API来接触,以服务更多的用户。更重要的是,Kubernetes是由一支在集群层面应用开发能力更强的开发者开发的,他们主要的设计目标是用更容易的方法去部署和管理复杂的分布式系统,同时仍然能经过容器所提高的使用效率来受益。
这篇文章描述了谷歌从Borg到Kubernetes这个旅程中所得到知识和经验教训。程序员
历史上,第一个容器提供的仅仅是root file system的隔离(经过chroot),再加上FreeBSD jails提供额外的例如process ID这样的namespaces。Solaris后来成为先锋而且作了不少增强的探索。Linux control groups(cgroups)运用了不少这些想法,在这个领域的发展一直延续到今天。
容器的资源隔离特性使得谷歌的资源使用率远远高出业界同行。例如,Borg使用容器将对延迟敏感、面向用户的任务和批量任务放在相通的物理机上,并会为它们预留更多的资源,这样能够解决load spikes、fail-over等问题。容器提供的资源管理工具使这些得以实现,稳定的内核层面的资源隔离也使进程之间不互相干扰。咱们经过在研发Borg的同时增强Linux容器的方式来得到成功。然而,隔离并非完美的,容器在内核操做系统不能管理的资源隔离方面鞭长莫及,好比level 3 processor caches、内存带宽、以及容器须要被一个额外的安全层支持以抵抗云端的各类恶意攻击。
现代的容器不只仅是隔离机制:它也包括镜像,即包含了在容器内可以让应用跑起来的文件。在谷歌内部,MPM(Midas Package Manager)被用来建造和部署容器镜像。在隔离机制和MPM之间一样的共生关系,也能够在Docker daemon和Docker镜像之间被发现。在这篇文章剩余的篇幅中,咱们会使用“容器”这个词来包含这两方面:运行时隔离和镜像。web
随着时间的推移,咱们愈来愈清楚容器在更高一层使用时的好处。容器化能使数据中心从面向机器转为面向应用。这个部分讨论两个例子:express
Linux内核里的cgroup、chroot和namespace的本来是为了保护应用不受周边杂乱邻里的影响。把这些和容器镜像组合起来建立一个抽象事物把应用从运行它们的(纷杂的)操做系统里隔离出来,提升了部署可靠性,也经过减小不一致性和冲突而加快了开发速度。
能让这个抽象事物得以实现的关键在于有一个自包含的镜像,它把一个应用几乎全部的依赖环境都打包而后部署在一个容器里。若是这个过程作的正确,本地的外部环境就只剩下Linux内核的system-call interface. 这个有限制的interface极大提升了镜像的便携性,它并不完美:应用仍然暴露给了OS interface,尤为是在socket选项的普遍表面上、/proc、和给ioctl call的所传参数上。咱们但愿后面相似Open Container Initiative(OCI: https://www.opencontainers.org/)的努力能继续把容器抽象的表层能理清。
然而,容器提供的隔离和对环境依赖的最低性在谷歌内部颇为有效,容器也是谷歌内部底层惟一支持的应用程序运行实体。这样的好处之一就是在任什么时候候,谷歌在它一整台机器上只有不多量的OS版本部署,只须要不多量的人员来管理或升级。
有不少种方式能够实现这些自包含的镜像。在Borg里,程序的二进制文件在构建时静态地链接到公司范围内repo库里已知的library版本。即使这样,Borg容器镜像也并不是100%的自包含:由于应用会共享一个所谓的基础镜像,而不是将这个基础镜像打包在每一个容器中。这个基础镜像包含了一些容器须要用到的utilities,好比tar和libc library,所以对基础镜像的升级会影响运行中的应用,偶尔也会变成一个比较严重的问题产生来源。
如今的容器镜像格式,好比Docker和ACI把容器进一步抽象,经过消除内在的主机OS环境依赖和要求外在的user命令来共享容器之间的镜像数据,使得距离理想的自包含性又近了一步。编程
搭建面向容器而非机器的管理API把数据中心的关键字从机器转向了应用。这样有不少好处:(1)减轻应用开发者和运维团队操心机器和系统具体细节的压力;(2)提供底层团队弹性,得以升级新硬件和操做系统,但同时对在跑的应用和开发者影响甚小;(3)它把管理系统收集的telemetry(好比CPU和内存用量的metrics)和应用而非机器绑在一块儿,极大提高了应用监测和检查,尤为是在扩容、机器失败或者运维致使应用实例迁移的时候。
容器可以注册通用的API使得管理系统和应用之间尽管互相不甚明了对方的实现细节,但也能信息流通。在Borg里,这个API是一系列HTTP终端衔接到每个容器上。举例来讲,/healthz终端对编排器报告应用的健康状态。当一个不健康的应用被发现,它就被自动终止和重启。这种自我修复对可靠的分布式系统而言是一个关键的砖头块。(Kubernetes提供了相似的功能;健康检查使用了一个用户指定的HTTP终端或者跑在容器里的exec命令。)
容器也能提供其余面向应用的监测:举例来讲,Linux内核cgroups提供关于应用的资源利用数据,这些能够和先前提到的由HTTP API导出的客户metrics一块儿被延伸。这些数据可以实现例如自动扩容或cAdvisor这样通常工具的开发,这些开发记录或者使用这些metrics,不须要理解每一个应用的细节。由于容器就是应用,就再也不须要从在一个物理机或者虚拟机上跑的多个应用来多路分配信号。这个更简单、更稳定一些,并且也容许对metrics和日志进行更细粒度的报告和控制。拿这个跟须要ssh到一台机器去跑top去比一下。尽管对开发者来讲,ssh到他们的容器是可能的,但程序员不多会须要这么去作。
监测,只是一个例子。面向应用的这个变化在管理底层上是有连带效果的。咱们的负载均衡器并不平衡机器间的传输,它们是针对应用实例来平衡。日志也是根据应用,而非机器,所以它们能够很容易的被收集以及在实例之间集合,而不受到多个应用或者操做系统的影响。咱们能够查探到应用失败,更容易对这些失败的缘由来归类,而不须要对它们进行机器层面信号的分离。
最后,尽管到目前为止,咱们对应用的关注和对容器的关注是1:1,但在现实中咱们使用在同一台机器上联动的容器:最外面的容器提供一个资源池,里面的这些容器提供部署隔离。在Borg,最外面那层容器被称为资源调配器(或者alloc),在Kubernetes里,被称为pod。Borg也容许最顶端的应用容器跑在alloc的外面,这个带来了不少不方便,因此Kubernetes把这些规范化而且老是在一个顶端的pod里来跑应用容器,即使这个pod只有单一的一个容器。
一个广泛的使用样式,是用一个pod来装一个复杂应用的实例。应用的主要部分在它其中一个容器(child containers)里,其余容器跑着支持功能,例如日志处理。跟把这些功能组合到一个单一的二进制相比,这使得开发团队开发不一样功能的部件容易不少,也提升了稳定性(即使主体应用有新的东西进来,日志发送依然能够继续运行)和可编辑性。安全
原始的Borg系统能够在共享的机器上跑不一样的工做负荷来提升资源利用率。但在Borg内支持服务的迅速进化显示,容器管理的本质只是开发和管理可靠的分布式系统的开始,不少不一样的系统在Borg上和周边被开发,用来提升Borg所提供的基本的容器管理服务。下面这个不完整的列表给出了这些服务大概的一个范围和多样性:服务器
构建这些服务是用来解决应用开发团队所经历的问题。成功的服务被普遍采用,那其余开发者就受益。不幸的是,这些工具经常选一些怪癖的API,conventions(好比文件位置)和Borg的深度结合。一个反作用就是增长了Borg生态系统部署应用的复杂性。
Kubernetes企图经过对API采用一种一致的方法来避免这种增长的复杂性。好比说,每个Kubernetes的对象在它的描述里有三个基本的属性:对象的metadata、spec和状态(status)。
对象的metadata对系统中的全部对象都是同样的,它包含了例如对象名称、UID(特殊标示)、一个对象的版本号码(为了乐观的进程控制)以及标签(key-value对,见下面描述)。Spec和status的内容根据不一样的对象类型会不一样,但它们的概念是同样的:spec时用来描述对象的理想状态,而status提供了该对象目前当下的只读信息。
这种统一的API带来不少好处,可让咱们更容易的了解系统,由于系统中全部对象都有相似的信息。另外,统一的API能够更容易地编写通用的工具来做用于全部对象,这样反过来也让使用者感受更为连贯。经过对Borg和Omega的总结,Kubernetes创建在一整套可自由拆装的部件之上,能够由开发者任意延展。一个有共识的API和对象metadata结构可使这个过程更为简单。
一致性还能够经过在Kubernetes API内解构来完成。在API组建之间考虑进行一些分离意味着在更高层的服务上须要共享一些基本的构建组件。一个很好的例子是在Kubernetes的RC(replication controller)和它水平自动扩容系统之间的分离。一个RC保证了对某个角色(好比“前端”)理想的pod数量的存在。自动扩容器,反过来,须要依赖这个能力而且简单地调整理想的pod数量,不须要担忧pod如何生成和删除。自动扩容器的实现可以把精力集中在需求和使用的预测,忽略如何实现这些决定的细节。
分离保证了多个关联但不一样的组件共享一个类似的外表和感受。举个例子,Kubernetes有三个不一样的pod副本模式:网络
尽管在规则上有区别,全部这三个控制器都依赖共同的pod对象来制定它们想要运行在上面的容器。
一致性也能够经过不一样Kubernetes组件上共同的设计形式来达到。在Borg、Omega和Kubernetes上用来提升系统弹性,有一个概念:“reconciliation controller loop”(清理控制器循环),这个概念是来比较一个理想的状态(好比须要多少个pod才能来达到一个标签选择的query,即 label-selector query),和相对于观测到的状态(能够发现的这样的pod数量)来进行比较,而后采起行动去把这两个状态作到一致。数据结构
在研发这些系统的时候,咱们也学到了许多关于哪些事情不应作,哪些事情值得去作的经验。咱们把其中的一些写出来,指望后来者再也不重蹈覆辙,而是集中精力去解决新问题。架构
全部跑在Borg机器上的容器都共享主机的IP地址,因此Borg给容器分发了独特的port端口做为调度过程的一部分。一个容器当它移到一个新的机器上已经有时候若是在一样的机器上重启的话,会拿到一个新的端口号码。这意味着传统的例如像DNS(Domain Name System)网络服务须要被home-brew版本取代;由于服务的客户不会先验地知道给到服务的port端口,须要被告知;port端口号码不能被嵌在URL里,就须要以名字为基础的再次导向(redirection)机制;并且依赖于简单的IP地址的工具须要被重写来搞定IP:端口对的形式(port pairs)。
从咱们在Borg的经验来看,咱们决定Kubernetes能够来给每一个pod制定IP地址,这样把网络身份(即IP地址)和应用身份能统一块儿来。这会使得在Kubernetes上跑现成的软件容易的多:应用能够随意使用静态已知的端口(好比80做为HTTP端口),已经存在的、熟悉的工具能够被用来作网络分段、带宽调节管理。全部流行的云平台提供网络的基础层,可以有每一个pod的IP,在裸机上,可使用SDN覆盖层或者配置L3路由来管理每一个机器上的多个IP.
若是你让用户很容易地建立容器,他们会倾向于建立不少,那么很快就会须要一种方式来管理和组织它们。Borg对于群组的相同的task提供了jobs(对于容器而言任务的名称)。一个job是一个压缩的容器(vector)装了一个或多个相同的task,从0开始计数。这提供了许多能量,并且很简单直白,但时间长了咱们又会后悔它过于死板。好比说,当一个task死掉了,须要在另外一台机器上被从新启动,在task这个vector上的相同的slot就要双倍的工做:既要指认这个新的备份,同时还要指向旧的那个,万一可能须要作debug。当task出如今vector的当中,那vector就有洞。所以vector很难去支持在Borg的一层上跨越多个集群的job.同时,也有潜在的、不期而遇的在Borg更新job的语意上(典型的是在作滚动升级的时候按照index标记来重启)和应用使用的task index标记(好比作一些sharding活着跨task的数据的分区)的互动:若是应用使用基于task index的range sharding,那么Borg的重启政策会致使拿不到数据,由于它会拉掉附近的任务。Borg也没有简单的办法去job里面增长跟应用有关的metadata,好比角色属性(好比“前端”)或者展现的状态(好比是canary),因此人们要把这些信息写到job名称里,这样他们能够用常规表达式(regular expression)来解析。
相比之下,Kubernetes主要使用标签(labels)来识别成组的容器。一个标签是一对key/value组,包含着容器信息能够用来识别对象。一个pod可能有这样的标签:role=frontend 和 stage=production,代表这个容器服务于前端生产。标签能够动态地被自动工具、用户来添加、移除和修改,也能够被其余不一样的团队独立地来管理他们本身的标签。成组的对象,能够由label selectors来定义(好比 stage==production && role==frontend)。这些组(set)能够重叠,并且一个对象能够在多个的组(set)里,所以标签自己要比明确的对象列表或简单静态的属性更灵活。由于一个组(set)是由一个动态队列来定义的,一个新的组能够在任什么时候候被生成。在Kubernetes里label selectors是grouping(成组)的机制,来定义跨越多个实体的管理操做的范围。
即使在那样的环境里知道在一个set里的一个task的身份是颇有帮助的(好比说静态角色的分配和工做分区或分片),适当的每一个pod有标签能够被用来再次产生任务标示的效果,尽管这个是应用的责任(或者一些其余在Kubernetes外部的管理系统的责任)来提供这样的标签。标签和标签选择器提供了一个对这二者的最好的通用机制。
在Borg里,tasks并非独立于jobs存在的。生成一个job,也会生成它的task,那些tasks永远和特定的job是有关联的,若是删除job,也会删除task。这样很方便,但也会有一个主要的缺点:由于只有一个成组的机制,须要来解决全部出现的状况。举例来讲,一个job须要存储参数,这些参数或者是对应服务(service)或者是对应批量工做(batch job)但不会是二者同时,并且用户必需要写出workarounds当job的抽象没法来解决某个状况的时候(好比一个DaemonSet对这个集群里的全部节点都去复制一个简单的pod)。
在Kubernetes里,pod生命周期的管理组件例如RC决定了哪一个pod它们有责任要用标签选择器,所以多个控制器均可能会认为它们本身对于一个单一的pod有管辖权。经过适当的配置选择来预防这样的冲突就很是重要。可是标签的弹性也有额外的好处,好比说,控制器和pod的分离意味着能够孤立和启用一些容器。考虑到一个负载均衡的服务使用一个标签选择器去标示一组pod去发送请求。若是这些pod中的一个行为异常,那这个pod的被Kubernetes服务负载均衡器识别出来的标签会被删除、这个pod被隔离再也不进行服务。这个pod再也不服务接受请求,但它会保持线上的状态,在原处能够被debug.同时,管理pod的RC自动实现服务,为有问题的pod建立一个复制的pod.
Borg、Omega和Kubernetes之间一个关键的差异在于它们的API构架。Borgmaster是一个单一的组件,它知道每个API运做的语义。它包含了诸如关于jobs,tasks和机器的状态机器的集群管理的逻辑;它跑基于Paxos的复制存储系统用来记录master的状态。反观Omega,Omega除了存储以外没有集中的部件,存储也是简单地聚集了被动的状态信息以及增强乐观的并行进程控制:全部的逻辑和语义都被推动存储的client里,直接读写存储的内容。在实践中,每个Omega的部件为了存储使用一样的客户端library,来打包或者解体数据结构、从新尝试活着增强语义的一致性。
Kubernetes选择了一个中间地提供了像Omega部件结构的弹性和可扩容性,同时还能增强系统层面的无变化、政策和数据传输。它经过强制全部存储接触必须经过一个中央的API服务器来隐藏存储的实现细节和给对象验证、版本控制提供服务来作到这些。在Omega里,client的部件互相之间是分离的,能够进化或者单独被替换(这对开源环境而言尤为重要),但中央化对增强共同语义、不变性和政策会容易不少。
有了八年的容器管理经验,咱们感受依然还有大量的问题咱们没有很好的解决方案。这个部分描述了一些咱们感到特别棘手的问题,做为抛砖引玉吧。
在全部咱们面对的问题中,最多的心思和笔墨涉及到的都是关于管理配置,即一整套的提供给应用的配置,而非硬生生写进应用里去。咱们彻底能够把整篇文章都拿来写这个主题(可能都说不完)。下面这些是一些咱们想要强调的内容。
首先,应用配置变成了一个关联一切的抓手,来实现全部的东西,全部这些容器管理系统(尚且)不作的事情,包括:
为了解决这些要求、配置管理系统趋向于发明一个domain-specific的配置语言,最终具备图灵完备性,起源于但愿可以在配置的数据里进行计算(好比对一个服务调整给它的内存,做为在一个服务里进行分区的功能)。结果就产生一个难以理解的“配置是代码”,你们都经过不在应用当中hardcode参数来尽可能避免的这种状况。它并无减小操做上的复杂性或者使得配置更容易debug或改变,它只是把计算从一个真正的编程语言挪到了一个特殊领域。
咱们相信最有效的方法是去接受这个需求,拥抱无所不在的程序配置和在计算和数据之间保持一个清楚的界线。表明数据的语言应该是简单的、仅数据格式的,好比像JSON或者YAML,对这种数据的程序化修改应该在一个真实的编程语言里,有被很好理解的语义和工具。有趣的是,一样的在计算和数据之间的分离在前端开发的不一样领域是雷同的,好比像Angular在markup(数据)和JavaScript(计算)之间是有清晰的划分的。
起一个服务每每也意味着提供一系列相关的服务(监控、存储、CI/CD等等)。若是一个应用对其余应用有依赖,其余这些依赖条件(和任何它们可能有涉及的依赖条件)可以被集群系统自动管理,是否是很好呢?
更麻烦的是,对依赖条件的实例化不多会像起一个新的备份这么简单,好比说,它可能会须要对现有的服务注册一个新的消费者(好比Bigtable as a service)而后经过这些间接的依赖环境来传递认证、受权以及帐号信息。然而,基本上没有系统会抓、保持或者透露这些依赖信息,因此在底层自动化这些即使是很是常见的状况都是近乎不可能的。起来一个新的应用对用户来讲就很复杂,对开发者而言来建新的服务就变难,常常致使一些最佳实践没法进行,影响服务的可靠性。
一个标准的问题是:若是是手动更新,很难保持依赖信息的及时更新。并且同时,能自动地(好比跟踪access)决定它的这种企图也没法掌握须要了解结果的语义信息。(好比是否这个acess要给那个实例?或者任何一个实例就足够了?)一个可以改进的多是要求应用枚举它所依赖的服务,而后让底层拒绝对其余服务的接触(咱们在咱们的build system里对compiler imports这么作过)。这个动机是让底层作有用的事情,好比自动的setup、认证和链接。
不幸的是,咱们所观察到的系统在表达、分析和使用系统依赖这方面的复杂性都过高,因此它们尚未被夹到一个主流的容器管理系统里。咱们依然但愿Kubernetes可能能够成为一个这样的平台,在这个平台上有这样的工具,但这么作是一个很大的挑战。
十多年搭建容器管理系统的经验教会了咱们不少。并且咱们把不少已有的经验融入进了Kubernetes,谷歌最近的这个容器管理系统。它的目标是基于容器的能力来提供编程生产力方面的极大收获,简化人工和自动化系统管理。咱们但愿你会来加入咱们来延伸和提升这个项目。
原文连接:Borg, Omega, and Kubernetes:Lessons learned from three container-management systems over a decade(翻译:韩佳瑶)