持续交付:发布可靠软件的系统方法
上QQ阅读APP看书,第一时间看更新

1.2 一些常见的发布反模式

软件发布的当天往往是紧张的一天。为什么会这样呢?对于大多数项目来说,在整个过程中,发布时的风险是比较大的。

在许多软件项目中,软件发布是一个需要很多手工操作的过程。首先,由运维团队独自负责安装好该应用程序所需的操作系统环境,再把应用程序所依赖的第三方软件安装好。其次,要手工将应用程序的软件产物复制到生产主机环境,然后通过Web服务器、应用服务器或其他第三方系统的管理控制台复制或创建配置信息,再把相关的数据复制一份到环境中,最后启动应用程序。假如这是个分布式的或面向服务的应用程序,可能就需要一部分一部分地完成。

如上所述,发布当天紧张的原因应该比较清楚了:在这个过程中有太多步骤可能出错。假如其中有一步没有完美地执行,应用程序就无法正确地运行。一旦发生这种情况,我们很难一下子说清楚哪里出了错,或到底是哪一步出了错。

本书其他部分将讨论如何避免这些风险,如何减少发布当天的压力,以及如何确保每次发布的可靠性都是可预见的。

在此之前,让我们先明确到底要避免哪类失败。下面列出了与可靠的发布过程相对应的几种常见的反模式,它们在我们这个行业中屡见不鲜。

1.2.1 反模式:手工部署软件

对于现在的大多数应用程序来说,无论规模大小,其部署过程都比较复杂,而且包含很多非常灵活的部分。许多组织都使用手工方式发布软件,也就是说部署应用程序所需的步骤是独立的原子性操作,由某个人或某个小组来分别执行。每个步骤里都有一些需要人为判断的事情,因此很容易发生人为错误。即便不是这样,这些步骤的执行顺序和时机的不同也会导致结果的差异性,而这种差异性很可能给我们带来不良后果。

这种反模式的特征如下。

❑ 有一份非常详尽的文档,该文档描述了执行步骤及每个步骤中易出错的地方。

❑ 以手工测试来确认该应用程序是否运行正确。

❑ 在发布当天开发团队频繁地接到电话,客户要求解释部署为何会出错。

❑ 在发布时,常常会修正一些在发布过程中发现的问题。

❑ 如果是集群环境部署,常常发现在集群中各环境的配置都不相同,比如应用服务器的连接池设置不同或文件系统有不同的目录结构等。

❑ 发布过程需要较长的时间(超过几分钟)。

❑ 发布结果不可预测,常常不得不回滚或遇到不可预见的问题。

❑ 发布之后凌晨两点还睡眼惺忪地坐在显示器前,绞尽脑汁想着怎么让刚刚部署的应用程序能够正常工作。

相反,随着时间的推移,部署应该走向完全自动化,即对于那些负责将应用程序部署到开发环境、测试环境或生产环境的人来说,应该只需要做两件事:(1)挑选版本及需要部署的环境,(2)按一下“部署”按钮。对于套装软件的发布来说,还应该有一个创建安装程序的自动化过程。

我们将在本书中讨论很多自动化问题。当然,并不是所有的人都热衷于这个想法。那么,我们先来解释一下为什么把自动化部署看做是一个必不可少的目标。

❑ 如果部署过程没有完全自动化,每次部署时都会发生错误。唯一的问题就是“该问题严重与否”而已。即便使用良好的部署测试,有些错误也很难追查。

❑ 如果部署过程不是自动化的,那么它就既不可重复也不可靠,就会在调试部署错误的过程中浪费很多时间。

❑ 手动部署流程不得不被写在文档里。可是文档维护是一项复杂而费时的任务,它涉及多人之间的协作,因此文档通常要么是不完整的,要么就是未及时更新的,而把一套自动化部署脚本作为文档,它就永远是最新且完整的,否则就无法进行部署工作了。

❑ 自动部署本质上也是鼓励协作的,因为所有内容都在一个脚本里,一览无遗。要读懂文档通常需要读者具备一定的知识水平。然而在现实中,文档通常只是为执行部署者写的备忘录,是难以被他人理解的。

❑ 以上几点引起的一个必然结果:手工部署过程依赖于部署专家。如果专家去度假或离职了,那你就有麻烦了。

❑ 尽管手工部署枯燥且极具重复性,但仍需要有相当程度的专业知识。若要求专家做这些无聊、重复,但有技术要求的任务则必定会出现各种我们可以预料到的人为失误,同时失眠,酗酒这种问题也会接踵而至。然而自动化部署可以把那些成本高昂的资深高技术人员从过度工作中解放出来,让他们投身于更高价值的工作活动当中。

❑ 对手工部署过程进行测试的唯一方法就是原封不动地做一次(或者几次)。这往往费时,还会造成高昂的金钱成本,而测试自动化的部署过程却是既便宜又容易。

❑ 另外,还有一种说法:自动化过程不如手工过程的可审计性好。我们对这个观点感到很疑惑。对于一个手工过程来说,没人能确保其执行者会非常严格地遵循文档完成操作。只有自动化过程是完全可审核的。有什么会比一个可工作的部署脚本更容易被审核的呢?

❑ 每个人都应该使用自动化部署过程,而且它应该是软件部署的唯一方式。这个准则可以确保:在需要部署时,部署脚本就能完成工作。在本书中我们会提到多个原则,而其中之一就是“使用相同的脚本将软件部署到各种环境上”。如果使用相同的脚本将软件部署到各类环境中,那么在发布当天需要向生产环境进行部署时,这个脚本已经被验证过成百上千次了。如果发布时出现任何问题的话,你可以百分百地确定是该环境的具体配置问题,而不是这个脚本的问题。

当然,手工密集型的发布工作有时也会进行得非常顺利。有没有可能是糟糕的情况刚巧都被我们撞见了呢?假如在整个软件生产过程中它还算不上一个易出错的步骤,那么为什么还总要这么严阵以待呢?为什么需要这些流程和文档呢?为什么团队在周末还要加班呢?为什么还要求大家原地待命,以防意外发生呢?

1.2.2 反模式:开发完成之后才向类生产环境部署

在这一模式下,当软件被第一次部署到类生产环境(比如试运行环境)时,就是大部分开发工作完成时,至少是开发团队认为“该软件开发完成了”。

这种模式中,经常出现下面这些情况。

❑ 如果测试人员一直参与了在此之前的过程,那么他们已在开发机器上对软件进行了测试。

❑ 只有在向试运行环境部署时,运维人员才第一次接触到这个新应用程序。在某些组织中,通常是由独立的运维团队负责将应用程序部署到试运行环境和生产环境。在这种工作方式下,运维人员只有在产品被发布到生产环境时才第一次见到这个软件。

❑ 有可能由于类生产环境非常昂贵,所以权限控制严格,操作人员自己无权对该环境进行操作,也有可能环境没有按时准备好,甚至也可能根本没人去准备环境。

❑ 开发团队将正确的安装程序、配置文件、数据库迁移脚本和部署文档一同交给那些真正执行部署任务的人员,而所有这些都没有在类生产环境或试运行环境中进行过测试。

❑ 开发团队和真正执行部署任务的人员之间的协作非常少。

每当需要将软件部署到试运行环境时,都要组建一个团队来完成这项任务。有时候这个团队是一个全功能团队。然而在大型组织中,这种部署责任通常落在多个分立的团队肩上。DBA、中间件团队、Web团队,以及其他团队都会涉及应用程序最后版本的部署工作。由于部署工作中的很多步骤根本没有在试运行环境上测试过,所以常常遇到问题。比如,文档中漏掉了一些重要的步骤,文档和脚本对目标环境的版本或配置作出错误的假设,从而使部署失败。部署团队必须猜测开发团队的意图。

若不良协作使得在试运行环境上的部署工作问题重重,就会通过临时拨打电话、发电子邮件来沟通,并由开发人员做快速修复。一个严格自律的团队会将所有这类沟通纳入部署计划中,但这个过程很少有效。随着部署压力的增大,为了能够在规定的时间内完成部署,开发团队与部署团队之间这种严格定义的协作过程将被颠覆。

在执行部署过程中,我们常常发现系统设计中存在对生产环境的错误假设。例如,部署的某个应用软件是用文件系统做数据缓存的。这在开发环境中是没有什么问题的,但在集群环境中可能就不行了。解决这类问题可能要花很长时间,而且在问题解决之前,根本无法完成应用程序的部署。

一旦将应用程序部署到了试运行环境,我们常常会发现新的缺陷。遗憾的是,我们常常没有时间修复所有问题,因为最后期限马上就到了,而且项目进行到这个阶段时,推迟发布日期是不能被人接受的。所以,大多数严重缺陷被匆忙修复,而为了安全起见,项目经理会保存一份已知缺陷列表,可是当下一次发布开始时,这些缺陷的优先级还是常常被排得很低。

有的时候,情况会比这还糟。以下这些事情会使与发布相关的问题恶化。

❑ 假如一个应用程序是全新开发的,那么第一次将它部署到试运行环境时,可能会非常棘手。

❑ 发布周期越长,开发团队在部署前作出错误假设的时间就越长,修复这些问题的时间也就越长。

❑ 交付过程被划分到开发、DBA、运维、测试等部门的那些大型组织中,各部门之间的协作成本可能会非常高,有时甚至会将发布过程拖上“地狱列车”。此时为了完成某个部署任务(更糟糕的情况是,为了解决部署过程中出现的问题),开发人员、测试人员和运维人员总是高举着问题单(不断地互发电子邮件)。

❑ 开发环境与生产环境差异性越大,开发过程中所做的那些假设与现实之间的差距就越大。虽然很难量化,但我敢说,如果在Windows系统上开发软件,而最终要部署在Solaris集群上,那么你会遇到很多意想不到的事情。

❑ 如果应用程序是由用户自行安装的(你可能没有太多权限来对用户的环境进行操作),或者其中的某些组件不在企业控制范围之内,此时可能需要很多额外的测试工作。

那么,我们的对策就是将测试、部署和发布活动也纳入到开发过程中,让它们成为开发流程正常的一部分。这样的话,当准备好进行系统发布时就几乎很少或不会有风险了,因为你已经在很多种环境,甚至类生产环境中重复过很多次,也就相当于测试过很多次了。而且要确保每个人都成为这个软件交付过程的一份子,无论是构建发布团队、还是开发测试人员,都应该从项目开始就一起共事。

我们是测试的狂热者,而大量使用持续集成和持续部署(不但对应用程序进行测试,而且对部署过程进行测试)正是我们所描述的方法的基石。

1.2.3 反模式:生产环境的手工配置管理

很多组织通过专门的运维团队来管理生产环境的配置。如果需要修改一些东西,比如修改数据库的连接配置或者增加应用服务器线程池中的线程数,就由这个团队登录到生产服务器上进行手工修改。如果把这样一个修改记录下来,那么就相当于是变更管理数据库中的一条记录了。

这种反模式的特征如下。

❑ 多次部署到试运行环境都非常成功,但当部署到生产环境时就失败。

❑ 集群中各节点的行为有所不同。例如,与其他节点相比,某个节点所承担的负载少一些,或者处理请求的时间花得多一些。

❑ 运维团队需要较长时间为每次发布准备环境。

❑ 系统无法回滚到之前部署的某个配置,这些配置包括操作系统、应用服务器、关系型数据库管理系统、Web服务器或其他基础设施设置。

❑ 不知道从什么时候起,集群中的某些服务器所用的操作系统、第三方基础设施、依赖库的版本或补丁级别就不同了。

❑ 直接修改生产环境上的配置来改变系统配置。

相反,对于测试环境、试运行环境和生产环境的所有方面,尤其是系统中的任何第三方元素的配置,都应该通过一个自动化的过程进行版本控制。

本书描述的关键实践之一就是配置管理,其责任之一就是让你能够重复地创建那些你开发的应用程序所依赖的每个基础设施。这意味着操作系统、补丁级别、操作系统配置、应用程序所依赖的其他软件及其配置、基础设施的配置等都应该处于受控状态。你应该具有重建生产环境的能力,最好是能通过自动化的方式重建生产环境。虚拟化技术在这一点上可能对你有所帮助。

你应该完全掌握生产环境中的任何信息。这意味着生产环境中的每次变更都应该被记录下来,而且做到今后可以查阅。部署失败经常是因为某个人在上次部署时为生产环境打了补丁,但却没有将这个修改记录下来。实际上,不应该允许手工改变测试环境、试运行环境和生产环境,而只允许通过自动化过程来改变这些环境。

应用软件之间通常会有一些依赖关系。我们应该很容易知道当前发布的是软件的哪个版本。

发布可能是一件令人兴奋的事情,也可能变成一件累人而又沉闷的工作。几乎在每次发布的最后都会有一些变更,比如修改数据库的登录账户或者更新所用外部服务的URL。我们应该使用某种方法来引入此类变更,以便这些变更可以被记录并测试。这里我们再次强调一下,自动化是关键。变更首先应该被提交到版本控制系统中,然后通过某个自动化过程对生产环境进行更新。

我们也应该有能力在部署出错时,通过同一个自动化过程将系统回滚到之前的版本。

1.2.4 我们能做得更好吗

当然可以,本书就是来讲如何做好这件事的。即使是在一个非常复杂的企业环境中,我们所说的这些原则、实践和技术的目标都是将软件发布工作变成一个没有任何突发事件且索然无味的事情。软件发布能够(也应该)成为一个低风险、频繁、廉价、迅速且可预见的过程。这些实践在过去的几年中已经被使用,并且我们发现它们令很多项目变得非比寻常。本书所提到的所有实践既在具有分布式团队的大型企业项目中验证过,也在小型开发组中验证过。我们确信它们是有效的,而且可以应用在大项目中。

自动化部署的威力

曾经有个客户,他们在过去每次发布时都会组建一个较大的专职团队。大家在一起工作七天(包括周末的两天)才能把应用程序部署到生产环境中。他们的发布成功率很低,要么是发现了错误,要么是在发布当天需要高度干预,且常常要在接下来的几天里修复在发布过程中引入的问题或者是配置新软件时导致的人为问题。

我们帮助客户实现了一个完善的自动构建、部署、测试和发布系统。为了让这个系统能够良好运行下去,我们还帮助他们采用了一些必要的开发实践和技术。我们看到的最后一次发布,只花了七秒钟就将应用程序部署到了生产环境中。根本没有人意识到发生了什么,只是感觉突然间多了一些新功能。假如部署失败了,无论是什么原因,我们都可以在同样短的时间里回滚。

本书的目标是描述如何使用部署流水线,将高度自动化的测试和部署以及全面的配置管理结合在一起,实现一键式软件发布。也就是说,只需要点击一下鼠标,就可以将软件部署到任何目标环境,包括开发环境、测试环境或生产环境。

接下来,我们会描述这种模式及其所需的技术,并提供一些建议帮你解决将面临的某些问题。实现这种方法,实在是磨刀不误砍柴工。

所有这些工作并不会超出项目团队的能力范围。它不需要刚性的流程、大量的文档或很多人力。我们希望,读完本章以后,你会理解这种方法背后的原则。