关于两种CI/CD策略以及git分支模型的思考
holymapj
8年前
<p>借这个周末闲适的下午和明媚的阳光,决定把近来项目上的 CI/CD(持续集成/持续交付)策略 以及 git分支模型 和以前的项目做一下分析比较,希望对各位有所帮助,也能有所思考,尤其是那些期望 搭建项目部署流水线 或者想 了解git分支模型 的开发、运维人员。</p> <h2>背景</h2> <p>废话不多说,由于近期做了N次release,所以对自己目前所处的新项目的部署方式有了一定的了解。为了方便,本文就叫该项目为A项目吧。发现A项目的部署方式和我之前接触的TW“传统”CI/CD策略差异比较大(在 TW ,几乎每个项目都有持续集成/持续交付流水线,如果你对它们的概念还不是很清楚,建议阅读 持续交付 这本书,将对你梳理整个交付流程帮助巨大)。</p> <p>关于A项目的背景,受客户保密协议的限制,我只能透露几点。A项目所属公司为国外某大型电信运营商,主要内容为用户账户自服务平台。该平台涉及诸多内外部服务,如认证、订单跟踪、短信认证等等,数量总数在三十多个左右,而每个服务都是一个独立的子系统,有独立的代码库、独立的机器实例(AWS EC2 实例)用于运行,以及一套独立的jenkins job用于自动化构建和部署(即我们接下来谈的内容)。当然,这也是为什么A项目想往微服务架构迁移的主要目的。</p> <p>接下来,让我剥去诸多项目的其他内容,仅仅讨论一下它的CI/CD策略,也可以说是它的构建、部署方式。</p> <h2>A项目的CI/CD策略</h2> <p>千言万语还是不及一张图(作者小学美术数学老师教的,望见谅):</p> <p><img src="https://simg.open-open.com/show/6e23218a9a9e31af00fb25ff10990f1c.jpg"></p> <p>上图,为一个独立子项目(如背景中所说的某个服务)在其 jenkins 里面的任务(job)结构图,主要有两种自动化任务,build和deploy:</p> <ul> <li><strong>build</strong> - 即构建任务。developer在代码仓库(这里是github上某个私有仓库)某个分支上提交了代码后,自动或者手动地被触发。它会根据对应的分支,如develop、一些feature分支或release分支上,而在其对应的任务上构建、运行各层测试以及生成对应的 AMI 镜像。</li> <li><strong>deployment</strong> - 即部署任务。该任务需要人工手动点击触发,因为很多时候需要改动一些部署配置,比如说选择刚刚build任务生成的哪个分支的那个AMI文件以及更改一些endpoint的值。它会根据你需要部署的环境,利用自动化部署工具chef,基于对应的AMI镜像生成对应的EC2实例、ELB等等资源,让我们的服务在对应的环境中正式地运行起来(当然也伴随着销毁旧的资源的过程)。这个过程如果目标环境是prod的话,其实就是真实的发布了。</li> </ul> <p>这用在该项目组中几乎所有的以服务为单位的子系统之上,也就是说,我们有将近三十套左右类似这样的jenkins任务。</p> <p>需要说明的是,上图中的黑色圆圈、黑色圆圈加横线和黑色空心圆圈分别代表 <strong>完全自动化</strong> 、 <strong>需要手动更改配置后点击触发</strong> 和 <strong>需要手动点击出发</strong> 三种情况,即如下图所示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/6be38a46f8051ad021110ff4f97efade.jpg"></p> <p>这样的方式有如下几个特点:</p> <ol> <li><strong>以分支和环境为中心</strong> 这种策略在构建时以 <strong>分支(branch)</strong> 来区分构建的产物,如果你的工作模式是在各个不同的分支上开发且测试的话,你可以基于分支的相互独立的进行对应的部署和测试。举个栗子,如果你基于feature1分支构建生成了一个AMI镜像,然后你基于该镜像部署它到qa-1环境中,然后同样的将feature2分支部署到qa-2环境中,然后测试人员就可以同时在两个环境测试不同的功能。</li> <li><strong>保持了CI/CD中的自动化</strong> 构建和部署实际上还是自动化的,不过需要在运行自动化脚本之前,手动更新一些配置,比如该使用哪个AMI镜像等。</li> <li><strong>自动化测试时间不会特别长</strong> 这里所说的特别长其实不容易定义,具体多长时间为长,都是相对而论。个人感觉,只要你觉得不用给各层测试做独立的jenkins任务(全都放在build中),仍然可以清晰的知道什么时候运行什么测试,什么测试出现了问题,即可。</li> <li><strong>环境之间的递进关系不明显</strong> 这种策略下,由于是手动选择和触发部署过程,所以一次代码更改可能不会被部署到所有环境中,可能只会被部署到某一个测试环境中用于测试。所以环境之间的递进(如下文中越来越接近产品环境的)只能体现它对应的部署任务里的一些配置参数上,比如说preprod环境的部署job用的是真实数据库,而QA环境的部署job用的是mock的数据。</li> </ol> <h2>B项目的策略及比较</h2> <p>而我曾经接触过的一些项目,同样为了便于说明,这里我们统称它为B项目,不管它的CI/CD工具用的是jenkins还是 <a href="/misc/goto?guid=4959734088576164140" rel="nofollow,noindex">go.cd</a> ,它们都会是一种流水线(pipeline)的形式,如下图所示(没错,请叫我灵魂画师,<手动羞耻脸>):</p> <p><img src="https://simg.open-open.com/show/e96a3550b982e179d9b8d2ff2492ae98.jpg"></p> <p>如上图所示,相对A项目的策略来说,这些jenkins任务分的更加细,中间的各层测试视具体项目而言可能包含单元测试、集成测试、回归测试、集成测试等等,然后就是将其部署到Dev环境(开发人员手动测试、验证的环境)。毋庸置疑,这里从开发人员提交代码到部署至Dev环境,包括测试的运行在内,都是自动化的。这意味着如果你的代码没有问题,你不需要做任何事,除了提交代码和看一下这个pipeline的状态。之后的几个环境,由于越来越接近产品环境,而且会提供给不同的人用于测试或者演示(showcase),所以很多时候需要对应的人手动的触发对应的部署/发布。当然,这样的部署/发布过程也是自动化,所以说在发布到产品环境之前,类似的部署/发布方式其实已经被验证过很多次了,而且是一次更改必须强制性地必须经过各个环境的测试和验证。</p> <p>结合《持续交付》一书中提到的部署流水线的三个目标而言,我们来比较一下A项目和B项目用的这两种部署策略优缺点:</p> <ol> <li><strong>可视化</strong> - 让软件的构建、部署、测试和发布过程对所有人可见,这一点对于合作至关重要。A项目这种分离的任务形式,其实不够直观,也不太能够让开发人员之外的业务人员、管理人员等直观地明白我们在哪里出现了问题,任务的划分也相对简单。B项目的这种策略,任务划分相对直观明了,任何人只要关注这条流水线,就大概知道应该是什么流程出现了问题。</li> <li><strong>及时反馈</strong> - 持续交付的最大好处其实就是及时反馈了。而这一点在A和B上都有体现,任务的成功与失败都可以给出对应的反馈,告诉我们是否哪儿出了问题。不过A相对来说,反馈方式(可视化程度)更弱一点,反馈周期(集成周期较长)更长一点。</li> <li><strong>自动化</strong> - 很明显,从上面两个图可以看出,B的自动化程度肯定是高于A的,无论是构建还是部署,A都需要去手动更改配置和手动触发。不过两种策略中间的实现毋庸置疑都是自动化的。</li> </ol> <p>如果只从上面看,其实B项目的策略理应优于A项目的策略的。但是,很显然,“没那麼简单 就能去爱 别的全不看”。还记得我们说过A项目服务众多吗,A的采用这种策略很大一部分原因,个人猜测(还未经验实),一是重视任务之间的 <strong>隔离性</strong> ,二是为了便于 <strong>管理各个服务之间依赖</strong> 。比如,在A项目中,我想把之前feature1的某个测试环境里面的某个服务改为另外一个合适的版本,我只需要在部署时,将部署任务执行前的某个参数改为对应的endpoint就行,这在B项目策略中虽然也是可行的,但A项目的方式相当于在每次部署前都会提醒你这些参数的值,你可以决定是否修改。</p> <p>当然,我个人觉得这与它们的git分支模型也不无几分关系。接下来就让我们来看看它们分别使用什么样的git分支模型。</p> <h2>A项目的git分支模型</h2> <p>A项目使用的git分支模型 - git flow</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/f3ecc3cf5b3902dbb1ff2356fe65e67e.png"></p> <p>简单介绍一下的各种分支:</p> <ul> <li><strong>master</strong> - 与产品环境代码保持一致的分支,也就是每次发布完成之后发布的功能分支就要合并于此,以保持master更新。</li> <li><strong>develop</strong> - 开发的主分支,feature和release分支会基于此分支。</li> <li><strong>feature</strong> - 具体要开发的功能的分支,完成后合并到develop。</li> <li><strong>release</strong> - 用于发布新版本的分支,完成后合并到develop和master。</li> <li><strong>hotfix</strong> - 用于紧急修复已发布的产品问题的分支,完成后合并到develop和master。</li> </ul> <p>这种模型的话,理论上来说相对安全。但是一般feature分支都是需要用于开发一个较大的功能才做的分支,在此之上,我们还要建对应的故事卡(敏捷中,一个不可/不宜划分的需求单位)的分支,如下所示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/8421f505ba75593961af22bf441c5ab4.jpg"></p> <p>这么做的好处有:</p> <ol> <li><strong>隔离性比较好,更加安全</strong> 所有的功能都会有对应的分支,开发和测试工作不会互相干扰,发布进程也不会受其他未开发完的功能干扰。</li> <li><strong>分支职责明确</strong> 对应的分支做对应的事情,职责明确。</li> </ol> <p>但是缺点也比较明显:</p> <ol> <li><strong>集成的周期太长</strong> 如果同时有几个大的功能在各自的分支上开发,每个功能的开发周期都不短的话,那之后他们之间的合并、集成工作将会十分痛苦。如果以《持续集成》这一本书中观点来看,这甚至算不上持续集成。</li> <li><strong>会有比较多的重复测试</strong> 完成分支的测试之后,在集成到主分支之后,还要重复一遍测试。自动化测试重复还可以接受,重复地手动的测试就比较烦人了。</li> <li><strong>结构相对复杂</strong> 分支较多,且存在层级关系(比如故事卡分支出自feature分支,feature分支出自develop分支)。</li> </ol> <h2>B项目的git分支模型</h2> <p>对应地,B项目,存在分支的话(我这么说,是因为也有不使用分支的真实项目),以我之前的某个离岸海外项目为例,会像如下图所示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/2c62c6abc69ee9ba93cc4de398e197eb.jpg"></p> <p>明显地,这种结构(其实也有种说法是github flow)看起来简单很多。所有分支都是基于develop或者叫master这样的主分支。基于故事卡建分支,合并分支。</p> <p>这么做有如下好处:</p> <ol> <li><strong>结构相对简单</strong> 所有分支的都是以故事卡为单位,结构简单。全部围绕一条主分支。</li> <li><strong>符合小步提交、持续集成思想</strong> 以一张故事卡为集成的最小单位,相对来说集成的周期短,反馈的速度也快,能够及早的遇到问题,从而及早的解决问题。</li> </ol> <p>但是,金无足赤,它有时候也可能会有一些缺点:</p> <ol> <li><strong>feature toggle的引入与测试</strong> 这种模型下,为了不让某些没有完成的功能影响已经完成的功能发布进程。在软件的设计初级以及后期测试,都需要把对应的feature toggle加入进来。也就是说,需要确保在各个环境中那些没有完成的功能应该处于disable状态。这无疑增加一部分工作量,也会带来一点风险。不过,这种工作量和风险大部分团队都会承担,毕竟如果计划分析的合理,发生的几率还是挺小的。</li> <li><strong>隔离性较差</strong> 引入feature toggle的很大一部分原因就是为了弥补隔离性上的缺陷。但是如果你主张:所有的分支终究是要合并到一个分支、发布成一个产品的,那这一缺点其实并不重要。</li> </ol> <h2>总结</h2> <p>当然,还有很多其他的策略和分支模型(或者没有分支的模型),我这里不再探讨过多。其实就我目前提到的AB两种,甚至可以交叉使用(比如A项目情况用B项目的策略),具体如何采用以及何时适合采用,这个问题可以留给有心的读者自己 <strong>思考</strong> 。</p> <p>最后我想说,这几种方式虽然各有优缺点,但相比更加传统的缺乏自动化的方式而言,已然进步太多。</p> <p> </p> <p>来自:http://dockone.io/article/1995</p> <p> </p>