为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

[计算机]持续集成系列小说

2017-12-19 38页 doc 314KB 22阅读

用户头像

is_037433

暂无简介

举报
[计算机]持续集成系列小说[计算机]持续集成系列小说 持续集成系列小说 一、持续集成之戏说Check-in Dance .................................................................................... 1 二、持续集成之“测试三角形与分段构建策略原则” ........................................................... 6 三、持续集成之“分支策略” ...............................
[计算机]持续集成系列小说
[计算机]持续集成系列小说 持续集成系列小说 一、持续集成之戏说Check-in Dance .................................................................................... 1 二、持续集成之“测试三角形与分段构建策略原则” ........................................................... 6 三、持续集成之“分支策略” ................................................................................................. 12 四、持续集成之“分支策略”(续)........................................................................................... 15 五、持续集成之“依赖管理” ................................................................................................. 20 六、持续集成之“自动化部署” ............................................................................................. 24 一、持续集成之戏说Check-in Dance 尽管Thoughtworks的首席科学家Martion folwer 为“持续集成 ”下了定义, 但由于自身背景与经历的不同,每个人对其都有不同的理解。从狭义上讲,持续 集成可以认为是一种基于某种或者某些变化对软件系统进行的经常性的构建活 动(注:这里的构建活动不仅指编译打包工作,还包含各类自动化测试、部署及 发布活动)。然而,它忽视了一点,即:任何实践中都应该包含“与人的交互” 这一因素。因此,从广意上讲,持续集成应该是软件开发团队在上述活动的约束 下所采用的整个开发流程及活动。它强调开发团队与持续集成系统之间的互动 性。我们既见过持续集成做得非常成功的团队,也见过效果不佳的持续集成,甚 至失败的案例。 那么,到底如何从持续集成中得到最大的收益呢,这要从持续集成所涉及的诸多 方面进行分析,并根据团队具体情况(比如团队规模、人员组成以及是否为分布 式团队 等)及所开发软件自身的特点(是企业应用软件,还是中间件,是嵌入 式软件,还是互联网产品等)制定实践策略与实现步骤。本专栏将与大家共同探 讨与持续集成、持续部署及持续交付相关的方法、工具与。作者本人在 Thoughtworks公司曾参与的一款持续集成与发布管理产品Go的交付和对外咨询 服务为专栏提供了很有素材,同时感谢肖鹏 、 李彦辉 、胡凯 、李剑等对栏目 内容的支持和帮助。 在软件开发中,持续集成实践能够解决的问是尽早的集成和尽早的反馈。因此, 尽管目前流行的所有版本控制工具都提供了分支/合并功能,但在少于20人的团 队中实现持续集成的话,推荐使用Single Branch开发策略。这样会减少多分支 开如在合并时的开销。另外,由于理想情况下,每个分支都需要有专属的持续集 成环境(包括持续集成服务器、构建环境和测试环境等),所以Single Branch 也减少了对持续集成环境的需求量(当编译时间较长或测试用例较多时,这个因 素的影响尤其重要)。 当团队完成最初搭建持续集成服务器,编写好一键式编译和测试脚本工作后,就需要考虑如何利用持续集成环境高效地进行团队协作开发了。一定有人会问: “多人同时在一个分支上开发的话,每个人提交时都要合并代码,不是更浪费时间吗,” 这个问题也正是持续集成期望解决的问题。每当开发人员提交代码时,就是其与其他开发人员工作成果的一次集成。如果每个人都能够频繁提交代码,那么代码集成的频率就会提高,在持续集成的有力支持下,代码中潜在的问题就会更早地暴露出来(比如代码编译链接问题,自动化测试失败反映出来的代码功能问题,或需求理解不一致等问题),以便团队尽早解决之。 当然,持续集成所鼓励的频繁提交并不是指那种仅将版本控制库当成备份工具,无约束的“随意”提交,还需要团队开发流程约束的。下面我们来一同探讨“持续集成环境中的团队开发流程是什么样的”。 让我们先设想一个软件开发场景。 一、使用版本管理工具做备份 故事的主人公叫Joe,他打算写一个游戏,所以用Subversion建立了一个版本控制库用于保存代码,然后就动手写代码了。Joe的开发流程是这样的。 1. 从代码库中检出一份代码; 2. 为增加某个功能修改一些代码; 3. 在本地运行了一下自动化测试; 4. 测试通过之后,提交代码到版本控制库; 5. 重复前面的步骤。 如图1所示。 二、搭建持续集成服务器做自动构建 “每次在本地手工运行自动化测试太麻烦了,”Joe想到,“这种重复的工作为什么不让机器来做呢”。 于是,Joe上网查了一下,发现持续集成工具是做这个事情的,就找来一台旧机器,用CruiseControl搭建了一个持续集成服务器。他的开发流程也变为: 1. 从代码库中检出一份代码; 2. 开发新功能或修改bug; 3. 提交到版本控制库,思考下一个功能的实现; 4. 持续集成服务器运行自动化构建和测试; 5. 如果测试通过,转到步骤(1); 6. 如果测试没有通过,转到步骤(2)。如图2所示。 三、多人并行开发 两周后,游戏初见原型,Joe向他的几个朋友介绍了他的游戏创建,他们都非常喜欢,因此也加入了游戏开发。麻烦很快就出现了。持续集成服务器中构建结果经常失败,所以每次检出代码后都要做问题清理工作。于是,Job与朋友们坐下来讨论如何解决这个问题。 Alice说:“我们每个人都拉一个独立分支,当每个人的功能开发完成以后,再合并到一起不就行了吗,” Joe不同意这样的做法。“游戏的需求还不明晰,要经常合在一起看一下效果。所以还是在同一个分支上开发吧。下面,我们讨论一下如何让这种失败少一些吧。” 于是,他们花了点儿时间,发现有两个主要原因导致失败。 1. 本地代码有问题,原本就编译不了或会导致测试失败,但还是提交了; 2. 开始做新功能时,没有特别注意分支上的持续集成状态,直接将主分支上 的代码直接就与本地代码合并了; Joe提出,开发流程应该变成如图3所示: 1. 每个人在开发新代码之前,只能从持续集成已成功的那个最新版本检出代 码; 2. 开发新功能或修改bug; 3. 提交前将主分支上的代码再次取到本地合并; 4. 运行本地测试,确保测试可以通过; 5. 提交代码到主分支,由持续集成服务器再次运行测试。 6. 如果测试通过,转到步骤(1); 7. 如果测试没有通过,转到步骤(2),直到修复持续集成上的构建。 可是,Alice提出反对。她认为:“既然本地已经运行了测试,为什么还要在持续集成服务器上再次运行呢,” Joe解释到:“主要是因为我们每个人的本地环境都不完全相同,很可能出现‘它在我的机器没有问题呀’的这个现象,所以还是要在独立的持续集成服务器上再运行一次。” 因此,大家就这么决定了。 四、两次本地构建的目的 四周后的一天,Joe花了很长时间完成了某个新功能后,打算提交了。于是他把分支当前的代码与其本地代码进行了一次合并。然后运行了本地测试,但测试失败了。他用了很长时间来定位该问题是在他自己修改的功能里,还是在被合入的代码中。这让他对提交流程进行了反思。 “要是在合入他人代码之前,能够先运行一次本地测试,验证一下我的代码没问题就好了,反正本地测试所花的时间也不长。” 于是,他把这个想法告诉了其他人,最后大部分人都同意这么做。于是,其提交流程就变成了这样:? 1. 每个人在开发新代码之前,只能从持续集成完全成功的那个最新版本检出 代码; 2. 开发新功能或修改bug; 3. 运行本地测试,如果有失败就立即修复,直至测试成本; 4. 提交前将主分支上的代码再次取到本地合并; 5. 运行本地测试,确保测试可以通过; 6. 提交代码到主分支,由持续集成服务器再次运行测试。 7. 如果测试通过,转到步骤(1); 8. 如果测试没有通过,转到步骤(2)。 这个过程就被称为“Check-in Dance”。 Alice还说道:“我们在从主分支上检出代码时,一定是那个通过持续集成验证的最新版本。这样可以避免检出的代码就是有问题的,而影响自己本地的代码。”整个过程如图4所示。 五、持续集成令牌 过了几天,有人把大家叫到了一起,这次是Alice。她说: “我今天遇到一个问题。我提交代码之后,正等着持续集成服务器返回结果呢,Bob就提交代码了。幸好我提交的代码通过了测试,否则的话,我就要在Bob的代码之上修复啦。所以,我建议我们需要设立一个提交令牌,只有拿到这个提交令牌的人才能提交。也就是说,当一个人做完本地测试之后,去拿这个令牌。拿到之后,再进行代码合并、本地测试和提交。提交以后当持续集成服务器返回成功通过的结果时,才能交还令牌。这样就不会出现我和Bob这种情况了。” 可Bob并不同意这样的做法,“这次没有出什么问题,为什么还要这么做呢,” 此时,Joe把话接了过来,说道:“Alice的这个建议很好,我已经遇上过一次这样的事情了,那次测试失败以后,我花了很长时间才发现问题并不在我的提交中,而是在Mary的提交中。我把它修复后,又做了一次提交。”由于大多数人都同意这么做,因此团队决定试一试。因为目前测试运行时间很短,所以提交和集成工作没有遇到什么瓶颈。提交流程如图5所示。 似乎事情到这里就结束了。然而,这个游戏被某投资公司看中,决定做更大的投入,招更多的开发人员,让它成为一个开放游戏平台。那么,接下来Joe与他的朋友们还会遇到哪些问题呢, 二、持续集成之“测试三角形与分段构建策略原则” 随着软件产品新特性的不断增加,软件自动化测试用例的数量也会成倍增长。对于一些历史“悠久”的遗留系统来说,甚至会积累数以万计的自动化测试用例。如果对这样的系统进行持续集成,还要求每个开发人员都要进行本地验证的话,困难的确不小。让我们还是看看Joe的团队是如何解决类似问题的吧。 在《戏说Checkin Dance》一文 中,咱们说到:Joe?的团队实施了带有令牌的持续集成提交流程纪律。由于每个人都做本地构建进行验证后再提交,所以持续集成平台上的构建结果比较稳定,每天持续集成服务器上的构建最多只有 一两次失败(常见的原因是忘记提交某个文件而导致失败,和因本地环境配置与平台环境配置不一致而导致失败),但一般都能在30分钟内修复。随着项目的进 行,新功能不断地增加,自动化测试用例也越积越多。由于不做任何修改,本地构建脚本就会运行所有自动化测试用例,所以本地构建的运行时间也越来越长。团队里有人开始抱怨,“每次提交代码前,运行本地构建都超过15分钟,这样太浪费时间。我们可否把那些不太重要的测试拿出去,不再运行了,” 一、自动化测试黄金三角形 作为团队的技术负责人,Joe把大家叫到一起,就这个问题进行了专门的讨论。 “我们不能放弃运行这些测试。”Alice说道,“在我前一个项目中,我们就是这么做的,结果,这些花精力写的测试都作废了。” “那是为什么呢,”?Bob问道。 Alice回答道:“因为并不经常运行这些测试,随着功能的修改,有些测试的逻辑就不再是正确的了。而当再次运行发现这类问题时,通常的结果就是把这个测试删掉,因为修复这个测试的工作量太大了。” “那持续运行所有测试的话,等待的时间太长了,也是一种浪费呀。?”Bob说 道。 此时,作为团队技术负责人的Joe说话了。“让我们先分析一下,到底有哪些什么原因让我们的测试在这么短的时间里就变成需要这么长时间了呢,” “功能增加的多了,测试自然就多了呗。” “功能增加了,自动化测试数据的准备工作也多了,需要的时间当然就长了。” “现在我们的测试中有很多地方需要测试在原地等待结果返回,所以等待时间也挺长的。” “大家还有没有其它原因,”Joe追问道。 大家沉默了一会儿,Bob说道:“好象主要就这些原因吧”。 “那好吧。功能多而导致测试多这是好事儿,说明我们大家都非常重视我们的自动化测试。对于‘测试准备时间变长’这个问题可以理解,因为我们的产品越来越复杂了。对于‘结果返回的等待问题‘嘛,需要具体问题,具体分析。前几天,我看到一个‘测试黄金三角形’,讲的就是自动化测试中各类测试的应具有的比例关系,对我很有启发。我在白板上画一下吧。”于是,Joe走到白板前,将这个测试黄金三角形画了下来,如图1所示。 然后,Joe将这个图形解释了一下。原来,这个三角形讲的就是单元测试、集成测试和验收测试的关系。首先,左边向上的箭头表示,越高层次的测试维护成本越高,运行时间越长。因此,对于单个测试来说,单元测试运行最快,维护最容易,而集成测试次之,验收测试则最高。?每类测试的面积代表着该测试的数量。现在,业界有很多种工具支持单元测试,因此它的编写及维护成本相对其它两种测试来说较低,应使用单元测试对代码做尽可能多的测试覆盖。一般来说,单元测试覆盖率达到70~80,是比较理想的状态。 接着,Joe问了大家一个问题:“我们产品中的这些自动化测试属于哪一类测试,” Alice说道:“那要看你怎么定义单元测试中的这个单元。” “根据WikiPedia上的定义,一个单元是指应用程序中最小可测试的部分。既然我们使用面向对象的开发语言C++,那么单元测试的粒度应该是类中的一个方法吧。而且,通常来说,如果一个测试包括以下任何一个情形,它就不是一个单元测试:(1)需要连接数据库;(2)需要网络通信;(3)需要与文件系统打交道;(4)不能和其它单元测试同时运行;(5)需要对环境进行一些配置(如编辑配置文件)才能运行它。”Joe回答道。 “要是这么说的话,我们的测试中,一部分是模块集成测试,一部分是验收测试,只有一小部分算是单元测试。我们的测试集合正好是一个倒三角。”Bob边说,边在白板上画了出来,如图2所示。 “既然高层次上的测试(集成测试和验收测试)维护量比较大,今后我们应该加入更多的低层次测试(单元测试),对于关键功能进行集成测试和验收测试。如果对于测试用例具有等价性的话,我们应该用低层次测试来实现。这样我们就会达到自动化测试的黄金三角状态啦。”Joe边说边在白板上笔划着,如图3所示。 “我同意你说法,但是仍旧没有解决我们目前遇到的问题。如何解决我们现在本地构建时间太长的问题呢,”Alice有点儿不耐烦地问道。 二、分阶段构建? “这还不容易,Martin Folwer(敏捷宣言的创造者之一)已经给出了一个解决方案,那就是两阶段构建(Secondary Build)。也就是说,我们可以把那些运行比较慢,时间比较长且基本上不会失败的自动化测试用例挑选出来,组成一个新的测试集,在第二阶段运行,可以叫做‘二级构建阶段’。剩余的测试集仍旧放在第一个阶段运行,我们可以把第一个阶段叫做‘提交构建阶段’。”Joe回答道。 “那什么时间运行这两个阶段的构建呢,”Bob问道。 “提交阶段构建当然就是在我们每个人提交之后就运行啦。而且在我们提交之前,作为本地验证集合,在我们开发环境上也要运行同样的提交构建。一般来说,本地构建和提交构建最好都在五分钟内完成,最长也不要超过十分钟,否则开发人员就不愿意花时间做频繁地代码提交啦。另外,一旦提交阶段构建成功以后,就马上自动触 发第二阶段构建。而我们开发人员在持续集成服务器上的提交阶段构建成功以后,就可以继续进行其它的工作啦。”Joe说道,“我们原来的六步提交图就变成这 个样子了。”说着,Joe拿起白板笔就画了出来,如图4所示。 “不对,这里有问题~持续集成强调尽早反馈。如果把测试分成两个阶段了,那 吗,”Bob反驳道。 反馈周期不是加长了 Joe 点点头,说道:“你说的没有错。但是,根据我们现有的软硬件资源条件,我们目前还无法通过增加资源的方式来缩短所有测试运行的时间。所以我们必须在质量与速度之前做出平衡。这也是我为什么要把那些不易出错的自动化测试集合放在第二阶段构建的原因,这样可以降低但不能完全解除第二阶段构建失败的风险。所以, 这也要求我们大家当第二阶段构建失败时,也要找人尽快把它解决,并且把相关的测试再次放回提交测试阶段中运行,或者在提交测试阶段加入新的测试来补充。” ? Alice此时插话,问道:“既然第二阶段构建不常失败,为什么我们不定时运行它,比如每天晚上运行一次呢,这样不是更节省资源吗,另外,如果第二阶段构建运行得慢,那它不是一直都落后吗,” “因为每次提交阶段构建成功以后就触发第二阶段构建,这样无论如何都比每天晚上运行一次的更多的反馈。因为每天晚上运行一次的话,如果出了问题,我们只能在第二天早上才能发现。对于你的第二个问题,我画一张图来解释。”Joe找了一张大白纸,在上面开始画了起来。 一会儿功夫,几个示意图就画好了。看到这几个示意图以后,大家恍然大悟。如图5所示。从图中我们可以看到: 1. 当版本123的第二阶段构建被触发并正在运行,Alice又提交了一次,触 发了版本124的提交构建; 2. 当版本124的提交构建完成之后,由于版本123的第二阶段构建仍在运行, 所以不再触发第二阶段构建; 3. 当版本125的提交构建完成时,版本123的第二阶段构建仍旧在运行,所 以也不触发第二阶段构建; 4. 当版本126提交构建正在运行时,版本123的第二阶段构建刚完成,此时 由于版本125的提交阶段构建是一个最近 成功完成的提交构建,所以持 续集成服务器就会启动该版本的第二阶段构建,而忽略版本124的提交构 建。 “那根据我们持续集成纪律,谁的提交让构建失败,就由谁来修复。如果版本 125的第二阶段构建失败了,就包括版本124和125两次提交的变更,由谁来修 复呢,?”Bob接着问道。 “这个好办,由这两个提交人一起负责修复。如果想确切找到谁的提交有问题,还可以手动触发版本124的第二次构建。假如构建成功,说明版本125有问题,假如构建失败,说明问题在版本124就引入了。”Alice抢着说道。 开始加强单元测试的力度;(2)在反馈讨论到这里,团队成员都达成了共识,(1) 速度和反馈质量之间做出折衷,使用二级构建构建的方式。 整个产品的开发非常顺利,马上就要进行版本发布了。团队还会遇到什么问题呢,他们是如何解决的呢,请听下回分解。? 三、持续集成之“分支策略” 现代版本控制系统(SCM)的作用已不仅仅是保存历史版本,它还是各软件开发组织利用其分支功能实现多人并行开发,提高生产效率的一种工具。对于稍有历史的软件产品来说,一般都会有代码分支的出现,也常常见到一些历史悠久的产品其错综复杂的分支版本树甚至将产品交付团队拖入“无尽维护”的泥潭。分支 分而治之”,而持续集成的目的是“频繁集成”,这二者之间又的目的是希望“ 有哪些联系呢, 在《测试三角形与分段构建策略原则》一文中,咱们说到:由于自动化测试时间较长,Joe的团队实施了分阶段的持续集成。虽然这么做引入了一些风险(比如因提交阶段构建中的测试覆盖面小而不能尽早发现代码中问题),但提高了整个团队的开发效率。而且,Joe会根据实际运行情况,在提交构建和次级构建之间不断调整自动化测试用例集来缓解分阶段构建带来的风险。 现在,这个软件游戏平台的第一个版本已经接近完成,马上就要进行内测了。团队面临的问题是:“如何做分支管理,持续集成该怎么做,” 一、短周期发布分支策略 今天是星期五。下班后,Joe和Alice等主要开发人员并没有马上回家,而是在一个小酒吧里聊天呢。 Alice说道:“现在我们一直使用主干开发方式,团队所有人都工作在Trunk上,与之对应的只有一个持续集成环境。下星期就要做内测了,我们是不是应该拉一个测试分支,用于修复测试中发现的缺陷,在主干继续开发新功能呢,一旦修复完内测缺陷的话,我们就可以在这个分支上进行发布,再把这个测试分支的代码变更合并回主干。就像这样。”她拿了一张纸画了出来(如图1所示)。 “好啊,好啊。我们分成两个团队,一个在测试分支上工作,修复内测过程中发现的缺陷;另一个在主干上工作,开发新的功能。”Bob回应道。 “对于拉分支做测试这件事,我没有疑问。但是,我不同意最后再把代码合并到主干上。”Joe说道。“我们一直在使用持续集成实践,目的就是尽早集成。为什么要等到发布以后再将测试分支的代码合并回主干,而不是每次修复一个缺陷就合并回来呢,每次缺陷修复的代码变更不会太多,所以合并起来很容易。等到最后再合并,首先是容易漏掉一些代码,其次是一次合并代码太多,容易出错。所以,我建议下星期拉分支时,为测试分支也建立一个持续集成环境。每次发现缺陷时,都为它写一个测试,加到测试套件中。修复代码提交后,就会触发测试 说完之后,他分支对应的持续集成构建。一旦构建成功,就将其合并回主干。”在Alice画的那张图上修改了一下(如图2所示)。 1. 拉分支之后,开发团队可以继续开发新的功能。而测试团队可以单独对分 支进行测试、部署,不受开发团队的影响。 2. 一旦测试中发现问题,载发人员要在该分支上修复。 3. 在分支发布之后,一旦发现了严重问题,仅在该发布分支上修复后即可发 布补丁版本。 4. 在分支上做修改后,就要根据实际情况进行分析,是否要合并回主干。如 果需要合并,应该立即进行。 “那由谁来负责把发布分支中的Bugfix合并回主干呢,” “当然是由Bugfix的人来负责了,他是确保合并正确性的关键。如果Trunk上的代码已被修改,无法合并,Bug负责人就要与主干开发人员交流,这个Bug在主干的有效性,然后再决定是否修改,在哪里修改的问题。” “我们要对发布分支上的Bug定义修复,尽量在Trunk上修复Bug,除非这个Bug严重影响发布质量。这样可以避免无休止地在发布分支上做代码修改。这样,Bug数才会收敛,发布分支的活跃期才会缩短。” “嗯,相对于我们一直使用的主干开发方式来说,这种短发布分支策略的成本是: 1. 需要多套独立的持续集成环境。即每个分支在处于活跃期时,要有与之对 应的一套持续集成环境,以便不受影响。 2. 每次发布分支上修复缺陷后,只要分支对应的持续集成构建成功,就要将 其合并回主干。 3. 由于主干开发的代码可能因架构改进使原有缺陷不复存在,所以每次合并 时都需要人为判断一下合并的必要性。” 二、长周期发布分支策略? “哦,我之前工作的一家公司,就是用这种分支策略。”Bob说道,“但情况变得非常复杂。版本满天飞,想做合并都不容易。” Joe说道:“我想,可能是因为他们的客户不想升级版本,所以必须在已发布的版本上再发小版本吧,” “的确是这样的。”Bob回答道,“他们的发布周期大约是半年。由于已发布的版本质量不佳,所以总是有紧急修复的版本上线。另外,客户比较担心新版本的稳定性,所以只要满足自己的当前需求,就会一直使用旧版本。有些大客户还会要求公司开发针对其自身的特别需求,并快速上线,结果可想而知。” Alice说道:“其实,这已经是短周期发布分支的变形,即有多个活跃分支的长周期发布分支策略(如图3所示)。这种分支策略是应该尽量避免的,它的复杂性和维护成本都很高,因为: 1. 每次都要把缺陷修复代码合并到后续的多个发布?分支上,尤其是当该缺 陷发生在较老的版本,而当前已有多个活跃版本需要维护时。 2. 随着时间的推移,每个分支上的自动化测试用例增多,更多的分支会对持 续集成环境中的测试机数量的需求快速增加。 3. 发布周期长诱使团队在已有的发布分支上再做子分支(如图3中的R1.1), 这会让集成和验证工作变得更加复杂(如图3中从R1.1到R2.0的Cherry Picking操作表明:需要向多个分支上合并部分代码)。 4. 由于每个活跃分支都要对应一个持续集成环境,因此,分支越多,对持续 集成环境的维护成本也就越高。 Bob问道:“有什么办法避免这种糟糕的多活跃分支开发策略吗,” “办法当然有,但不能解决所有问题。”Joe回答道,“比方说,首先,要确保每个版本的开发质量,让客户放心升级。其次,软件产品要支持自动升级。在通常情况下,只要满足需求,用户就不会轻易升级软件。所以,要让软件具有自动发现新版本并在后台自动升级的能力。当然,在升级后要用户。这样,只要将新版本发布到互联网上的某个服务器上就行了。最后,也是最重要的一点,新版本发布周期要短一些,不断快速地推出新特性,这样就可以让用户对产品及研发团队有信心,让客户感觉他们的需求很快就会被满足。” “对于那些企业用户来说,这种方法可能不管用。因为,企业内网很少可以连通外网。”Alice说道。 “如果是这种情况的话,除了软件本身质量好且能自动无缝升级以外,在销售时可以与客户签订协议,告知所售软件版本的生命周期(比如18个月)以及升级条款,促使企业升级该软件,比如免费的大版本升级,或者因缺陷原因可免费升级等等。”Joe回答道。 “嗯,我们开发的是游戏软件平台,部署在互联网上,所以不会遇到这个问题。”Alice说道。 Joe微笑着说道:“我们将会面临另外一种问题,即多个小团队开发不同的游戏组件问题。” “哦,对了~现在我们的游戏平台中虽然仅有几个游戏,目前还一起在主干上开发。但在下一版本中,我们会增加大量的游戏组件,那应该如何应对,我们的持续集成环境应该是什么样的呢,”Alice大声地问道。 “我已经有了一些想法。我们内测结束后,“嗯,是个好问题~”Joe回答道。 再详细讨论吧~时间也不早啦,大家回去休息吧,周末愉快~” 四、持续集成之“分支策略”(续) 在前文中,咱们谈到生命周期长短不同的两种分支策略。对于不超过二十人的小团队来说,推荐使用短生命周期的分支策略。Joe的团队在首次发布之前,也一直使用这种方式。然而,首次发布之后,因市场反响非常好,公司决定加大开发投入,希望更快地推出升级平台,以及更多基于平台的游戏。 一、按特性分支的持续集成策略 现在,Joe的团队中,开发人员快速增加,已接近30人了。由于首次发布后的市场压力,大家一直在赶进度,持续集成的失败频率越来越高,修复构建的时间也越来越长,排队等待提交的代码也越积越多。“这种状况不能再持续下去了,需要想个办法解决它。”Joe决定下午召集主要人员开会,分析一下原因和对策。 “现在我们还在使用提交令牌(参见《Checkin Dance》一文的最后一节),可我们的开发人数已经翻了一倍。而且,我们自动化测试用例的数量也激增。”Joe说道,“有时候我想提交代码都要排队等很长时间。” “嗯,每天等待提交的人也挺多的。”Alice说道,“现在看来,虽然持续集成让我们每次提交的质量都更有保证,但是在同一个主干上开发的人数太多,它就成了一个提高开发效率的瓶颈了。” “要不这样吧:我们把大家分成小组,每个小组从主干上拉出一个分支,完成一组相近特性的开发后,再合并回主干。”Bob边说边在白板上画了出来(如图1所示)。 “对应的持续集成方案也需要调整。包括: , 保留现有主干对应的持续集成平台,但不许在主干上直接开发代码; , 每个分支增加一个相对应的持续集成平台; , 每个分支的持续集成平台构建中需要包括该分支对应特性的单元测试、功 能测试; , 每次向主干合并时,都会触发主干上的持续集成,构建中应包含整个系统 的单元测试、功能测试等。 这样,每个小组的人数不会太多,提交时需要等待他们提交完成的概率应该不会太大。另外,每个分支的持续集成上只运行自己分支对应特性的单元测试和功能测试,这样,构建时间也会缩短。” “听上去是个好办法,”Alice答道,“可是,我对这个方案有几个疑问。比如说,这几个小组在什么时候做同步,每个小组什么时候向主干合并代码,” “嗯,好问题。我还没有想到这么多呢。”Bob皱了皱眉,感到很沮丧。 Joe笑了笑,说道:“的确是不错的方案。只要加一点同步与合并规则,改进一下。”然后,他拿起白板笔,在图上加了几笔(所图2所示)。 “规则如下: , 每个小功能在尽可能短的时间里开发且测试完成,最好是在一周之内。 , 每组做完一个小功能后,一旦该分支上的持续集成构建通过,而且手工验 证没有问题,就可以向主干合并代码。 , 合并后,与主干对应的持续集成平台会立即验证这些代码。 , 如果主干持续集成平台的构建失败,那么是哪个小组提交导致的,就由哪 个小组负责修复。 , 每天各组在开始工作之前,都要将主干上那个最新且通过主干持续集成构 建成功的代码检出,并与各自分支的代码进行合并。 其实,这就是小组级别的“Checkin Dance”。目的还是要持续集成,即尽要将各小组的工作成果集成在一起。如果每个小组能够做到频繁与主干代码同步的” Alice问道:“由于每个分支上都是多人开发,那么当某个功能完成后,并需要合并回主干时,该分支上可能已经有一些代码是属于尚未完成功能的代码。我们需要把属于该功能的代码修改挑选出来后提交到主干吗,” “你是说Cherry Picking吧。只要我们能够通过技术手段确保用户无法访问到未完成的功能,就不需要Cherry Picking了。比如通过配置项或功能开关的方式。”Joe说道。 “这样做,听起来挺好的,但还有一个问题需要解决,那就是:现在大家的代码耦合度太高啦。每增加一个小功能,都要修改很多个位置的代码。”?Bob说道,“如果这么做的话,各组之间的代码冲突会很多,合并可能带来很多问题。” “的确是这样的,目前的持续集成方案只能缓解合并问题,但无法解决合并中的代码冲突问题,只有通过对代码的结构进行调整才能够解决。”Job说道。“而且,对于我们这样的软件系统来说,对架构进行调整带来的益处更大。” 二、模块化应用程序的持续集成 “啊哈~架构调整,”Bob笑道,“架构这个词让人用得太滥了,还是不要提的好。一提到架构调整,我就想起在前一雇主公司干的活了——每次架构调整都是重写代码。” “哦,事实上,我们系统的架构基本上是模块化的,比如平台与具体游戏之间的边界还算清晰。”Joe回应道,“现在我们所要做的是强化模块化。因为,新加入的开发人员对系统了解不够深入,有些功能的耦合度开始增高了。我希望每个 游戏就作为一个独立模块,进行开发与测试。而它所依赖的游戏平台需要提供稳定的对外接口。” Alice说道:“那我们就可以不用前面提到的特性分支策略了,只要把每个模块做为一个独立的代码库进行开发,将它所依赖的游戏平台作为外部依赖进行集成就行了。” “的确是这样的。”Joe肯定的回答道。“如果每个模块对外都有某种形式的接口(比如API,接口定义文件),而所有外部依赖都通过这些接口与其进行交互的话,就可以这样做。”如图3所示。 “如果这么做的话,我们的持续集成方案应该是什么样的呢,”Bob问道。 “那不是一样嘛,即然都是独立的,各模块做各自的持续集成不就行了嘛。”Alice说道。 “当然不行,因为这些模块之间仍旧需要通过彼此交互才能正常运行起来,尤其是对于那些有信息交换的游戏模块,集成测试就更加重要。”Joe回答道,“既然需要集成,就要做持续集成。” Alice问:“那我们有这么多个游戏,每个游戏都要与基础游戏平台进行持续集成,到底应该怎么做呢,” “我们可以这么做。”Bob拿起笔在白板上画了起来(如图4所示)。“为每个模块的代码库建立对应的持续集成环境,包括每个游戏和基础平台。无论哪个模块代码库修改了代码,都会触发对应的持续集成构建,一旦该模块的持续集成构建成功以后,就会触发一个包含所有游戏和平台的集成构建。” “这样不错,但是现在每个模块都对应独立的代码库了,那么在最后各模块集成构建时,到底用各模块的哪个版本呢,”Alice问道。 Joe说道,“Alice的问题非常好。在最后各模块集成构建时,除了那个主动触发构建的模块使用最新版本外,其它模块都使用最后一次令该集成构建成功的那个对应版本。”Joe边说边在白板上画了一个例子。 “比如,对于我们目前的系统来说,一共有四个游戏模块和一个基础平台。假如最后一次成功的集成构建中,各模块对应的版本分别是123,245,212,467 和12387。当我们对游戏模块A进行了一次提交,其版本变为124,并且通过了它自己的持续集成构建以后,就会触发最后的集成构建。这次集成构建所对应的各模块版本分别为124,245,212,467和12387。如果这次构建成功,则下次最后集成构建就以这些版本为基础;如果这次构建失败了,则标记游戏模块A的124版本是可疑版本,尽管它通过了其自身模块的构建。同时需要有人对这次集成构建进行分析,进行问题定位并修复。”如图5所示。 “那么,我们的基础游戏平台也是由多个模块组成的。我们是否也需要把这些模块独立成库,使用同样的方式进行持续集成呢,”Bob问道。 Joe回答道:“我认为现在还不需要。平台内部模块化是应该的,但因为它自身的构建时间并不长,还没有必要独立成库。” Alice此时说道:“这样看来,我们的持续集成问题可以按这种方案来解决。让我们试试吧。” 那么,Joe的团队使用这种持续集成方案以后,还会遇到什么情况呢,比如,基 础平台的构建时间变长,会怎么样呢, 需要注意的是,无论采用哪种方法,我建议都不要让同一组人一直工作在一个模块上(虽然这是在各组织中经常见到的),而是让一组人工作在一组模块或功能上,并让小组成员在各组间流动。这样有利于组间的知道共享,对保持架构的一致性也会起到积极作用。 五、持续集成之“依赖管理” 在前文《分支策略(续)》中,我们讨论了多组件应用程序的持续集成策略,即:为相对独立的组件创建自己专属的代码库,然后通过现代持续集成工具进行组件间的持续集成。Joe的团队在首次发布之后,开始使用这种方式。然而,没有多久,他们就遇到了一个问题:一次提交构建所花费的时间太长。 一天,Joe就早早地来到了办公室。因为他前一天下班前,他开发的用户故事还有一小点就完事儿了。他想利用早上这点儿时间把它搞完,交给测试人员进行测试。他修改了某个模块的一段代码,在本地构建测试通过以后,就提交了, 然后起身去楼下买些早点。十五分钟后,他回到了电脑前,令他沮丧的是,这次构建还在进行最后的阶段,即所有模块集成测试和系统级测试。他只好又起身去冲了杯咖啡。然后,一边看着屏幕上的构建进度条,一边喝着咖啡。七分钟后,构建终于成功结束了。虽然这是一次成功的构建,但总是觉得不爽,花了二十多分钟才做完提交构建。于是,他开始仔细地查看起构建脚本和构建日志。 一、一次生成,多次复用 中午吃过午饭,他把Bob和Alice叫到一起,开始讨论早上他遇到的问题。 “的确是非常烦人,现在构建时间太长了。”Alice说道。 “我今天早上查看了一下我们的构建日志,发现构建时间长的原因之一是:每种测试开始之前都要更新代码,再重新编译一次。”Joe说道。 Bob提出了一个解决方案,并画在了白板上。“我们是否可以建立统一的产物库,每次构建的产物都以一定的规则放在其中,这样,后续的测试需要使用这些二进制产物的话,直接从产物库中获取即可。”(如图1所示) “听上去不错。然而,我们是否需要把每次构建中产生的内容都放入产物库,这会非常快地吃掉我们的磁盘空间。”Alice不无担心的说。 “目前构建完成以后,所有的产物都放在那台构建机器上。我们也遇到过因构建机器硬件问题或误操作将所有重要历史信息都丢失的事情。所以,我们至少需要备份。”Bob回答道,“另外,将每次构建的产物放在统一产物库中,我们就可以解决Joe刚才提出的重复编译问题。当然,我们需要有选择地将重要的构建产物放到统一产物库中,而不是所有内容。通过在每次构建后增加一个上传任务,让各小组将其认为有用的信息上传到产物库,比如构建日志、测试报告、构建后的二进制文件等。但一些临时文件就没有必要了。当然,这只能缓解产物库膨胀的速度。尽管持续构建的次数非常多,但我们并不是需要一直保持所有构建的产物,所以,可以定期删除那么没有保留价值的构建产物,比如对那些重要构建的产物进行标记,其它的就可以删除了。” Alice和Joe都点了点头,表示同意。但Joe的眉头马上又皱了起来。“嗯,好象这里还有点儿问题。” “什么问题,”Alice和Bob同时问道。 Joe说道:“对于我们平台中的一些小游戏组件来说,这没有什么问题。因为它们的构建产物都不太大,网络传输带宽和速度都不是问题。但是,对于那些很大的二进制文件或测试数据来说,这么做的话,可能就有问题了。”大家都点了点头,并开始思考这个问题。 忽然,Joe叫道:“不好意思,其实这不是个真正的问题。首先,我们的测试数据变化就不频繁,原来也没有放在产物库中,而是放在了一个共享目录中进行版本管理。所以,这部分在构建中的做法与之前没有什么不同。其次,对于较大的二进制文件,只要在需要它的构建机器上把它缓存起来。那么在下一次构建时,构建脚本可以对这个本地版本进行验证,如果版本正确且没有被破坏(比如通过MD5验证)就可以继续使用。否则,就再从统一产品库取出正确的文件将其覆盖就行了。” “这么做还有一个好处,而且是非常重要的好处。”Alice补充道,“我们的手工测试版本也可以从统一的产物库中拿到,这就保证了自动化测试所有的二 进制文件与部署到手工测试环境中的二进制文件是同一个文件了,也就不会出现因重新编译时的环境不同而导致的不一致问题了。而当我们做上线部署时,也从这个统一产品库中获取,从而做到自编译开始直到上线部署的二进制包的一致性啦。” 于是,Joe与团队一起对其持续集成平台和所有构建进行了改造,将其打造成了一个具有组织级产物库的持续集成和发布管理平台。他们不但有效地缩短了每次构建的时间,还可以轻松地通过产物库追踪到每个上线版本在代码版本控制库中的对应代码,让问题追查变得更容易了。 二、依赖管理 一个月后,根据市场的需求反馈,他们开发的一个游戏升级了,反应速度非常快,效果非常好。但引申出来的一个问题是:游戏和平台的升级频率不一致,持续集成应该怎么做。对于Joe的团队来说,是一个非常大的问题,因为他们的开发流程严重地依赖于持续集成平台。于是,Joe和团队的核心成员打算讨论一下,如何应对目前这种情况。 在会议室的白板前,Joe画出了当前所用的持续集成策略(如前图所示)。 Bob说道:“到目前为止,我们已经发布了几次,而且最近一次只发布了一个游戏应用。我们如何管理我们的发布流程呢,在我之前工作过的公司中,产品会有几个版本,包括稳定版本、已对外发布或即将发布的版本、最新版本:用于公司内部测试。每当将要发布新版本时,就拉出一个分支,进行内部测试,并修复严重的缺陷。当没有严重缺陷时,才能作为稳定版本公开发布。” 对于单个的软件交付产品来说,通常可以通过“按发布拉分 Alice答道:“ 支” 的方式进行开发,正如我们最开始所使用的持续集成策略。但是,现在我们的游戏平台与单个交付产品不同。我们有自己的服务器集群,只要测试覆盖率及测试质量足够好,测试速度足够快,我们就可以通过小流量试验部署后再大规模上线的方式进行发布。现在,我们的问题是由于各个游戏组件的发布频率各不相同,组件存在依赖关系,导致很难决定在持续集成过程中,到底应该使用哪个依赖版本。尤其是我们现在还有一个公共库,被多个组件使用。” Joe说道:“我们先梳理一下整个平台上的依赖关系吧。通常来说,软件中的依赖关系通常包括编译时依赖、测试时依赖和运行时依赖。而从依赖形式上可以分为库依赖和组件依赖。所谓库依赖,是指依赖于那些不受控的库文件,比如我们使用了一些开源或者付费的的类库文件或工具,这些库文件的特点是更新较慢,甚至基本不需要更新。而组件依赖是指依赖于那些由自己团队或公司内的其它团队开发的组件,这类依赖的特点是更新频率相对高,有些甚至非常频繁。对于库文件依赖,我们可以在代码库中建立一个目录,叫做lib,并在其下建立build、test、run三个子目录,把我们所依赖的库文件放到相应的子目录中。同时,每个库文件的文件名中最好包含它的版本号,如nunit-2.6.0.11089.bin。这样,就很容易看出依赖了哪些库文件。” Bob接道:“可惜我们不是用Java平台,否则我们可以用象Maven或Ivy这样的工具来管理这些外部库依赖了。而且,同时可以在公司内部利用Artifactory或Nexus这样的开源工具建立一个内部统一服务器,专门管理公司内部所用的这些库依赖。” Alice说道:“我们也可以自己做一个简单的依赖管理系统。比如使用Key-value的格式用文本文件来描述所用到的库文件名及版本号及存放位置,然后再写个通用脚本读取信息下载到本地使用。” Bob接着问道:“对于这种库文件的依赖管理相对容易一些。而我们面临的重要问题好象是组件依赖管理。有什么好办法吗,” Joe想了想,说道:“方法倒是有几个,各有优缺点。一种方法是将组件依赖转成库依赖。其适用的场景是该组件经过一段时间的开发的维护后已趋于稳定,变化不太多。此时就可以将这个组件打包后与其它外部依赖库放在一起,并加入正确的描述,以便依赖于它的所有组件都可以正确地拿到正确的版本。还有一种方法是我们目前所用的方法。即每个组件各自进行持续构建,然后再做集成构建。其中存在的问题是我们如何管理各组件不同版本之间的组合关系。我们一直使用的策略是无论哪次提交,都会触发整个构建。目前要做的有两件事:一是将公共库独立出来,进行单独构建,并且一旦构建成功,自动触发那些依赖于它的其它组件构建,最后进行集成构建。只要我们记录每次构建后的版本及源代码的 revision就行,以便可以追踪。二是将游戏平台的持续构建触发其它游戏组件的持续集成。所以,触发关系应该是这样的。”Joe拿起笔,在白板上重新画了一下触发关系图(图2)。 Bob摇了摇头,说道:“这样还是解决不了我们之前说过的问题,即我们的发布频率不一致,如何来管理这些发布之间的关系。” “噢,这个问题是这样的。”Joe回答道:“我认为,我们之前单独发布一个游戏组件是不对的。我们因市场压力而将该游戏组件直接部署到生产环境中,尽管在发布前的评估认为,该游戏所依赖的平台接口没有发生变化。正确的做法有两种:(方案A)将平台作为一个整体一同发布,因为我们对平台也做了修改,当时,所有的持续集成测试都是基于主干的最新版本所做的。(方案B)让所有游戏组件依赖于游戏平台的最新发布的稳定版本进行开发。由于平台的新功能开发较慢,所以只要平台接口不发生变更,各游戏应用都可以基于平台的稳定发布版本进行快速更新。但只要某个游戏需要修改平台的接口,就必须与平台的最新代码进行持续集成,并一同发布。” Alice皱了皱眉,说道:“这么看来,对于整个软件来说,能够保持主干随时可以发布才更容易管理组件依赖。因为每当需要发布时,直接做主干发布就行了。实在不行的话,只要将所有组件在同一时间点拉出一个发布分支,然后统一上线就行了。” Bob说道:“这样也有问题。我们的部署会很麻烦,时间可能会很长。” Joe笑着说:“部署麻烦,我们可以通过一系统列的自动化操作来解决。部署时间长的话,我们使用的是集群部署,因此可以采用分批替换的方式来部署。但这种发布方式给我们带来的益处是可以很快的响应市场需求。” Joe拿起杯子喝了口咖啡,接着说道:“当然,这对我们的开发工作也提出了挑战。我们必须使用多种手段才能做到主干持续可发布状态。比如(1)将新功能隐蔽起来,直到它完成为止;(2)把所有的变更都变成一次次非常小的增量式修改,每个修改都做到可发布;(3)通过抽象达到分支的目的(Branch by Abstraction)。另外,我们的自动化测试也需要保持在较高的覆盖率,并丰富其它类型的自动化测试,比如性能测试,压力测试等。如果遇到特殊情况,我们再坐下来商量对策。” Bob仍旧有点迟疑,“这样可能会增加我们的开发成本。不过,可以试一下,看看效果如何。” 于是,整个团队开始行动起来了。他们在这条道路上还会遇到什么情况呢,让时间来回答这个问题吧。 六、持续集成之“自动化部署” 在前文《依赖管理》中,我们讨论了如何在代码变得庞大,组件增多的情况下,做好外部库和内部组件依赖管理,从而提高构建效率。可以应用的实践包括:一次生成,多次复用;建立统一制品库,外部依赖库可以使用像Maven或Ivy这样的工具进行统一管理;对架构进行调整,使一个大的代码库分成多个组件;每个组件有自己的持续集成体系;对多个组件做持续集成。然而,解决一个问题后,总会有另一个问题等在那里,需要你来解决。这次Joe的团队遇到了部署问题。 星期一早上,Alice一进办公室,就看到一脸倦意的Joe坐在椅子上,喝着咖啡。 “今天怎么来得这么早,看样子,你没睡好啊,”Alice问道。 “当然啦,昨天晚上我就来了。”Joe无精打采地回答道。 “怎么啦,” “还不是因为新版本上线出了点儿问题”,Joe说道。“看来我们要把部署这件事好好讨论一下,再这样下去,不只我要来,你们也要和我一样啦~呵呵~” 当天下午,Joe邀请了运维团队的主要负责人Tom和Steven,召开了一个关于部署问题的讨论会。 Joe说道:“先请运维部门的Tom介绍一下上周末的新版本上线过程和发现的问题吧。” Tom描述了上线部署全过程。 不可重复且不可靠、易出错的手工部署过程 1. 当新版本开发测试完成后,由开发团队的成员在浏览器上登录运维平台, 填写上线申请单。申请单的内容包括新版本的上线部署步骤。 2. 测试人员为了保证能够升级部署成功,首先要复制生产环境中的程序和数 据到本地的测试环境中,然后根据上线申请单中所描述的上线部署步骤进 行操作,对上线步骤进行验证。 3. 运维人员登录到运维平台,收到上线申请单后,确认“已收到”。 4. 运维人员发现上线部署步骤有问题,生产环境的路径与上线部署步骤中描 述的不一致。于是与开发人员进行沟通,让开发人员修改上线部署步骤。 5. 开发人员修改后,再次通知测试人员和运维人员查看并确认。 6. 确认无误后,运维人员根据部署计划,登录到生产环境中,依照上线部署 步骤,手工操作完成。 “上周末上线部署时出现的情况是:在本次部署之前,我们的集群中,有两台机器因HotFix,其程序配置被修改过,与其它机器不一致。因此,该机器上的部署失败,导致部分服务不可用。运维人员查了很长时间没有发现问题,星期日打电话把Joe叫来帮助我们查问题时,Joe才回忆起有那么一次HotFix,但当时负责的运维人员已经离职,没人其它运维人员知道这件事情。”Tom说道,“我们对问题进行了分析,认为应该加强我们的上线流程管理,对于那种HotFix也应该发起一个审批流程,并且在该流程中不但要主要负责人审批,而且要对相关人发出周知通报。另外,我们的运维人员应该对上线单进行严格审核,并对部署中所涉及的机器进行更详细的验证,对生产环境中的任何修改都要进行登记。 即使非常紧急,也要在事后补充记录一下。” “这些方法固然很好,但其实我们可以采用更好的办法来解决。”Joe接着说到,“假如我们在部署运维工作也能够借鉴持续集成的做法,利用一些最佳实践,那么这次部署事故根本就不会发生。比如(1)将部署操作脚本化;(2)进行持续部署验证测试;(3)部署脚本通用化,环境变量等使用配置方式传入;(4)让测试环境尽可能与生产环境一致,至少在成本条件允许的情况下尽量保持相似;(5)对环境配置进行版本控制;(6)任何人不得直接对生产环境进行直接的手工操作,等等。” 将部署操作脚本化,并进行部署验证测试 Bob说道:“嗯,其实那些上线步骤中所描述的内容都可以进行脚本化,之前也讨论过这一问题。目前上线步骤中的内容基本都可以写成自动化脚本,即使现在不行,也可以通过少量改造,使其可以自动化。但问题是... ...”Bob犹豫了一下,接着说道,“如何来验证这些脚本是正确的呢,” Joe 说道:“保证运维人员是如何验证上线申请单上的上线步骤是正确的呢,同样,我们也可以做一些部署验证就行了。这些部署的验证也可以通过脚本方式来进行,比如在安装之前验证程序所用端口没有被占用,安装之后验证该端口已被该程序所使用;比如安装之前验证程序日志中记录了该程序已停止运行,在安装之后验证程序日志中刻录该程序已重新启动;等等”。 Alice问道:“那我们还要调试这些部署脚本呀,没有线上生产环境,我们怎么调试呢,” 各类环境尽可能相似,并使部署脚本通用化 Joe 回答道:“首先我们应该加强基础设施这方面的投入。在力所能及的情况下,让测试环境与生产环境相似。比如,生产环境可能有100台机器的集群,那我们至少要找两台机器的集群做测试环境。生产环境中使用Tomcat,我们的测试环境和开发环境中也应该使用相同的Tomcat,而不用Jetty。” Joe 停下来,喝了一口咖啡,接着说道:“这样一来,我们的部署脚本就可以在开发环境、测试环境进行测试了。当开发人员进行本地测试时,可以使 用这个脚本进行单机的部署。当测试人员进行集成测试时,可以使用同样的脚本进行多机部署。与机器数量无关的配置可以统一放在某配置文件中。而与机器数量等相关的配置可以放在另外的配置文件中。由于在真正上线部署之前,开发人员和测试人员已经使用同一个脚本进行多次部署,就是对该脚本进行的测试。当我们上线部署时,只有与机器相关的配置文件会有变化,其它配置基本相同,所以上线部署时脚本出错的几率已经比较小了。而且,这种自动化没有人工干预, 也不会发生手工误操作。” Tom问道:“那这些脚本由谁来写,由谁维护呢,” Joe回答道:“谁最了解情况,就由谁来写。其实,我们也应该像对待产品代码一样,来对待这些脚本和配置文件,把它们放在我们的代码库里,进行版本控制。无论是运维人员还是开发人员,或者测试人员,对这些脚本的修改都应该提交到版本控制库中,除非他所做的修改只是为了测试他自己在本地的程序,那就不用提交了。这样一来,‘谁在什么时候对什么进行了修改,为什么做修改,’这个审计问题就可以直接由版本控制系统来回答,也就做到了所有内容可追踪了。” 对环境管理进行版本控制,杜绝对生产环境的手工直接修改 “听上去,对于配置文件、脚本等进行版本管理的确是解决了运维部署的很多问题。但如何对环境管理进行版本控制呢,”Tom问道。 Joe想了想,说道:“环境管理比较复杂。一般来说,环境包括几个层次,包括硬件及网络配置、操作系统、我们的应用程序所依赖的软件堆栈及其配置、以及我们的应用程序运行时所需的数据及其配置。目前对我们来说,对于硬件及网络配置、操作系统这两层来说,有两种方式进行管理。一种是利用一些专用软件进行自动化的远程配置,即只要给机器加电,就可以通过一些技术对一台机器进行系统的安装与配置。另一种是使用虚拟化技术来进行系统配置管理。对我们现在的游戏平台来说, 使用后者即可。只要将基本的环境做成虚拟机镜像文件,并将其作为环境基线进行版本管理。当然,由于镜像通常较大,所以最好不要使用常见的版本控制工具(如subversion,Git等)进行,而使用某种简单的机制即可。” Joe停了一下,看看大家没有提问的意思,于是接着说道:“至于基于其上的软件堆栈及堆栈中各软件的配置管理完全可以利用类似于CfEngine,Puppet或Chef的工具进行。这些软件环境管理工具 都提供某种领域专属语言来描述软件堆栈配置,并保存在文本文件中。这些工具一般通过服务器/客户端的工作方式运行,客户端向服务器发送请求,验证本机器节点的软件配置是否与服务器中的设置相符,如果不符,就会自动更新。尤其重要的是,这些更新操作都是幂等的,即无论这些配置在该客户机上执行多少遍,每次的结果状态都是相同的。另外,它们通常能与版本控制工具集成。所以,只要将我们的软件堆栈配置管理信息放到版本控制库中,就可以同时管理数台机器。” “oh, 对不起,Joe,我想打断一下,”Tom问道:“你能画一个图来解释一下你刚才所说的这种软件环境配置管理工具吗,” “当然没问题。”Joe拿起笔在白板上画了一个Puppet的工作示意图,如下图所示。 “看上去清楚多啦。”Tom笑道,“通过这种方式,我们就只需要将版本控制库中保存的配置信息检出到本地,进行相应的修改,再提交到版本控制库中,这种工具就会自动帮我们完成必要的配置更新了。是这样的吗,” “对,”Joe点了点头,说道,“如果我们的部署脚本也是通过这种方式来做的,那么我们就根本没有必要登录到生产环境的机器上,进行手工操作了。而且,Puppet还提供一种Try Run功能,可以进行配置变更的模拟,让你能够对比一下变更前后的不同之处。” 你说的这些听上去都不错。但并不是所有人都能够修改生Tom说道:“ 产环境的配置信息的。所以我们还是需要一个软件平台来管理上线的申请审批流程。” 在任何企业中,这种申请审批流程和生产环境变更的授权都是必要的,“ 但这仅仅是审核流程的操作。而真正与软件部署相同的具体操作都不应该在这种审批流程当中。”Joe回答道。 Tom接过话来,说道:“嗯,这样的话,我们仍旧能够做到:有权限的人才能真正修改生产环境的配置文件,同时达到了无人真正直接操作生产环境的目的,避免了手工误操作带来的问题。” 参加本次会议的测试人员和运维人员对这种做法产生了浓厚的兴趣,并要求开发人员给予配合,将目前游戏平台的部署自动化。Tom说道:“这就是我们运维工作的一个方向。让枯燥易出错的重复性手工操作变成受控的自动化,从而解放运维人员,让我们可以关注于更加有价值的运行监控等工作中。” Alice说道:“这看上去还是有一定的工作量啊。” “当然,我们可能需要做一些工作,但我想这些投入是值得的。”Joe回答道。“同时,还需要各种角色之间更紧密的配合,而不是像之前那样,通过一个代表上个世纪八十年代先进技术的办公自动化平台来描述部署上线步骤这类关键的业务操作信息。” Tom也点了点头,说:“嗯,应该使用版本控制方式。但我们还是需要一个上线审批的流程,只不过,这个流程中不再保存上线步骤这类与实际部署相关的业务信息,而只是为了部署人员的资格审核与信息周知的目标。” 经过一番讨论,开发、测试和运维团队在这件事情上达成了一致,并按计划开始实施了。
/
本文档为【[计算机]持续集成系列小说】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索