体验docker+mesos+marathon

本文接上篇《在docker中安装php运行环境》,为了将docker集群化部署需要用到开源PAAS项目marathon,而marathon是基于开源分布式资源管理框架mesos实现的。

下面会记录我的实践过程,除了基本的部署外,会额外记录一下关键知识点的分析,希望帮助更多朋友来快速理解整套系统。

在你按照我的博客部署环境的过程中要注意如下几点,以便减少你遇到问题的可能性:

  • 严格按照次序执行命令
  • 不要无脑执行命令,理解每个步骤的用意
  • 关注外链里的内容是否与本博客一致,因为官方文档随时在更新,而我的博客并不会。

安装centos7虚拟机

毋庸置疑,服务器一般都是centos操作系统,所以我们要先虚拟一个环境出来。

下载centos7 minimal最小化版本(600M+)

下载地址:https://www.centos.org/download/,选择minimal完成下载。

下载virtualbox虚拟机

下载地址:https://www.virtualbox.org/wiki/Downloads,选择适合自己操作系统的下载。

将centos7安装到virtualbox

安装时记得将磁盘大小调整为20G,因为编译mesos会下载很多的依赖库,虚拟机默认8G完全不够。

修改虚拟机网络模式为桥接

这样虚拟机可以分配到物理局域网的真实IP地址,宿主机和虚拟机也可以互相访问

桥接

初始化centos7

因为minimal版本自带的工具很少,所以需要安装一下。

关闭防火墙

如果不关闭,后面docker容器做端口映射就会失败。

配置网卡

minimal默认网卡不会开机启动,因此无法上网,需要改一下配置把网络拉起来,参考:http://blog.csdn.net/think_ycx/article/details/50240757

替换国内YUM源

国外的太慢,换成国内的,这是可选的一步,参考:http://mirrors.163.com/.help/centos.html

更新YUM源

安装ifconfig

minimal没有这个命令,我们要用它看虚拟机的IP地址,安装一下:

安装killall

minimal没有这个命令,安装一下:

安装vim

安装docker

一键安装

从daocloud上下载安装脚本,一键完成安装。

镜像加速

国外的docker hub镜像被墙的痛不欲生,配置一下使用daocloud镜像加速器,参考这里:https://www.daocloud.io/mirror#accelerator-doc

开机启动docker

第一个命令是开机启动,第二个是立即启动。

确认docker正常

运行docker ps,docker images,可以正常访问即可。

安装zookeeper

mesos和marathon都使用zookeeper选主,我们在虚拟机里模拟部署一个3实例的zk集群。

官方文档可以参考:https://zookeeper.apache.org/doc/r3.4.9/zookeeperStarted.html,我的部署安装如下:

拷贝3份zk

修改3份zoo.cfg配置

三个配置都拷贝自conf/zoo_sample.cfg,主要修改:

  • dataDir用来分别存储各自的数据
  • clientPort用来分别响应客户端的连接(因为3个zk都部署在一个虚拟机内,所以要区分一下端口)
  • server.x来定义zk集群的3个实例的id和通讯端口

配置3份myid

用于告知每个zk实例自己的ID

配置启动脚本

启动zookeeper

验证zookeeper

进入zookeeper1目录,执行zkCli.sh看看是否可以访问成功:

安装mesos

安装依赖

参考https://mesos.apache.org/gettingstarted/中的CentOS 7.1部分,逐条执行即可,你将会体会到替换yum源是多么的有先见之明。

替换maven源

maven的配置在/etc/maven/settings.xml,具体修改方法参考:http://dreamlikes.cn/archives/584,因为编译mesos要用到maven安装很多jar包。

编译安装mesos

参考https://mesos.apache.org/gettingstarted/的Downloading Mesos部分下载源代码,以及Building Mesos (Posix)部分编译与安装mesos。

整个编译过程耗时很长,需做好心理准备,中途任何报错或者卡住都请强制退出,重新执行命令,不要干等。

配置启动脚本

创建/root/mesos目录,创建master1-3和agent1一共4个子目录,用于作为mesos的运行目录。

mesos-mater

之后编写srtart_master.sh,它启动包含3个master实例的高可用master集群:

其中HOSTNAME=部分的IP需要修改为你虚拟机的网卡IP,参数均为必填,否则无法正常工作,各个命令行参数解释如下:

  • advertise_ip:影响master在zk中保存的IP名称,会影响其他从zk中获取master地址的模块。
  • hostname:一方面影响master在zk中保存的host名称,会影响其他从zk中获取master地址的模块;另外会影响mesos webui的http jsonp调用地址,必须修改才能正常访问webui。
  • port:master的对外服务地址,因为在一台虚拟机配置了3个master实例,因此需要区分端口
  • quorum:我们配置了3个master,mesos-master用zk选出leader负责集群调度,而集群状态持久化是通过replica log在多个master之间同步的,master内部直接实现paxos选举算法来选leader负责replica log的写入,因此mesos-master当前需要明确告知master集群的”大多数”是2,这也是为什么master一定要部署3台而不是2台,这是为了满足pasox算法硬性要求(足够产生大多数,从而选举出leader)。
  • zk:master使用zk选主,只有leader master才能进行集群调度。(注意调度的leader和replica log的leader是2个不同目的的选主,实现方式也不同)
  • work_dir:工作目录,会存储数据(主要是replica log记录集群的状态信息)。
  • log_dir:日志目录

现在启动master,但是当前集群还没有启动agent,我们可以通过mesos master的webui访问集群状态:

打开浏览器访问虚拟机中的mesos-master,http://虚拟机IP:5050,可以看到下面这样的图片,正常你应该看到0条agent记录(我截图时已经把agent跑起来了):

webui

mesos-agent

很多配置项和master是通用的,别忘了配置HOSTNAME:

  • advertise_ip和hostname:依旧是给其他模块看的,如果登记错误别人是无法访问到agent的,必须配置。
  • port:mesos-agent的服务端口。
  • work_dir:工作目录,存储各类数据。
  • master:mesos-master在zk上的地址,agent从zk获取master地址并连接过去进行后续交互。
  • log_dir:日志存储路径。
  • containerizers:配置agent支持mesos原生namespace+cgroup隔离以及直接使用docker进行资源隔离两种方式。

现在启动agent,你将会和我一样在上面图片中看到一个agent,说明master已经发现了agent:

注意:一台服务器只能启动一个agent,否则多个agent会将资源重复计算,比如原本1个cpu的机器启动2个agent会认为总资源有2个cpu,这是不行的。

相关文档

建议看一下这个mesos架构相关的介绍,另外还有官方对架构的介绍,上面的启动参数都可以在官方看到介绍,可以自己研究研究。

marathon

mesos集群提供资源管理能力,但是需要应用者自己开发framework对接到mesos上,才能将资源利用起来。

理解mesos很重要,简单的来看mesos像是一个分布式操作系统,mesos-master是内核,而mesos-agent是分布式的cpu+mem+disk硬件资源池,每个agent上面可以跑若干的任务可以看做是进程。

mesos上开发framework需要包含scheduler和executor两部分,mesos本身不提供scheduler,但是提供原生的2个通用executor:一个是基于linux namespace/cgroup实现资源隔离的mesos-executor,一个是基于docker实现资源隔离的mesos-docker-executor。无论你向mesos提交什么任务,mesos都会将它们放入一个资源隔离的容器中执行,因此通常我们只需要编写scheduler负责发起任务即可,具体任务执行则通过mesos-agent内置的executor就可以完成,任务即可以是shell命令,也可以是一个docker容器。

marathon项目就是这样一个scheduler,它和mesos交互完成任务(app)的部署与扩容,并且当任务宕机它能够快速的启动新的任务进行补充,用户只需要通过web界面操作即可满足绝大多数需求。

下载marathon

它是scala编写的,属于jvm系语言,不需要编译,下载方式参考:https://mesosphere.github.io/marathon/docs/

不过官方的域名在国内被墙无法解析,可以用下面这个命令直接访问IP完成下载:

配置启动脚本

为了实现marathon scheduler的高可用,我们需要让marathon部署2个以上,它们也是通过zk选主的。

拷贝2份marathon如下:

编辑start_all.sh启动脚本:

别忘记改HOSTNAME为虚拟机的网卡IP,各个参数如下:

  • hostname:别人可以路由到自己的host,和mesos-master/mesos-agent一样重要,必须配置。
  • http_port:对外服务地址,主要是用于访问web界面。
  • master:marathon scheduler需要去zk中找到mesos-master在哪个地址,连接上去才能工作。
  • zk:marathon本身也是多实例高可用,需要基于zk选举出一个leader来负责工作。

启动marathon:

现在可以访问marathon的web界面,浏览器打开:http://虚拟机IP:8080即可:

marathon

正如图中看到的那样,接下来我要创建这2个app,一个基于mesos的namespace/cgroup资源隔离机制,一个基于docker的资源隔离机制,分别看一下marathon+mesos大概是怎么工作的。

部署mesos-containerizer

这种任务在mesos-agent那里会启动mesos-executor,它又会启动mesos-containerizer用于虚拟化一个容器环境执行我们的任务。

下面我通过配置一个死循环的shell命令,提交给marathon来创建app实例,叫做mesos-app:

mesos-app

我们为每个实例分配了0.1个cpu,32M内存,100M磁盘,共生成2个实例。

接下来,mesos容器很快被启动了2个实例,并且可以通过查看log看到打印出hello:

mesos-log

我们也可以方便的通过scale Application按钮扩容:

mesos-scale

这里有2点要注意:

  • 一个是marathon会保证总是有N个实例在运行,因此如果你传的shell命令是执行完立即退出的话,你会发现app的实例不停的在启动和关停。
  • 另外,由于我们的shell命令没有监听端口对外服务,所以我们也没有配置health check健康检查,容器虽然在running但是状态是unknown。

部署docker-containerizer

这种任务在mesos-agent处会被mesos-docker-executor处理,进一步将docker启动命令交给docker daemon守护进程,从而拉起一个docker容器运行。

我将直接使用docker hub上的nginx官方镜像,运行它可以创建一个可访问的nginx容器。

我们知道docker默认采用bridge网络模式,它不会分配公网IP并且只能和宿主机通讯,为了令外部用户可以访问到容器中的nginx,我们需要在创建app的时候指定让docker进行端口映射,将容器内的80端口映射到宿主机上的随机端口即可。

首先配置app资源:

docker-1

配置docker镜像名称为nginx(来自docker hub),网络模式为bridge桥接,这样容器内端口对宿主机不可见,不会造成不同app端口冲突:

docker-2

配置端口映射,将容器内的80端口映射到宿主机的随机端口。(注意下方提示:servicePort只能在JSON mode编辑,用处后面负载均衡部分会讲)

docker-3

为这个任务配置一个label,这是mesos本身支持的参数,也是为后面做负载均衡用的。

docker-4

配置健康检查,定期检查80端口的http服务:

docker-5

点击创建应用,可以看到实例均以启动,并且处于健康状态:

docker-6

具体看一下marathon提交给mesos框架的task配置:

docker-7

选项的含义如下:

  • labels:是我们给任务(task)分配的标签,HAPROXY_GROUP用于后续做负载均衡用。
  • type:容器类型是docker
  • image:docker的镜像是nginx
  • network:采用bridge网络模式
  • portMappings:端口映射,将容器内的containerPort=80映射到宿主机的hostPort=0,也就是宿主机的随机端口。
  • servicePort:用于后续负载均衡用的haproxy监听服务端口,这里启动的容器并不会去使用这个端口,仅仅是将该信息传递给mesos而已。

运行原理

ps aux看一下宿主机内的进程:一共有2个mesos-executor,它们启动了2个mesos-containerizer容器环境,执行我们的shell命令。另外有2个mesos-docker-executor,它们启动了2个docker容器环境,执行我们的nginx镜像。

再看一下docker ps,会发现mesos的确在docker daemon中启动了2个容器,并且完成了端口的映射,具体映射到宿主机的端口也可以看到:

访问容器中的Nginx

分别访问2个容器,可以正常访问服务:

问题来了:

  • 端口是随机映射到宿主机的,那么外部用户就不知道该用哪个端口才能访问到nginx。
  • marathon启动容器是根据资源选择agent的,那么容器不一定部署在哪个ip上。

这些问题的解决就需要用到下面的marathon-lb负载均衡了。

marathon-lb

这是为marathon框架提供负载均衡的中间件,它监听marathon上的容器启停事件变化,利用haproxy将请求转发到对应的容器实例中,每当容器发生变化(比如退出或者新增),它就会重新生成haproxy配置并触发haproxy reload。

以往传统物理机部署,我们做反向代理也会常用到haproxy,并且配合keepalived实现主备防止单点haproxy故障,在marathon里也是一样的道理,只不过haproxy被marathon-lb包装了一下,可以在这里了解

另外一种mesos的负载均衡手段是基于mesos-dns的,原理与marathon-lb类似,只不过它是直接监听mesos的事件并生成DNS记录,提供标准DNS协议的查询接口罢了,可以在这里了解,这里就不做试验了。

配置启动脚本

创建/root/marathon/marathon-lb目录并编写start.sh脚本,官方建议我们在docker中运行mesosphere/marathon-lb这个docker镜像,这样我们就不用去安装marathon-lb需要的运行环境了(很麻烦,要haproxy,要python等等…)。

  • -e:传递一个环境变量到docker容器中,这个PORTS=9090会被haproxy获取并监听,后面访问该端口可以查看haproxy的状态和实时配置文件。
  • –net:此前docker容器都使用的bridge模式,这样容器的IP是虚拟IP,并且PORT在宿主机不可见,只能通过端口映射访问。而这里指定host模式,容器不会为网络设置namespace隔离,因此容器将共享宿主机的IP,并且PORT在宿主机也可以直接访问。这样做的目的很简单,我们的haproxy希望外部用户直接访问,就像传统部署在物理机上一样,用镜像的目的只是简化环境安装而已,所以选择了host网络模式。
  • –group:指定组,也就是之前在marathon配置的labels:HAPROXY_GROUP=docker-haproxy,这样启动的marathon-lb将只反向代理配置了该label的容器,其他没有配置group或者配置了其他group的app是无法通过该haproxy访问的,也就是说:该marathon-lb只监听与维护关于这个group的haproxy配置。
  • –marathon:指定marathon服务的地址列表,marathon-lb会连接marathon用于监听event bus,也就是各类容器变动事件,从而动态的修改haproxy配置,实时热加载变化。

通过marathon-lb访问docker-app

首先,我们可以访问http://虚拟机IP:9090/_haproxy_getconfig来查看当前的haproxy配置,可以观察到如下信息:

最下方2个server其实就是我们2个nginx服务的可访问地址,当前haproxy对外监听10000端口,反向代理到172.18.9.71:31579和172.18.9.71:31936两个地址。那么10000端口是怎么得来的呢?回到之前的图片中,观察marathon配置中的servicePort项,它用于告知marathon-lb的haproxy应该用哪个端口对外服务,默认marathon会为每个app分配独一无二的servicePort配置,确保不同app间不会重复,当然我们也可以在创建app时显式编辑json文件配置这个端口的值。

现在我们访问marathon-lb:

成功访问到nginx!为了验证marathon-lb的服务发现能力,我们直接在docker命令行杀死其中一个容器并再次查看haproxy配置:

杀死后,我们访问haproxy的9090端口再次查看最新的配置文件:

不出意料,只剩下一个server了,服务仍旧可以正常访问。用不了几秒,marathon发现了该容器已死亡,会再次拉起一个容器补充上来:

并查看haproxy最新配置:

对比发现,31579端口的nginx始终正常服务,而新启动的nginx容器的映射端口已经随机变化为了31054。

关于marathon-lb还有另外一个用法值得一提,就是haproxy支持http vhost,需要通过给app配置label:HAPROXY_{n}_VHOST。其中将n替换为vhost,就可以通过haproxy的80/443标准端口访问到具体的app了,和Nginx vhost没有区别,具体参考:这里,当然servicePort仍旧可以访问到app。

后话

之前百度也一直在用类似的PAAS平台,对比marathon来看的话,发现原生marathon主要缺少几个功能:

  1. 发布升级代码:百度内部支持持续集成,每次可以指定发布的项目版本,新老容器会平滑过渡。而在marathon里只有一个重启app的功能,必须通过某个手段告知容器本次应该拉取哪份代码,或者干脆重新打包镜像发布到docker hub再重启容器(创建app时需要勾选每次强制拉取镜像)。
  2. 不支持回滚:上线有bug想回滚到前一个代码版本,同样也不支持。
  3. 跨机房部署:百度内部支持多机房,可以配置每个机房启动几个实例。在marathon里要实现这个功能,一方面要通过为mesos-agent配置attribute来标记所属机房,另外一方面需要使用marathon的constraints功能来控制app只能运行在符合指定attribute的mesos-agent上。为了实现多机房部署,我们需要为每个机房创建一个app,并通过为app配置constraints的方式来实现。

当然,这些问题可能都已经有解决方案了,只是我还没有去扩展研究。另外,后续我会单独把基于marathon+mesos的分布式定时任务chronos部署一下。

补充阅读

为了更深刻的理解mesos+marathon,除了亲自部署和探索整个过程外,也建议读一下这篇博客:点这里,它主要讲解了mesos的框架执行原理和关系,有利于更好的理解系统。

祝玩的愉快。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~