此文已由作者刘超授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
Mesos架构如下
这个图比较的著名了,也有很多文章介绍这个图,详情可以看文章http://mesos.apache.org/documentation/latest/architecture/,这里不做过多的介绍。
从图中可以看到,Mesos有Framework(Framework里面有Scheduler), Master(Master里面有allocator), Agent, Executor, Task几部分组成。这里面有两层的Scheduler,一层在Master里面,allocator会将资源公平的分给每一个Framework,二层在Framework里面,Framework的scheduler将资源按规则分配给Task。
Mesos的这几个角色在一个任务运行的生命周期中,相互关系如下:
Agent会将资源汇报给Master,Master会根据allocator的策略将资源offer给framework的scheduler。Scheduler 可以accept这个资源,运行一个Task,Master将Task交给Agent,Agent交给Executor去真正的运行这个Task。
这个图相对比较的简略,真正详细的过程比这个复杂很多,大家可以参考这篇博客http://www.cnblogs.com/popsuper1982/p/5926724.html,在代码级别分析了整个任务运行的过程,还画了一个泳道图http://images2015.cnblogs.com/blog/635909/201608/635909-20160806163718778-1628977219.png。
要研究Mesos,熟悉整个过程非常重要,这样一个任务运行出现问题的时候,才能比较好的定位问题在哪里,如果解决。Mesos将一个简单的任务的运行过程,分成如此多的层次,如此多的角色来做,是为了双层调度和灵活配置,这是一个内核应该做的事情。
我们如何干预一个Task的运行过程呢?
如果你想完全自己控制Task的运行,而非让Marathon来运行并保持一个无状态的Task长运行,就需要自己写一个Framework,在你的Framework里面,三个Task之间的关系你可以自己定义,而非像Marathon一样,Task * 3,3个任务不分彼此,你的Framework可以控制这三个Task一主两备,可以控制三个Task的启动顺序,可以将一个先启动的Task的IP,位置等通过环境变量告知另外两个Task。
写一个Framework需要写一个Scheduler,实现一些接口,如文档http://mesos.apache.org/documentation/latest/app-framework-development-guide/中所述。
然后使用使用MesosSchedulerDriver来运行这个Scheduler。
其实Mesos这些模块之间的通信都是通过Protocol Buffer定义消息来交互的,然而如果让Framework的开发人员还要学会如何使用Protocol Buffer消息和Mesos Master通信,是很痛苦的事情,所以MesosSchedulerDriver帮助你做了这个事情,你只需要实现Scheduler定义的接口就可以了,不需要了解这些接口是谁调用的,调用了接口之后,消息如何传给Mesos Master。
所有的接口里面,最重要的是resourceOffers函数,根据得到的offers(每个slave都有多少资源),创建一系列tasks,然后调用MesosSchedulerDriver的launchTasks函数,MesosSchedulerDriver会将这些tasks封装为LaunchTasksMessage发送给Mesos Master。
通过上面的描述,Mesos有两层调度,第一层就是Allocator,将资源分配给Framework。
Mesos允许用户通过自己写Module的方式,写一个so,然后启动的时候加载进去,然后在命令行里面指定使用so中的哪个Module。
当然写Allocator的不多,因为Mesos的DRF算法是Mesos的核心,如果不用这个算法,还不如不用mesos。
Mesos源码中默认的Allocator,即HierarchicalDRFAllocator的位置在$MESOS_HOME/src/master/allocator/mesos/hierarchical.hpp,而DRF中对每个Framework排序的Sorter位于$MESOS_HOME/src/master/allocator/sorter/drf/sorter.cpp,可以查看其源码了解它的工作原理。
HierarchicalDRF的基本原理
如何作出offer分配的决定是由资源分配模块Allocator实现的,该模块存在于Master之中。资源分配模块确定Framework接受offer的顺序,与此同时,确保在资源利用最大化的条件下公平地共享资源。
由于Mesos为跨数据中心调度资源并且是异构的资源需求时,资源分配相比普通调度将会更加困难。因此Mesos采用了DRF(主导资源公平算法 Dominant Resource Fairness)
Framework拥有的全部资源类型份额中占最高百分比的就是Framework的主导份额。DRF算法会使用所有已注册的Framework来计算主导份额,以确保每个Framework能接收到其主导资源的公平份额。
举个例子
考虑一个9CPU,18GBRAM的系统,拥有两个用户,其中用户A运行的任务的需求向量为{1CPU, 4GB},用户B运行的任务的需求向量为{3CPU,1GB},用户可以执行尽量多的任务来使用系统的资源。
在上述方案中,A的每个任务消耗总cpu的1/9和总内存的2/9,所以A的dominant resource是内存;B的每个任务消耗总cpu的1/3和总内存的1/18,所以B的dominant resource为CPU。DRF会均衡用户的dominant shares,执行3个用户A的任务,执行2个用户B的任务。三个用户A的任务总共消耗了{3CPU,12GB},两个用户B的任务总共消耗了{6CPU,2GB};在这个分配中,每一个用户的dominant share是相等的,用户A获得了2/3的RAM,而用户B获得了2/3的CPU。
以上的这个分配可以用如下方式计算出来:x和y分别是用户A和用户B的分配任务的数目,那么用户A消耗了{xCPU,4xGB},用户B消耗了{3yCPU,yGB},在图三中用户A和用户B消耗了同等dominant resource;用户A的dominant share为4x/18,用户B的dominant share为3y/9。所以DRF分配可以通过求解以下的优化问题来得到:
max(x,y) #(Maximize allocations)
subject to
x + 3y <= 9 #(CPU constraint)
4x + y <= 18 #(Memory Constraint)
2x/9 = y/3 #(Equalize dominant shares)
最后解出x=3以及y=2,因而用户A获得{3CPU,12GB},B得到{6CPU, 2GB}。
HierarchicalDRF核心算法实现在Src/main/allocator/mesos/hierarchical.cpp中HierarchicalAllocatorProcess::allocate函数中。
概况来说调用了三个Sorter(quotaRoleSorter, roleSorter, frameworkSorter),对所有的Framework进行排序,哪个先得到资源,哪个后得到资源。
总的来说分两大步:先保证有quota的role,调用quotaRoleSorter,然后其他的资源没有quota的再分,调用roleSorter。
对于每一个大步分两个层次排序:一层是按照role排序,第二层是相同的role的不同Framework排序,调用frameworkSorter。
每一层的排序都是按照计算的share进行排序来先给谁,再给谁。
这里有几个概念容易混淆:Quota, Reservation, Role, Weight
每个Framework可以有Role,既用于权限,也用于资源分配
可以给某个role在offerResources的时候回复Offer::Operation::RESERVE,来预订某台slave上面的资源。Reservation是很具体的,具体到哪台机器的多少资源属于哪个Role
Quota是每个Role的最小保证量,但是不具体到某个节点,而是在整个集群中保证有这么多就行了。
Reserved资源也算在Quota里面。
不同的Role之间可以有Weight
在allocator算法结束之后,便调用Master::Offer,最终调用Framework的Scheduler的resourceOffers,让Framework进行二次调度。同上面的逻辑就串联起来。
你可以写hook模块,讲代码插在很多关键的步骤,从而改写整个Executor或者Docker或者Task的启动的整个过程。
可以干预的hook的地方定义在mesos/hook.hpp中。
Class hook定义如下:
其中比较常用的是slavePrelaunchDockerHook,可以在Docker启动之前做一些事情,比如准备工作。
还有slaveRemoveExecutorHook,这个可以在executor结束的时候,做一些事情,比如清理工作。
当你有一种新的资源需要管理,并且每个Task需要针对这个资源进行隔离的时候,写一个Isolator就是有必要的了。
例如默认的容器并不能动态指定并限制任务硬盘使用的大小,所以mesos-containerizer就有了"disk/du"来定时查看任务使用的硬盘大小,当超出限制的时候采取操作。
Src/slave/containerizer/mesos/containerizer.cpp里面列出了当前支持的isolator,你也可以实现自己的isolator,并且通过modules参数load进去。
Isolator定义了以下函数
在运行一个容器的最后,会调用每一个isolator的isolate函数,通过这个函数,可以对资源进行一定的限制,例如写入cgroup文件等,但是对于硬盘使用量,其实没有cgroup可以设置,需要过一段时间du一些,这就需要实现watch函数,过一段时间查看一下硬盘使用量,超过后做一定的操作。
如果运行一个普通的容器,或者命令行,则不需要实现Executor,仅仅Mesos默认的Executor就能够实现这个功能。如果你需要在Executor里面做很多自己定制化的工作,则需要自己写Executor。
写一个Executor需要实现一些接口,最重要的就是launchTask接口,然后MesosExecutorDriver将这个Executor运行起来。
就像Framework一样,Executor也是通过protocol buffer协议和Mesos-Agent进行沟通,通过MesosExecutorDriver,你不需要关心协议的事情,仅仅需要实现接口即可。
容器服务是网易云提供的无服务器容器服务,让企业能够快速部署业务,轻松运维服务。容器服务支持弹性伸缩、垂直扩容、灰度升级、服务发现、服务编排、错误恢复及性能监测等功能。点击可免费试用。
更多网易技术、产品、运营经验分享请点击。