持续交付:发布可靠软件的系统方法(一)
第一部分——基础篇
第一部分描述了理解部署流水线的前提条件。每章的内容都建立在上一章的基础之上。
- 第1章首先描述了在很多软件开发团队中常见的反模式,然后阐述了我们的目标以及实现方式。最后总结了软件交付的一些原则,本书的其他内容都以这些原则为基础。
- 第2章阐述了管理构建、部署、测试和发布应用程序所需的一些要素,包括源代码、构建脚本,以及环境和应用程序配置。
- 第3章讲述了在程序每个变更后执行构建和运行自动化测试相关的实践,以便确保你的软件一直处于可工作状态。
- 第4章介绍了每个项目中的各种手工和自动化测试,并讨论了如何确定适合自己项目的策略
第 1 章 软件交付的问题
1.1 引言
本书描述了软件从开发到发布这一过程的有效模式。书中讲述了帮助大家实现这种模式的技术和最佳实践,展示了它与软件交付中其他活动是如何联系的。
本书的中心模式是部署流水线。从本质上讲,部署流水线就是指一个应用程序从构建、部署、测试到发布这整个过程的自动化实现。部署流水线的实现对于每个组织都将是不同的,这取决于他们对软件发布的价值流的定义,但其背后的原则是相同的。
部署流水线的示例如图1-1所示
部署流水线以持续集成过程为其理论基石,从本质上讲,它是采纳持续集成原理后的自然结果。
部署流水线的目标有三个:
- 首先,它让软件构建、部署、测试和发布过程对所有人可见,促进了合作。
- 其次,它改善了反馈,以便在整个过程中,我们能够更早地发现并解决问题。
- 最后,它使团队能够通过一个完全自动化的过程在任意环境上部署和发布软件的任意版本。
1.2 一些常见的发布反模式
软件发布的当天往往是紧张的一天。为什么会这样呢?对于大多数项目来说,在整个过程中,发布时的风险是比较大的。
在许多软件项目中,软件发布是一个需要很多手工操作的过程。
- 首先,由运维团队独自负责安装好该应用程序所需的操作系统环境,再把应用程序所依赖的第三方软件安装好。
- 其次,要手工将应用程序的软件产物复制到生产主机环境,然后通过Web服务器、应用服务器或其他第三方系统的管理控制台复制或创建配置信息,再把相关的数据复制一份到环境中,
- 最后启动应用程序。假如这是个分布式的或面向服务的应用程序,可能就需要一部分一部分地完成。
如上所述,发布当天紧张的原因应该比较清楚了:在这个过程中有太多步骤可能出错。假如其中有一步没有完美地执行,应用程序就无法正确地运行。一旦发生这种情况,我们很难一下子说清楚哪里出了错,或到底是哪一步出了错。下面列出了与可靠的发布过程相对应的几种常见的反模式,它们在我们这个行业中屡见不鲜
1.2.1 反模式:手工部署软件
对于现在的大多数应用程序来说,无论规模大小,其部署过程都比较复杂,而且包含很多非常灵活的部分。许多组织都使用手工方式发布软件,也就是说部署应用程序所需的步骤是独立的原子性操作,由某个人或某个小组来分别执行。每个步骤里都有一些需要人为判断的事情,因此很容易发生人为错误。即便不是这样,这些步骤的执行顺序和时机的不同也会导致结果的差异性,而这种差异性很可能给我们带来不良后果。
这种反模式的特征如下
- 有一份非常详尽的文档,该文档描述了执行步骤及每个步骤中易出错的地方。
- 以手工测试来确认该应用程序是否运行正确。
- 在发布当天开发团队频繁地接到电话,客户要求解释部署为何会出错。
- 在发布时,常常会修正一些在发布过程中发现的问题。
- 如果是集群环境部署,常常发现在集群中各环境的配置都不相同,比如应用服务器的连接池设置不同或文件系统有不同的目录结构等。
- 发布过程需要较长的时间(超过几分钟)。
- 发布结果不可预测,常常不得不回滚或遇到不可预见的问题。
- 发布之后凌晨两点还睡眼惺忪地坐在显示器前,绞尽脑汁想着怎么让刚刚部署的应用程序能够正常工作
相反,随着时间的推移,部署应该走向完全自动化,即对于那些负责将应用程序部署到开发环境、测试环境或生产环境的人来说,应该只需要做两件事:
- (1)挑选版本及需要部署的环境,
- (2)按一下“部署”按钮。对于套装软件的发布来说,还应该有一个创建安装程序的自动化过程。
我们先来解释一下为什么把自动化部署看做是一个必不可少的目标
- 如果部署过程没有完全自动化,每次部署时都会发生错误。唯一的问题就是“该问题严重与否”而已。即便使用良好的部署测试,有些错误也很难追查。
- 如果部署过程不是自动化的,那么它就既不可重复也不可靠,就会在调试部署错误的过程中浪费很多时间。
- 手动部署流程不得不被写在文档里。可是文档维护是一项复杂而费时的任务,它涉及多人之间的协作,因此文档通常要么是不完整的,要么就是未及时更新的,而把一套自动化部署脚本作为文档,它就永远是最新且完整的,否则就无法进行部署工作了。
- 自动部署本质上也是鼓励协作的,因为所有内容都在一个脚本里,一览无遗。要读懂文档通常需要读者具备一定的知识水平。然而在现实中,文档通常只是为执行部署者写的备忘录,是难以被他人理解的。
- 以上几点引起的一个必然结果:手工部署过程依赖于部署专家。如果专家去度假或离职了,那你就有麻烦了。
- 尽管手工部署枯燥且极具重复性,但仍需要有相当程度的专业知识。若要求专家做这些无聊、重复,但有技术要求的任务则必定会出现各种我们可以预料到的人为失误,同时失眠,酗酒这种问题也会接踵而至。然而自动化部署可以把那些成本高昂的资深高技术人员从过度工作中解放出来,让他们投身于更高价值的工作活动当中。
- 对手工部署过程进行测试的唯一方法就是原封不动地做一次(或者几次)。这往往费时,还会造成高昂的金钱成本,而测试自动化的部署过程却是既便宜又容易。
- 另外,还有一种说法:自动化过程不如手工过程的可审计性好。我们对这个观点感到很疑惑。对于一个手工过程来说,没人能确保其执行者会非常严格地遵循文档完成操作。只有自动化过程是完全可审核的。有什么会比一个可工作的部署脚本更容易被审核的呢?
- 每个人都应该使用自动化部署过程,而且它应该是软件部署的唯一方式。这个准则可以确保:在需要部署时,部署脚本就能完成工作。在本书中我们会提到多个原则,而其中之一就是“使用相同的脚本将软件部署到各种环境上”。如果使用相同的脚本将软件部署到各类环境中,那么在发布当天需要向生产环境进行部署时,这个脚本已经被验证过成百上千次了。如果发布时出现任何问题的话,你可以百分百地确定是该环境的具体配置问题,而不是这个脚本的问题。
1.2.2 反模式:开发完成之后才向类生产环境部署
在这一模式下,当软件被第一次部署到类生产环境(比如试运行环境)时,就是大部分开发工作完成时,至少是开发团队认为“该软件开发完成了”。
这种模式中,经常出现下面这些情况。
- 如果测试人员一直参与了在此之前的过程,那么他们已在开发机器上对软件进行了测试。
- 只有在向试运行环境部署时,运维人员才第一次接触到这个新应用程序。在某些组织中,通常是由独立的运维团队负责将应用程序部署到试运行环境和生产环境。在这种工作方式下,运维人员只有在产品被发布到生产环境时才第一次见到这个软件。
- 有可能由于类生产环境非常昂贵,所以权限控制严格,操作人员自己无权对该环境进行操作,也有可能环境没有按时准备好,甚至也可能根本没人去准备环境。
- 开发团队将正确的安装程序、配置文件、数据库迁移脚本和部署文档一同交给那些真正执行部署任务的人员,而所有这些都没有在类生产环境或试运行环境中进行过测试。
- 开发团队和真正执行部署任务的人员之间的协作非常少。
一旦将应用程序部署到了试运行环境,我们常常会发现新的缺陷。遗憾的是,我们常常没有时间修复所有问题,因为最后期限马上就到了,而且项目进行到这个阶段时,推迟发布日期是不能被人接受的。所以,大多数严重缺陷被匆忙修复,而为了安全起见,项目经理会保存一份已知缺陷列表,可是当下一次发布开始时,这些缺陷的优先级还是常常被排得很低。
有的时候,情况会比这还糟。以下这些事情会使与发布相关的问题恶化。
- 假如一个应用程序是全新开发的,那么第一次将它部署到试运行环境时,可能会非常棘手。
- 发布周期越长,开发团队在部署前作出错误假设的时间就越长,修复这些问题的时间也就越长。
- 交付过程被划分到开发、DBA、运维、测试等部门的那些大型组织中,各部门之间的协作成本可能会非常高,有时甚至会将发布过程拖上“地狱列车”。此时为了完成某个部署任务(更糟糕的情况是,为了解决部署过程中出现的问题),开发人员、测试人员和运维人员总是高举着问题单(不断地互发电子邮件)。
- 开发环境与生产环境差异性越大,开发过程中所做的那些假设与现实之间的差距就越大。虽然很难量化,但我敢说,如果在Windows系统上开发软件,而最终要部署在Solaris集群上,那么你会遇到很多意想不到的事情
- 如果应用程序是由用户自行安装的(你可能没有太多权限来对用户的环境进行操作),或者其中的某些组件不在企业控制范围之内,此时可能需要很多额外的测试工作。
我们的对策就是将测试、部署和发布活动也纳入到开发过程中,让它们成为开发流程正常的一部分。这样的话,当准备好进行系统发布时就几乎很少或不会有风险了,因为你已经在很多种环境,甚至类生产环境中重复过很多次,也就相当于测试过很多次了。而且要确保每个人都成为这个软件交付过程的一份子,无论是构建发布团队、还是开发测试人员,都应该从项目开始就一起共事。
我们是测试的狂热者,而大量使用持续集成和持续部署(不但对应用程序进行测试,而且对部署过程进行测试)正是我们所描述的方法的基石。
1.2.3 反模式:生产环境的手工配置管理
这种反模式的特征如下。
- 多次部署到试运行环境都非常成功,但当部署到生产环境时就失败。
- 集群中各节点的行为有所不同。例如,与其他节点相比,某个节点所承担的负载少一些,或者处理请求的时间花得多一些。
- 运维团队需要较长时间为每次发布准备环境。
- 系统无法回滚到之前部署的某个配置,这些配置包括操作系统、应用服务器、关系型数据库管理系统、Web服务器或其他基础设施设置。
- 不知道从什么时候起,集群中的某些服务器所用的操作系统、第三方基础设施、依赖库的版本或补丁级别就不同了。
- 直接修改生产环境上的配置来改变系统配置。
相反,对于测试环境、试运行环境和生产环境的所有方面,尤其是系统中的任何第三方元素的配置,都应该通过一个自动化的过程进行版本控制。
本书描述的关键实践之一就是配置管理,其责任之一就是让你能够重复地创建那些你开发的应用程序所依赖的每个基础设施。这意味着操作系统、补丁级别、操作系统配置、应用程序所依赖的其他软件及其配置、基础设施的配置等都应该处于受控状态。你应该具有重建生产环境的能力,最好是能通过自动化的方式重建生产环境。虚拟化技术在这一点上可能对你有所帮助。
实际上,不应该允许手工改变测试环境、试运行环境和生产环境,而只允许通过自动化过程来改变这些环境。变更首先应该被提交到版本控制系统中,然后通过某个自动化过程对生产环境进行更新。
1.2.4 我们能做得更好吗
本书的目标是描述如何使用部署流水线,将高度自动化的测试和部署以及全面的配置管理结合在一起,实现一键式软件发布。也就是说,只需要点击一下鼠标,就可以将软件部署到任何目标环境,包括开发环境、测试环境或生产环境。
接下来,我们会描述这种模式及其所需的技术,并提供一些建议帮你解决将面临的某些问题。实现这种方法,实在是磨刀不误砍柴工。
所有这些工作并不会超出项目团队的能力范围。它不需要刚性的流程、大量的文档或很多人力。我们希望,读完本章以后,你会理解这种方法背后的原则。
1.3 如何实现目标
- 速度是至关重要的,因为未交付的软件就意味着机会成本。软件发布之时就是投资得到回报之时。因此,本书有两个目标,其中之一就是找到减少周期时间(cycle time)的方法。周期时间是从决定进行变更的时刻开始,包括修正缺陷或增加特性,直至用户可以使用本次变更后的结果
- 快速交付也是非常重要的,因为这使你能够验证那些新开发的特性或者修复的缺陷是否真的有用。决定开发这个应用程序的人(我们称为客户)会猜测哪些特性或缺陷修复对用户是有用的。然而,直到使用者真正使用之前,这些全是未经过验证的假设。这也是为什么减少周期时间并建立有效反馈环如此重要的原因
- 有用性的一个重要部分是质量。我们的软件应该满足它的业务目的。质量并不等于完美,正如伏尔泰所说“追求完美是把事情做好的大敌”,但我们的目标应该一直是交付质量足够高的软件,给客户带来价值。因此,尽快地交付软件很重要,保证一定的质量是基础
我们及我们的同修发现,为了达到这些目标(短周期、高质量),我们需要频繁且自动化地发布软件。为什么呢?
- 自动化。如果构建、部署、测试和发布流程不是自动化的,那它就是不可重复的。由于软件本身、系统配置、环境以及发布过程的不同,每次做完这些活动以后,其结果可能都会有所不同。常常说软件发布像是一种艺术,但事实上,它应该是一种工程学科。
- 频繁做。如果能够做到频繁发布,每个发布版本之间的差异会很小。这会大大减少与发布相关的风险,且更容易回滚。频繁发布也会加快反馈速度,而客户也需要它。本书很多内容都聚焦于如何尽快得到对软件及其相关配置所做变化的反馈,这包括其环境、部署过程及数据等。
对于频繁地自动化发布来说,反馈是至关重要的。下面关于反馈的三个标准是很有用的:
- 无论什么样的修改都应该触发反馈流程;
- 反馈应该尽快发出;
- 交付团队必须接收反馈,并依据它作出相应的行动。
让我们逐一审视一下这三个标准,考虑如何能达到这样的标准。
1.3.1 每次修改都应该触发反馈流程
一个可工作的软件可分成以下几个部分:可执行的代码、配置信息、运行环境和数据。如果其中任何一部分发生了变化,都可能导致软件的行为发生变化。所以我们要能够控制这四部分,并确保任何修改都会被验证。
什么是反馈流程?它是指完全以自动化方式尽可能地测试每一次变更。根据系统的不同,测试会有所不同,但通常至少包括下面的检测。
- 创建可执行代码的流程必须是能奏效的。这用于验证源代码是否符合语法。
- 软件的单元测试必须是成功的。这可以检查应用程序的行为是否与期望相同。
- 软件应该满足一定的质量标准,比如测试覆盖率以及其他与技术相关的度量项。
- 软件的功能验收测试必须是成功的。这可以检查应用是否满足业务验收条件,交付了所期望的业务价值。
- 软件的非功能测试必须是成功的。这可以检查应用程序是否满足用户对性能、有效性、安全性等方面的要求。
- 软件必须通过了探索性测试,并给客户以及部分用户做过演示。这些通常在一个手工测试环境上完成。此时,产品负责人可能认为软件功能还有缺失,我们自己也可能发现需要修复的缺陷,还要为其写自动化测试来避免回归测试。
运行测试的这些环境应该尽可能与生产环境相似,从而验证对于环境的任何修改都不会影响应用程序的正常运行。
1.3.2 必须尽快接收反馈
快速反馈的关键是自动化。对于实现完全自动化过程来说,唯一的约束条件就是你能够使用的硬件数量。部署流水线的关键目的之一就是对人力资源利用率的优化:我们希望将人力释放出来做更有价值的工作,将那些重复性的体力活交给机器来做。
对于整个流水线中的提交(commit)阶段,其测试应具有如下特征。
- 运行速度快。
- 尽可能全面,即75%左右的代码库覆盖率。只有这样,这些测试通过以后,我们才对自己写的软件比较有信心。
- 如果有测试失败的话,就表明应用程序有严重问题,无论如何都不能发布。也就是说,像检查界面元素的颜色是否正确这类测试不应该包含在这个测试集合当中。
- 尽可能做到环境中立。这个环境没必要和生产环境一模一样,可以相对简单廉价一些
相对而言,提交阶段之后的测试一般有如下这些特点。
- 它们通常运行更慢一些,所以适合于并行执行。
- 即使某些测试有可能失败,但在某种场合下,我们还是会发布应用程序。比如某个即将发布的版本有一个不稳定的修复,会导致其性能低于预先定义的标准,但有时我们还是会决定发布这个版本。
- 它们的运行环境应该尽可能与生产环境相同。除了测试功能以外,它同时还会对部署过程以及对生产环境的任何修改进行测试。
1.3.3 交付团队必须接收反馈并作出反应
参与软件交付过程的所有人(包括开发人员、测试人员和运维人员、数据库管理员、基础设施的专家以及管理者)都应该参与到这个反馈流程中,这是至关重要的。如果这些人无法做到每天都在一起工作(尽管我们认为团队应该是全功能团队),就一定要常常碰头并一起探讨如何改进软件交付的流程。对于快速交付高质量的软件来说,基于持续改进的过程是非常关键的。迭代过程有助于为这类活动建立规律性,例如每个迭代至少开一次回顾会议,在会上每个人都应参与讨论如何在下一个迭代中改进交付过程。
想要能够根据反馈来调整行动,就要对信息进行广播。当然,如果最后没有引发什么改进行动,反馈也就没有什么用了。因此,这就要求纪律性和计划性。当需要采取行动时,整个团队有责任停下他们手中的事情,来决定接下来采取哪些行动。在完成此事之后,团队才能继续自己的工作。
1.3.4 这个流程可以推广吗
很多人认为我们所描述的过程太理想化了。他们认为,这样的事情在小团队中可能行得通,但在大型分布式项目中是不行的!
书中的很多东西都来自于精益思想和哲学。精益制造的目标是确保快速地交付高质量的产品,它聚焦于消除浪费,减少成本。多个行业的实践已经证明,精益制造可以节省大量成本和资源,带来高质量的产品,缩短产品上市时间。这一哲学在软件开发领域也渐成主流,而且影响着本书中的很多内容。精益并不仅仅局限于应用在小系统上,它已在大型组织甚至整个经济体系中得到应用。
1.4 收效
对于前面我们讲到的这种方法,其主要收益是创建了一个发布流程,这个流程是可重复的、可靠的且可预见的,从而大大缩短了发布周期,使新增功能和缺陷修复能更早与用户见面。节省下来的成本将不仅仅是金钱,还包括建立和维护这样一个发布系统所需要的时间投入。
除此之外,还有其他一些收益,虽然其中一些我们应该能够预见到,但另一些很可能是我们无法预测的,但一旦被发现,可以给我们带来惊喜。
1.4.1 授权团队
部署流水线的一个关键点是,它是一个“拉动”(pull)系统,它使测试人员、运维人员或支持服务人员能够做到自服务,即他们可以自行决定将哪个版本的应用程序部署到哪个环境中。根据我们的经验,对缩短发布周期的主要贡献者是那些在整个交付流程中,等待拿到应用程序的某个“好”版本的人。为了得到一个可用的版本,通常需要很多的电子邮件沟通、问题跟踪单,以及其他效率不高的沟通方式。假如是分布式交付团队的话,这一点就成了主要的低效之源。然而实现了部署流水线之后,这个问题就彻底解决了,因为每个人都能看到应该拿哪个版本部署到相应的环境中,而且只需要单击按钮就能完成部署。
我们常常看到在不同的环境中运行着不同的版本,而不同角色的人工作在其上。能够轻松地将任意版本的软件部署到任意环境的能力能带来很多好处。
- 测试人员可以选择性地部署较旧的版本,以验证新版本上的功能变化。
- 技术支持人员可以自己部署某个已发布的版本,用于重现缺陷。
- 作为灾难恢复手段,运维人员可以自己选一个已知的正确版本,将其部署到生产环境中。
- 发布方式也变成一键式的了。
总而言之,团队成员可以更好地控制工作节奏,从而改进工作质量,这就会让应用程序的质量得以提高。
1.4.2 减少错误
我们可能从方方面面将错误引入到软件中。最初委托制作这个软件的人就可能出错,比如提出错误的需求。需求分析人员可能将需求理解错了,开发人员也可能写出了到处都是缺陷的程序,而我们在这里要说的错误是指由不良好的配置管理
引入到生产环境的错误。
当我们说配置管理时,指的是让你识别并控制一组完整信息的流程与机制,这些信息包括每个字节和比特。
通过积极地管理在版本控制库中的所有可能变动的内容,比如配置文件、创建数据库及其模式的脚本、构建脚本、测试用具,甚至开发环境和操作系统的配置,我们让计算机来做它们擅长的所有事情,即确保所用的比特和字节都在它们应该在的位置上,至少确保在代码将要运行时确实如此。
当所有的配置信息都放在版本控制系统中以后,接下来就要消除“中间人”了,即让计算机直接使用这些配置信息,而不是再通过手工输入的方式来进行软件配置。虽然某些技术相对来说更顺应这种方式,但是当你(通常是基础设施提供商)仔细思考一下如何管理这类配置信息,尤其是那些最难驾驭的第三方系统的配置信息时,会惊奇地发现还有很长的路要走。
1.4.3 缓解压力
明显的好处中,可以缓解压力是最吸引所有与发布相关的人的一点。绝大多数经历过项目发布的人都会认为,当项目越临近发布日期,就越能感觉到压力。根据我们的经验,压力本身就是问题的根源所在。
不要误解,我们也遇到过同样的事情。我们并不是说这种处理方式一定是错误的,如果刚把代码部署到了生产环境中,而你的组织因为它的某个缺陷遭受经济上的损害时,任何阻止这种损害的行为都是可以理解的。
现在,让我们来设想一下。如果接下来的发布只需要单击一下按钮,而且只需要等上几分钟,甚至几秒钟内就可以完成。另外,假如发生了非常糟糕的事情,你只要花上相同的几分钟或几秒钟的时间就可以把刚部署的内容恢复到从前的老样子。再大胆地设想一下,假如你的软件发布周期总是很短,那么当前生产环境中的版本与新版本之间的差异应该非常小。如果上述设想都是事实的话,那么发布的风险一定会大大降低,那种将职业生涯压注在发布是否成功的不爽感觉也将大大减少。
对于很少的一部分项目来说,这种理想状态可能很难成为现实。然而,对于大多数项目来说,尽管可能需要花上一些精力,但肯定是可以做到的。减少压力的关键在于拥有一个我们前面所描述的自动化部署过程,并频繁地运行它,当部署失败后还能够快速恢复到原来状态。尽管刚开始做自动化时可能会很痛苦,但它会渐渐地变得容易起来,而它给项目和团队带来的好处是不可限量的。
1.4.4 部署的灵活性
在一个全新环境上运行应用程序应该是相当简单的事。理想情况下,只要安装机器或虚拟镜像,然后配置一些与具体运行环境相关的特定选项。然后,你就可以使用自动化过程准备好新的部署环境,并选择指定的应用程序版本进行部署。
正如敏捷所强调的,在每个迭代结束时进行发布这件事就变得很容易了。
1.4.5 多加练习,使其完美
最好的策略就是无论部署到什么样的目标环境,都使用相同的部署方法。不应该有特殊的QA部署策略,或者一个特殊的验收测试或生产部署策略。在每次以同一种方式部署应用软件时,也是验证我们的部署机制是否正确的时机。事实上,向其他任何环境的任何一次部署过程都是生产环境部署的一次演练。
只有一种环境可以有多变性,那就是开发环境。开发人员应该在自己的开发环境中自行生成二进制文件,而不需要在别处构建生成。所以,对这种开发环境的部署流程要求太严格是没有必要的。虽然我们能够做到在开发人员的开发机器上也以同样的方式部署软件,但实际上对开发环境的部署没有必要严格要求。
1.5 候选发布版本
什么是候选发布版本(release candidate)
?对于代码的任何一次修改都有被发布出去的可能性。然而,恰恰是构建、部署、测试流程能够验证是否可以发布这次修改后的版本。对于“是否可以发布这次修改的版本”这个问题,这一流程会不断让我们增强信心。
尽管每次修改都可以产生一个能够交给用户的最终产物,但是我们应该首先对每次修改都进行适用性评估。只有这次修改没有缺陷,而且满足由客户定制的验收条件,才能够发布它。
大多数软件发布方法都是在其流程的最后阶段才能识别出可以发布的那些版本。当说到与跟踪(tracking)相关的工作时,这是有些意义的。在写作本书时,Wikipedia上对开发阶段的描述中将“候选发布版本”作为这一流程中的一个步骤进行了说明
在传统软件开发方法中,通常以较长时间的验证过程来确保软件满足质量要求并实现了全部功能需求,之后才确定能够发布的候选版本。然而,当有全面的自动化测试,并且构建和部署也是自动化过程时,我们在项目后期就不再需要冗长且手工密集型的测试了。在这一阶段应用程序的质量通常也会比较高,手工测试只是用于证实功能完备就行了
根据我们的经验,直到开发阶段之后才做测试的话,无疑会降低应用程序的质量。最好还是在缺陷被引入时,就发现并将其解决。发现得越晚,修复的成本越高。开发人员已经不记得他们是在实现哪个功能时把缺陷引入的,而这个功能很可能已经发生了变化。直到最后才做测试,这通常意味着没有足够的时间真正地修复缺陷,或者只能修复其中很少的一部分缺陷。因此,我们想尽早地发现并修正这些缺陷,最好是在将其提交到代码库之前。
每次提交代码都可能产生一个可发布的版本
开发人员对代码库的每次修改都应该是以某种方式为系统增加价值。每次代码到版本控制系统的提交都应该是对当前所开发软件的提高或增强。我们如何知道它的正确性呢?唯一的方法就是运行这个软件,看它的行为是否符合我们的期望。大多数项目都将这部分工作推迟到了开发的后期。这就意味着,即使它不能工作,也只有当有人测试或使用这个软件时才能被发现,而此时的修复成本通常会比较高。这个阶段通常称作集成阶段,常常是整个开发过程中最不可预测、最不易管理的阶段。由于集成这件事太痛苦了,所以团队总是推迟集成工作。然而,集成频率越低,集成时我们就会越痛苦
如果在软件开发中的某个任务令你非常痛苦,那么解决痛苦的方法只有更频繁地去做,而不是回避。因此,我们应该频繁做集成,事实上应该在每次提交修改后都做集成。持续集成这个实践将频繁集成发挥到了极至,而“持续集成
”转变了软件开发过程。持续集成会及时检测到任何一次破坏已有系统或者不满足客户验收测试的提交。一旦发生这种情况,团队就立刻去修复问题(这是持续集成的首要规则)。如果能够坚持这个实践,那么软件会一直处于可用状态。假如测试足够全面,且运行测试的环境与生产环境足够相近(甚至相同)的话,那么可以说,你的软件一直处于可发布状态。
我们可以把每次修改都作为一个有可能被发布的候选版本。每次将修改后的代码提交到版本控制系统时,我们都希望它能够通过所有的测试,产生可工作的软件,并能够发布到生产环境中,而这只是我们的一个假设。持续集成系统的职责就是推翻这一假设,证明某个版本并不适合部署到生产环境中。
1.6 软件交付的原则
1.6.1 为软件的发布创建一个可重复且可靠的过程
这个原则是我们写这本书的一个目标:让软件发布成为一件非常容易的事情。事实上,它的确应该是件很容易的事,因为在发布之前,对发布流程中的每一个环节,你都已经测试过数百次了。
这种可重复性和可靠性来自于以下两个原则:(1)几乎将所有事情自动化;(2)将构建、部署、测试和发布软件所需的东西全部纳入到版本控制管理之中。归根结底,软件部署包括三件事:
- 提供并管理你的软件所需要的运行环境,这包括硬件配置、所依赖的软件、基础设施以及所需的外部服务;
- 将你的应用程序的正确版本安装在其之上;
- 配置你的应用程序,包括它所需要的任何数据以及状态。
对于应用程序的部署,应该由版本控制系统中的全自动化过程来完成。通过保存在版本控制系统或数据库中的必要脚本和状态信息,应用程序的配置也可以是一个全自动化过程。显然,硬件是无法纳入版本控制的,但利用廉价的虚拟化技术和像Puppet这样的工具,这类支撑过程也可以全部自动化。
1.6.2 将几乎所有事情自动化
有些工作是不可能被自动化的。比如,探索性测试就依赖于有经验的测试人员。向用户代表们演示程序也无法利用计算机来自动完成。人工的审批流程也需要人的干预。但是,这类不能被自动化的事情要比人们想象的要少很多。通常,在需要人做决定的那一时刻之前,构建流程应该是完全自动化的。对于部署流程也是一样。也就是说,整个软件发布流程都适用这一原则。验收测试是可以自动化的,数据库的升级和降级也是可以自动化的,甚至网络和防火墙配置也是可以自动化的。你应该尽可能自动化所有的东西。
自动化是部署流水线的前提条件。
1.6.3 把所有的东西都纳入版本控制
将构建、部署、测试和发布的整个过程中所需的东西全部保存在某种形式的版本存储库中,包括需求文档、测试脚本、自动化测试用例、网络配置脚本、部署脚本、数据库创建、升级、回滚和初始化脚本、应用程序所依赖的软件集合的配置脚本、库文件、工具链以及技术文档等。所有这些内容都应该受到版本控制,与每次构建结果相关的版本都应可以识别。也就是说,这些变更集(change set)都应该有唯一标识,比如构建号、版本控制库中的版本号。
另外,应该也能够方便地知道当前每个环境中到底部署了应用程序的哪个版本,及其在版本库中所对应的版本号。
1.6.4 提前并频繁地做让你感到痛苦的事
这是最通用的原则,也是最有启发性的。
极限编程就是把这一启发式原则应用到软件开发后的一个结果。
1.6.5 内建质量
这一原则和上一原则(持续改进)都是从精益运动(lean movement)中借鉴来的。“内建质量”也是戴明(精益运动的先驱之一)提出的名言之一。越早发现缺陷,修复它们的成本越低。如果在没有提交代码到版本控制之前,我们就能发现并修复缺陷的话,代价是最小的。
交付团队必须执行铁一般的纪律:一旦发现缺陷,就要马上着手修复。
“内建质量”还有另外两个推论。
- (1)测试不是一个阶段,当然也不应该开发结束之后才开始。如果把测试留在最后,那就为时晚矣,因为可能根本没有时间修复那些
刚被发现的问题。 - (2)测试也不纯粹或主要是测试人员的领域。交付团队的每个人都应该对应用程序的质量负责。
1.6.6 “DONE”意味着“已发布”
我们认为,一个特性只有交到用户手中才能算“DONE”。这是持续部署实践背后的动机之一
对于一些敏捷交付团队来说,“DONE”意味着软件已经部署到生产环境上。对于软件项目来说,这是一种理想状态。将其作为衡量是否完成的标准,并不总是合适的。对于那些第一次发布的软件系统来说,它可能需要一段时间才能达到“让外部用户真正从该软件身上获益”的状态。因此,我们可以暂且退让一步,只要某个功能在类生产环境上向客户代表做过演示,并且客户代表试用之后就认为是完成了。
根本没有“已经完成了80%”这一说法。任何事情要么是完成了,要么就是没完成。我们可以估计尚未完成的某件工作还需要多少工作量,但仅仅是估计而已。当事实证明那些还剩余百分之几的估计不正确(事实总是如此)时,估计剩余工作总量的做法总是备受指责。
这一原则有个很有趣的推论:一件事情的完成与否,并不是一个人能控制得了的,它需要整个交付团队共同来完成。这就是为什么所有人(包括开发、测试、构建和运维人员和技术支持人员)在项目一开始就应该在一起工作。这也是为什么整个交付团队应该对交付负责。
1.6.7 交付过程是每个成员的责任
理想情况下,团队中的成员应该有共同的目标,并且每个成员应在工作中互相帮助来实现这一目标。无论成功还是失败,其结果都属于这个团队,而非个人。
从一个新项目的开始就要保证团队成员能够一起参与到发布程序的过程当中,以保证他们有机会频繁且有规律地进行交流。一旦障碍消失,交流就应持续进行,但我们可能需要逐步地向目标迈进。比如建立一个系统,在这个系统上,每个人都可以一眼就知道应用程序所处的状态,比如其健康状况、各种构建版本、构建通过了哪些测试、它们可被部署到的环境的状态。这个系统应该能让大家执行完成作业的动作,比如向某个环境中部署软件。
这是DevOps运动的核心原则之一。DevOps运动的焦点和我们这本书的目标一致:为了更加快速且可靠地交付有价值的软件,鼓励所有参与软件交付整个过程中的人进行更好的协作。
1.6.8 持续改进
这里我们要强调的是:应用程序的首次发布只是其生命周期中的第一个阶段。随着应用程序的演进,更多的发布将会接踵而来。更重要的是,你的交付过程应该随之不断演进。
在交付过程中,整个团队应该定期地坐在一起,召开回顾会议,反思一下在过去一段时间里哪些方面做得比较好,应该继续保持,哪些方面做得不太好,需要改进,并讨论一下如何改进。每个改进点都应该有一个人负责跟踪,确保相应的改进活动能够被执行。当下一次团队坐在一起时,他们应该向大家汇报这些活动的结果。这就是众所周知的戴明环:计划-执行-检查-处理(PDCA)。
关键在于组织中的每个人都要参与到这个过程当中。如果只在自己所在角色的内部进行反馈环,而不是在整个团队范围内进行的话,就必将产生一种“顽疾”:以整体优化为代价的局部优化,最终导致互相指责。
1.7 小结
传统上,软件发布过程充满压力。而且与我们对代码的创建和管理相比,软件发布过程更像是一个缺乏验证的手工过程,它的系统配置的关键部分都依赖于临时性的配置管理方法。在我们看来,这种软件发布的压力与发布过程中的手工且易错的特质是密不可分的。
通过采用自动构建、测试和部署技术,可以获得很多益处,我们将能够验证变化,重现各种环境中的部署过程,在很大程度上减少产品出错的机会。由于发布过程本身已不再是一个障碍,我们可以部署软件变更,从而更快地获得商业利益。实施自动化系统会促使我们将好的实践付诸行动,比如行为驱动的开发(behavior-driven development)
和综合的配置管理
等。