在AgileChina2010大会上,老马 提到了“持续交付 ”这一概念。这是在持续集成的基础,应用精益原则,针对软件交付模式的全新诠释。Jez Humble , ThoughtWorks Studio 的发布管理工具GO (原名为Cruise) 的产品经理与David Farley一起,历经三年完成了《持续交付:通过自动化构建、测试、部署流水线实现可靠的软件发布 》一书,老马其作序,并引为“2010最重要的书之一”。而产品Cruise于2007年底开始构建,很多理念已深深植入到该产品。直到本书出版之前,Cruise更名为Go,发布版本也跃升为2.0,加强了其在软件发布管理方面的卓越表现。
《持续交付》详细讨论了持续交付这一理念及其实现方式,并指出当今软件交付中普遍存在的反模式。作为Cruise团队的一员,很高兴我们在产品中所传达的理念被述诸文字,并在第一时间看到这本书,还联系到北京图灵 的杨海玲,在国内引入本书。本书正在翻译中,希望中译本明年与大家见面。部分译文如下,尚未修订。如需转载或引用,请注明原文及原书出处 。该书的网站地址是http://continuousdelivery.com/ ,同时建有GoogleGroup,名为[email protected] ,讨论与软件持续交付相关话题。
以下是第一章中的部分译稿,尚未审校。请自觉维护版权,购买正版书籍。
作为软件人员,我们面临的最重要的问题就是:如果有人想到了一个好点子,我 们如何将它以最快的速度交付给用户? 本书将给出这个问题的答案。
我 们将专注于 构 建、部署、 测试 和 发 布 过 程 , 因 为 相 对 于 软 件生 产 全 过 程的 其他环节 来 说 , 这 部分内容的 论 著相 对 较 少。我 们 并不 认为软 件 开 发 方法不重要 , 恰恰相反 , 如果没有 对 软 件生命周期中 其他方面 [H1] 的 关 注 , 而只把这些方面作为问题的次要因素草草对待, [H2] 就不可能 实 现 可靠、迅速且低 风险 的 软 件 发布 , 以高效的方式将我 们 的 劳动 转 化成 劳动 成果 , 并交到我 们 的用 户 手中。
当今有很多 种 软 件 开 发 方法,但它们主要关注于需求管理及其对开发工作的影响。也有很多优秀的书籍,详细讨论了在软件设计、开发和测试方面各种各样的方法,但它们也只仅仅覆盖了将软件交付给客户或组织这一完整价值流的一部分。
一旦完成了需求定义、方案设计、开发和测试,我们接下来做什么?我们如何协调这些活动,使我们的交付过程更加可靠有效呢?我们如何让开发人员、测试人员,构建和运维人员[H3]在一起高效地工作呢?
这本书描述了从开发到发布这一过程的有效模式。书中讲述的技术和最佳实践将帮助大家实施这种模式,并展示它与软件交付中其他活动的交互协作方式。
本书的中心模式就是部署流水线(deploymentpipeline[H4])。从本质上讲,部署流水线就是指一个应用程序从构建、部署、测试到发布这整个过程的自动化实现。部署流水线的实现对于每个组织都将是不同的,这取决于他们对软件发布的价值流(valuestream)的定义,但大家的实现原则是相同的。
某个部署流水线的实例如图1.1所示。
图1-1[H5]部署流水线
部署流水线的工作方式如下。对于某个应用程序的配置、源代码、环境或数据来说,任何变化都会触发一个新的流水线实例的创建。流水线的第一个步骤就是创建二进制文件和安装包。而其余部分都是基于第一步的产物所做的一系列的测试,用于证明第一步的产物达到了发布质量。每通过一步测试,都会使我们对“这些二进制产物与配置信息、环境和数据可以正常工作”更有信心。如果这个产品代码通过所有的测试环节,那么它就可以发布了。
尽管在持续集成这一流程中,部署流水线有其自身的基础,但本质上来说,它也是采纳持续集成原则后的自然而然的结果[H6]。部署流水线的目标有三个。首先,它让软件构建、部署、测试和发布过程对所有人变得可视,促进了协作。其次,它加速了反馈,以便在整个过程中,我们能够更早地发现问题并解决它。最后,它通过一个完全自动化的过程,使团队可以在任意环境上部署和发布软件的任意版本。
软件发布的当天是最紧张的一天。为什么会这样呢?对于大多数项目来说,整个过程中发布时的风险是比较大的。
在许多软件项目中,软件发布是一个需要很多手工操作的过程。首先,由运维团队独自负责安装好该应用软件所需的操作系统环境,再把应用软件所依赖的第三方软件安装好。其次,将应用程序的软件成品[H7]复制到生产主机环境,然后将配置信息通过Web服务器、应用服务器或其他第三方系统的管理控制台复制到环境中,再把相关的数据复制[H8]一份到环境中,最后启动应用程序。假如这是个分布式的或面向服务的应用程序的话,可能需要一部分一部分地完成。
如上所述,发布当天紧张的原因应该比较清楚了:在这个过程中有很多步骤有可能出错。假如其中有一步没有完美地执行的话,应用程序就无法正确地运行。一旦这种情况发生,我们很难一下子说清楚到底是哪一步出了错。
本书将讨论如何避免这些风险,减少发布当天的压力,以及如何确保每次发布的可靠性都是可预见的。在此之前,让我们先澄清一下我们到底要避免哪类失败。下面列出了与可靠的发布过程相对应的几种反模式,虽然这些情况在我们这个行业中屡见不鲜。
对于现在的应用程序来说,无论规模大小,其部署过程都比较复杂,而且包含很多非常灵活的部分。许多组织都使用手工方式来发布软件。也就是说部署应用程序所需的这些步骤是独立的原子操作,且由某个人或某个小组来分别执行。每个步骤里都有一些需要人为判断的事情,因此很容易发生人为错误。即便不是这样,这些步骤的执行顺序和时机的不同也会导致结果的差异性。而这种差异性很可能给我们带来不良结果。
这种反模式的特征是:
•有一份非常详尽的文档,该文档描述了执行步骤及每个步骤中易出错的地方。
•以手工测试的方式来确认该应用程序是否正确运行了。
•经常发生这样的情形:在发布当天开发团队会接到电话,被通知部署出错了,以及出错的现象是怎样的。
•在发布时,常常会修正一些在执行过程中发现的问题。
•如果是集群环境部署,常常发现在集群中各环境的配置都不相同,比如应用服务器的连接池设置不同,文件系统有不同目录结构等。
•整个发布过程需要较长的时间。
•发布结果不可预测,常常不得不回滚或遇到不可预见的问题。
•发布之后凌晨两点还睡眼惺忪地坐在显示器前,绞尽脑汁想让刚刚部署的应用程序正常工作。
相反,随着时间的推移,部署应该走向完全自动化。即对于那些负责将应用程序部署到开发环境、测试环境或生产环境的人来说,应该只需要做两件事:(1)挑选版本及需要部署的环境,(2)按一下“部署”按钮。对于套装软件的发布来说,还应该有一个创建安装程序的自动化过程。
我们将在本书中讨论很多自动化问题。当然,并不是所有的人都能接受这个想法。那么,我们先来解释一下,我们为什么把自动化部署看作是一个必不可少的目标。
•如果部署过程没有完全自动化,每次部署时都会发生错误。唯一的问题就是“该问题严重与否”而已。即使通过了良好的部署测试,有些错误也很难追查。
•如果部署过程不是自动化的,那么它就是不可重复的或不可靠的,就会在调试部署错误的过程中浪费很多时间。
•手动部署进程不得不记录在案。文档维护是一个复杂而费时的任务。它涉及多人之间的协作,因此该文档基本上要么是不完整的,要么是未及时更新的。而把一个自动化部署脚本作为文档,它就永远是最新的和完整的,否则就无法进行部署工作了。
•自动部署本质上也是鼓励协作的,因为所有的内容都在这个脚本里,一览无遗。而文档是建立在一种假设的基础之上的,即读者的知识水平与作者的水平相当。可事实上,这个文档通常只是执行部署者所写的备忘录,他人并不清楚当时写作的上下文。
根据以上几点,我们可以得到这样的结论:手工部署过程需要部署专家。如果专家去度假或离职了,那你就有麻烦了。
•尽管手工部署是枯燥且重复性的劳动,但仍需要有相当程度的专业知识。一方面,专家要做这些无聊且重复性的工作,而另一方面,技术要求高的任务仍需要他们来完成。结果,我们只有通过“剥夺睡眠”这样的方式来解决时间不足等问题啦。而自动化部署可以解放那些昂贵的高技能人员,让他们投身于更高价值的工作活动当中。
•对手工部署过程本身进行测试的唯一方法就是原封不动地做一次(或者几次)。这往往费时又昂贵。而自动化的部署过程却是既便宜又容易测试。
•另外,还有一种说法:“自动化过程不如手工过程的可审计性好”。我们不同意这个观点。对于一个手工过程来说,没人能确保其执行者会非常严格地遵照文档中所记述的内容。只有自动化过程是完全可审核的。有什么东西会比一个可工作的部署脚本更能够被审核的呢?每个人都应该使用自动化部署过程,而且它应该是软件部署的唯一方式。这个纪律可以确保:在需要部署时,部署脚本一定会很好地完成工作。在本书中我们会提到多个原则,而其中之一就是“使用相同的脚本将软件部署到各种环境”。如果您使用相同的脚本将软件部署到各类环境中,那么在发布当天需要向生产环境进行部署时,这个脚本已经被验证过成百上千次了。如果此时出现任何问题的话,你可以百分百地确定是该环境的具体配置问题,而不是这个脚本的问题。
当然,手工密集操作的发布工作有时也会非常顺利。但不幸的是,我们经常看到的却是糟糕的情况。假如在整个软件生产过程中它不算是一个易出错的步骤,那为什么还总是出错呢?为什么需要这些流程和文档呢?为什么团队在周末还要加班呢?为什么还要求大家原地待命,以防意外发生呢?
当软件开发完成后第一次被部署到类生产环境(比如试运行环境)时,开发团队认为“该软件开发完成了”。
这种模式中,经常看到下面这些情况:
• 测试人员部署之前就已参与开发流程,但只在开发环境中对软件应用进行了测试。
• 只有在向试运行环境部署时,运维人员才第一次接触到这个应用程序。在某些组织中,通常是由独立的运维团队负责将应用程序部署到试运行环境和生产环境。在这种工作方式下,运维人员只有在产品发布到生产环境时才第一次见到这个软件。
• 有可能由于类生产环境非常昂贵,权限控制严格,操作人员自己无权对该环境进行操作,也有可能环境没有按时准备好,甚至根本没人去准备环境。
• 开发团队将正确的安装程序、配置文件、数据库迁移脚本和部署文档一同交给那些真正执行部署任务的人员——而所有这些产物都没有在生产环境或试运行环境中进行过测试。
• 开发团队和真正执行部署任务的人员之间的协作非常少。
当需要将软件部署到试运行环境时,只是临时地组成一个团队来完成这项任务。有时候这个团队可能是一个全功能团队,然而在一个大型组织中,这种部署责任通常落在多个分立的团队肩上。DBA、中间件团队、Web团队,以及其他团队都会染指应用程序最后版本的部署工作。因为部署工作中的很多步骤根本没有在试运行环境上测试过,所以常常遇到问题。文档中也漏掉了一些重要的步骤。这些文档都基于一个基本假设,即软件版本或目标环境的配置都是正确的。然则情况常常恰恰相反,而引起部署失败。部署团队不得不猜测开发团队到底是怎么做的。
这种因缺乏合作而导致部署问题的情况最终会通过临时打电话、发邮件给开发人员,让他们做一些快速修复来解决。一个严格自律的团队会将所有可能性写到部署计划中,但却很少真正让这个过程变得更有效。随着部署压力的增大,为了能够在规定的时间内完成部署,开发团队与部署团队之间这种严格定义的协作过程被证明是失败的。
在执行部署过程中,我们常常发现在我们的系统设计中,存在对生产环境的假设,而这些假设很多是不正确。例如,当部署的某个应用软件是用文件系统做数据缓存的。这在开发环境是没有什么问题的,但在集群环境中可能就不行了。解决这类问题要花很长时间,而且在问题解决之前,应用程序是无法部署的。
一旦应用程序部署到试运行环境之后,我们常常会发现新的缺陷。不幸的是,我们常常没有时间去修复它们,因为最后期限马上就到了。项目进行到这个阶段时,推迟发布日期常常是无法被接受的。所以,大多数严重缺陷被勿忙修复。为了安全起见,项目经理会保存一份已知缺陷列表,可当下一个Release开始时,这些缺陷的优先级还是常常被排得很低。
有的时候,情况会比这还糟。 以下这些事情会使问题恶化。
•当一个应用程序是全新开发的,当第一次将它部署到试运行环境时,可能会比较棘手。
• 发布周期越长,开发团队在错误的假设下开发的时间就越长,修复这些问题的时间就越长。
• 在那些划分有开发、DBA、运维、测试等部门的大型组织中,这些独立部门之间的协作成本可能会非常高,有时甚至会将发布过程拖上地狱列车。此时为了完成某个部署过程,开发人员、测试人员和运维人员之间总是是高举着问题单(不断地互发EMail)。更糟糕的是,这些Email都是为了解决部署过程中发现的问题。
• 开发环境与生产环境差异性越大,开发过程中所做的那些假设与现实差距就越大。虽然很难度量,但我敢说,如果你在Windows系统上开发软件,却最终需要部署到Solaris集群上,那么你会遇到很多意想不到的事情。
• 如果你的应用程序是由客户自行安装的(你可能没有权限操作客户的环境),或者其中的某些组件不在企业控制范围之内。此时可能需要很多额外的测试工作。
那么,
良药就是将测试、部署和发布活动也纳入到开发过程中,让它们成为正常开发流程的一部分。当需要进行系统发布时就不会有什么风险了,因为你已经在很多种环境,甚至类生产环境中已经做过很多次,相当于测试过很多次了。而且要确保每个人都成为这个软件交付流程的一份子,无论是构建发布团队、还是开发测试人员,都应该从项目开始就在一起工作。
我们是测试的狂热者,并将持续集成和持续部署的使用范围扩大了,即不但要对应用程序进行测试,还要对部署过程进行测试,这正是我们所推荐的方法的基石。
很多组织通过专门的运维团队来管理生产环境的配置。如果需要修改一些东西,比如修改数据库的连接配置或者增加应用服务器线程池的线程数,就由这个团队登录到服务器上手工修改一下。如果每次都将这样的修改记录下来,那么这就相当于变更管理数据库的一条记录。
这种反模式的特征是:
• 多次部署到试运行环境都非常成功,但当部署到生产环境时就失败。
• 集群中各节点的行为有所不同。例如,与其它节点相比,某个节点所承担的负载少一些,或者处理请求的时间花得多一些。
• 运维团队需要较长时间为每次发布来准备环境。
• 系统无法回滚到之前部署的某个配置上,这些配置包括操作系统、应用服务器、关系数据库、Web服务器或其它基础设施。
• 不知道从什么时候起,集群中的某些服务器所依赖的操作系统和第三方库的版本就不同了,或者更新了错误的补丁。
• 直接修改生产环境上的信息来改变配置。
相反,
应该让测试环境、试运行环境和生产环境的所有方面,尤其是系统中的第三方软件配置,通过一个自动化的过程从版本控制系统中取出并应用到该环境中。
本书描述的关键实践之一就是:配置管理,其责任之一就是让你能够可重复地创建那些你所开发的应用程序所依赖的基础设施。这意味着操作系统及其补丁、操作系统配置、你的应用程序所依赖的其它软件及其配置、基础设施的配置等等都应该处于受控状态。你应该具有重建生产环境的能力,最好是能通过自动化的方式重建生产环境。虚拟化技术在这一点上可能对你有所帮助。
你应该完全掌握关于生产环境中的任何信息。这意味着生产环境中的每次变更都应该被记录下来,而且做到今后可以查阅。部署失败经常是因为某个人在上次部署时为生产环境打了补丁,但却没有将这个修改记录下来。实际上,应该不允许手工改变测试环境、试运行环境和生产环境。只能通过自动化过程来改变这些环境。
应用软件之间通常会有一些依赖关系。 我们应该很容易知道该软件当前发布的是哪个版本。如果是该软件是由多个组件构成,就应该知道每个组件的版本。发布可能是一件令人兴奋的事儿,也可能变成一件累人而又沉闷的工作。几乎在每次发布的最后都会有一些变更,比如修改了数据库的登录帐户或者更新了所用外部服务的URL。我们应该使用某种方法来引入此类变更,以便这些变更可以被记录并测试。这里我们再次强调一下,自动化是关键。变更首先应该被提交到版本控制系统中,
然后通过某个自动化过程对生产环境进行更新,我们也应该有能力在部署出错时,通过一个自动化过程将系统回滚到之前的版本上。
我们能做得更好吗?
当然可以,本书就是来讲如何做好这件事的。我们所说的这些原则、实践和技术的目标都是即使是在一个非常复杂的企业环境中,也能将软件发布工作变成一个没有任何突发事件的索然无味的事情。软件发布可以也应该成为一个低风险、较频繁、廉价、迅速且可预见的过程。这些实践在过去的几年中已经被使用,并且我们发现它们让很多项目变得非比寻常。本书所提到的所有实践都已经在大型企业项目、分布式的团队中测试过,小团队就更不用说了。我们确信它们是有效的,而且可以应用在大项目中。
自动化部署的威力
曾经有个客户,他们在过去每次发布时都会组建一个较大的专职团队。大家在一起工作七天(包括周末的两天)才能把应用程序部署到生产环境中。他们的发布成功率很低,要么是发现了错误,要么是在发布当天需要高层来协调。常常要在接下来的几天里修复在发布过程中发现的新问题或者是由于配置时手工误操作导致的问题。
我们帮助客户实现了一个完善的自动构建、部署、测试和发布系统。为了让这个系统能够良好运行下去,我们还引入了一些必要的开发实践和技术。我们离开之前的一次发布只花了七秒钟就将应用程序部署到了生产环境中。根本没有人意识到发生了什么,只是感觉突然间多了一些新功能。假如部署失败了,无论是什么原因,我们都可以在同样短的时间里回滚。