在上述方案中,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
你可以写hook模块,讲代码插在很多关键的步骤,从而改写整个Executor或者Docker或者Task的启动的整个过程。
可以干预的hook的地方定义在mesos/hook.hpp中。
Class hook定义如下:
其中比较常用的是slavePrelaunchDockerHook,可以在Docker启动之前做一些事情,比如准备工作。
还有slaveRemoveExecutorHook,这个可以在executor结束的时候,做一些事情,比如清理工作。
第四、创建Isolator
当你有一种新的资源需要管理,并且每个Task需要针对这个资源进行隔离的时候,写一个Isolator就是有必要的了。
例如默认的容器并不能动态指定并限制任务硬盘使用的大小,所以mesos-containerizer就有了"disk/du"来定时查看任务使用的硬盘大小,当超出限制的时候采取操作。
Src/slave/containerizer/mesos/containerizer.cpp里面列出了当前支持的isolator,你也可以实现自己的isolator,并且通过modules参数load进去。
Isolator定义了以下函数
在运行一个容器的最后,会调用每一个isolator的isolate函数,通过这个函数,可以对资源进行一定的限制,例如写入cgroup文件等,但是对于硬盘使用量,其实没有cgroup可以设置,需要过一段时间du一些,这就需要实现watch函数,过一段时间查看一下硬盘使用量,超过后做一定的操作。
第五、写一个Executor
如果运行一个普通的容器,或者命令行,则不需要实现Executor,仅仅Mesos默认的Executor就能够实现这个功能。如果你需要在Executor里面做很多自己定制化的工作,则需要自己写Executor。
写一个Executor需要实现一些接口,最重要的就是launchTask接口,然后MesosExecutorDriver将这个Executor运行起来。