一键发布

来源:互联网 发布:网络爬虫 c语言 编辑:程序博客网 时间:2024/06/10 22:43

(选自《 ThoughtWorks文集 》第12章,作者是Dave Farley,原ThoughtWorks技术主管

 

 

持续构建

敏捷软件开发的核心实践之一就是持续集成(continuous integration,简称CI)持续集成是指开发人员一旦将代码提交到版本仓库之后,就进行构建,并运行一系列自动化测试套件的过程

这一实践已被采纳多年,用于提供软件可靠性的检验手段:即任何时候,正在开发的软件都要进行构建并通过所有的单元测试。这显著提高了团队对其开发的 软件达到其质量要求的信心。虽然对于复杂项目来说,潜在的问题并不在于能否成功构建和通过单元测试,但对于许多项目(甚至大多数项目)来说,这是最终交付高质量且可靠软件的过程中,向前跨进的巨大一步。

较高的单元测试覆盖率当然好,然而从本质上来说,单元测试是开发人员用于证明所写的产品代码的确是他们心中所想的解决方案,而并不是用于证明产品代码满足业务需求

一旦软件被开发出来,就要被部署。而对于现在大多数软件来说,部署过程不再是拷贝一个可执行文件就完事大吉了。相反,该它通常包括很多部署及配置操作,比如Web服务器,数据库,应用服务器,消息中间件等等。这样的软件通常都会有一个相当复杂的发布过程,部署到各种各样的环境中。比如,它首先可能会被部署在开发环境中运行,然后会被部署到测试环境,还可能被部署到性能测试环境、试运行环境,最后才是生产环境。而且,绝大多数项目的这一过程都会有人为干预。工程人员会手工管理配置文件,对那些需要部署的软件进行剪裁,来适应当时的部署环境。此时,人们常会漏掉一些操作步骤或忘记某些文件的所在位 置,比如,你常常会听到这样的话:我花了两个小时的时间才发现这个测试环境中的模板文件与生产环境中的位置不同

上面提到的持续集成(CI)是非常有用的实践,但叫它持续集成有些不恰当,可能持续构建更为恰当一些。那么,它在整个软件发布过程中处于什么位置?而整个系统的持续集成又应该是什么样子的呢

超越持续构建

在过去几年实践中,我所在的团队建立了一个端到端的持续集成发布过程,只要轻轻点击一下鼠标,就可以将大型复杂系统部署到任何一个我们想要部署的环境上。 这种方法大大减轻了发布时的压力,而且遇到的问题也减少了。在建立这种端到端的持续集成发布过程中,我们总结出了构建过程的一般步骤,这让我们在项目一开 始就可以很快建立这个持续集成系统。即每个版本都要通过一系列的检验阶段来证明其质量;每成功通过一个阶段,就证明该版本的软件质量可靠性就增加了一些; 通过最后一个检验阶段的版本就是可以被认为是满足发布条件的候选发布版本。对于敏捷软件项目,每次代码提交都会让其产生一个版本,而每个版本都有可能成会 满足发布条件的候选软件

对于多数软件来说,这个包含一系列检验阶段的过程都是适用的,仅对于个别软件来,可能还需要剪裁一下。我们将这一过程称为构建管道,或持续集成管道,或者管道式构建

全生命周期的持续集成

一个典型的全生命周期就是一个持续集成管道,也是本方法的关键所在。

管道式持续集成过程的起点是:代码被提交到代码仓库。持续集成工具(我们使用CruiseControl)就会发现这次提交,并触发以后的一系列阶段, 即它对代码进行编译、运行自动化测试,如果测试通过,就会将其打包生成二进制文件,并将其保存到统一管理的二进制文件库中

本方法的基本出发点在于:每通过管道中的一个阶段,该版本就向终点靠近一步,其观念就是:让错误走到最后发布的机会最小化

 

二进制文件的管

假如我们只保存源代码,那么当以后需要部署某一版本时,我们还要重新编译打包,假如我们打算用这个版本重新做一次性能测试,这次重新编译打包就有可能在性 能测试环境中得到与前一次不同的结果。可能是因为本次编译打包使用了与前一次不同的编译器版本,也可能因为使用了不同版本的动态链接库等等。我们希望尽 可能消除这种可能性

为了在该过程中避免重复劳动,我们将其中每一步的脚本都写得非常简单明了,而且将与环境相关的内容同与环境无关的内容分离。

然而,当以这种方式进行二进制文件管理时,需要注意有一个问题,即这很容易浪费大量的存储空间去保存那些很少用到的二进制文件。一般来说,我们会把它们压 缩后保存,而且尽量不将它们保存于版本控制服务器中(因为这不值得)。相反,我们使用共享文件系统做为二进制文件仓库,将二进制文件保存在打过版本标记并 压缩的镜像中。迄今为止,我们用脚本来做这件事,还没有找到更好的替代方法

由于这些镜像文件都标有与之相对应的源代码版本,而这种标识可以表明源代码、二进制文件及相应配置信息、脚本文件等所有相关信息之间的关联关系

这些受控的二进制文件形成了近期构建的缓冲区,当容量达到一定值后,我们就会删除旧文件。一旦由于某种原因,我们想将部署回滚到某一版本时,完全可以从该版本的标识中得到相关的源代码信息,并从代码仓库中得到这个源代码(这种情况很少发生)

第一道关卡——运行提交测试套件

这种自动构建管道由创建一个候选版本开始。而候选版本由任意一个开发人员向代码库提交代码产生。持续集成工具一旦发现开发人员的提交,立即代码编译并运行一组测试套件。这组测试套件包括所有的单元测试、一些冒烟测试,以及任何能够证明该版本质量满足发布候选条件的测试

这些测试的目标就是快速失败(俗话说得好:早死早投生,译者注)。开发人员要等到这些测试全部成功以后才能做下一项任务。从这一点来说,速度是维持高效开 发过程的关键。而在持续集成管道中,越晚的失败,其修复成本就越高。因此,出于维持团队高效性的目的,在这组提交测试套件中,除了单元测试以外,对其它测 试应进行适当挑选

一旦这组测试套件全部通过,虽然管道中后续的阶段还没有开始执行,但我们可以认为本次提交成功,开发人员就可以开始做下一个任务了

这不只是过程级别上的优化而已。因为在理想情况下,如果所有的验收性测试、性能测试以及集成测试可以在很短的时间里完成,我们就不需要管道式持续集成了。 然而现实情况是:很多测试需要更长时间来执行,如果让整个团队停下来等着这些测试完全成功再继续工作的话,无疑团队生产率会显著下降

将提交测试套件通过与否做为团队是否可以继续下一个任务的条件。当然,我并不是后续的阶段就不再关注了,而是希望团队时刻关注每次提交在管道中的执行情况,其目标是尽可能早地发现错误,并修复它们,同时当后续阶段执行过程中,又可以让团队去做其它任务

只有当这种提交测试套件的覆盖率足以捕获大多数错误的情况下,这种方法才可能被接受。如果大多数错误是被后续的阶段捕获的,那就表明需要加强一下提交测试套件

我们希望开发人员能够尽早提交代码,可是这还取决于提交测试套件是否能够找到我们引入的绝大多数错误。要达到这一点,我们还需要通过不断试验来优化

最初,我们可以将所有的单元测试做为提交测试套件,如果某个部分在后续阶段中经常失败,则再为其编写测试并将其加入到提交测试套件中

第二道关卡——验收测试套件

 

单元测试是敏捷开发过程的关键部分,但仅有单元测试并不足以保证软件质量。虽然全部单元测试都能通过,但仍不满足业务需求的情况时有发生

因此,除包括单元测试在内的提交测试套件之外,我所在的团队还依赖于自动化验收测试。这些测试根据用户故事的验收条件编写,用于证明我们所写的代码满足用 户验收条件。这些测试就是是一种对系统进行端到端的功能测试,如果所开发的软件与其它外部系统有交互的话,我们会编写一些码桩(Stub)来完成这种端到 端的功能测试

我们将创建并维护验收测试也纳入到开发流程中,即只有根据验收条件写好验收测试并将其加入到验收测试套件中,且通过了该测试,才能认为完成了一个用户故事 的开发工作。我们还尝试让这些测试更加易读,让非技术人员也可以理解。然而这超出了本文所讨论的范围,就不在这里深究了

验收测试运行于一个受控环境中,而且可以由持续集成工具来监控

在发布管理当中,验收测试是第二个关键点。我们的自动部署系统仅部署那些通过所有验收测试的软件版本。即任何版本都不可能绕过验收测试被部署到生产环境当中

部署准备阶段

在某些项目中,可以实现部署自动化,即将软件自动部署到生产环境中,然而,对于大型企业级软件应用来说,这基本上是不可能的。可是,如果我们能够将整个基 础环境的管理和配置进行自动化,那会消除很多错误的来源,尤其是在大型企业级应用环境且手工操作如些之多的情况下,更是如此

我们采取了一些卓有成效的方法来解决这一问题。例如我们保存了很多镜像,包括标准的操作系统环境、应用服务器、消息代理服务器以及数据库等。而将这些镜像恢复以后,就组成了部署完整且包括基本配置信息的系统快照

而这些镜像可以以各种各样的形式存在,只要满足项目的需要即可。通常情况下,我们一定会有一个数据库脚本,用它来建立一个初始数据库结构并做一些数据初始化工作,还有一个标准操作系统或应用服务器的标准配置,而这个配置可能仅是一个文件系统的目录结构复本而已

无论这些镜像是什么,其目的就是建立一个基础环境配置基线,以便以此为基础,对后续的变化进行维护,这样我们就迅速建立一个环境,而且会避免人工干预而产生错误

但并不是每次部署软件时都重新建立这种初始环境。一般来说只在部署新环境时才使用,平时很少使用。但每次部署软件时,都会将其重置到基本点,以便让后续的部署工作建立在一个基线之上

一旦这种基本环境准备好后,还要运行一些简单的部署测试。这些测试的目的在于确保当前的基础环境满足部署软件的基本要求,一般来说这些测试都非常简单,比 如测试一下是否可以连接上数据库,应用服务器是否响应请求等。如果此时有测试失败的话,我们就会断定,要么我们的备份镜像有问题,要么我们的硬件环境有问 题。而所有测试通过的话,我们就可以开始下一步的部署了

部署时,运行那些用于软件部署的脚本,它就会从二进制文件仓库拷贝已通过提交测试、验收测试的指定版本的二进制文件到相应的路径去。除拷贝文件之外,我们的脚本还能启动或停止应用服务器或WEB服务器,运行脚本初始化或更新相应的数据库,适当地配置消息代理等

基本上,部署过程共分为五步,其中四步是独立的:

·  从镜像安装基础结构。(只有初始新的服务环境时才需要做这一步)

·  清理环境,使其重置到基本点,以便让后续的部署工作建立在一个基本点之上。

·  运行部署测试,以确保基础结构满足软件部署的基本要求。

·  将软件放到相应位置。

·  对其进行适当配置,满足应用软件的运行需要。

我们将构建或部署脚本根据其责任分成多个部分,使它们尽可能的简单,而且每个脚本都仅做一个具体任务,并定义尽可能清晰的输入参数

后续的测试阶段

如前所述,验收测试是项目生命周期中的一个关键里程碑。某版本一旦通过了验收测试,便可以部署到各种不同的环境中,假如它没能通过这一步的话,也就不需要浪费人力去做部署。这表明了一个原则,即只有通过测试的代码才能发布

验收测试套件这一步为止,持续集成管道都是自动执行的。即新的版本通过前一步的测试后,自动进入下一步,并运行该阶段的测试

在大多数项目中,这种自动化方法在后续的步骤中就不那么适用了。因此,我们让后续的阶段成为多选项。即那些通过验收测试的版本可以选择性地进入人工验收测试阶段,或性能测试阶段,亦或是部署到生产环境中

对于这些阶段的部署工作来说,执行上面提到的部署过程五步骤有助于确保一个纯净的部署过程。当某版本被部署到生产环境时,这样的步骤应该已经被执行过几次,出现差错的机会很少

我的上一个项目中,每个装有我们的应用软件的机器上都有一个页面,用于显示候选的有效发布版本,还可以选择重新运行功能测试套件或性能测试套件。这为我们能够任意时刻无差错部署软件提供了高灵活性

除了这些后续测试阶段的自动化程度可能有所不同外,整个过程基本是一致的。对于某些项目而言,可能每次都运行性能测试是有必要的,而对另外一些来说,可能 就没有必要。其实,验收测试套件的后续阶段之间的关系以及自动化程度的高低并不是问题,而如何提供并管理这些持续集成过程的脚本才是需要考虑的事情

让过程自动化

2反映了自动化持续集成管道脚本。每个方框代表一个阶段,方框内的每一行代表了一个脚本来完成相应的功能

大多数项目中,前两个阶段(提交测试及验收测试)通常可由持续集成工具来管理

使用这种方法组织构建脚本的最大好处就是:每个脚本或元素都只负责做好一件事,而不是用相对复杂的步骤来管理整个构建过程。随着项目的演进与成熟,这一点对于确保构建过程的可管理性及易检验性是非常重要的

至于如何写这些脚本并不在本文的范围之内。实际上,这些脚本严重依赖于具体项目,毫无趣味可言。然而,当将这一过程应用于多个项目之后,发现它为我们提供了可靠的、可重复的值得信任的部署过程,让我们可以在几秒或几分钟内完成此前可能需要一两天才能完成的工作

2 过程示例

小结

如果你还没有使用持续集成方法进行构建,请从明天开始。我们的实践证明,这是提高系统可靠性最有效的途径。持续集成在消除人为错误方面效果显著,不但可以提高生产效率,而且提高交付质量,降低交付压力

译者注:

注:因成文较早,作者在项目中使用CruiseControl作为持续集成服务器,需要自编脚本去维护持续集成管道的配置,以及二进制文件仓库的管理工作等。现在,用Cruise可以轻松完成这些事情

 

原创粉丝点击