大家好,今天我将分享通联数据使用Docker和Rancher构建自动化发布管道的经验,我会介绍通联数据自动化发布的流程及方案设计,我们从踩过的一些坑中总结出来的经验,以及我们的自动化发布管道的系统运行现状。
 

一、通联数据的需求及选择Rancher的原因

 
通联数据是一家这几年新崛起的金融科技公司,主要目标是致力于将大数据、云计算、人工智能等技术和专业投资理念相结合,打造国际一流的、具有革命性意义的金融服务平台。这里面涉及到几个关键词:大数据、云计算、人工智能,这些热词已经有很多企业提及过,因此我也不赘述了。
 
通联数据作为一家创业公司,也会遇到很多创业公司都会遇到的问题,比如产品太多,而且每年很多产品都会推翻重来,就会遇到各种各样的问题。比如,应用在开发时,涉及到需要多少CPU、需要用什么语言去编程等问题,开发人员不会与后台人员事先沟通,出来一个产品,直接让运维人员上线。
 
基于这样一个背景,我们迫切需要打通上线这条管道,因为在可预见的将来,通联数据每年将会有大量的新应用出现,如何打通上线这条管道亟待解决。
 
我们做的第一件事,就是做持续集成。由于我们公司每年会有上百个新的项目产生,每个项目的语言、框架、部署方法都不尽相同,发布流程也是比较冗长低效的,部署应用占用了运维人员大量时间。
 
我们决定用容器解决这个问题,评估了多家供应商后选择了Rancher,主要原因在于:
 
首先,Rancher的操作界面非常简单,相信用过Rancher的人都会有这种感受,它不需要太多专业的知识,很容易上手;其次,Rancher在部署时也非常简单,可以一键式部署,Rancher还提供了良好的API支持,方便集成。
 
二、自动化发布的流程
 
随后,我们开始搭建自己的CI/CD。当时我们在流程方面遇到了很多困境,下图已经是简化制作后的了,实际上还有更多各种各样的分杈和分枝:
通联数据是如何使用Docker+Rancher构建自动发布管道的?_第1张图片

就我们原来的流程来说,流程最开始的部分是研发;随后进入QA环境部署,这时候就需要人去部署,通常是运维人员,但是运维一般不愿意做这个事情;部署完成后,进入QA环境测试阶段,通知开发和测试人员进行测试,过程中可能会出现延迟,因为测试人员可能正忙于其它的事情,不能马上进行测试;QA环境测试通过后,进入STG环境部署;随后再进行STG环境测试,这几个过程可能会循环很多次。跟着进入安全测试阶段;测试通过后,还有一个正式包的一个准备过程,最后才能上生产部署。即使是这个简化后的流程也是非常复杂的,而且其中涉及到很多线下的沟通,效率不可能高得起来。
 
在使用Rancher之后,原本的流程大大简化了,改进后的简化流程如下图:
通联数据是如何使用Docker+Rancher构建自动发布管道的?_第2张图片
流程的第一步即持续集成,意味着开发人员写好代码后可以直接通过CI,CI触发自动编译,随后自动部署脚本,测试环境已经就绪。简单来说,每次开发人员提交代码之后,测试环境就始终处一个就绪的状态,这时候就可以直接进入测试阶段,整个过程都处于线下,不需要走任何流程,全部实现自动化了。
 
测试人员完成测试后,再进行STG环境测试,因为后台已经跟Rancher完成对接并实现自动化,这赋予了QA环境测试从未有过的强大的自动化能力,意味着QA环境测试可以自动对接到STG环境测试。测试通过后,进入安全测试阶段,这个阶段是公司要求的,无法避免,安全测试通过后就进入到生产部署。以前绕不开的线下沟通那些步骤和一些部署就可以省去了,整个流程优化并且简洁,效率也提升了。
 
CI/CD说起来可能很简单,比如PUSH代码、QA环境自动就绪。但是实际操作起来并非如此,仍然存在很多需要解决的问题。比如开发的分支模型就涉及到在开发代码的时,要PUSH什么分支才能部署,还是PUSH所有的分支都能部署?
 

三、开发分支模型

 
当时我们想到最好就是PUSH任意分支都能部署,这样就非常方便。但随后就发现这方法行不通,会造成混乱,而且难以管理。此前我们Git的一个The Successful Branch Modeling分支模型就类似于此,此模型规定了一个develop分支、一个feature类型的分支、一个release类型的分支、一个hotfixes类型的分支和一个master分支。
通联数据是如何使用Docker+Rancher构建自动发布管道的?_第3张图片
在平时开发时,开发人员常常会在develop分支上切出一个feature的分支,比如,开发一个包含很多功能的story,那么所有人切一个story,这边story有自己的ID,然后再去开发,把story开发完之后,再把它merge过来。最终,我们只选择了一条线做CI,当代码PUSH或者合并到develop分支时,我们帮你去做这个,而在这个feature分支的时候,我们另作处理。
 
feature分支意味着当用户提交一个feature分支之后,我们会另外帮你部署一套,每一个feature分支部署一套,相当于每一个story都可以分开独立去测试,最后把它合并到develop分支。那么测试的时候,测试可以根据自己的需求来决定它到底在哪一条分支线上去测试。
 
比如,A测试中如果用户只关心story A,那就在story A这个分支测试环境去测,这些story全部合并进来之后,再进行一次集中测试,测试通过后,在发布时把这个分支切到release 的一个分支上来,然后,在release上发布正式包,让QA在STG环境继续进行测试,就如前面看的那个流程图。分支模型非常混乱,为了做CI,我们会跟开发人员定义好每一条分支,每个分支对应的每个不同行为也定义好,这在混乱的分支模型下非常有用。
 

四、版本号规则

 
为了做CI,版本号的规则必须一致,如果每个团队版本号命名不一样的话,匹配规则就会非常麻烦和混乱。后来,我们选择了Semantic Versioning的一个版本号的规则,就是几点几点X,这是一种常见的版本号命名方法,此版本号包含标准的一个文档,文档描述了此版本号的具体定义,第一个叫MAJOR,第二个叫MINOR,第三个叫PATCH,后面还可以加各种自己的版本号。
通联数据是如何使用Docker+Rancher构建自动发布管道的?_第4张图片
 

五、CI触发路径

 
下面我将介绍我们的CI的触发路径——Git push,push到develop分支,Git push就会push到Gitlab的server上,随后通过webhook去调用Jenkins,Jenkins会把这个包bulid出来。原本我们是想通过Jenkins调用Rancher的API,后来发现直接调用Rancher有差距,过程进行的也不那么顺利,为了解决Jenkins和Rancher之间的gap这一问题,我们在它们之间装了一个Ponyes的软件。
通联数据是如何使用Docker+Rancher构建自动发布管道的?_第5张图片
为什么需要Ponyes这一中间层?它到底提供了哪些价值?
 
(1)动态修改版本号
 
首先,它可以解决动态修改版本号的问题,大家用Rancher的时候,发现Rancher的商店非常好用,我们可以在商店里把一些东西定义好,接着QA只要填几个参数,就可以把一个应用部署起来了,没有Ponyes这一中间层之前,必须找运维人员去做,过程也比较复杂。
 
为了用起来Rancher应用商店,我们还定义了一个应用商店的模板,这样我们就可以在每次PUSH代码的时候,把这个模板生成一个真正可以部署的应用。但是,版本号还是存在一些问题,我们每次PUSH代码的时候,到Jenkins,我们会根据阅览数给Jenkins升级一次版本号,比如说1.0.1.0-1,第一次阅览是-1,第二次阅览即-2,Jenkins的版本号根据阅览次数相应变化。这时候应用商店也要随之而变。
 
因此,我们做了这样一个模板,通过这样一种方式,在Ponyes中,每个Jenkins可以获得对应的版本号,然后把这个版本号通过变量把它注入进去:

sample:
  image:    {{ REGISTRY }}/automation/auto-sample:{{ m['auto-sample']}}

 
这一过程还涉及到registry,因为QA环境和STG环境是完全分开的,在进行模板渲染时,我们需要知道到底是发到QA环境还是STG环境,从而对registry的地址做出相应改变,这样的话,上面说的修改版本号的问题就解决了。
 
(2)多service管理
 
还有一个比较棘手的问题,即一个stack中有多个服务怎么办?比如,一个比较小的团队可能总共就几个人,每个人负责好几个项目,与微服务的关系有些相似,那么一个stack可能有好几个服务,最典型的就是前端、后端,或者是其他的一些中间件,每一项是一个服务。
 
开始部署时,Rancher里面的这些服务也要用一个stack来管理,有多个服务就会面临怎样管理的问题。比如,一个stack有ABC三个服务,服务C更新时,整个stack也应该更新版本号,因为在Rancher里面,stack被部署出来之后,有个update available的×××按钮,如果有新版本,点这个按钮,就可以升级这个stack,而且必须升级整个stack,而不是只升级某个服务,这时候就需要管理多个服务。Ponyes记录了服务和stack之间的关系,任意一个服务更新,也会触发stack的更新,更新的方法就是每个服务每次更新,stack的版本号+1。
 
(3)多版本并行发布
 
接下来还有一个更严重的问题,如果多版本并行发布,我们应该怎么去处理?比如说我们ABC三个模块,A发布过1.0和2.0,B发布过3.0、4.0,C发布过5.0和6.0。如果发布一个C应用的5.0.2,那么对应的AB模块号应该选什么,这个问题困扰了我们很久。
 
有好几种方法可以解决,比如用某个东西记录C5.0和AB的一个版本号之间的关系,这表示用户可以自定义。但也存在一个问题,用户需要自己去管理它们之间的版本号关系,时间长了,可能会弄乱或弄错版本号,或者弄错版本号之间的关系,随后上线到生产环境,后果更严重。
 
还有一种方法,就是将用户的C版本号最后一次发布到5.0时AB是什么版本静态地记录下来。但这样的话系统会变得相当复杂,很容易出错。
 
最后,我们选择去除多版本并行发布的能力,只支持单个版本的发布,意味着如果要发布,必须是最新版本,历史版本可以用另外的方法进行人工处理,这样的话,系统会更简单,而且不容易出错,也不需要用户去维护一个版本号之间的关系。以上这些功能都是借助我们自己写的Ponyes解决的。
 
Rancher 2.0也提到了CI/CD这样一个功能,在实际过程中,会遇到一个非常现实的问题,从代码研发到整个生产环境部署有许多环节,具体情况也非常复杂,而CI的作用可能仅止于QA,后面的环节还会有新的难题,这时就需要一个体系把整个生命周期贯穿起来管理,Rancher承担的作用就在于此。
 

六、肉身踩坑总结的三条经验

 
(1)部署几套Rancher环境?
 
这问题看似很小,最初也在我们内部也引发过不少讨论。最初我们只部署了一套Rancher,在Rancher里面用环境去区分QA或production。部署一套Rancher的方式最简单,但是有一个严重的问题:Rancher升级了怎么办?这套Rancher既管了QA,又管了production,升级的时候需要把生产环境也升级,此刻如果出现bug,问题将非常严重。
 
后来我们把它拆成四套,Rancher平台本身也得有一个升级的环境。建议大家在前期部署Rancher的时候要部署多套环境(至少两套),我们实际上是dev、QA、staging、production每个环境都有一套,总共四套。
 
(2)配置项爆炸
 
第二个问题是关于配置项。我们最开始是使用Rancher compose的,它非常简单强大,我们在Rancher Catalog里点一下“部署”,配置选项全弹出来,我们只需要点选一些东西,就可以部署一个很复杂的应用。后来我们却发现,应用的配置项越来越多(甚至多达上百个)。这导致它们难以在一个页面里展现,同时上百项的配置让我们无从填写,运维也无法成功部署,我们从而面临了配置项爆炸的问题。
 
我们的解决经验是,在容器平台里面,配置项一定要集中管理。我们把配置项全部用consul管理,每次容器启动时到consul里把配置拉到对应的容器里来。如此一来,容器可以在任意平台漂移。另外配置项本身在原server存有副本,我们copy原配置项加以修改,就可以部署另一个实例了。所以说,配置项一定要全部集中管理起来。
 
(3)泛域名+Rancher LB
 
我们每一个业务都有一个web服务,要申请自己的域名。每年我们有上百个项目上线,这意味着有上百个域名要申请。加上这些域名都分别需要在开发、测试、生产环境中使用,所以我们每年要申请将近五百个域名。这是一件恐怖的事情。
 
后来我们就用了泛域名的方法。比如用.sub.example.com的域名,直接CNM到Rancher环境中的其中一台主机。然后在Rancher上设置它的LB,LB就可以分布在所有的主机上,每个主机上都会有同样的LB。这个域名任意指向一台主机,它都可以工作。
 
比如说,在QA环境,这个LB上serve了如下这些域名,若主机坏掉了,容器本身会启动,入口问题则可以很简单地通过修改量来搞定。如果是生产环境则可以在上面再加层nginx,配三个upstream,死掉任意一台,还可以通过另外两个入口进来。
 
使用泛域名的方法,在配置好泛域名之后,在Rancher LB上再加任意域名都不用再去申请新的域名,而是可以直接写123.sub.example.com,然后直接在LB上配置,配完之后域名即可用,无须再走申请的流程了。