Docker背后的容器集群管理.docx
- 文档编号:26032613
- 上传时间:2023-06-17
- 格式:DOCX
- 页数:16
- 大小:28.42KB
Docker背后的容器集群管理.docx
《Docker背后的容器集群管理.docx》由会员分享,可在线阅读,更多相关《Docker背后的容器集群管理.docx(16页珍藏版)》请在冰豆网上搜索。
Docker背后的容器集群管理
Docker背后的容器集群管理
2015年4月,传闻许久的Borg论文总算出现在了GoogleResearch的页面上。
虽然传言Borg作为G家的“老”项目一直是槽点满满,而且本身的知名度和影响力也应该比不上当年的“三大论文”,但是同很多好奇的小伙伴一样,笔者还是饶有兴趣地把这篇“非典型”论文拜读了一番。
注:
本文作者张磊将在8月28日~29日的CNUT全球容器技术峰会上分享题为《从0到1:
Kubernetes实战》的演讲,演讲中他将重点剖析Kubernetes的核心原理和实践经验,并分享大规模容器集群管理所面临的问题和解决思路。
1.Borg在讲什么故事
其实,如果这篇论文发表在两三年前,Borg的关注度恐怕真没有今天这么高。
作为一篇本质上是关于数据中心利用率的半工程、半研究性的成果,这个领域的关注人群来自大厂的运维部以及系统部的技术同僚可能要占很大的比例。
而对于绝大多数研究人员,普通开发者,甚至包括平台开发者而言,Borg论文本身的吸引力应该说都是比较有限的。
相关厂商内容中国TOP互联网公司的容器应用经验分享京东618大促下的数据驱动个性化推荐如何构建软硬件结合的人工智能产品研发体系中国创新型互联网企业走向海外的技术机遇与挑战LinkedIn成员分类平台大数据应用的最佳实践相关赞助商全球架构师峰会,7月17日-18日,深圳大梅沙京基海湾大酒店。
马上报名。
不过,一旦我们把Borg放到当前这个时间点上来重新审视,这篇本该平淡的论文就拥有了众多深层意义。
当然,这一切绝非偶然,从2013年末以Docker为代表的容器技术的迅速兴起,2014年Google容器管理平台Kubernetes和ContainerEngine的强势扩张,再到如今由Mesos一手打造的DCOS(数据中心操作系统)概念的炙手可热。
容器技术令人咋舌的进化速度很快就将一个曾经并不需要被大多数开发人员关注的问题摆到了台面:
我们应该如何高效地抽象和管理一个颇具规模的服务集群?
这,正是Borg全力阐述的核心问题。
说得更确切一点,当我们逐步接纳了以容器为单位部署和运行应用之后,运维人员终于可以从无休止的包管理,莫名其妙的环境差异,繁杂重复的批处理和任务作业的中稍微回过一点神来,开始重新审视自己手中的物理资源的组织和调度方式:
即我们能不能将容器看作传统操作系统的进程,把所有的服务器集群抽象成为统一的CPU、内存、磁盘和网络资源,然后按需分配给任务使用呢?
所以,作为《Docker背后的技术解析》系列文章的特别篇,笔者将和读者一起从Borg出发,结合它的同源项目Kubernetes中尝试探索一下这个问题的答案。
2.Borg的核心概念
同大多数PaaS、云平台类项目宣称的口号一样,Borg最基本的出发点还是“希望能让开发者最大可能地把精力集中在业务开发上”,而不需要关心这些代码制品的部署细节。
不过,另一方面,Borg非常强调如何对一个大规模的服务器集群做出更合理的抽象,使得开发者可以像对待一台PC一样方便地管理自己的所有任务。
这与Mesos现在主推的观点是一致的,同时也是Borg同PaaS类项目比如Flynn、Deis、CloudFoundry等区别开来的一个主要特征:
即Borg,以及Kubernetes和Mesos等,都不是一个面向应用的产物。
什么叫面向应用?
就是以应用为中心。
系统原生为用户提交的制品提供一系列的上传、构建、打包、运行、绑定访问域名等接管运维过程的功能。
这类系统一般会区分”应用“和”服务“,并且以平台自己定义的方式为”应用“(比如Java程序)提供具体的”服务“(比如MySQL服务)。
面向应用是PaaS的一个很重要的特点。
另一方面,Borg强调的是规模二字。
文章通篇多次强调了Google内部跑在Borg上的作业数量、以及被Borg托管的机器数量之庞大。
比如我们传统认知上的“生产级别集群”在文章中基本上属于Tiny的范畴,而Borg随便一个Medium的计算单元拿出来都是一家中大型企业数据中心的规模(10K个机器)。
这也应证了淘宝毕玄老大曾经说过的:
“规模绝对是推动技术发展的最关键因素”。
Borg里服务器的划分如下:
Site=一组数据中心(Cluster),Cluster=一组计算单元(Cell),Cell=一组机器。
其中计算单元(Cell)是最常用的集群类别。
2.1Job,Task
既然Borg不关心“应用”和“服务”的区别,也不以应用为中心,那么它需要接管和运行的作业是什么?
是Job。
Borg文章里对Job的定义很简单,就是多个任务(Task)的集合,而所谓Task就是跑在Linux容器里的应用进程了。
这样看起来Job是不是就等同于Kubernetes里的Pod(容器组)呢?
其实不然。
Job映射到Kubernetes中的话,其实等同于用户提交的“应用”,至于这个应用运行了几个副本Pod,每个Pod里又运行着哪些容器,用户并不需要关心。
用户只知道,我们访问这个服务,应该返回某个结果,就够了。
举个例子,因为高可用等原因,用户常常会在Kubernetes里创建并启动若干个一模一样的Pod(这个功能是通过Kubernetes的ReplicationController实现的)。
这些一模一样的Pod“副本”的各项配置和容器内容等都完全相同,他们抽象成一个逻辑上的概念就是Job。
由于Job是一个逻辑上的概念,Borg实际上负责管理和调度的实体就是Task。
用户的submit、kill、update操作能够触发Task状态机从Pending到Running再到Dead的的转移,这一点论文里有详细的图解。
值得一提的是,作者还强调了Task是通过先SIGTERM,一定时间后后再SIGKILL的方式来被杀死的,所以Task在被杀死前有一定时间来进行“清理,保存状态,结束正在处理的请求并且拒绝新的请求”的工作。
2.2Alloc
Borg中,真正与Pod对应的概念是Alloc。
Alloc的主要功能,就是在一台机器上“划”一块资源出来,然后一组Task就可以运行在这部分资源上。
这样,“超亲密”关系的Task就可以被分在同一个Alloc里,比如一个“Tomcat应用”和它的“logstash服务”。
Kubernetes中Pod的设计与Alloc如出一辙:
属于同一个Pod的Docker容器共享NetworkNamepace和volume,这些容器使用localhost来进行通信,可以共享文件,任何时候都会被当作一个整体来进行调度。
所以,Alloc和Pod的设计其实都是在遵循“一个容器一个进程”的模型。
经常有人问,我该如何在Docker容器里跑多个进程?
其实,这种需求最好是通过类似Pod这种方法来解决:
每个进程都跑在一个单独的容器里,然后这些容器又同属于一个Pod,共享网络和指定的volume。
这样既能满足这些进程之间的紧密协作(比如通过localhost互相访问,直接进行文件交换),又能保证每个进程不会挤占其他进程的资源,它们还能作为一个整体进行管理和调度。
如果没有Kubernetes的话,Pod可以使用“DockerinDocker”的办法来模拟,即使用一个Docker容器作为Pod,真正需要运行的进程作为Docker容器嵌套运行在这个Pod容器中,这样它们之间互不干涉,又能作为整体进调度。
另外,Kubernetes实际上没有Job这个说法,而是直接以Pod和Task来抽象用户的任务,然后使用相同的Label来标记同质的Pod副本。
这很大程度是因为在Borg中JobTaskAlloc的做法里,会出现“交叉”的情况,比如属于不同Job的Task可能会因为“超亲密”关系被划分到同一个Alloc中,尽管此时Job只是个逻辑概念,这还是会给系统的管理带来很多不方便。
2.3Job的分类
Borg中的Job按照其运行特性划分为两类:
LRS(LongRunningService)和batchjobs。
上述两种划分在传统的PaaS中也很常见。
LRS类服务就像一个“死循环”,比如一个Web服务。
它往往需要服务于用户或者其它组件,故对延时敏感。
当然论文里Google举的LRS例子就要高大上不少,比如Gmail、GoogleDocs。
而batchjobs类任务最典型的就是Map-Reduce的job,或者其它类似的计算任务。
它们的执行往往需要持续一段时间,但是最终都会停止,用户需要搜集并汇总这些job计算得到的结果或者是job出错的原因。
所以Borg在Google内部起到了YARN和Mesos的角色,很多项目通过在Borg之上构建framework来提交并执行任务。
Borg里面还指出,batchjob对服务器瞬时的性能波动是不敏感的,因为它不会像LRS一样需要立刻响应用户的请求,这一点可以理解。
比较有意思的是,Borg中大多数LRS都会被赋予高优先级并划分为生产环境级别的任务(prod),而batchjob则会被赋予低优先级(non-prod)。
在实际环境中,prod任务会被分配和占用大部分的CPU和内存资源。
正是由于有了这样的划分,Borg的“资源抢占”模型才得以实现,即prod任务可以占用non-prod任务的资源,这一点我们后面会专门说明。
对比Kubernetes,我们可以发现在LRS上定义上是与Borg类似的,但是目前Kubernetes却不能支持batchjob:
因为对应的JobController还没有实现。
这意味着当前Kubernetes上一个容器中的任务执行完成退出后,会被ReplicationController无条件重启。
Kubernetes尚不能按照用户的需求去搜集和汇总这些任务执行的结果。
2.4优先级和配额
前面已经提到了Borg任务优先级的存在,这里详细介绍一下优先级的划分。
Borg中把优先级分类为监控级、生产级、批任务级、尽力级(也叫测试级)。
其中监控级和生产级的任务就是前面所说的prod任务。
为了避免在抢占资源的过程中出现级联的情况触发连锁反应(A抢占B,B抢占C,C再抢占D),Borg规定prod任务不能互相抢占。
如果说优先级决定了当前集群里的任务的重要性,配额则决定了任务是否被允许运行在这个集群上。
尽管我们都知道,对于容器来说,CGroup中的配额只是一个限制而并非真正割据的资源量,但是我们必须为集群设定一个标准来保证提交来任务不会向集群索要过分多的资源。
Borg中配额的描述方法是:
该用户的任务在一段时间内在某一个计算单元上允许请求的最大资源量。
需要再次重申,配额一定是任务提交时就需要验证的,它是任务合法性的一部分。
既然是配额,就存在超卖的情况。
在Borg中,允许被超卖的是non-prod的任务,即它们在某个计算单元上请求的资源可能超出了允许的额度,但是在允许超卖的情况下它们仍然有可能被系统接受(虽然很可能由于资源不足而暂时进入Pending状态)。
而优先级最高的任务则被Borg认为是享有无限配额的。
与Kubernetes类似的是,Borg的配额也是管理员静态分配的。
Kubernetes通过用户空间(namespace)来实现了一个简单的多租户模型,然后为每一个用户空间指定一定的配额,比如:
apiVersion:
v1beta3
kind:
ResourceQuota
metadata:
name:
quota
spec:
hard:
cpu:
"20"
memory:
10Gi
pods:
"10"
replicationcontrollers:
"20"
resourcequotas:
"1"
services:
"5"
到这里,我们有必要多说一句。
像Borg、Kubernetes以及Mesos这类项目,它们把系统中所有需要对象都抽象成了一种“资源”保存在各自的分布式键值存储中,而管理员则使用如上所示的“资源描述文件”来进行这些对象的创建和更新。
这样,整个系统的运行都是围绕着“资源”的增删改查来完成的,各组件的主循环遵循着“检查对象”、“对象变化”、“触发事件”、“处理事件”这样的周期来完成用户的请求。
这样的系统有着一个明显的特点就是它们一般都没有引入一个消息系统来进行事件流的协作,而是使用“ectd”或者“Zookeeper”作为事件系统的核心部分。
2.5名字服务和监控
与Mesos等不同,Borg中使用的是自家的一致性存储项目Chubby来作为分布式协调组件。
这其中存储的一个重要内容就是为每一个Task保存了一个DNS名字,这样当Task的信息发生变化时,变更能够通过Chubby及时更新到Task的负载均衡器。
这同Kubernetes通过Watch监视etcd中Pod的信息变化来更新服务代理的原理是一样的,但是由于使用了名为“Service”的服务代理机制(Service可以理解为能够自动更新的负载均衡组件),Kubernetes中默认并没有内置名字服务来进行容器间通信(但是提供了插件式的DNS服务供管理员选用)。
在监控方面,Borg中的所有任务都设置了一个健康检查URL,一旦Borg定期访问某个Task的URL时发现返回不符合预期,这个Task就会被重启。
这个过程同Kubernetes在Pod中设置health_check是一样的,比如下面这个例子:
apiVersion:
v1beta3
kind:
Pod
metadata:
name:
pod-with-healthcheck
spec:
containers:
-name:
nginx
image:
nginx
#definesthehealthchecking
livenessProbe:
#anhttpprobe
httpGet:
path:
/_status/healthz
port:
80
#lengthoftimetowaitforapodtoinitialize
#afterpodstartup,beforeapplyinghealthchecking
initialDelaySeconds:
30
timeoutSeconds:
1
ports:
-containerPort:
80
这种做法的一个小缺点是Task中服务的开发者需要自己定义好这些/healthzURL和对应的响应逻辑。
当然,另一种做法是可以在容器里内置一些“探针”来完成很多健康检查工作而做到对用户的开发过程透明。
除了健康检查,Borg对日志的处理也很值得借鉴。
Borg中Task的日志会在Task退出后保留一段时间,方便用户进行调试。
相比之下目前大多数PaaS或者类似项目的容器退出后日志都会立即被删除(除非用户专门做了日志存储服务)。
最后,Borg轻描淡写地带过了保存event做审计的功能。
这其实与Kubernetes的event功能也很类似,比如Kube的一条event的格式类似于:
发生时间结束时间重复次数资源名称资源类型子事件发起原因发起者事件日志
3.Borg的架构与设计
Borg的架构与Kubernetes的相似度很高,在每一个Cell(工作单元)里,运行着少量Master节点和大量Worker节点。
其中,Borgmaster负责响应用户请求以及所有资源对象的调度管理;而每个工作节点上运行着一个称为Borglet的Agent,用来处理来自Master的指令。
这样的设计与Kubernetes是一致的,Kubernetes这两种节点上的工作进程分别是:
Master:
apiserver,controller-manager,scheduler
Minion:
kube-proxy,kubelet
虽然我们不清楚Borg运行着的工作进程有哪些,但单从功能描述里面我们不难推测到至少在Master节点上两者的工作进程应该是类似的。
不过,如果深入到论文中的细节的话,我们会发现Borg在Master节点上的工作要比Kubernetes完善很多。
3.1Borgmaster
首先,Borgmaster由一个独立的scheduler和主Borgmaster进程组成。
其中,主进程负责响应来自客户端的RPC请求,并且将这些请求分为“变更类”和“只读”类。
在这一点上Kubernetes的apiserver处理方法类似,kuber的API服务被分为“读写”(GET,POST,PUT,DELETE)和“只读”(GET)两种,分别由6443和7080两个不同的端口负责响应,并且要求“读写”端口6443只能以HTTPS方式进行访问。
同样,Kubernetes的scheduler也是一个单独的进程。
但是,相比Kubernetes的单点Master,Borgmaster是一个由五个副本组成的集群。
每一个副本都在内存中都保存了整个Cell的工作状态,并且使用基于Paxos的Chubby项目来保存这些信息和保证信息的一致性。
Borgmaster中的Leader是也是集群创建的时候由Paxos选举出来的,一旦这个Leader失败,Chubby将开始新一轮的选举。
论文中指出,这个重选举到恢复正常的过程一般耗时10s,但是在比较大的Cell里的集群会由于数据量庞大而延长到一分钟。
更有意思的是,Borgmaster还将某一时刻的状态通过定时做快照的方式保存成了checkpoint文件,以便管理员回滚Borgmaster的状态,从而进行调试或者其他的分析工作。
基于上述机制,Borg还设计了一个称为Fauxmaster的组件来加载checkpoint文件,从而直接进入某时刻Borgmaster的历史状态。
再加上Fauxmaster本身为kubelet的接口实现了“桩”,所以管理员就可以向这个Fauxmaster发送请求来模拟该历史状态数据下Borgmaster的工作情况,重现当时线上的系统状况。
这个对于系统调试来说真的是非常有用。
此外,上述Fauxmaster还可以用来做容量规划,测试Borg系统本身的变更等等。
这个Fauxmaster也是论文中第一处另我们眼前一亮的地方。
上述些特性使得Borg在Master节点的企业级特性上明显比Kubernetes要成熟得多。
当然,值得期待的是Kube的高可用版本的Master也已经进入了最后阶段,应该很快就能发布了。
3.2Borg的调度机制
用户给Borg新提交的任务会被保存在基于Paxos的一致性存储中并加入到等待队列。
Borg的scheduler会异步地扫描这个队列中的任务,并检查当前正在被扫描的这个任务是否可以运行在某台机器上。
上述扫描的顺序按照任务优先级从高到低来Round-Robin,这样能够保证高优先级任务的可满足性,避免“线头阻塞”的发生(某个任务一直不能完成调度导致它后面的所有任务都必须进行等待)。
每扫描到一个任务,Borg即使用调度算法来考察当前Cell中的所有机器,最终选择一个合适的节点来运行这个任务。
此算法分两阶段:
第一,可行性检查。
这个检查每个机器是所有符合任务资源需求和其它约束(比如指定的磁盘类型),所以得到的结果一般是个机器列表。
需要注意的是在可行性检查中,一台机器“资源是否够用”会考虑到抢占的情况,这一点我们后面会详细介绍。
第二,打分。
这个过程从上述可行的机器列表中通过打分选择出分数最高的一个。
这里重点看打分过程。
Borg设计的打分标准有如下几种:
尽量避免发生低优先级任务的资源被抢占;如果避免不了,则让被抢占的任务数量最少、优先级最低;
挑选已经安装了任务运行所需依赖的机器;
使任务尽量分布在不同的高可用域当中;
混合部署高优先级和低优先级任务,这样在流量峰值突然出现后,高优先级可以抢占低优先级的资源(这一点很有意思)。
此行文本用于列表编号,不因该出现在正文中。
Borg其实曾经使用过E-PVM模型(简单的说就是把所有打分规则按照一定算法综合成一种规则)来进行打分的。
但是这种调度的结果是任务最终被平均的分散到了所有机器上,并且每台机器上留出了一定的空闲空间来应对压力峰值。
这直接造成了整个集群资源的碎片化。
与上述做法的相反的是另一个极端,即尽量让所有的机器都填满。
但是这将导致任务不能很好的应对突发峰值。
而且Borg或者用户对于任务所需的资源配额的估计往往不是很准确,尤其是对于batchjob来说,它们所请求的资源量默认是很少的(特别是CPU资源)。
所以在这种调度策略下batchjob会很容易被填充在狭小的资源缝隙中,这时一旦遇到压力峰值,不仅batchjob会出问题,与它运行在同一台机器上的LRS也会遭殃。
而Borg采用的是“混部加抢占”的模式,这种做法集成了上述两种模型的优点:
兼顾公平性和利用率。
这其中,LRS和batchjob的混部以及优先级体系的存在为资源抢占提供了基础。
这样,Borg在“可行性检查”阶段就可以考虑已经在此机器上运行的任务的资源能被抢占多少。
如果算上可以抢占的这部分资源后此机器可以满足待调度任务的需求的话,任务就会被认为“可行”。
接下,Borg会按优先级低到高“kill”这台机器上的任务直到满足待运行任务的需求,这就是抢占的具体实施过程。
当然,被“kill”的任务会重新进入了调度队列,等待重新调度。
另一方面Borg也指出在任务调度并启动的过程中,安装依赖包的过程会构成80%的启动延时,所以调度器会优先选择已经安装好了这些依赖的机器。
这让我想起来以前使用VMware开发的编排系统BOSH时,它的每一个Job都会通过spec描述自己依赖哪些包,比如GCC。
所以当时为了节省时间,我们会在部署开始前使用脚本并发地在所有目标机器上安装好通用的依赖,比如Ruby、GCC这些,然后才开始真正的部署过程。
事实上,Borg也有一个类似的包分发的过程,而且使用的是类似BitTorrent的协议。
这时我们回到Kubernetes上来,不难发现它与Borg的调度机制还比较很类似的。
这当然也就意味着Kubernetes中没有借鉴传说中的Omega共享状态调度(反倒是Mesos的Roadmap里出现了类似”乐观并发控制“的概念)。
Kubernetes的调度算法也分为两个阶段:
“Predicates过程”:
筛选出合格的Minion,类似Borg的“可行性检查”。
这一阶段Kubernetes主要需要考察一个Minion的条件包括:
容器申请的主机端口是否可用
其资源是否满足Pod里所有容器的需求(仅考虑CPU和Memory,且没有抢占机制)
volume是否冲突
是否匹配用户指定的Label
是不是指定的hostname
“Priorities过程”:
对通过上述筛选的Minon打分,这个打分的标准目前很简单:
选择资源空闲更多的机器
属于同一个任务的副本Pod尽量分布在不同机器上
从调度算法实现上差异中,我们可以看到Kubernetes与Borg的定位有着明显的不同。
Borg的调度算法中资源抢占和任务混部是两个关键点,这应是考虑到了这些策略在Google庞大的机器规模上所能带来的巨大的成本削减。
所以Borg在算法的设计上强调了混部状态下对资源分配和任务分布的优化。
而Kubernetes明显想把调度过程尽量简化,其两个阶段的调度依据都采用了简单粗暴的硬性资源标准,而没有支持任何抢占策略,也没有优先级的说法。
当然,有一部分原因是开源项目的用户一般都喜欢定制自己的调度算法,从这一点上来说确实是“lessismore”。
总之,最终的结果是尽管保留了Borg的影子(毕竟作者很多都是一伙人),Kubernetes调度器的实现上却完全是另外一条道路,确切的说更像Swarm这种偏向开发者的编排项目。
此外,还有一个非常重要的因
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Docker 背后 容器 集群 管理