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