第1章 持续交付:是什么和怎么做

1.1 什么是持续交付

这个问题可不太容易回答,就连“持续交付”这个术语的发明者也没有给出一个真正的定义。①Martin Fowler 在关于持续交付的讨论中重点阐述了这样一个事实:软件可以随时发布到生产环境。这需要将软件安装过程自动化,并对软件质量做出相关反馈。维基百科则将“持续交付”定义为软件发布过程的优化和自动化。

持续交付的主要目标是分析和优化整个软件发布过程。确切地说,这个过程经常隐含在开发过程中。

1.2 为什么软件发布如此复杂

软件发布是一项挑战,很可能每个IT 部门都曾在周末加班加点,为的是将软件发布到生产环境中。结果往往是软件的确被发布到了生产环境中,但这是因为回退到旧版本甚至比发布新版本风险更大,也更困难。然而,新版本在安装之后通常要经历一个非常漫长的阶段才能稳定下来。

1.2.1 持续集成带来希望

如今的挑战是向生产环境发布软件,而曾几何时,问题在更早的阶段就已暴露出来。每个团队独立负责自己的模块,在发布之前,必须先集成不同的版本。当这些模块第一次集成在一起时,系统常常无法编译,通常需要几天甚至几周的时间才能成功地集成和编译所有变更。之后,才能开始部署。如今,这些问题基本上已经得到解决:所有团队都在共享的代码版本上开展工作,这些代码一直保持自动地集成、编译和测试。这种方法称为持续集成。第3 章会详细介绍持续集成所需的基础设施。事实上,上述问题的解决为我们带来了希望:在软件发布过程的其他阶段中出现的问题也将得到解决。

① 详见由Jez Humble 和David Farley 合著的《持续交付:发布可靠软件的系统方法》。中译本已由人民邮电出版社出版,详见http://ituring.cn/book/758。——编者注

1.2.2 过程缓慢且有风险

后面的阶段通常十分复杂和精细。此外,手动操作的步骤非常烦琐且容易出错。不仅发布阶段如此,前面的阶段也是如此,比如测试阶段。特别是在手动流程中,那些每年只执行几次的手动操作更加糟糕,它们很可能会出现错误。这当然会增加整个过程的风险。

正是因为这样的高风险性和复杂性,软件版本发布到生产环境中的频率很低。最终,由于缺乏实践,整个过程会花费更长的时间。此外,这还使得流程优化变得很难。

1.2.3 变快是有可能的

在某些紧急情况下需要将软件迅速发布到生产环境,例如,必须紧急修复错误。然而,此时所有的测试和据此设立的所有安全网都被省略了,它们本来是标准过程的组成部分。当然,这得冒相当高的风险,无论如何都应该正常运行这些测试。

因此,向生产环境发布软件的正常路径缓慢且具有风险,而紧急情况下的发布路径虽然可能更快,但代价是风险更大。

1.3 持续交付的价值

我们希望利用持续集成的动机和方法,优化向生产环境发布软件的方式。

持续集成的一个基本原则是:“如果一件事会触及你的痛处,就更频繁地去做这件事,让疼痛来得更早一些。”这听起来像是受虐狂的做法,但实际上正是一种解决问题的方法。与其通过尽量减少版本发布来避免发布的问题,不如更频繁、更早地执行这些流程,以尽快优化速度和可靠性。因此,持续交付迫使组织转变观念,并采用新的工作方式。说到底,这种方法并不新鲜:如前所述,每个IT 组织都能够将修复后的版本快速发布到生产环境,这通常只需要执行一小部分常规测试和安全检查。这完全行得通,因为改动量很小,所以相应的风险也很小。此时,很容易想到另一种最小化风险的方法:将小的变更更频繁地发布到生产环境,而不是试图通过复杂的流程和减少版本发布来防止失败。本质上,这种方法与持续集成相同。持续集成的意思是,即使个人开发者和团队开发的再小的软件变更也是一直在集成的,而不是让团队和开发人员各自工作数日甚至数周,到了最后再集成所有积累的变更。后者常常会导致大量的问题;在某些情况下,问题会非常严重,以至于软件根本就无法编译。

但是,持续交付的特点不仅仅是“快和小”。持续交付基于的是不同的价值观。从这些价值观出发,可以推导出具体的技术措施。

1.3.1 规律性

规律性意味着更频繁地执行流程。将软件发布到生产环境中的所有必要流程都应该定期执行,而不只是在必须发布某个版本时才执行。例如,搭建测试环境和准生产环境是很有必要的,测试环境可用于验收测试或技术性测试,准生产环境则可用于由最终客户测试和评估新版本的特性。通过提供这些环境,生成环境的流程就可以转变为常规的流程,而不仅仅是在必须搭建生产环境时才执行。如果想不投入太多的工作量就生成诸多环境,就必须在很大程度上实现流程的自动化。规律性通常会带来自动化。类似的规则也适用于测试:推迟到发布之前才进行必要的测试是没有意义的,相反,应该定期执行。在这种情况下,实现自动化可以尽量减少所需的工作量。规律性还会带来高度的可靠性,频繁执行的流程可以可靠地重复和执行。

1.3.2 可追溯性

所有期望发布到生产环境中的软件变更,以及对基础设施的变更,都必须是可追溯的。软件和基础设施的每个状态都必须可重现。所以,版本控制的范围不应仅局限于软件,还应包括必需的环境。理想情况下,可以根据运维的需要在正确的配置中生成软件以及环境的每个状态,从而使软件和环境的所有变更都可以追溯。同样,也可以很容易地生成一个用于分析错误的配套系统。最后,还可以用这种方式记录或审计变更。

对此,有一个解决方案可供选择:只允许特定的人员访问生产环境和准生产环境。这样做是为了避免既没有文档记录也无法追溯的“快速修复”。此外,出于安全性需求和数据安全性的考虑,也不允许访问生产环境。

通过持续交付,只有更改了安装脚本才可能对环境进行干预。如果脚本被保存到了版本控制系统中,那么就可以追溯到它们的变更。连脚本的开发人员也无法访问生产数据,因此数据安全性得到了保障。

1.3.3 退化

为了尽可能降低将软件发布到生产环境中的风险,必须对软件进行测试。可以肯定的是,在测试期间必须确保新特性能够正确运作。然而,为了避免退化(即因为修改已经测试过的软件而出现错误)需要做很多工作。这其实就要求在进行修改时要重新运行所有的测试,因为系统一个位置的修改可能会导致其他位置出现错误。这需要自动化测试,否则,执行测试将耗费大量的精力。即使错误流入生产环境,仍然可以通过监控发现它。理想情况下,可以尽可能简单地在生产环境中安装没有错误的旧版本(回滚),或者将修复快速地发布到生产环境(前滚)。最终的想法是建立一种早期预警系统,在项目的不同阶段(比如测试阶段和生产阶段)采取不同的措施发现和解决退化问题。

1.4 持续交付的优势

持续交付具有很多优势。在不同的场景下,各种优势的重要性也不尽相同,这将影响如何实现持续交付。

1.4.1 持续交付可加快上市速度

持续交付缩短了将变更发布到生产环境所需的时间。这会为业务端带来巨大的优势:更容易响应市场变化。

持续交付带来的优势还不仅仅是更快上市。像精益创业①之类的现代方法提倡的是一种通过更快的上市速度获得更多收益的策略。精益创业的重点是基于市场定位产品,以尽可能少的投入评估产品在市场上的机会。就像进行科学试验一样,预先定义如何衡量产品在市场上的成功,然后开展试验,最后衡量产品成功与否。

1.4.2 示例

来看一个具体的例子。某家在线商店想增加一个新特性:可以在指定的日期送货。作为这个新特性的第一个试验,我们采取广告宣传的方式。广告中链接的点击量可以作为试验结果的度量指标。这时还没有进行任何软件开发,也就是说,还没有实现该特性。如果这个试验没有得到预期的结果,那么说明这个特性不会带来多大的收益,可以考虑把其他特性的优先级排在它前面。这个试验并没有投入太多的精力。

1.4.3 实现特性并将其发布到生产环境

如果试验成功,那么该特性将被实现并发布到生产环境。甚至这一步也可以像做试验一样进行。度量指标有助于管控特性的成功。例如,可以计算固定送货日期的订单数量。

1.4.4 下一个特性

对指标的分析显示,订单数量已经足够多了,有趣的是,大多数订单不是直接发给客户,而是发给了第三方。进一步度量发现,订购的物品明显是生日礼物。基于这些数据,可以扩展这个特性,例如添加生日日历并推荐合适的生日礼物。当然,这需要设计、实现、发布,并最终对成功与否进行评估。在还没有做任何实现时,也可以通过广告、客户访谈、调查或其他方法来评估这些特性的市场潜力。

1.4.5 持续交付能带来竞争优势

持续交付使得将所需的软件变更更快地发布到生产环境成为可能。如此,企业能够更快地验证不同的想法,并进一步开发业务模型。于是这带来了竞争优势:因为可以评估的想法更多了,所以更容易筛选出其中正确的想法。而且,这不是基于对市场机会的主观臆测,而是基于客观数据,如图1-1 所示。

① 详见由Eric Ries 所著的《精益创业:新创企业的成长思维》。

图像说明文字

1.4.6 如果没有持续交付

如果没有持续交付,固定送货日期的特性将会按部就班地计划在下一个版本中发布,总共可能需要几个月的时间。在发布之前,市场营销部门甚至不敢为这个特性做广告,因为到下一个版本发布还有很长的一段时间,在这段时间内做任何广告都是徒劳。如果这个特性最终证明是不成功的,那么实现它只是带来了高昂的成本,而没有任何收益。在经典方法中,当然也可以评估新特性成功与否,但是反应会慢得多。下一步的开发(例如支持购买生日礼物的特性),将在很长一段时间后才能投入使用,因为这需要重新把软件发布到生产环境,再一次经历那漫长的发布过程。此外,该特性的表现能否得到足够细致的分析,从而对其市场潜力获得充分的认识,这仍然值得怀疑。

1.4.7 持续交付和精益创业

得益于持续交付,可以更快地完成优化周期,因为每个特性在任何时候都可以发布到生产环境。这使像精益创业之类的方法成为可能。这影响了业务端的工作方式:它必须更快地定义新特性——不再需要关注长期规划,而是能够对当前试验的结果立即做出反应。在初创企业中这很容易做到,但传统组织也可以建立这样的业务方式。然而,精益创业这个名称有些误导性:它是一种通过一系列试验在市场上定位新产品的方法,传统企业当然可以应用这种方法,而不仅限于初创企业。当产品必须以传统方式交付时也可以使用它。例如,以CD 为介质进行交付,与其他复杂的安装过程一起交付,或者作为其他产品(如机器)的一部分交付。在这种情况下,必须简化软件的安装,或者最好实现自动化。此外,还必须确定愿意测试新软件版本并提供反馈的客户范围,即传统的Beta 测试人员或高级用户。

1.4.8 对开发过程的影响

持续交付会影响软件开发过程:如果应该将单个特性发布到生产环境,那么流程就必须对此提供支持。有些流程要使用长达一个或几个星期的迭代,在每次迭代的末期,会在生产环境中发布一个包含几个特性的新版本。对于持续交付来说,这不是一种理想的方式,因为这种方式无法单独发布单个特性。这也为精益创业带来了障碍:如果同时推出多个特性,那么它们与度量结果的相关性就不明显了。假设同时发布了固定送货日期的特性与对运输成本的变更,将无法区分这两个变更中的哪一个对商品销量的影响更大。

因此,Scrum、XP(极限编程)、瀑布式流程等流程是有缺陷的,它们总是将几个特性合在一起发布。相反,看板①关注的是经历不同阶段将单个特性发布到生产环境中。这非常贴合持续交付。当然,也可以修改其他的流程,以支持单个特性的交付。然而,这样流程就经过了调整,不再一成不变地实现。还可以一开始就停掉那些额外的特性,以便在一个版本中将多个特性组合到一起发布,但仍然能够分别度量它们的效果。

最后特别要说的是,这种方式还意味着团队包含多个角色。除了特性的开发和运维之外,还有业务端的角色,比如市场营销。由于减少了组织结构上的障碍,来自业务端的反馈可以更快地转化为试验。

尝试和实验

□ 收集精益创业和看板的相关信息。看板最初源自哪里?选择一个你了解的项目或者项目中的一个特性。

□ 最小的产品应该是什么样的?最小的产品应该体现出所规划的完整产品的市场机会。

□ 没有软件也可以评估产品吗?例如,能做广告吗?是否可以选择对潜在用户做个采访?

□ 如何衡量该特性是否成功?例如,是否会影响销量、点击量或其他可测量的值?

□ 市场营销和销售通常提前多长时间来规划产品或特性?这在多大程度上契合了精益创业的理念?

1.4.9 最小化风险

如上一节所述,持续交付应结合特定的业务模型进行使用。然而,对于传统企业来说,业务往往依赖于长期的规划。在这种情况下,就无法实现像精益创业这样的方法。此外,上市时间对于很多企业来说不是决定性因素,并非所有市场都存在这方面的竞争压力。当然,如果这些公司突然面临竞争对手,而这些竞争对手能够以精益创业的模式打入市场,情况就会发生变化了。

在许多情况下,上市时间并不能激励引入持续交付。尽管如此,这些技术仍然很有用,因为持续交付还能带来其他好处。

□ 手动发布的工作量很大。为了发布,整个IT 部门往往整个周末都处于待命状态,这种情况早已司空见惯了。而且发布之后,通常还有大量的后续工作要做。

① 详见由David J. Anderson 所著的《看板方法:科技企业渐进变革成功之道》。

□ 手动发布的风险也很高。依赖于诸多手动修改的软件发布很容易产生错误。而如果错误没有及时发现并修复,会对企业产生深远的影响。

在IT 部门,经常有开发人员和系统管理员在周末和晚上加班,为的是将版本发布到生产环境,以及修复发现的错误。除了长时间的工作,他们还承受着巨大的压力,因为风险太大了。可别低估了风险,例如,骑士资本(Knight Capital)因为软件发布事故亏损了4.4 亿美元,最终这家公司破产了。这样的场景可以引发很多思考,最典型的问题是:事故为什么会发生?为什么没有及时发现问题?在其他环境中如何预防此类事件的发生?

持续交付可以作为一种解决方案:持续交付的宗旨即提供更加可靠、质量更高的发布过程。这样,开发人员和系统管理员就可以真正地高枕无忧。下面列出了与此有关的各种因素。

□ 由于发布过程的自动化程度更高,结果更容易重现。因此,如果软件已经在测试环境或准生产环境中部署和测试,那么就会在生产环境获得完全相同的结果,因为环境是完全相同的。这在很大程度上消除了错误的来源,从而降低了风险。

□ 软件测试变得更容易了,因为测试在很大程度上实现了自动化。这进一步提高了质量,因为可以更频繁地执行测试了。

□ 频繁部署同样会降低风险,因为每次部署只对生产环境做很少的改动。显然,改动越少,引入错误的风险就越低。

在某种程度上,这种情况是矛盾的。一方面,传统的IT 试图尽可能减少发布次数,因为发布往往伴随着很高的风险。在每个发布过程中,都可能出现一个会带来灾难性后果的错误。因此,发布的版本越少,导致的问题就会越少。

而另一方面,持续交付提倡的是频繁发布。在这种情况下,每次发布中所做的改动会更少,这也降低了错误发生的概率。自动化和可靠的流程是这个策略的先决条件,否则,频繁的发布会让执行手动流程的技术人员疲于奔命。而且手动流程更容易出错,因此会增加风险。传统IT 的做法旨在保持低发布频率、自动化相关的流程,以减少与发布相关的风险。与之相比,持续交付有一个额外的优势——提高发布频率,让每次发布只包含很少的改动,从而降低出错的风险。

在此,持续交付的动机(如图1-2 所示)与精益创业有很大的不同:其关注点是发布的可靠性和更高的技术质量,而不是按时上市;而且受益者是IT 部门,而不仅仅是业务领域。

图像说明文字

因为优势不同,所以可以做出不同的妥协。例如,在持续交付流水线上投入通常是值得的,即使它不能最终扩展到生产环境(即仍然需要手动构建生产环境)。虽然每次发布最终只需要构建一次生产环境,但是不同的测试需要多个环境。然而,如果上市时间是持续交付的主要动机,那么流水线就必须包括生产环境的发布。

尝试和实验

看看你目前的项目。

□ 在安装过程中,通常哪里会出现问题?

□ 这些问题可以通过自动化解决吗?

□ 如何简化当前的方法,以便于自动化和优化?需要评估所需的工作量和预期的效益。

□ 目前生产系统和测试系统是如何构建的?是由同一个团队构建的吗?应该将自动化应用于这两个环境,还是仅应用于其中一个?

□ 自动化对哪些系统有用?系统多久构建一次?

1.4.10 更快的反馈和精益

当开发人员修改代码时,他会从自己的测试、集成测试、性能测试以及最后的生产环境中获得反馈。如果每个季度只发布一次变更,那么从代码修改到从生产环境中获得反馈,需要间隔几个月的时间。对于验收测试或性能测试也是如此。如果出现错误,开发人员必须回忆他几个月前实现了什么,以及可能存在的问题。

持续交付能加快反馈周期:每次代码通过流水线时,开发人员和整个团队都会收到反馈。每次变更之后都可以运行自动化的验收测试和容量测试。这使得开发人员和开发团队能够更快地发现和修复错误。通过加快测试(比如单元测试)速度,或者先进行广泛的测试再进行深入的测试,可以进一步提升反馈的速度。这从一开始就确保了所有特性至少在简单情况下能正常工作,即所谓的“快乐路径”,从而可以更容易、更快速地发现基本错误。此外,应该从一开始就执行那些(根据经验可知)频繁失败的测试。

持续交付也符合精益思想。精益思想认为,只要客户没有付费,所做的一切都是浪费。在代码部署到生产环境之前,对代码进行的任何变更都是一种浪费,因为只有部署之后客户才愿意为其付费。此外,持续交付缩短了反馈周期,这正是精益思想的另一个观念。尝试和实验

看看你目前的项目。

□ 从代码更改

● 到获得持续集成服务器的反馈经历了多久?

● 到获得验收测试的反馈经历了多久?

● 到获得性能/容量测试的反馈经历了多久?

● 到部署到生产环境经历了多久?

1.5 持续交付流水线的生成及其结构

如前所述,持续交付将持续集成的方法扩展到了其他阶段。图1-3 概述了这些阶段。

图像说明文字

本节将介绍持续交付环境的结构。本节借鉴了Humble 等人的理论(见第2 页脚注),将持续交付流水线分为以下几个阶段。

□ 提交阶段,包括构建、单元测试和静态代码分析等活动,这些活动通常会涉及持续集成的基础设施。第3 章将详细讨论流水线的这一部分。

□ 验收测试(参见第4 章),严格说来,应该是自动化测试:要么将与图形用户界面的交互自动化,以便测试系统,要么用一种自然语言来描述需求,使得这些需求可以用作自动化测试。从这个阶段开始,就必须要生成应用程序的运行环境了。第2 章将讨论如何生成此类环境。

□ 容量测试(参见第5 章)的目标是确保软件能够应付预期的负载。为此,应该使用自动化测试来判断软件是否足够快。重点不仅包括性能,还包括可扩展性。因此,也可以在与生产环境不一致的环境中进行测试。但是,这个环境必须能够提供可靠的结果,反映出软件在生产环境的预期表现。根据具体的用例,还可以用自动化的方式测试其他非功能性需求,比如安全性。

□ 在探索式测试(参见第6 章)中,不再基于严格的测试计划来检查应用程序,而是由领域专家以新特性和未预料到的行为为重点测试应用程序。因此,即使在持续交付中,也不必自动化所有的测试。事实上,如果具有了大量的自动化测试,就可以将更多的精力放在探索式测试上,因为不再需要手动进行常规测试了。

□ 部署到生产环境(参见第7 章)仅仅是在另一个环境中安装应用程序,因此风险相对较低。有几种方法可以进一步降低发布到生产环境的相关风险。

□ 在应用程序的运维阶段会遇到各种挑战,尤其是在日志文件的监测和监控方面。第8 章将讨论这些挑战。

原则上,版本是被依次提交到各个阶段的。设想一下,一个版本到达了验收测试阶段,并通过了该阶段的测试,但是在容量测试阶段表现出过低的性能。在这种情况下,版本永远不会提交到探索式测试或部署到生产环境等阶段。因此,软件必须能满足不断增长的需求,才能发布到生产环境中。

例如,假设这个软件中包含一个逻辑错误。这样的错误最迟会在验收测试阶段被发现,因为验收测试会检查应用程序的正确实现。于是,流水线中断了(如图1-4 所示),此时不再需要其他测试。

图像说明文字

开发人员将修复错误,然后重新构建软件。这一次它通过了验收测试。但是,新功能中仍然存在错误,而且这个新功能没有对应的自动化测试。于是,这个错误只能在探索式测试中被发现。因此,这一次流水线停在了探索式测试阶段,软件未能部署到生产环境(如图1-5 所示)。如果事实已经表明软件不能满足负载处理的需求,或者含有自动化测试能够检测到的错误,就应该这样中断流水线,防止测试人员浪费时间。

图像说明文字

原则上,可以在流水线中并行处理多个版本。当然,这要求流水线并行支持多个版本。如果只能在固定的环境中运行测试,是无法并行的,因为这个环境会被测试占用,无法同时运行第二个版本的并行测试。

然而,通过持续交付并行处理版本是非常罕见的。在版本管理中,一个项目应该只有一个状态,逐步经过流水线的各个阶段。即便软件的修改速度非常快,最多就是前一个版本还没有离开流水线,新的版本就已经发送到流水线中。热修复可能会有例外,但持续交付的一个目标是平等对待所有版本。

示例

全书贯穿使用了一款示例应用程序——大财团在线商务公司的用户注册(参见前言)。这个示例有意简化了业务逻辑,基本上就是注册用户的姓名和电子邮件地址。这些注册信息要经过验证:电子邮件地址的语法必须正确,并且每个地址只允许注册一次;此外,可以基于电子邮件地址搜索到注册信息,而且可以注销账号。由于该应用程序并不是很复杂,相对容易理解,因此读者可以专注于该示例所演示的持续交付的各个方面。

在技术层面,该应用程序是用Java 和Spring Boot 框架实现的。这样,无须安装Web 服务器或应用程序服务器就可以启动应用程序(包括Web 接口)。因为不需要安装任何基础设施,所以测试也变得更容易了。但是,如果有必要的话,也可以在应用程序或Apache Tomcat 之类的Web服务器中运行这个应用程序。该应用程序的数据存储在HSQLDB 中,这是一个在Java 进程内运行的内存数据库。这种做法也降低了应用程序的技术复杂性。

可以从http://github.com/ewolff/user-registration-V2 下载示例的源代码。需要注意,该示例代码包含的一些服务可以在root 权限下运行并通过网络访问。因为这存在安全问题,所以对于生产环境来说肯定无法接受。不过,该示例代码仅用于试验目的,因此保持结构简单会更有帮助。

1.6 小结

将软件交付生产的过程缓慢且具有风险。优化这个过程可能会在总体上使软件开发更加有效和高效。因此,持续交付可能是改进软件项目的一个最佳选择。

持续交付的目标是以常规的、可重复的过程来交付软件,就像持续集成集成所有变更的方式一样。虽然持续交付似乎是缩短上市时间的绝佳之选,但实际上它还可以带来其他收益。它可以最小化软件开发项目中的风险,因为它确保软件可以实际部署并在生产环境中运行。因此,任何项目都可以通过持续交付获得竞争优势,即使它所在的市场竞争并不激烈(上市时间根本没那么重要)。

目录

  • 前言
  • 第1章 持续交付:是什么和怎么做
  • 第2章 提供基础设施
  • 第二部分 持续交付流水线
  • 第3章 构建自动化和持续集成
  • 第4章 验收测试
  • 第5章 容量测试
  • 第6章 探索式测试
  • 第7章 部署:在生产环境中发布版本
  • 第8章 运维
  • 第三部分 持续交付的管理、组织和架构
  • 第9章 引入持续交付
  • 第10章 持续交付和DevOps
  • 第11章 持续交付、DevOps和软件架构
  • 第12章 总结:收益是什么