深入解析DC/OS 1.8
– 高可靠的微服务及大数据管理平台
大家好,欢迎大家参加这次DC/OS的技术分享。
先做个自我介绍,刘超,Linker Networks首席架构师,Open DC/OS社区贡献者,长期专注于OpenStack, Docker, Mesos等开源软件的企业级应用与产品化。
从事容器方面工作的朋友可能已经听说过DC/OS,往往大家误解DC/OS就是marathon + mesos,其实DC/OS包含很多的组件,DC/OS 1.8九月份发布了,此次分享给大家做一个介绍。
所谓的DC/OS,全称为数据中心操作系统,其基本的思想就是使得运维人员操作整个数据中如操作一台电脑一样。
DC/OS使用了哪些技术可以做到这一点呢?
如图,左面是普通的Linux操作系统,右面是DC/OS,在这里做了一个对比。
无论是哪种操作系统,都需要管理外部的硬件设备,最重要的四种硬件资源即CPU,内存,存储,网络。
最初使用汇编语言写程序的前辈,还是需要指定使用那些硬件资源的,例如指定使用哪个寄存器,放在内存的哪个位置,写入或者读取那个串口等,对于这些资源的使用,需要程序员自己心里非常的清楚,要不然一旦JUMP错了位置,程序就无法运行。这就像运维数据中心的一台台物理机的前辈一样,那个程序放在了哪台机器上,使用多少内存,多少硬盘,都需要心里非常的清楚。
为了将程序员从对硬件的直接操作中解放出来,提升程序设计的效率,从而有了操作系统这一层,实现对于硬件资源的统一管理。某个程序使用哪个CPU,哪部分内存,哪部分硬盘,程序只需要调用API就可以了,由操作系统自行分配和管理,其实操作系统只做了一件事情,就是调度。对应到数据中心,也需要一个调度器,将运维人员从指定物理机或者虚拟机的痛苦中解放出来,这就是Mesos。Mesos即使数据中心操作系统的内核。
在使用操作系统的时候,我们可以开发驱动程序来识别新的硬件资源,可以开发内核模块(例如openvswitch.ko)来干预对于硬件资源的使用,对于Mesos,同样可以开发isolator来识别新的硬件资源例如GPU,也可以开发Executor来干预资源的使用。
在内核之上,就是系统服务,例如systemd,是用来维护进程运行的,如果systemctl enable xxx,则保证服务挂掉后自动重启。对于DC/OS,保持服务long run的是marathon,但是仅仅只有marathon还不够,因为服务是启动在多台机器上的,而且服务之间是有依赖关系的,一个服务挂掉了,在另外一台机器启动起来,如何保持服务之间的调用不需要人工干预呢?这需要另外的技术,称为服务发现,多是通过DNS,负载均衡,虚拟机IP等技术实现的。
使用操作系统,需要安装一些软件,于是需要yum之类的包管理系统,使得软件的使用者和软件的编译者分隔开来,软件的编译者需要知道这个软件需要安装哪些包,包之间的依赖关系是什么,软件安装到什么地方,而软件的使用者仅仅需要yum install就可以了。DC/OS就有这样一套包管理软件,和其他的容器管理平台需要自己编译Docker镜像,自己写yml,自己管理依赖不同,DC/OS的软件使用者只需要dcos package install就可以安装好软件了,软件的配置,节点数目,依赖关系都是有软件编译者设置。
在最外层,DC/OS像普通的操作系统一样,有统一的界面和命令行。通过它们,可以管理安装包,管理节点,运行任务等。DC/OS不仅仅是运行容器的平台,如果仅仅运行容器,就是容器管理平台,而非数据中心操作系统。通过DC/OS,你可以在每台机器上运行一个命令来进行统一的配置,而无需登录到每台机器上去。你可以运行容器应用和大数据分析应用并共享资源,并且可以相互发现,这更加符合现代互联网应用,微服务和大数据不可分割。而且Mesos的架构非常开放,你可以通过开发Framework, Executor, Modules, Hooks等,轻松干预微服务或者大数据任务的执行过程,来定制化你的应用。这也符合操作系统微内核的概念。
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
在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,你不需要关心协议的事情,仅仅需要实现接口即可。
下面的图描述了DC/OS的部署架构图:
在DC/OS看来,所有的节点分为三个区域,一个是管理区域,主要处理对于服务的管理方面的操作,如增删查改,启停扩缩等。为了高可用,Master节点可以是多个,在多个Master节点之前,需要有一个负载均衡器。第二个是对外服务区域,也即外界能够访问DC/OS内部的服务的区域,这个区域里面的服务多为对外的Nginx之类的,也会有marathon-lb来做外部的负载均衡器,所有对外服务区域的节点之外还需要一个负载均衡器。第三个区域是内部服务区域,用于部署内部服务,如数据库,消息总线等,这些内部节点不能对外访问。
AdminRouter是一个反向代理,正是它将对外的区域和对内的区域完全隔离开来,在admin router之外,可以通过公网访问,在admin router之内全部是私网地址,这样提供了安全的统一访问机制。
安装完毕Open DC/OS之后,安装一个dcos的命令行工具,通过这个工具可以ssh到master的节点上。
在这个节点上/etc/systemd/system路径下面有三个systemd的service,Open DC/OS的所有组件都是用systemd进行管理的。
可以看到dcos-adminrouter.service是指向/opt/mesosphere/packages下面的一个路径,Open DC/OS的所有组件都是安装在这个路径下面的。
在/opt/mesosphere/packages/adminrouter--cee9a2abb16c28d1ca6c74af1aff6bc4aac3f134/nginx/conf这个路径下面,有一个文件nginx.master.conf,打开这个文件,就能看到熟悉的对于nginx的配置。
从这个配置文件可以看出,所有对内的访问marathon的页面,访问mesos的页面,都是通过leader.mesos进行,这个域名是mesos-dns给出的,对应的是内部的IP地址,如果从外部访问marathon或者mesos的页面,则必须通过admin router,通过http://admin-router-external-ip/marathon或者http://admin-router-external-ip/mesos来访问。
对于数据中心操作系统来讲,服务发现和负载均衡是最最核心的功能,只有有了这些功能,才能使得服务的物理布局,服务之间的依赖关系,服务挂掉之后的自动修复不需要用户关心,才能使得用户像用一台电脑一样使用整个数据中心。
如果服务之间的相互调用不使用IP地址,而使用域名的话,问题会简单很多。
如图所示,对于Mesos上运行的每一个Task,Mesos-DNS都可以通过调用Mesos-Master的API得到,并且为每个Task分配一个域名和IP的对应项。如果一个Task需要访问另一个Task,则需要配置域名即可,无论Task如何挂掉,如何分配到其他的节点上运行,域名都不会变,当然Task的IP可能会变,但是不用担心,Mesos-DNS会更新它。每个Mesos-Agent只需要配置/etc/resolv.conf指向mesos-dns就可以了。
当一个Task运行的时候,Mesos-DNS会创建一个域名
另外
使用DNS虽然可以实现服务的自发现,但是不容易实现服务的负载均衡和弹性伸缩,而marathon-lb实现了这些功能。
Marathon-lb是一个基于haproxy的负载均衡器,但是它会监听marathon event bus,每当注册到marathon-lb上的服务数目变化的时候,marathon-lb也会自动更新haproxy的配置文件,从而实现负载均衡。Marathon-lb可以如图中实现对外的负载均衡,也可以实现对内的服务之间相互调用的负载均衡。
Marathon的安装可以在界面中universe里面搜索marathon-lb安装,也可以通过命令行执行dcos package install Marathon-LB进行安装,默认安装的对外的负载均衡器。
我们在服务里面创建如下的应用:
在这个应用里面,servicePort为10000则说明我们注册到marathon-lb上的外部端口为10000, labels里面写的是external,也即注册到外部的负载均衡器上。
这个时候,我们访问public slave上的10000端口,就能看到启动的nginx的页面http://54.254.148.129:10000/,内部其他的应用可以通过http://marathon-lb.marathon.mesos:10000来访问这个nginx
如果我们访问public slave上的haproxy的配置页面http://54.254.148.129:9090/haproxy?stats,可以看到如下的映射关系。
对外marathon-lb监听10000端口,对内映射为10.0.1.78上的20215端口,如果我们从服务页面上查看,的确启动的nginx是监听20215端口的。
接下来我们部署marathon-lb-autoscale,它监控haproxy,发现RPS(request per seconds)超过一定的数目,就对应用进行弹性扩展。
接下来,我们部署应用siege向nginx发送请求
如果我们看haproxy的stats页面,发现已经有请求发过来了。这个时候我们增加siege到10,给nginx加压。
过一段时间就会发现marathon-lb-autoscale已经有动作了。
将一个nginx变成8个nginx
当我们将siege从10个变回0个的时候。
Minuteman是一个内部的东西向的负载均衡器,可用于设置VIP,多个实例使用同一个VIP来进行负载均衡。
在创建服务的时候,选择Load Balanced,则下面会出现一行地址:nginxdocker.marathon.l4lb.thisdcos.directory:80,这个就是minuteman分配的VIP。
当服务创建好了之后,通过curl http://nginxdocker.marathon.l4lb.thisdcos.directory:80就可以访问这个服务,但是我们如果ping这个域名却是不通的,而且对于的IP地址也是很奇怪的IP地址,这个IP就是VIP.
这是怎么做到的呢?minuteman的load balancer是基于Netfilter的,在dcos的slave节点上,我们能看到多出来了四个iptables规则。其中前两个规则是在raw表里面的,后两个规则是在filter表里面的。
根据iptbles的规则raw表中的规则会被先执行,一旦到达了filter表的minuteman的包就都过滤掉了。
NFQUEUE的规则表示将对于包的处理权交给用户态的一个进程。--queue-balance表示会将包发给几个queue,然后用户态进程会使用libnetfilter_queue连接到这些queue中,将包读出来,根据包的内容做决策后放回内核进行发送。
在每一个Mesos-Agent节点上都运行这一个minuteman的进程,监听这些queue,我们可以通过访问API查看VIP的映射关系,curl http://localhost:61421/vips。
我们可以看到VIP的11.112.175.214后面跟着两个节点10.0.1.78:27003和10.0.1.78:4989,正好对应nginx的两个实例。
DC/OS是基于Mesos的,Mesos的灵活框架机制可以使得DC/OS既能够部署容器,也能够部署大数据框架,大数据框架在不运行任务的时候,几乎不占用资源,从而真正实现微服务和大数据框架的资源共享。
前面我们部署容器的时候,都是自己准备marathon的json进行部署的,这就需要使用服务的人和设计服务的人同样的专业。
DC/OS采用了一种package管理机制,将运行一个微服务或者框架所需要的各种配置制作成模板,模板由专业人士制作好上传到package repository,使用者就不需要那么专业,只要运行dcos package install安装即可。
Mesosphere提供了官方的package repository,名为universe,地址为https://universe.mesosphere.com/repo,在github上可以找到对应的代码https://github.com/mesosphere/universe。
对于一个package,往往包含下面的部分:
所有的这些配置都像模板一样已经预先写好,安装的时候界面上一点,或者一行命令就安装好了。
当然你如果点击Advanced Installation,则所有的配置都可以定制化
就像yum里面一样,将mysql-server的yum包的制作者和mysql的使用者分开,普通用户作为使用者,不需要了解太多的细节,用就是了。
如果想在数据中心里面使用package管理,可以生成自己的local universe,里面放入自己的应用,只要专业人士设计一次,便可以多次使用。也可以一次安装多个软件形成一个group,里面包含微服务的,也包含大数据的,两者可以通过服务发现相互访问。
我们在这里先安装一个spark的软件
最初安装完毕spark,却发现只有一个docker
Spark不是一个集群计算框架吗,怎么会只有一个Docker呢?这就是mesos对大数据框架管理的特殊之处。在spark不运行任务的时候,就仅仅占用这一个docker,其实是一个框架。
安装过程如图所示:
真正运行spark任务的时候,才会有其他占用资源的任务被创建出来。
Spark运行过程如图:
正是这种模式,才实现微服务和大数据框架的共享资源,与此相对应的是使用Docker来部署spark集群,然后集群自管理,不归mesos管理。这样在创建spark集群的时候,就需要指定spark worker占用的资源,比如16G,然而这16G资源则无论spark是否在计算,都会被占用,都不会被其他的框架使用。
对于最新的DC/OS 1.8,有一个博客https://dcos.io/blog/2016/introducing-dc-os-1-8-ga/index.html描述了最新的功能。
其中第一个重要的功能为Mesos 1.0 and the Universal Container Runtime,也即可以使用mesos-containerizer来运行Docker的镜像了。这也是DC/OS对于容器的管理越来越独立的体现。
我们在mesos-agent所在的机器上可以查看
mesos-agent的配置在路径/opt/mesosphere/packages/mesos--19a545facb66e57dfe2bb905a001a58b7eaf6004下面,在/opt/mesosphere/packages/mesos--19a545facb66e57dfe2bb905a001a58b7eaf6004/dcos.target.wants_slave/dcos-mesos-slave.service里面是mesos-slave的启动参数的设置,通过mesos的文档,我们知道对于mesos的参数可以使用环境变量进行设置。
在文件/opt/mesosphere/etc/mesos-slave-common中配置了大量的mesos-agent的参数
默认的mesos-containerizer的隔离只包括cpu和memory,然而在最新的mesos版本里面,多了provisioner这一层,在上面的配置里面隔离了MESOS_ISOLATION=cgroups/cpu,cgroups/mem,disk/du,network/cni,filesystem/linux,docker/runtime,docker/volume,从而可以启动docker的镜像了。
第二个最重要的功能是CNI, container network interface。
CNI要工作需要三部分:
首先DC/OS不需要外置的IPAM,而是由mesos-master的replicated_log负责管理分配IP地址,Mesos需要启动的时候,载入overlay network的modules。
在路径/opt/mesosphere/etc/mesos-slave-modules下面有文件overlay_slave_modules.json
其次需要载入CNI isolator,这个在MESOS_ISOLATION这个环境变量里面已经配置了。
最后还需要navstar服务来实现跨节点之间的IP互访问
每个mesos-agent的机器上都有opt/mesosphere/packages/navstar--589afdaef03114a17576ee648ae433a052f7a4b9/,都会运行一个navstar进程。
每个机器上都会创建网卡d-dcos,如果Docker容器使用CNI获取IP的容器都Attach到这个网卡上,而非docker0上。
每个机器上都会创建网卡m-dcos,如果mesos容器使用CNI获取IP的容器都Attach到这个网卡上。
每台机器的d-dcos和m-dcos的网段都不同。
每台机器都会创建一个vtep1024的网卡,作为VTEP,背后是vxlan。
每台机器都会创建默认的路由表,从本节点连接到其他的节点默认走vtep1024这个网卡。
对DC/OS的网络的配置在/opt/mesosphere/etc/dcos/network/cni路径下
为了试验这两个新的功能,我们首先创建一个使用CNI的Mesos容器,但是启动的是Docker的Image nginx
在日志里面,打印出来容器的IP地址是m-dcos网段的。
然后我们再启动一个使用CNI的Docker容器
从日志我们看出,配置的IP是d-dcos网段的,而非docker0网段的。
从Mesos上我们看出这两个容器是在两个节点上的
登入Docker的容器,ping另外一个CNI的mesos的IP是没有问题的。