唱吧DevOps的落地,微服务CI/CD的范本技术解读----最大的难点并非实际业务代码的编写,而是服务的监控和调试以及容器的编排

 一、业务架构:从单体式到微服务

  K歌亭是唱吧的一条新业务线,旨在提供线下便捷的快餐式K歌方式,用户能够在一个电话亭大小的空间里完成K歌体验。K歌亭在客户端有VOD、微信和Web共三个交互入口,业务复杂度较高,如长链接池服务、用户系统服务、商户系统、增量更新服务、ERP等。对于服务端的稳定性要求也很高,由于K歌亭摆放地点不固定,不少场所的运营活动会形成突发流量。javascript

  为了快速开发上线,K歌亭项目最初采用的是传统的单体式架构,可是随着时间的推移,需求的迭代速度变得很快,代码冗余变多,常常会出现牵一发动全身的改动。重构不但会花费大量的时间,并且对运维和稳定性也会形成很大的压力;此外,代码的耦合度高,新人上手较困难,每每须要通读大量代码才不会踩进坑里。java

  鉴于上述弊端,咱们决定接下来的版本里采用微服务的架构模型。从单体式结构转向微服务架构中会持续碰到服务边界划分的问题:好比,咱们有user服务来提供用户的基础信息,那么用户的头像和图片等是应该单独划分为一个新的service更好仍是应该合并到user服务里呢?若是服务的粒度划分的过粗,那就回到了单体式的老路;若是过细,那服务间调用的开销就变得不可忽视了,管理难度也会指数级增长。目前为止尚未一个能够称之为服务边界划分的标准,只能根据不一样的业务系统加以调节,目前K歌亭拆分的
大原则是当一块业务不依赖或极少依赖其它服务,有独立的业务语义为超过2个的其余服务或客户端提供数据,那么它就应该被拆分红一个独立的服务模块。python

  在采用了微服务架构以后,咱们就能够动态调节服务的资源分配从而应对压力、服务自治、可独立部署、服务间解耦。开发人员能够自由选择本身开发服务的语言和存储结构等,目前总体上使用PHP作基础的Web服务和接口层,使用Go语言来作长链接池等其余核心服务,服务间采用thrift来作RPC交互。git

  二、系统架构的构思与解读

  2.1 容器编排

  唱吧K歌亭的微服务架构采用了Mesos和Marathon做为容器编排的工具。在咱们选型初期的时候还有三个其余选择,Kubernetes、 Swarm、 DC/OS:github

  • DC/OS:做为Mesosphere公司的拳头产品,基本上是但愿一统天下的节奏。因此组件不少,功能也很全面。可是对于咱们在进行微服务架构初期,功能过于庞大,学习成本比较高,后期的生产环境维护压力也比较大。
  • Swarm:Docker公司本身作的容器编排工具,当时了解到100个以上物理节点会有无响应的状况,对于稳定性有一些担心。
  • Kubernetes:Google开源的的容器编排工具,在选型初期尚未不少公司使用的案例,同时也听到了不少关于稳定性的声音,因此没有考虑。可是在整个2016年,愈来愈多的公司开始在线上使用Kubernetes,其稳定性逐步提升,若是再选型应该也是个好选择。
  • Mesos:由于了解到Twitter已经把Mesos用于生产环境,而且感受架构和功能也相对简单,因此最后选择了Mesos+Marathon做为容器编排的工具

  2.2 服务发现

  咱们采用了etcd做为服务发现的组件,etcd是一个高可用的分布式环境下的 key/value 存储服务。在etcd中,存储是以树形结构来实现的,非叶结点定义为文件夹,叶结点则是文件。咱们约定每一个服务的根路径为/v2/keys/service/$service_name/,每一个服务实例的实际地址则存储于以服务实例的uuid为文件名的文件中,好比帐户服务account service当前启动了3个能够实例,那么它在etcd中的表现形式则以下图:数据库

  当一个服务实例向etcd写入地址成功时咱们就能够认为当前服务实例已经注册成功,那么当这个服务实例因为种种缘由down掉了以后,服务地址天然也须要失效,那么在etcd中要如何实现呢?后端

  注意,图中的每一个文件有一个ttl值,单位是秒,当ttl的值为0时对应的文件将会被etcd自动删除。当每一个服务实例启动以后第一次注册时会把存活时间即ttl值初始化为10s,而后每隔一段时间去刷新ttl,用来像向etcd汇报本身的存活,好比7s,在这种状况下基本上能够保证服务有效性的更新的及时性。若是在一个ttl内服务down掉了,则会有10s钟的时间是服务地址有效;而服务自己不可用,这就须要服务的调用方作相应的处理,好比重试或这选择其它服务实例地址。服务器

  咱们服务发现的机制是每一个服务自注册,即每一个服务启动的时候先获得宿主机器上面的空闲端口;而后随机一个或多个给本身并监听,当服务启动完毕时开始向etcd集群注册本身的服务地址,而服务的使用者则从etcd中获取所需服务的全部可用地址,从而实现服务发现。微信

  同时,咱们这样的机制也为容器以HOST的网络模式启动提供了保证。由于BRIDGE模式确实对于网络的损耗太大,在最开始就被咱们否决了,采用了HOST模式以后网络方面的影响确实不是很大。网络

  2.3 监控,日志与报警

  咱们选择Prometheus汇总监控数据,用ElasticSearch汇总日志,主要的缘由有:

  1. 生态相对成熟,相关文档很全面,从通用的到专用的各类exporter也很丰富。
  2. 查询语句和配置简单易上手。
  3. 原生具备分布式属性。
  4. 全部组件均可以部署在Docker容器内。

  Mesos Exporter,是Prometheus开源的项目,能够用来收集容器的各项运行指标。咱们主要使用了对于Docker容器的监控这部分功能,针对每一个服务启动的容器数量,每一个宿主机上启动的容器数量,每一个容器的CPU、内存、网络IO、磁盘IO等。而且自己他消耗的资源也不多,每一个容器分配0.2CPU,128MB内存也毫无压力。

  在选择Mesos Exporter以前,咱们也考虑过使用cAdvisor。cAdvisor是一个Google开源的项目,跟Mesos Exporter收集的信息八成以上都是相似的;并且也能够经过image字段也能够变相实现关联服务与容器,只是Mesos exporter里面的source字段能够直接关联到marathon的application id,更加直观一些。同时cAdvisor还能够统计一些自定义事件,而咱们更多的用日志去收集相似数据,再加上Mesos Exporter也能够统计一些Mesos自己的指标,好比已分配和未分配的资源,因此咱们最终选择了Mesos Exporter。

  以下图,就是咱们监控的部分容器相关指标在Grafana上面的展现: 

  Node exporter,是Prometheus开源的项目,用来收集物理机器上面的各项指标。以前一直使用Zabbix来监控物理机器的各项指标,此次使用NodeExporter+Prometheus主要是出于效率和对于容器生态的支持两方面考虑。时序数据库在监控数据的存储和查询的效率方面较关系数据库的优点确实很是明显,具体展现在Grafana上面以下图:

  Filebeat是用来替换Logstash-forwarder的日志收集组件,能够收集宿主机上面的各类日志。咱们全部的服务都会挂载宿主机的本地路径,每一个服务容器的会把本身的GUID写入日志来区分来源。日志经由ElasticSearch汇总以后,聚合的Dashboard咱们统一都会放在Grafana上面,具体排查线上问题的时候,会用Kibana去查看日志。

  Prometheus配置好了报警以后能够经过AlertManager发送,可是对于报警的聚合的支持仍是很弱的。在下一阶段咱们会引入一些Message Queue来本身的报警系统,增强对于报警的聚合和处理。

  ElastAlert是Yelp的一个Python开源项目,主要的功能是定时轮询ElasticSearch的API来发现是否达到报警的临界值,它的一个特点是预约义了各类报警的类型,好比frequency、change、flatline、cardinality等,很是灵活,也节省了咱们不少二次开发的成本。

  2.4 事务追踪系统——KTrace

  对于一套微服务的系统结构来讲,最大的难点并非实际业务代码的编写,而是服务的监控和调试以及容器的编排。微服务相对于其余分布式架构的设计来讲会把服务的粒度拆到更小,一次请求的路径层级会比其余结构更深,同一个服务的实例部署很分散,当出现了性能瓶颈或者bug时如何第一时间定位问题所在的节点极为重要,因此对于微服务来讲,完善的trace机制是系统的核心之一。

  目前不少厂商使用的trace都是参考2010年Google发表的一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》来实现的,其中最著名的当属twitter的zipkin,国内的如淘宝的eagle eye。因为用户规模量级的逐年提高,分布式设计的系统理念愈来愈为各厂商所接受,因而诞生了trace的一个实现标准opentracing ,opentracing标准目前支持GoJavaScriptJava、 PythonObjective-CC++六种语言。 由sourcegraph开源的appdash是一款轻量级的,支持opentracing标准的开源trace组件,使用Go语言开发K歌亭目前对appdash进行了二次开发,并将其做为其后端trace服务(下文直接将其称之为Ktrace),主要缘由是appdash足够轻量,修改起来比较容易。唱吧K歌亭业务的胶水层使用PHP来实现,appdash提供了对protobuf的支持,这样只须要咱们本身在PHP层实现middleware便可。

  在trace系统中有以下几个概念

  (1)Annotation

  一个annotation是用来即时的记录一个事件的发生,如下是一系列预约义的用来记录一次请求开始和结束的核心annotation

  1. cs - Client Start。 客户端发起一次请求时记录
  2. sr - Server Receive。 服务器收到请求并开始处理,sr和cs的差值就是网络延时和时钟偏差
  3. ss - Server Send: 服务器完成处理并返回给客户端,ss和sr的差值就是实际的处理时长
  4. cr - Client Receive: 客户端收到回复时创建。 标志着一个span的结束。咱们一般认为一但cr被记录了,一个RPC调用也就完成了。

  其余的annotation则在整个请求的生命周期里创建以记录更多的信息 。

  (2)Span

  由特定RPC的一系列annotation构成Span序列,span记录了不少特定信息如 traceId, spandId, parentId和RPC name。

  Span一般都很小,例如序列化后的span一般都是kb级别或者更小。 若是span超过了kb量级那就会有不少其余的问题,好比超过了kafka的单条消息大小限制(1M)。 就算你提升kafka的消息大小限制,过大的span也会增大开销,下降trace系统的可用性。 所以,只存储那些能表示系统行为的信息便可。

  (3)Trace

  一个trace中全部的span都共享一个根span,trace就是一个拥有共同traceid的span的集合,全部的span按照spanid和父spanid来整合成树形,从而展示一次请求的调用链。

  目前每次请求由PHP端生成traceid,并将span写入Ktrace,沿调用链传递traceid,每一个service本身在有须要的地方埋点并写入Ktrace。举例以下图:

  每一个色块是一个span,代表了实际的执行时间,一般的调用层级不会超过10,点击span则会看到每一个span里的annotation记录的不少附加信息,好比服务实例所在的物理机的IP和端口等,trace系统的消耗通常不会对系统的表现影响太大,一般状况下能够忽略,可是当QPS很高时trace的开销就要加以考量,一般会调整采样率或者使用消息队列等来异步处理。不过,异步处理会影响trace记录的实时性,须要针对不一样业务加以取舍。

  目前K歌亭在生产环境里的QPS不超过1k,因此大部分的记录是直接写到ktrace里的,只有歌曲搜索服务尝试性的写在kafka里,由mqcollector收集并记录,ktrace的存储目前只支持MySQL。一个好的trace设计能够极快的帮你定位问题,判断系统的瓶颈所在。

  2.5 自动扩容

  在服务访问峰值的出现时,每每须要临时扩容来应对更多的请求。除了手动经过Marathon增长容器数量以外,咱们也设计实现了一套自动扩缩容的系统来应对。咱们扩缩容的触发机制很直接,根据各个服务的QPS、CPU占用、内存占用这三个指标来衡量,若是三个指标有两个指标达到,即启动自动扩容。咱们的自动扩容系统包括3个模块:

  1. Scout:用于从各个数据源取得自动扩容所须要的数据。因为咱们的日志所有都汇总在ElasticSearch里面,容器的运行指标都汇总到Prometheus里面,因此咱们的自动扩容系统会定时的请求两者的API,获得每一个服务的实时QPS、CPU和内存信息,而后送给Headquarter。
  2. Headquarter:用于数据的处理和是否触发扩缩容的判断。把从Scout收到的各项数据与本地预先定义好的规则进行比对,若是有两个指标超过定义好的规则,则通知到Signalman模块。
  3. Signalman:用于调用各个下游组件执行具体扩缩容的动做。目前咱们只会调用Marathon的/v2/apps/{app_id}接口,去完成对应服务的扩容。由于咱们的服务在容器启动以后会本身向etcd注册,因此查询完容器状态以后,扩缩容的任务就完成了。

  三、基于Mesos+Marathon的CI/CD

  3.1 持续集成与容器调度

  在唱吧,咱们使用Jenkins做为持续集成的工具。主要缘由是咱们想在本身的机房维护持续集成的后端,因此放弃了Travis之类的系统。

  在实施持续集成的工做过程当中,咱们碰到了下列问题:

  1. Jenkins Master的管理问题。多个团队共享一个Master,会致使权限管理困难,配置改动、升级门槛很高,Job建立和修改有不少规则;每一个团队用本身的Master,会致使各个Master之间的插件、更新、环境维护有不少的重复工做。
  2. Jenkins Slave 资源分配不平均:忙时Jenkins slave数量不足,Job运行须要排队;闲时Jenkins Slave又出现空闲,很是浪费资源。
  3. Jenkins job运行须要的环境多种多样,好比咱们就有PHP,java,maven,Go,python等多种编译运行环境,搭建和维护slave很是费时。
  4. 多个开发人员的同时提交,各自的代码放到各自独立的测试环境进行测试。

  基于以上问题,咱们选择使用Mesos和Marathon来管理Jenkins集群,把Jenkins Master和Jenkins Slave都放到Docker容器里面,能够很是有效的解决以上问题。基础架构以下图:

  1. 不一样开发团队之间使用不一样的Jenkins Master。把公用的权限、升级、配置和插件更新到私有Jenkins Master镜像里面,推到私有镜像仓库,而后经过Marathon部署新的Master镜像,新团队拿到的Jenkins Master就预安装好了各类插件,各个现有团队能够无缝接收到总体Jenkins的升级。
  2. 各类不一样环境的Jenkins Slave,作成Slave镜像。按照须要,能够经过Swarm Plugin自动注册到Jenkins master,从而组织成slave pool的形式;也能够每一个job本身去启动本身的容器,而后在容器里面去执行任务。
  3. Jenkins job从容器调度的角度分红两类,以下图:
  • 环境敏感型:好比编译任务,须要每次编译的环境彻底干净,咱们会从镜像仓库拉取一个全新的镜像启动容器,去执行Job,而后再Job执行完成以后关闭容器。
  • 时间敏感型:好比执行测试的Job,须要尽快获得测试结果,可是测试机器的环境对于测试结果没什么影响,咱们就会从已经启动好的Slave Pool里面去拉取一个空闲的Slave去执行Job。而后再根据Slave被使用的频率去动态的扩缩容Slave pool的大小就行了。

  3.2 CI/CD流程

  基于上述的基础架构,咱们定义了咱们本身的持续集成与持续交付的流程。其中除了大规模使用Jenkins与一些自定制的Jenkins插件以外,咱们也本身研发了本身的部署系统——HAWAII。

  在HAWAII中能够很直观的查看各个服务与模块的持续集成结果,包括最新的版本,SCM revision,测试结果等信息,而后选择相应的版本去部署生产环境。

  在部署以前,能够查看详细的测试结果和与线上版本的区别,以及上线过程当中的各个步骤运行的状态。

  基于上述基础架构,咱们的CI/CD流程以下:

  1. SVN或者GIT收到新的代码提交以后,会经过hook启动相应的Jenkins job,触发整个CI流程。
  2. Jenkins从私有镜像仓库拉取相对应的编译环境,完成代码的编译。
  3. Jenkins从私有镜像仓库拉取相对应的运行时环境,把上一步编译好的产品包打到镜像里面,并生成一个新版本的产品镜像。产品镜像是最终能够部署到线上环境的镜像,该镜像的metadata也会被提交到部署系统HAWAII,包括GUID,SCM revision,Committer,时间戳等信息
  4. 将步骤3中生成的产品镜像部署到Alpha环境(该环境主要用于自动化回归测试,实际启动的容器实际上是一个完整环境,包括数据容器,依赖的服务等)。
  5. Jenkins从私有镜像仓库拉取相对应的UT和FT的测试机镜像,进行测试。测试完成以后,会销毁Alpha环境和全部的测试机容器,测试结果会保存到部署系统HAWAII,并会邮件通知到相关人员。
  6. 若是测试经过,会将第3步生成的产品镜像部署到QA环境,进行一系列更大范围的回归测试和集成测试。测试结果也会记录到HAWAII,有测试不经过的地方会从第1步从头开始迭代。
  7. 所有测试经过后,就开始使用HAWAII把步骤3中生成的产品镜像部署到线上环境。

  四、小结

  随着互联网的高速发展,各个公司都面临着巨大的产品迭代压力,如何更快的发布高质量的产品,也是每一个互联网公司都面临的问题。在这个大趋势下,微服务与DevOps的概念应运而生,在低耦合的同时实现高聚合,也对新时代的DevOps提出了更高的技术与理念要求。

  这也是咱们公司在这个新的业务线上面进行,进行尝试的主要缘由之一。对于微服务、容器编排、虚拟化、DevOps这些领域,咱们一步一步经历了从无到有的过程,因此不少方面都是本着从知足业务的目标来尽可能严谨的开展,包括全部的服务与基础架构都进行了高并发且长时间的压力测试。

  在下一步的工做中,咱们也有几个核心研究的方向与目标,也但愿能跟你们一块儿学习与探讨:

  1. 完善服务降级机制
  2. 完善报警机制
  3. 完善负载均衡机制

http://kb.cnblogs.com/page/565901/

相关文章
相关标签/搜索