基于 Mesos 和 Docker 构建企业级 SaaS 应用 Elasticsearch as a Service

iceperson 3年前
   <p><strong>1. 前言</strong></p>    <p>Mesos从0.23版本开始提供了动态预留和持久化卷功能,可以利用本地磁盘创建数据卷并挂载到Docker容器内,同时配合动态预留的功能,保证容器,数据卷,机器三者的绑定,解决了Docker一直以来数据落地的问题。本文介绍了去哪儿网的ops团队利用Mesos/Docker开发的Elasticsearch(ESAAS)平台的建设的过程,以及实践过程中的经验总结。</p>    <p><strong>2. 背景</strong></p>    <p>从2015年底开始, 去哪儿网(以下简称Qunar)的Elasticsearch(以下简称ES)需求量暴增,传统的ES部署方式大部分是以kvm虚机为ES节点, 需要提前创建好虚拟机, 然后在每台机器上部署ES环境,费时费力, 并且最大的问题是集群扩容和多个集群的管理需要大量的人力成本。 随着ES节点/集群越来越多, 这种方式 已经严重加剧了业务线的使用/维护成本, 并且也不符合OPS的运维标准。 因此我们就开始考虑,是不是能将ES做成一个云化的服务, 能够自动化运维,即申请即用, 有条件的快速扩容/收缩资源。</p>    <p> </p>    <p>近几年Apache Mesos和Docker都是大家讨论最多的热门话题, 尤其是对于做运维的人来说,这些开源软件的出现, 使得运维人员对机器集群的管理,资源的管理,服务的调度管理都大大的节省了成本,也使得我们的预想成为可能。借助Mesos平台使我们提供的ES集群服务化,产品化。总的来说,我们的目标有这几点:</p>    <p>● 快速集群构建速度</p>    <p>● 快速扩容和快速迁移能力</p>    <p>● ES使用/运维标准化</p>    <p>● 良好的用户交互界面</p>    <p> </p>    <p>3.<strong>技术调研/选型 </strong></p>    <p>我们调研了目前的三个产品/框架,从中获取些功能方面的参考:</p>    <p>● Elastic Cloud</p>    <p>● Amazon Elasticsearch</p>    <p>● Elasticsearch on Mesos</p>    <p>前两者是收费的服务并且没有开源,我们从它们的文档和博客中借鉴到了许多实用的功能点,可以解决我们目前阶段的问题。我们根据这两款商用的ES服务,结合内部的运维体系整理了需要实现的功能点:</p>    <p>● 集群统一管理</p>    <p>● 集群资源quota限定</p>    <p>● 数据持久化存储</p>    <p>● 数据节点和节点资源快速水平/垂直扩容easily scale out</p>    <p>● 完整的集群监控和报警</p>    <p>● 完整的平台监控和报警</p>    <p>● 统一的集群详细信息和配置中心</p>    <p>● 集群行为和外围插件的自助配置</p>    <p>● 集群的发布和配置管理</p>    <p>第三个是一个开源的Mesos框架,它的executor就相当于一个ES Node节点, 一个框架服务就是一个ES集群,框架内部处理节点间的相互发现。它支持自动化部署、集群的水平扩容、集群状态实时监控以及查看集群信息,如shard/indices/node/cluster等,有web UI。 支持数据多副本容错等, 但是也有局限,比如集群维度的数据持久化存储,不能做到数据持久化到固定的节点,由于调度的策略,我们事先是无法预知节点在重启后被调度到集群中的哪些机器上去, 所以,数据的持久化显得尤为重要,例如,在集群重启这种情况下, 如若不能保证ES节点在原先的节点上启动, 那对于集群数据和集群服务都是毁灭性的,因为重启之后, ES节点都“漂移”了,一致性不能得到任何保证。第二个局限就是无法完全自助化ES集群配置, 比如无法配置不同角色的节点,无法自定义数据存储行为,包括索引分片,副本数量之类, 无法自定义安装插件,无法自助script的使用等,不满足业务线的使用需求。 除此之外, 还有一些, 比如ES节点纵向扩容(为某个实例添加更多的CPU和内存)、没有Quota管理等未能支持的局限。  这就和我们预期的功能有些差距,不能完全满足我们的需求。像这种自行实现一个调度框架的组织方式,缺少了许多灵活性,反而增加了我们许多开发的复杂程度,也使得我们的服务将会有较大局限。比如, 我们将来很可能的对服务的扩展和随着业务而出现的新的功能和组织方式。</p>    <p>考虑到上述因素,我们决定利用Marathon来部署ES集群。相比较于前三者的局限性,Marathon是一个比较成熟并且比较通用的资源调度框架,并且对二次开发非常友好。尤其是1.0版本之后,增加了动态预留和对持久化卷的功能,这些都为ES节点数据的持久化存储提供了强有力的支持。 鉴于这些特性,,我们决定采用Mesos + Marathon + Docker的方式, 构建我们自己的ESAAS服务。</p>    <p><strong>4. 实施过程</strong></p>    <p>下面是我们的ESAAS整个系统的总体结构图,关于Mesos/Marathon这里不在这里过多的介绍了:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/92cee44fd548cc0fdfa096220733d1e9.jpg"></p>    <p style="text-align: center;">图 1 总体结构图</p>    <p>从总体结构图中可以看到首先我们看看如何解决Quota分配和集群隔离问题。可以看到, 整个系统分了好多层, 最底层是一个一个的物理机, 这些物理机由上层的Mesos来管理,他们形成一个集群, 也就是一个资源池,Mesos管理着这些资源,包括cpu, 内存,磁盘和端口等。 Mesos上层是Marathon框架,它主要负责任务的调度。也是ESAAS系统最重要的一层,我们称之为Root Marathon。,  它主要负责调度更上层的Sub Marathon。这样做主要是考虑功能的隔离,以及为了后续的Quota等功能的实现。</p>    <p>在构建ESAAS的过程中,我们的工作主要围绕着以下几个核心的问题:</p>    <p>● 数据可靠性</p>    <p>● Quota分配</p>    <p>● 集群的隔离</p>    <p>● 服务发现</p>    <p>● 监控与报警</p>    <p>● 部署自动化</p>    <p>● ESAAS Console</p>    <p><strong>如何解决Quota分配</strong></p>    <p>Mesos中有Role(角色)的概念,Role是资源限制的最小单位, Mesos根据它来设定每个特定的Role能使用的最大资源,这其实就是限定资源的Quota。我们借助这一特性实现资源的隔离和分配。这里不仅涉及了静态资源分配,即通过--resources配置,还涉及到了Mesos 0.27中的动态申请Quota功能。</p>    <p>Root Marathon不做资源的限制,即可以使用整个集群的资源。同时,我们会为每个Role都划定Quota,并用Root-Marathon为每个Role构建一个独享的Marathon,即图2中的Sub Marathon。 , 这样,每个Role可以使用自有的Sub Marathon在Quota限定范围内的资源,并且享有逻辑上隔离的命名空间和路由策略,更不用担心某一个Sub Marathon无节制的使用整个平台的资源。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e6d2a5b4794166be05233b1e2b6362cc.png"></p>    <p style="text-align: center;">图 2 Marathon嵌套示意图</p>    <p><strong>如何解决集群的隔离</strong></p>    <p>使用嵌套结构来组织Marathon,除了方便根据Role来设置Quota之外,还有一个作用就是实现ES集群逻辑上的隔离。</p>    <p>我们的每一个Sub Marathon都负责维护了一个或多个完整的ES集群,为每一个Sub Marathon分配一个Role/Quota, 等同于每一个ES集群的group动态划分资源池,系统的ES Group根据业务线来划定。group与ES集群的关系是一对多的关系,即每个ES group内包含了多套相互隔离的ES集群。ESAAS最终的服务单元就是这一个个Sub Marathon所承载的Elasticsearch clusters group服务。基于Mesos + Marathon体系, 我们所有的组件都是跑在Docker容器里面。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c6bea7b0ecea94debcfd3656dc5fea90.png"></p>    <p style="text-align: center;">图 3 线上Marathon环境截图</p>    <p>图4是ESAAS系统中一个单台物理机的结构快照。一台物理机运行多个ES节点实例,使用不同的端口来隔离同一台机器上的不同ES集群的实例间的通讯,利用Mesos的持久化卷隔离ES落地的数据。除了ES节点之外,机器上还有一些其他组件, 这些组件和ES共同协作来保证服务的稳定可靠。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5d6fc575e19ea9ac9a968c180edad3d2.png"></p>    <p style="text-align: center;">图4 物理机部署模块示意图</p>    <p>每一个Sub Marathon内部结构如图5,包含了ES masternode、ES slavenode、bamboo/haproxy、es2graphite、pyadvisor以及StatsD,构成一个完整的集群模块。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8fb703e5fcf0f38e678526ff3d05d416.jpg"></p>    <p style="text-align: center;">图5 ESAAS集群模块关系图</p>    <p><strong>如何解决服务发现</strong></p>    <p>下面看看我们是如何解决Elasticsearch集群内服务发现问题的。首先说明一下我们选择了ES的单播模式来构建集群,主要的目的是降低建群之间相互污染的可能性。我们的ES集群的标准配置是6个ES节点:3个masternode节点, 3个datanode节点。他们会作为两个不同的Marathon APP存在。每一个APP有三个task, 每个task是一个ES node(无论是masternode还是datanode) 。之所以要将这两种节点分开来作为两个APP,主要的原因就是, 这两种角色的节点会有不同的配置方式和不同的资源分配方式,master节点不做数据存储,起到集群HA的作用, 所以我们为它分配了最小的cpu和内存资源。 datanode节点真正的存储数据在配置上,也会有不同, 因此将这两种节点分开来,方便管理。</p>    <p>两种类型的ES节点,是通过bamboo + haproxy相互连接成一个集群的。bamboo是一个用于服务发现的开源工具,能根据Marathon的callback信息动态的reload Haproxy,从而实现服务自动发现。它的基础原理就是注册Marathon的callback来获取Marathon事件。ES节点间的互连之所以要使用bamboo来进行服务发现的主要原因就是我们无法在节点被发布前预知节点被分配到了哪些Mesos Slave机器上。 因为节点的调度结果是由Marathon内部根据Slave可用的cpu,内存,端口等资源数量来决策,无法提前预知。这样就有一个问题,在发布的时候, 三个masternode之间就无法预知对方的地址。为了解决这个问题, 我们使用bamboo + haproxy来动态的发现masternode的地址实现节点之间的互连。</p>    <p>bamboo + haproxy能做到动态服务发现, 那么他和ES节点之间是怎么协作的呢 ?为了能保证集群节点之间正确的互连, 在ES两种类型的节点没有部署之前,首先需要部署一个bamboo + haproxy工具用作服务发现。 bamboo会注册Marathon callback从而监听Marathon事件。haproxy也会启动并监听一个前端端口。在开始部署masternode的时候,由于有新的APP部署,Marathon就会产生一个事件从而callback所有注册进入Marathon的接口,这时bamboo会根据这个callback调用Marathon api, 根据配置的规则去获取指定APP中task的机器和端口信息,也就是masternode的机器和端口信息。这些信息得到之后,bamboo会去修改Haproxy的配置并reload Haproxy,将task机器和端口信息做为后端Server,使得Haproxy前端所监听的端口可以正确的转发到后端这些机器的这些端口上去。这里,我们使用4层tcp协议。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/83c9e7275989221d17dbabdcf9fc4f93.png"></p>    <p style="text-align: center;">图6 端口转发示意图</p>    <p>至此,服务发现的过程就已经完成。在bamboo服务部署完成之后,haproxy服务就已经启动,所以,haproxy这个前端的端口是一直存在的。因此,在masternode启动的时候,可以先调用Marathon api获取到Haproxy这个前端的端口和机器信息,并将这个信息写入masternode的配置文件中,节点在启动的时候,就会以单播的方式去连接这个Haproxy的前端端口, 进而将请求转发到后端三台masternode节点上去。 可以看到,masternode的互相发现其实就是通过Haproxy再去连接自己,是一个环形连接。对于datanode,也是同样的方式, 惟一区别于masternode的就是datanode不会去自己连接自己。</p>    <p><strong>如何保证数据可靠性</strong></p>    <p>ES是一个带状态的服务,在软件层面提供了数据的冗余及分片,理论上是允许集群中部分节点下线并继续对外提供服务。我们要尽量保证底层的Mesos/Marathon能够充分利用ES的数据冗余能力,提高数据可靠性。低版本的Mesos/Marathon不支持动态预留和持久化卷,数据只能通过mount volume的方式驻留在宿主机的某个目录,不同的集群间的数据目录需要人工管理。而且默认的failover功能可能会导致datanode被调度到其他机器上,变成空节点。现在Mesos的动态预留和持久化卷功能可以解决这一问题,保证容器与数据绑定,“钉”在某个节点,保证实例重启后直接本地恢复数据。同时为了尽最大可能保证数据不会丢失,我们做了以下规定:</p>    <p>● 每个索引至少有一个副本(index.number_of_replicas >= 1);</p>    <p>● 每个宿主上同一个集群的节点等于replica的个数。如果某个集群A的配置为index.number_of_replicas= 2,那么每个宿主上可以为A启动2个节点实例</p>    <p>● index.routing.allocation.total_shards_per_node=2,禁止多个主shard集中同一实例。</p>    <p>除了数据层面的多份冗余外,我们还默认提供了snapshot服务, 业务线可以选择定时/手工snapshot集群的数据到HDFS上。最后就是集群级的冗余,ESAAS允许申请热备集群,数据双写到在线/热备集群中,当在线集群出现故障时切换到热备集群提供服务。</p>    <p><strong>监控与报警</strong></p>    <p>从上面可以看到,masternode,datanode,bamboo + haproxy这三个是组成一个ES集群必不可少的组件。 除此之外,我们还有一些用于监控的组件,如Sub Marathon结构图所示的那样, 我们提供了两个维度的监控,一个是集群维度的监控,一个是容器维度的监控。</p>    <p>我们选择了es2graphite(一个py脚本)来收集ES集群指标,,其主要原理就是通过ES的api获取ES内部的各项指标,将收集的指标打到后端的监控系统——watcher中(watcher是Qunar OPSDEV基于graphite+grafana+nagios等开发的一套监控/报警系统),最后通过监控系统的指标看板展示集群当前状态。如图7所示,为某个ES集群在watcher上的监控看板:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3d2e1845f437be3c534786f1fc84e895.jpg"></p>    <p style="text-align: center;">图7 ES监控截图</p>    <p>为了监控容器的运行状态,我们自主开发了容器指标收集工具——pyadvisor。pyadvisor会收集容器的cpu, 内存和io等信息,并将其发往后端的监控系统watcher。图8为容器的监控看板截图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e522e9af0cbd2540c6c2f852f5390c09.jpg"></p>    <p style="text-align: center;">图8 容器监控截图</p>    <p>监控的重要性不言而喻,它就相当于维护者的眼睛,通过这些监控,我们可以定位集群问题。除了监控,我们还提供了两个集群的基础报警,一个是集群状态报警,一个是集群的gc时间报警。ES集群状态有green,yellow,red三种状态。我们的报警条件是非green就报警,这样尽早发现可能出现的异常情况。GC时间报警指的是一分钟内集群节点Full GC的时间,报警阈值我们会根据集群的使用场景去调节。GC时间指标在一定程度上往往标识着集群当前的health程度,过高的GC时间可能导致集群停止对外界请求的一切响应, 这对于一个线上服务往往是致命的。</p>    <p><strong>自动化部署</strong></p>    <p>ESAAS系统设计初衷有快速构建的要求,否则我们避免不了繁重的人力成本。整个系统的组件几乎全部依赖于Docker,Docker容器的平台无关特性使得我们可以提前将环境和可执行体打包成一个镜像,这在很大程度上节约了我们为部署环境差异而付出的人力成本。快速的构建和发布是一个SaaS系统必须具备的特性。,我们使用jenkins来完成ESAAS系统的构建和发布工作。</p>    <p>对于ESAAS系统来说, 快速的构建就是快速的生成配置,发布就是按照规则顺序创建Marathon APP, 图9是一个Jenkins构建发布的流程图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/51bff73629582ce37e963042720bfcc3.png"></p>    <p>ES集群的发布,由两个Jenkins任务完成。首先,第一个Jenkins任务会调用集群初始化脚本,生成Marathon和ES的配置,并将这些配置提交Gitlab做归档管理。然后,第二个Jenkins任务(如图10)会通过Gitlab API读取配置,并使用Marathon API创建集群中的各组件(Marathon APP). 图10是我们线上一个Jenkins发布任务的截图(二次开发过的任务面板):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4cbbe809f2a37a6d11239d560e27a1d1.jpg"></p>    <p style="text-align: center;">图10 Jenkins job截图</p>    <p><strong>ESAAS Console</strong></p>    <p>为了提供一个标准化的ES产品,统一用户界面,我们模仿ES Cloud开发了ESAAS Console(以下简称Console)。ESAAS Console包括了集群概况展示,集群配置,操作日志查询等功能。</p>    <p>集群概况页汇总了节点地址,端口信息,配置Git地址,监控地址以及常用插件等信息(如图11),方便用户接入ES集群和插件工具的使用。 </p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/58991fcb7f1a2c29dddd2466a0e4ce70.png"></p>    <p style="text-align: center;">图11 ESAAS Console截图</p>    <p>同时,我们也将配置管理和插件管理整合到了Console中(如图12所示)。用户可以通过使用ES API修改集群配置。另外,ES插件多种多样,相互搭配使用可以提高集群管理的灵活度,我们提供一键安装插件功能,免去了手动为每一个ES节点安装插件的麻烦。集群配置页还提供了Kibana一键安装功能(Kibana是一个ES数据可视化看板)。这些功能都大大降低了用户使用ES的成本。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d4b00ec954cb132f5e4d622a95f45530.png"></p>    <p style="text-align: center;">图 12 ESAAS Console 截图</p>    <p>所有对集群的操作都会记录下来(如图13),方便回溯问题。我们这些操作信息后端存储也是使用的ESAAS, 一定程度的做到了服务闭环。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/29d50a61c2d3170800acf2fdb7962dd8.jpg"></p>    <p style="text-align: center;">图13 ESAAS Console截图</p>    <p><strong>5. 总结</strong></p>    <p>截止到写这篇文章时,  ESAAS已经稳定运行了半年有余,为业务线提供了44组ES集群,涵盖了离线/热备/在线集群。并且和我们OPS提供的实时日志系统完成了功能的整合,支持线上日志的导入,双写以及ETL等,为业务线提供数据/服务闭环的平台。目前ESAAS服务的几个规模指标如下:</p>    <p>● ESAAS集群机器数量: 77台服务器</p>    <p>● Datanode数据节点机器数量: 66台服务器</p>    <p>● 当前托管的集群数量: 44个集群</p>    <p>● 当前数据存储总量量: 120TB左右</p>    <p>● 当前覆盖业务线: 30 个</p>    <p>● 最大的集群数据量: 25.6 T</p>    <p><strong>遇到的问题和解决办法</strong></p>    <p>(1)Mesos Role 不能动态的创建</p>    <p>Mesos的Role不能动态的创建, 而我们ESAAS的资源隔离是根据Mesos的Role来完成.  我们提前创建了Role来解决这一问题</p>    <p>(2)Mesos slave 重启之后重新加入集群, 原先跑在这台 slave 上的task 将不可恢复</p>    <p>Mesos Slave节点由于某些原因机器需要重新启动的时候, Mesos会将机器识别为新的Slave, 这就导致了机器重启之后, 原来跑在该节点上的task不可恢复.   我们在研究了Mesos内部机制之后解决了这一问题,即持久化boot_id文件。</p>    <p><strong>下一步计划</strong></p>    <p>我们的ESAAS系统还处于探索阶段,仍然存在不少待解决的问题。比如:</p>    <p>● ESAAS服务的计费</p>    <p>● ES和平台日志的收集,  </p>    <p>● 独立ES集群间的数据迁移服务</p>    <p>● 独立ES集群间的IO/CPU优先级管理</p>    <p><img src="https://simg.open-open.com/show/39c9484f71fb5c13a36d8aa8f7b16087.png"></p>    <p> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzA3NDcyMTQyNQ==&mid=2649256130&idx=1&sn=3a341cc0f5d88662ac5c271a8a73ada0&chksm=8767a13cb010282a710ebe8ba4ea443eae72cafd211c9042d59d172e1eb44b866b898a7e90d3&scene=0</p>    <p> </p>