1.4 收效
对于前面我们讲到的这种方法,其主要收益是创建了一个发布流程,这个流程是可重复的、可靠的且可预见的,从而大大缩短了发布周期,使新增功能和缺陷修复能更早与用户见面。节省下来的成本将不仅仅是金钱,还包括建立和维护这样一个发布系统所需要的时间投入。
除此之外,还有其他一些收益,虽然其中一些我们应该能够预见到,但另一些很可能是我们无法预测的,但一旦被发现,可以给我们带来惊喜。
1.4.1 授权团队
部署流水线的一个关键点是,它是一个“拉动”(pull)系统,它使测试人员、运维人员或支持服务人员能够做到自服务,即他们可以自行决定将哪个版本的应用程序部署到哪个环境中。根据我们的经验,对缩短发布周期的主要贡献者是那些在整个交付流程中,等待拿到应用程序的某个“好”版本的人。为了得到一个可用的版本,通常需要很多的电子邮件沟通、问题跟踪单,以及其他效率不高的沟通方式。假如是分布式交付团队的话,这一点就成了主要的低效之源。然而实现了部署流水线之后,这个问题就彻底解决了,因为每个人都能看到应该拿哪个版本部署到相应的环境中,而且只需要单击按钮就能完成部署。
我们常常看到在不同的环境中运行着不同的版本,而不同角色的人工作在其上。能够轻松地将任意版本的软件部署到任意环境的能力能带来很多好处。
❑ 测试人员可以选择性地部署较旧的版本,以验证新版本上的功能变化。
❑ 技术支持人员可以自己部署某个已发布的版本,用于重现缺陷。
❑ 作为灾难恢复手段,运维人员可以自己选一个已知的正确版本,将其部署到生产环境中。
❑ 发布方式也变成一键式的了。
我们的部署工具为他们提供的灵活性,改变了他们的工作方式,能让其变得更美好。总而言之,团队成员可以更好地控制工作节奏,从而改进工作质量,这就会让应用程序的质量得以提高。他们之间的协作更加有效,无用的交互更少,可以更高效地工作,因为不需要花太多的时间等待可用的版本。
1.4.2 减少错误
我们可能从方方面面将错误引入到软件中。最初委托制作这个软件的人就可能出错,比如提出错误的需求。需求分析人员可能将需求理解错了,开发人员也可能写出了到处都是缺陷的程序,而我们在这里要说的错误是指由不良好的配置管理引入到生产环境的错误。我们将在第2章详细阐述配置管理。现在,让我们想一下到底需要哪些东西才可以让一个应用程序正确地工作,当然肯定需要正确版本的代码,除此之外呢?我们还需要数据库模式(schema)的正确版本、负载均衡器的正确配置信息、应用程序所依赖的Web服务(比如用于查阅价格的Web服务)的正确URL等。当我们说配置管理时,指的是让你识别并控制一组完整信息的流程与机制,这些信息包括每个字节和比特。
一个比特能有多大的影响
几年前,Dave为一个知名零售商开发了一个大型销售系统。当时还是我们考虑自动化部署的初期。因此,有些东西被很好地自动化了,而有些则没有。有一次,生产环境中出现了一个非常难修复的缺陷。我们在日志中突然发现很多从来没见过的异常记录,很难诊断是由于什么原因造成的,而且在所有的测试环境中都不能重现这个问题。我们尝试了很多方法,比如在性能环境中进行负载测试,试图模拟生产环境中可能的不合理情况,但最终还是无功而返。之后,我们还做了很多研究分析(当然不止我在这里描述的)。我们最终决定,调查每一个我们认为在两个系统(生产环境和测试环境)中有可能引起行为差异的东西。最后发现,我们的应用程序所依赖的一个二进制库(它属于我们所用的应用服务器软件的一部分),在生产环境和测试环境中是不同的版本。我们修改了生产环境中二进制库文件的版本,问题就解决了。
这个故事并不是想说明我们工作不够勤勉或小心,或者我们非常聪明能想到检查系统。其关键在于说明软件真的非常脆弱。这是一个相当庞大的系统,有数万个类,几千个库,以及很多的外部集成点。然而这个严重的问题却是由某个第三方二进制文件不同版本间几个字节的不同引入到生产环境中的。
现在的软件系统常常是由几GB的内容组成,没有哪个团队或个人能够在没有机器帮助的情况下,轻松地查出这种大规模软件中的一小处不同。与其等到问题发生,为什么不利用机器的辅助作用在第一时间防止它发生呢?
通过积极地管理在版本控制库中的所有可能变动的内容,比如配置文件、创建数据库及其模式的脚本、构建脚本、测试用具,甚至开发环境和操作系统的配置,我们让计算机来做它们擅长的所有事情,即确保所用的比特和字节都在它们应该在的位置上,至少确保在代码将要运行时确实如此。
手工配置管理的成本
我们曾开发的另一个项目有大量专门的测试环境。每个专门的测试环境运行一个普通的EJB应用程序服务器。此项目是作为敏捷项目开发的,具有良好的自动化测试覆盖率。本地构建得到了很好的管理,开发人员可以很容易地让代码在本地运行,方便开发。然而,这是我们在更仔细考虑应用程序的部署自动化问题之前。那时,我们的每个测试环境都是通过应用服务器供应商基于控制台的工具进行手工配置的。虽然开发人员用于自己本地安装的配置文件副本都被施加了版本控制,但是对每个测试环境的配置却没有做到这一点。而且这些测试环境之间都有所不同。它们配置属性的顺序不同,有些属性丢失了,有些属性的值不相同,还有一些属性的名字不同,而某些属性是针对某个特定环境的,在其他环境中无效。根本找不到两个完全一样的测试环境,而且所有的测试环境都和生产环境不一样。这使我们极难确定哪些属性是必需的,哪些是多余的,哪些应该是环境共有的,哪些是某种环境特有的。结果,这个项目需要一个五人团队来专门负责管理这些不同环境的配置。
根据我们的经验,这种依赖手工的配置管理很常见。在我们所参与项目的很多客户组织中,这些都是在生产环境和测试环境中实际发生的事情。一般来说,服务器A的连接池限数为100,而B的限数是120,这类问题通常并不打紧,但某些时候却是至关重要的。
你绝对不想在业务交易最忙的时段里有突发事故,更不想发现它是由于配置项的不一致性导致的。这种情况通常发生在那些用于指定软件运行环境的配置项上,而且这种配置信息实际上经常通过代码指定新的执行路径。我们必须考虑到这类配置信息的更改,并且需要像对待代码一样,对代码运行的环境进行良好的定义与控制。假如我们能够接触到你的数据库配置、应用服务器或Web服务器的话,肯定可以让你的应用程序更快出故障,而且比直接修改你的编译器或源代码来得更快更容易。
假如这类配置参数都是由手工配置和管理的话,难免会在那种重复性的工作中出现人为错误。在一些重要的位置上,只要一个简单的输入失误就可以让应用程序停止运行。编程语言可以通过语法检查来发现编译问题,单元测试可以验证代码中有没有输入错误。可是很少有哪种检查方式可以用于配置信息的验证,尤其是当这些配置信息是在某个控制台上直接输入的时候。
所以,请将配置信息放在版本控制系统中。这个最简单的动作就是一个巨大的进步。至少当你不小心修改了配置信息,版本控制系统会提醒你。这就至少消除了一种非常常见的错误源。
当所有的配置信息都放在版本控制系统中以后,接下来就要消除“中间人”了,即让计算机直接使用这些配置信息,而不是再通过手工输入的方式来进行软件配置。虽然某些技术相对来说更顺应这种方式,但是当你(通常是基础设施提供商)仔细思考一下如何管理这类配置信息,尤其是那些最难驾驭的第三方系统的配置信息时,会惊奇地发现还有很长的路要走。我们将在第4章详细讨论相关内容,而更深入的讨论将在第11章进行。
1.4.3 缓解压力
明显的好处中,可以缓解压力是最吸引所有与发布相关的人的一点。绝大多数经历过项目发布的人都会认为,当项目越临近发布日期,就越能感觉到压力。根据我们的经验,压力本身就是问题的根源所在。一些敏感、保守且具有质量意识的项目经理常常对开发人员说:“都这个时候了,你就不能直接修改一下代码吗?”或者让数据库管理员把他们并不清楚来路的数据录入到应用程序的数据库表中。像以上两种情况,或者其他许多类似的情况下,其压力是通过传达“只要让它可以工作就行了”这一信息表现出来的。
不要误解,我们也遇到过同样的事情。我们并不是说这种处理方式一定是错误的,如果刚把代码部署到了生产环境中,而你的组织因为它的某个缺陷遭受经济上的损害时,任何阻止这种损害的行为都是可以理解的。
我们的不同观点在于,上面所提到的为了让刚部署的产品环境可以正常运行的这两种快速修补并不一定是业务需要使然,而更多的可能是由于“今天是计划已久的发布日期”所带来的压力导致的。这里的问题在于系统上线是一个非常重大的事件。只要这是事实,就会有很多的庆典和紧张气氛。
现在,让我们来设想一下。如果接下来的发布只需要单击一下按钮,而且只需要等上几分钟,甚至几秒钟内就可以完成。另外,假如发生了非常糟糕的事情,你只要花上相同的几分钟或几秒钟的时间就可以把刚部署的内容恢复到从前的老样子。再大胆地设想一下,假如你的软件发布周期总是很短,那么当前生产环境中的版本与新版本之间的差异应该非常小。如果上述设想都是事实的话,那么发布的风险一定会大大降低,那种将职业生涯压注在发布是否成功的不爽感觉也将大大减少。
对于很少的一部分项目来说,这种理想状态可能很难成为现实。然而,对于大多数项目来说,尽管可能需要花上一些精力,但肯定是可以做到的。减少压力的关键在于拥有一个我们前面所描述的自动化部署过程,并频繁地运行它,当部署失败后还能够快速恢复到原来状态。尽管刚开始做自动化时可能会很痛苦,但它会渐渐地变得容易起来,而它给项目和团队带来的好处是不可限量的。
1.4.4 部署的灵活性
在一个全新环境上运行应用程序应该是相当简单的事。理想情况下,只要安装机器或虚拟镜像,然后配置一些与具体运行环境相关的特定选项。然后,你就可以使用自动化过程准备好新的部署环境,并选择指定的应用程序版本进行部署。
在笔记本电脑上运行企业级软件
我们最近做过一个项目,该项目就是根据新的业务要求创建一个企业核心系统。该业务涉及跨国事务,软件需要部署在不同类型的昂贵的计算机上。可是,由于政府法规的突然变化,客户业务流程也不得不作出相应的调整。项目可能会被取消的消息自然让每个人都有点失望。
但对于我们来说,还有一点儿希望。聘请我们开发软件的客户做了一个小规模分析。“这个系统的最小硬件配置是什么?我们如何节省资金成本?”他们问道。“让它在笔记本电脑上运行”,我们回答道。他们非常吃惊,因为这可是一个非常复杂的多用户系统。“你们如何确保它的可行性?”他们仔细想了想后,还是担心地问道。“我们可以使用这种方式来运行所有的验收测试,……”然后,我们给客户做了演示。“它的负载需求是什么?”我们问道。他们把负载需求告诉了我们,而我们只修改了一行代码,增加了几个参数,就可以在笔记本电脑上做性能测试了。尽管在笔记本电脑上运行的确比较慢,但并不是慢得离谱。只要一个配置稍好一点儿的服务器就可以满足他们的需求,而事实证明,在这样的服务器上只需要几分钟就可以让应用程序运行起来。
当然,这种部署上的灵活性不只是由于我们本书所讲的这种自动化部署技术,良好的应用程序架构设计也很好地支持了这种方式。然而,这种“只要需要,就可以让软件运行在任何环境中”的能力使我们和客户对我们随时管理所有版本发布过程充满信心。这样一来,发布变得不再让大家那么焦虑,正如敏捷所强调的,在每个迭代结束时进行发布这件事就变得很容易了。尽管并不一定每个项目都能够完全做到这种程度,但这会让我们享受属于自己的周末时光。
1.4.5 多加练习,使其完美
在所参与的项目中,我们都会设法让每个开发人员都拥有自己的专属开发环境。但是,即使在那些做不到这一点的项目中,使用持续集成或迭代增量开发的团队也要频繁地部署应用程序。
最好的策略就是无论部署到什么样的目标环境,都使用相同的部署方法。不应该有特殊的QA部署策略,或者一个特殊的验收测试或生产部署策略。在每次以同一种方式部署应用软件时,也是验证我们的部署机制是否正确的时机。事实上,向其他任何环境的任何一次部署过程都是生产环境部署的一次演练。
只有一种环境可以有多变性,那就是开发环境。开发人员应该在自己的开发环境中自行生成二进制文件,而不需要在别处构建生成。所以,对这种开发环境的部署流程要求太严格是没有必要的。虽然我们能够做到在开发人员的开发机器上也以同样的方式部署软件,但实际上对开发环境的部署没有必要严格要求。