《持续交付:发布可靠软件的系统方法》- 读书笔记(十)

持续交付:发布可靠软件的系统方法(十)

    • 第 10 章 应用程序的部署与发布
      • 10.1 引言
      • 10.2 创建发布策略
        • 10.2.1 发布计划
        • 10.2.2 发布产品
      • 10.3 应用程序的部署和晋级
        • 10.3.1 首次部署
        • 10.3.2 对发布过程进行建模并让构建晋级
        • 10.3.3 配置的晋级
        • 10.3.4 联合环境
        • 10.3.5 部署到试运行环境
      • 10.4 部署回滚和零停机发布
        • 10.4.1 通过重新部署原有的正常版本来进行回滚
        • 10.4.2 零停机发布
        • 10.4.3 蓝绿部署
        • 10.4.4 金丝雀发布
      • 10.5 紧急修复
      • 10.6 持续部署
      • 10.7 小贴士和窍门
        • 10.7.1 真正执行部署操作的人应该参与部署过程的创建
        • 10.7.2 记录部署活动
        • 10.7.3 不要删除旧文件,而是移动到别的位置
        • 10.7.4 部署是整个团队的责任
        • 10.7.5 服务器应用程序不应该有 GUI
        • 10.7.6 为新部署留预热期
        • 10.7.7 快速失败
        • 10.7.8 不要直接对生产环境进行修改
      • 10.8 小结

第 10 章 应用程序的部署与发布

10.1 引言

将软件发布到生产环境和部署到测试环境是有差异的。从理论上讲,这些差异应该被封装在一组配置文件中。当在生产环境部署时,应遵循与其他任何环境部署同样的过程。启动自动部署系统,将要部署的软件版本和目标环境的名称告诉它,并点击“开始”就行了。所有后续部署和发布都要使用同样的流程。

这些过程(测试环境及生产环境的部署与回滚)都应该是部署流水线具体实现中的组成部分。我们应该能有一个列表,其中包含能够部署到每个环境的所有构建,并且只要通过点击按钮或鼠标就可以选择一个软件版本向某个环境进行自动部署。事实上,这种方式应该是对环境进行修改的唯一途径(包括对操作系统和第三方软件配置的修改)。这样,就能看到每个环境中究竟运行的是哪个版本的应用程序,谁授权部署了这个版本,从上次部署之后应用程序到底有哪些修改。

10.2 创建发布策略

创建发布策略的最重要部分是在项目计划阶段就与应用程序的所有干系人会面。讨论的关键在于,要对整个应用程序的生命周期中的部署与维护达成共识。然后把这个共识作为发布策略写下来。在整个生命周期中,干系人应该对该文档进行更新和维护。

当在项目一开始创建发布策略的第一个版本时,应该考虑下列内容。

  • 每个环境的部署和发布都是由谁负责的。
  • 创建一个资产和配置管理策略。
  • 部署时所用技术的描述。运维团队和开发团队应该对其达成共识。
  • 实现部署流水线的计划。
  • 枚举所有的环境,包括用于验收测试、容量测试、集成测试、用户验收测试的环境,以及每个构建在这些环境中的移动过程。
  • 描述在测试和生产环境中部署时应该遵循的流程,比如提交一个变更申请,以及申请授权等。
  • 对应用程序的监控需求,包括用于通知运维团队关于应用程序相关状态的API或服务。
  • 讨论部署时和运行时的配置方法如何管理,以及它们与自动化部署流程是如何关联在一起的。
  • 描述应用程序如何与所有外部系统集成。比如,在哪个阶段进行集成?作为发布过程里的一份子,如何对这种外部集成进行测试?一旦出现问题,运维人员如何与供应商进行沟通?
  • 如何记录日志详情,以便运维人员能够确定应用程序的状态,识别出错原因。
  • 制定灾难恢复计划,以便在灾难发生之后,可以恢复应用程序的状态。
  • 对软件的服务级别达成一致,比如,应用程序是否有像故障转移以及其他高可用性策略等方面的需求。
  • 生产环境的数量大小及容量计划:应用程序会创建多少数据?需要多少个日志文件或数据库?需要多少带宽或磁盘空间?客户对响应延迟的容忍度是什么?
  • 制订一个归档策略,以便不必为了审计或技术支持而保留生产数据。
  • 如何对生产环境进行首次部署。
  • 如何修复生产环境中出现的缺陷,并为其打补丁。
  • 如何升级生产环境中的应用程序以及迁移数据。
  • 如何做应用程序的生产服务和技术支持。

“创建发布策略”这个活动非常有用。它通常会对软件开发以及硬件环境的设计、配置和托管提出一些功能需求和非功能需求。当发现这些需求时,也需要将它们加到开发计划中。当然,创建这个策略只是一个开始而已。随着项目的进行,它也会改变。

发布策略的一个关键部分就是发布计划,它用来描述如何执行发布。

10.2.1 发布计划

通常来说,第一次发布风险最高,需要细致地做个计划。而这种计划活动的结果可能是产出一些文档、自动化脚本或其他形式的流程步骤(procedure),用来保证应用程序在生产环境上的部署过程具有可靠性和可重复性。除了在发布策略中的这些材料以外,还要包括以下内容。

  • 第一次部署应用程序时所需的步骤。
  • 作为部署过程的一部分,如何对应用程序以及它所使用的服务进行冒烟测试。
  • 如果部署出现问题,需要哪些步骤来撤销部署。
  • 对应用程序的状态进行备份和恢复的步骤是什么。
  • 在不破坏应用程序状态的前提下,升级应用程序所需要的步骤是什么。
  • 如果发布失败,重新启动或重新部署应用程序的步骤是什么。
  • 日志文件放在哪里,以及它包括什么样的信息描述。
  • 如何对应用程序进行监控。
  • 作为发布的一部分,对必要的数据进行迁移的步骤有哪些。
  • 前一次部署中存在问题的记录以及它们的解决方案是什么。

有时候,还要考虑一些其他方面的事情。例如,如果新系统是某个遗留系统的替代品,应该把向新系统迁移用户的步骤写下来,另外还有如何停止旧系统,特别是不要忘记制订一个回滚流程,以应对突发问题。

再次强调,在项目执行过程中,还需要对这个计划进行维护,不断重新审视。

10.2.2 发布产品

前面所说的策略和计划都是通用的。在所有的项目中都值得考虑,即便经过考虑,最后可能也只使用了其中的几条。
另外,对于商业产品软件来说,还有如下一些事情需要考虑。

  • 收费模式。
  • 使用许可策略。
  • 所用第三方技术的版权问题。
  • 打包。
  • 市场活动所需要的材料(印刷材料、网站、播客、博客、新闻发布会等)。
  • 产品文档。
  • 安装包。
  • 销售和售后支持团队的准备。

10.3 应用程序的部署和晋级

要让软件的部署活动能以一种可靠且一致的方式进行,其关键在于每次部署时都使用同样的实践方法,即使用相同的流程向每个环境进行部署,包括生产环境在内。在首次向测试环境部署时就应该使用自动化部署。写个简单的脚本来做这件事,而不是手工将软件部署到环境中。

10.3.1 首次部署

对于任何一个应用程序,首次部署发生在第一个迭代结束时,即当向客户演示第一个开发完的用户故事或需求的时候。在第一个迭代里,选择一至两个具有高优先级但非常简单的用户故事或需求(这么做的前提条件是迭代长度只有一个或两个星期,并且团队规模不大。如果不符合这些条件的话,就要多选择一些来做)。把这个演示活动作为一个借口或理由,以便在类生产环境(UAT)部署应用程序。我们认为,项目首个迭代的主要目标之一就是在迭代结束时,让部署流水线的前几个阶段可以运行,且能够部署并展示一些成果,即使可展示的东西非常少。尽管我们不建议让技术价值的优先级高于业务价值的优先级,但此时是个例外。你可以把这一策略看做实现部署流水线的“抽水泵”。

当这个启动迭代结束时,你应该已经有了以下内容。

  • 部署流水线的提交阶段。
  • 一个用于部署的类生产环境。
  • 通过一个自动化过程获取在提交阶段中生成的二进制包,并将其部署到这个类生产环境中。
  • 一个简单的冒烟测试,用于验证本次部署是正确的,并且应用程序正在运行。

对于一个刚刚开发几天的应用程序来说,这些应该不会有太多的麻烦。其中比较复杂的一点就是如何定义类生产环境。目标部署环境不必是最终生产环境的副本,但是,在生产环境中,某些因素一定要比其他因素更重要一些。

有一个问题提得非常好,那就是“生产环境与开发环境到底有多大的不同?”如果生产环境要运行在不同的操作系统上,那么UAT环境应该使用与生产环境一样的操作系统。如果生产环境是一个集群环境,那么应该搭建一个有限的小集群作为试运行环境。如果生产环境是一个分布式且多节点的环境,那么就要确保类生产环境至少用一个独立的进程来代表每类进程边界。

虚拟化和chicken-counting (0, 1, many)是你的好朋友。利用虚拟化技术在一个物理机器上创建一个环境来模拟生产环境的某些重要特征,还是非常容易的。chickencounting意味着,假如生产环境里有250个Web服务器的话,用两个服务器就足以代表进程边界了。随着开发工作的进行,可在以后适当的时间再根据需要不断完善它。

一般来说,类生产环境具有如下特点。

  • 它的操作系统应该与生产环境一致。
  • 其中安装的软件应该与生产环境一致,尤其不能在其上安装开发工具(比如编译器或IDE)。
  • 使用第11章所描述的技术,用与管理生产环境相同的方式对这种环境进行管理。
  • 对于客户自行安装的软件,UAT环境应该基于客户硬件环境的统计结果,具有一定的代表性,至少要基于别人做过的真实统计
10.3.2 对发布过程进行建模并让构建晋级

随着应用程序变得越来越复杂,部署流水线的实现也会越来越复杂。由于部署流水线应该是对测试和发布流程的建模,所以首先要知道这个流程是什么。尽管,一般来说就是构建版本在各种不同环境之间的晋级,然而我们还要考虑更多的细节。尤其重要的是注意以下内容。

  • 为了达到发布质量,一个构建版本要通过哪些测试阶段(例如,集成测试、QA验收测试、用户验收测试、试运行以及生产环境)。
  • 每个阶段需要设置什么样的晋级门槛或需要什么样的签字许可。
  • 对于每个晋级门槛来说,谁有权批准让某个构建通过该阶段。

分析完这些以后,你可能会得到一张图
《持续交付:发布可靠软件的系统方法》- 读书笔记(十)_第1张图片
一旦创建了这个价值流图,就可以在所用的管理部署工具上为发布流程中的每个部分创建占位符。Go和AntHill Pro都具有这样的功能。另外,大多数持续集成工具可能需要通过一定的定制工作,也可以对这种发布部署过程进行建模和管理。做完这些以后,负责对某个阶段进行审核的人就可以使用这个工具对某个构建版本进行审批了。

部署流水线的管理工具还必须提供另一个关键功能,即在每个阶段都能够看到流水线里的哪些构建已经成功通过前面的所有阶段,并已准备好进入下一阶段了。然后,还应该可以选择这些构建版本中的某个版本,并通过单击一下按钮来部署它。这个过程就是“晋级(promoting)”。通过点击按钮的方式让构建版本晋级使部署流水线变成了一个“拉动”系统,让所有参与到交付过程的人都能够安排他们自己的工作。分析人员和测试人员可以通过自服务方式部署某个构建版本,做探索性测试、演示或可用性测试。运维人员可以自己选择某个构建版本,点一下按钮就将其部署到试运行环境或生产环境上。

自动化部署机制使构建版本的晋级变成了一件非常简单的事,只需要选择某个发布候选版本并把它部署到正确的环境中即可。每个需要部署应用程序的人都能用这种自动化部署机制,而不需要了解部署本身相关的任何技术知识。最后,一旦部署完成后,自动运行一个冒烟测试来验证部署成功与否也很有用。这样,做应用程序部署操作的人(包括分析人员、测试人员或运维人员)就可以确认该系统运行正常,即使不能正常运行,也很容易找到原因。

在测试与发布流程中的每个阶段都基本包含相同的工作内容:根据一套验收条件对应用程序某个特定的构建版本进行测试,确定它是否可以发布。为了执行这个测试,就要把选中的构建版本部署到某个环境中。如果该应用程序是用户自行安装的软件,且又需要手工测试的话,那么该环境可能就是测试人员的台式机。对于嵌入式软件来说,这可能需要一个专用的硬件环境。如果是托管软件服务的话,可能是由一组机器组成的生产环境。当然,也许是上述情况的组合。

无论是哪种情况,对于流程中的每个测试阶段来说,其工作过程都是相似的。

  1. 做测试的人(或团队)通过某种方式在一个列表中选出他们要部署到测试环境中的应用程序版本,该列表中包括所有已通过部署流水线前面各阶段的构建版本。选择某个特定版本之后就会自动执行后续的步骤,直至真正的测试活动。
  2. 准备环境和相关的基础设施(包括中间件),以便能在一个干净的状态下进行应用程序的部署。这应该是以完全自动化的方式进行的,如第11章所描述的那样。
  3. 部署应用程序的二进制包。这些二进制包应该是从制品库中拿到的,而不是每次部署时重新构建出来的。
  4. 对应用程序进行配置。在应用程序中,配置信息应该以某种统一的方式来管理,并在部署和运行时使用。更多的信息请参见第2章。
  5. 准备或迁移该应用程序所管理的数据,如第12章所述。
  6. 对部署进行冒烟测试。
  7. 执行测试(可能是手工的,也可能是自动化的)。
  8. 如果应用程序的这个构建版本通过了这些测试,允许其晋级到下一个环境中。
  9. 如果应用程序的这个构建版本没能通过这些测试,记录一下是什么原因。
10.3.3 配置的晋级

需要晋级的并不仅仅是二进制包。与其同时得到晋级的还包括环境及应用程序的配置信息。然而,你并不想让所有的配置信息都晋级,这让事情变得复杂了一些。例如,你要确保任何新增的配置信息都得到晋级,但是绝不能把那些指向SIT数据库的应用程序或某个外部服务的测试替身对象的配置信息也晋级到生产环境中。先不用说与环境相关的配置信息,就是那些与应用程序本身相关的配置信息的晋级管理就很复杂。

对于这个问题,一种解决办法是用冒烟测试来验证配置信息的指向是正确的。比如,可以用一个字符串返回值来代表测试替身对象的服务所在的环境。然后,让冒烟测试检查应用程序从外部服务得到的返回值与其想要部署环境的预期返回字符串是否一致。对于中间件的配置,比如线程池,你可以利用像Nagios这样的工具来监控这些设置。你还可以写一些对基础设施的测试,用于检查关键设置,并将其返回给监控软件。11.9.4节提供了更多的细节。

在面向服务架构和组件化应用程序中,所有的服务或者构成应用程序的组件都需要一同晋级。正如在前一节中所讨论的,在系统集成测试环境中通常是由服务和组件的各自不同版本共同组成一个组合版本(good combination)。部署系统要强制把这种组合当做一个整体进行晋级,以避免有人部署了某服务或组件的错误版本而导致应用程序失败,或者更糟糕的后果,比如引入那些时有发生却很难追查的缺陷。

10.3.4 联合环境

几个应用程序常常会共享同一个环境。此时,有两种途径引入复杂性。

  • 首先,当为某个应用程序的新版本准备部署环境时,需要花额外的精力,来保证不会破坏同一环境中正在运行的其他应用程序。这通常意味着,你要确保对操作系统或其他中间件配置信息的修改不会引起其他应用程序出现问题。如果该生产环境由同一个应用程序的不同版本共享,那么要确保该应用程序的各版本之间没有冲突。如果事实证明这的确是非常复杂的话,可能就要考虑使用某种形式的虚拟化技术将这些应用程序隔离开来。
  • 其次,共享环境中的这些应用程序之间可能互相依赖。这在使用面向服务架构时是很常见的。这种情况下,集成测试(也叫系统集成测试,或SIT)环境是这些应用程序第一次真正彼此互通的环境,而不是与测试替身对象打交道。因此,在SIT环境中更多的工作是部署每个应用程序的新版本,直至所有应用程序可以互相联通。在这种情况下,冒烟测试套件通常是运行在所有应用程序之上的一个已完全成熟的验收测试集合。
10.3.5 部署到试运行环境

在用户使用应用程序之前,应该在试运行环境(与生产环境非常相似)上执行一些最终测试。如果能想办法得到一个容量测试环境(它几乎是生产环境的复制品),有时也可以跳过试运行这一步骤:可以用这个容量测试环境同时做容量测试和试运行。总之,建议使用这种相对复杂的环境而不是一个简单的系统。如果应用程序需要与外部系统集成,试运行就是最后一个验证各系统生产版本之间所有集成工作的时机了。

在项目一开始,就应该准备好试运行环境。如果你已经为生产环境准备好了硬件,而这些硬件尚没有其他用途的话,那么在第一次发布之前就可以把它作为试运行环境。以下是项目开始时就需要计划的一些事。

  • 确保生产环境、容量测试环境和试运行环境已准备好。尤其是在一个全新的项目上,在发布前的一段时间就准备好生产环境,并把它作为部署流水线的一部分向其进行部署。
  • 准备好一个自动化过程,对环境进行配置,包括网络配置、外部服务和基础设施。
  • 确保部署流程是经过充分冒烟测试的。
  • 度量应用程序的“预热”时长。如果应用程序使用了缓存,这一点就尤其重要了。将这也纳入到部署计划中。
  • 与外部系统进行测试集成。你肯定不想在第一次发布时才让应用程序与真实的外部系统集成。
  • 如果可能的话,在发布之前就把应用程序放在生产环境上部署好。如果“发布”能像重新配置一下路由器那样简单,让它直接指向生产环境,那就更好了。这种被称作蓝绿部署(blue-green deployment)的技术会在本章后面详细描述。
  • 如果可能的话,在把应用程序发布给所有人之前,先试着把它发布给一小撮用户群。这种技术叫做金丝雀发布,也会在本章后续部分描述。
  • 将每次已通过验收测试的变更版本部署在试运行环境中(尽管不必部署到生产环境)。

10.4 部署回滚和零停机发布

万一部署失败,回滚部署是至关重要的。在运行的生产环境中通过调试直接查找问题的这种做法几乎总会导致晚上加班、具有严重后果的错误和用户的不满。当出现问题时,你应该有某种方法恢复服务,以便自己能在正常的工作时间内调试所发现的错误。接下来将讨论执行回滚的几种方法。更先进的技术(蓝绿部署和金丝雀发布)也可以用于零停机发布和回滚。

在开始讨论之前,先要声明两个重要的约束。首先是数据。如果发布流程会修改数据,回滚操作就比较困难。另一个是需要与其他系统集成。如果发布中涉及两个以上的系统(也称联合环境的发布,orchestrated releases),回滚流程也会变得比较复杂。

当制定发布回滚计划时,需要遵循两个通用原则。首先,在发布之前,确保生产系统的状态(包括数据库和保存在文件系统中的状态)已备份。其次,在每次发布之前都练习一下回滚计划,包括从备份中恢复或把数据库备份迁移回来,确保这个回滚计划可以正常工作。

10.4.1 通过重新部署原有的正常版本来进行回滚

这通常是最简单的回滚方法。如果你有自动化部署应用程序的流程,让应用程序恢复到良好状态的最简单方法就是从头开始把前一个没有问题的版本重新部署一遍。这包括重新配置运行环境,让它能够完全和从前一样。这也是能够从头开始重建环境如此重要的原因之一。

为什么创建环境和部署要从头开始呢?有以下几个理由。

  • 如果还没有自动回滚流程,但是已有自动部署流程了,那么重新部署前一版本是一种可预知时长的操作,而且风险较低(因为重新部署相对更不容易出错)。
  • 在此之前,已经对这个操作做过数百次测试(希望如此)。另外,执行回滚的频率相对比较低,所以包含bug的可能性要大一些。

我们没有想到在哪些什么情况下,这种方式会不适用。然而,它也有如下一些缺点。

  • 尽管重新部署旧版本所需的时间固定,但并不是不需要时间。所以一定会有一段停机时间。
  • 更难做调试,找到问题原因。重新部署旧版本通常是覆盖那个新版本,所以也失去了找到问题原因的最佳机会。如果生产环境使用的是虚拟化技术,那么还有办法来弥补这个缺点,后续部分会讲到。对于那些相对简单的应用程序来说,把新版本安装到一个新目录中,改一下符号链接(Unix系统中的目录链接方式),让它指向这个新目录,就可以把旧版本保留下来,非常容易。
  • 如果你在部署新版本前已经备份了数据库,那么在重新安装旧版本时把数据库备份文件恢复回来的话,那些在新版本运行时产生的数据就丢失了。如果问题发现及时且回滚速度足够快的话,这也没什么大不了的,但有些时候这却可能是个严重问题。
10.4.2 零停机发布

零停机发布(也称为热部署),是一种将用户从一个版本几乎瞬间转移到另一个版本上的方法。更重要的是,如果出了什么问题,它还要能在瞬间把用户从这个版本转回到原先的版本上。

零停机发布的关键在于将发布流程中的不同部分解耦,尽量使它们能独立发生。尤其是,在升级应用程序之前,就应该能将应用程序所依赖的共享资源(比如数据库、服务和一些静态资源)的新版本放在适当的位置。

对于静态资源和基于Web的服务来说,这相对容易一些。你只要在URI中包含这些资源或服务的版本就可以了,而且它们的很多版本可以同时并存。比如,Amazon的Web服务有一个基于日期的版本标识系统,其中EC2的API最新版本(在撰写本书时)在这里http://ec2.amazonaws.com/doc/2009-11-30/AmazonEC2.wsdl。当然,他们还会保持旧版本的API按原有的URI工作。对于资源来说,当发布网站的一个新版本时,你可以将这些静态资源(比如图片、JavaScript、HTML和CSS)放在一个新目录中,比如,可以将应用程序版本2.6.5的图片放在目录/static/2.6.5/images之下。

对于数据库而言,事情就有点儿难办了。在第12章有专门的小节描述在零停机情况下如何管理数据库。

10.4.3 蓝绿部署

对于发布管理来说,蓝绿部署是我们所知道的最强大的技术之一。做法是有两个相同的生产环境版本,一个叫做“蓝环境”,一个叫做“绿环境”。

在图10-2的例子中,系统的用户被引导到当前正在作为生产环境的绿环境中。现在我们要发布一个新版本,所以先把这个新版本发布到蓝环境中,然后让应用程序先热身一下(你想多长时间都行),这根本不会影响绿环境。我们可以在蓝环境上运行冒烟测试,来检查它是否可以正常工作。当一切准备就绪以后,向新版本迁移就非常简单了,只要修改一下路由配置,将用户从绿环境导向蓝环境即可。这样,蓝环境就成了生产环境。这种切换通常在一秒钟之内就能搞定。
《持续交付:发布可靠软件的系统方法》- 读书笔记(十)_第2张图片
如果出了问题,把路由器切回到绿环境上即可。然后在蓝环境中调试,找到问题的原因。

这种方式比重新部署要有一些改进。然而,在做这种蓝绿部署时,要小心管理数据库。通常来说,直接从绿数据库切换到蓝数据库是不可能的,因为如果数据库结构有变化的话,数据迁移要花一定的时间。

解决这个问题的一种方法是在切换之前暂时将应用程序变成只读状态一小段时间。然后把绿数据库复制一份,并恢复到蓝数据库中,执行迁移操作,再把用户切换到蓝系统。如果一切正常,再把应用程序切换到读写方式。如果出了什么问题,只要把它再切回绿数据库就可以了。如果这发生在切成读写方式之前,那么什么额外工作也不需要做。如果应用程序中已经写入了一些你想保留的数据,那么,当再次切换回去之前,你就要找到一种方法可以拿到新记录并把它们迁回到绿数据库中。另外,你还可以找个办法让应用程序的新版本把数据库事务同时发向新旧两个数据库。另一种方法是对应用程序进行一下重新设计,以便能够让迁移数据库与升级流程独立,第12章会详细描述。

如果只有一个生产环境,也可以使用蓝绿部署。只要让应用程序的两份副本一起运行在同一个环境中,每个副本都有自己的资源(自己的端口、在文件系统中有自己的根目录,等等)。这样它们就可以同时运行且互不干扰了。你也可以分别对每个环境进行部署。还有一种方法就是使用虚拟化技术,但是要先测试一下这种虚拟化对应用程序在容量方面的影响有多大。

如果有足够预算的话,蓝绿环境应该是相互完全分离的环境副本。这需要的配置较少,但需要的成本较高。该方法的一种变形[也叫做影子域发布(shadow domain releasing)、影子环境发布(shadow environment releasing)或者双热发布(live-live releasing)]是使用试运行环境和生产环境作为蓝绿环境。将应用程序的新版本部署到试运行环境上,然后把用户从生产环境引导至试运行环境中,让用户开始使用这个新版本。此时,试运行环境就变成了生产环境,生产环境就变成了试运行环境。

10.4.4 金丝雀发布

通常来说,“在任意时刻,生产环境中只有应用程序的一个版本正在运行”这个假设都是正确的。这会让缺陷补丁以及基础设施的管理更容易一些。然而,这同时也是对软件测试的一种阻碍。即便有稳固且全面的测试策略,还是会在生产环境上发现缺陷。而且即便周期时间(cycle time)很长,开发团队仍可以从新特性或其他工作的快速反馈中得到收益,作出适当调整,让软件更有价值。

而且,如果生产环境极其庞大的话,创建出一个有意义的容量测试环境也是不可能的——除非应用程序的架构是那种端到端共享(end-to-end sharing)方式。那么,你又如何确保应用程序新版本的性能不差呢?

金丝雀发布就是用来应对这些问题的。如图10-3所示,金丝雀发布就是把应用程序的某个新版本部署到生产环境中的部分服务器中,从而快速得到反馈。就像发现一只煤矿坑道里的金丝雀那样,很快就会发现新版本中存在的问题,而不会影响大多数用户。这是一个能大大减少新版本发布风险的方法。
《持续交付:发布可靠软件的系统方法》- 读书笔记(十)_第3张图片
像蓝绿部署一样,你要先部署新版本到一部分服务器上,而此时用户不会用到这些服务器。然后就在这个新版本上做冒烟测试,如果必要,还可以做一些容量测试。最后,你再选择一部分用户,把他们引导到这个新版本上。有些公司会首先选择一些“超级用户”来使用这个新版本。甚至可以在生产环境中部署多个版本,根据需要将不同组的用户引导到不同的版本上。

金丝雀发布有以下几个好处。

  • 非常容易回滚。只要不把用户引到这个有问题的版本上就行了。此时就可以来分析日志,查找问题。
  • 还可以将同一批用户引至新版本和旧版本上,从而作A/B测试。某些公司会度量新特性的使用率,如果用的人不多,就会废弃它。另外一些公司会度量该版本产生的收入,如果收入较低,就把该版本回滚。如果软件产生了研究结果,那么可以对新旧版本之间从真正用户那儿得到的结果质量进行对比。你不必使用大量用户对新版本做A/B测试,只要有代表性的样本就足够了。
  • 可以通过逐渐增加负载,慢慢地把更多的用户引到新版本,记录并衡量应用程序的响应时间、CPU使用率、I/O、内存使用率以及日志中是否有异常报告这种方式,来检查一下应用程序是否满足容量需求。如果生产环境太大,无法创建一个与实际情况相差不多的容量测试环境,那么这对于容量测试来说,是一个风险相对比较低的办法。

当然,做A/B测试还有一些其他方法。金丝雀发布并不是做A/B测试的唯一方法。比如,也可以在应用程序中利用开关方式让不同的用户使用不同的行为。另外,还可以使用运行时配置设置来改变系统行为。然而,这些变体都无法提供金丝雀发布带来的其他一些好处。

可是,金丝雀发布也并不适用于所有情况。对于那些需要用户安装到其自己环境中的软件来说,这么做就比较困难了。对于这个问题,有另一个解决方案(使用网格计算),那就是让客户软件或桌面应用程序自动从设置的服务器上拿到新版本并自动升级。

金丝雀发布在对数据库升级以及其他共享资源方面引入了更进一步的约束,即任何共享资源(如共享的会话缓存或外部服务等)要能在生产环境中的所有版本中相兼容。另一种方法是使用非共享架构(shared-nothing architecture),即每个结点与其他结点绝对独立,不共享数据库或外部服务,也可以将两种方法结合使用。

最后,在生产环境中保留尽可能少的版本也是非常重要的,最好限制在两个版本之内。支持多个版本是非常痛苦的,所以要将金丝雀的数目减少到最低限度。

10.5 紧急修复

在每个系统中,总会遇到这种情况:发现了一个严重的缺陷,必须尽快修复。此时,需要牢记在心的最重要的事情是:任何情况下,都不能破坏流程。紧急修复版本也要走同样的构建、部署、测试和发布流程,与其他代码变更没什么区别。为什么这么说呢?因为我们看到过很多场合,修复版本直接被放到生产环境中,而产生一个未受控版本。

这会导致两个不幸的后果。

  • 首先是这种紧急修改没有做适当的测试,可能引发回归问题,或者该补丁不但没有修复问题,反而引起了更严重的问题。
  • 其次,这种修改常常没有被记录在案(或者即使第一次记录了,接下来为了修复由第一次修改引入的问题而做的第二和第三次修改却没有记录)。此时,该环境会陷入某种未知状态,使团队很难重现问题,而且以一种不可管理的方式破坏或影响后续的部署流程。

这个故事的寓意是:让每个紧急修复都走完标准的部署流水线。这是另一个应该保持更短周期的原因。

有时候并不真正需要紧急修复一个缺陷。你需要考虑多少人会受到缺陷的影响,这个缺陷是否经常发生,发生后对用户有多大的影响。如果缺陷只影响少数人,而且发生频率不高,影响较低,而部署一个新版本的风险相对较高的话,可能就没有必要做紧急修复了。当然,通过有效的配置管理和自动部署过程来减少部署风险还有一些争议。紧急修复的另一种做法是回滚到以前使用的好版本上,如前所述。

下面是处理生产环境中的缺陷时应该考虑的一些因素。

  • 别自己加班到深夜来做这事儿,应该与别人一起结对做这事儿。
  • 确保有一个已经测试过的紧急修复流程。
  • 对于应用程序的变更,避免绕过标准的流程,除非在极端情况下。
  • 确保在试运行环境上对紧急修复版本做过测试。
  • 有时候回滚比部署新的修复版本更划算。做一些分析工作,找到最好的解决方案。想一想,假如数据丢失了,或者面对集成或联合环境时,会发生什么事?

10.6 持续部署

遵循极限编程的座右铭:如果它令你很受伤,那么就做更多的练习(If it hurts, do it more often)。合乎逻辑的极限就是每当有版本通过自动化测试之后,就将其部署到生产环境中。这种技术叫做“持续部署”,Timothy Fitz发明的一个术语[aJA8lN]。当然它不只是持续部署(你可能会说:只要我愿意,我就可以不断地向UAT环境上部署:没什么大不了的)。关键点在于它是持续部署到生产环境中

指导思想非常简单:使用部署流水线,并让最后一步(部署到生产环境)也自动化。这样,如果某次提交的代码通过了所有的自动化测试,就直接部署到生产环境中。如果想让这种做法不引发问题,自动化测试(应该包括自动化的单元测试、组件测试、功能性和非功能性验收测试)就必须异乎寻常的强大,覆盖整个应用程序。必须先写所有的测试(包括验收测试),然后再写代码。这样你才能做到,只有用户故事完成的最后那次代码提交才能使验收测试通过。

持续部署可以与金丝雀发布结合使用。首先通过一个自动化过程将一个新版本发布给一小撮用户使用。一旦确认(可能是人为决策)新版本没有问题,就把它发布给所有的用户。由良好的金丝雀发布系统提供的这层安全网让持续部署的风险甚至更小。

持续部署并不是适合所有人。有时候,你并不想立即将最新版本发布到生产环境中。在某些公司,由于制度的约束,产品上线需要审批。产品公司通常还要对已发布出去的每个版本做技术支持。然而,在很多情况下,这种方式还是可行的。

有些人反对持续部署,因为在直觉上,这么做的风险太高。但是,如前所述,越频繁的发布会让发布风险越低。这是非常明显的,因为发布越频繁,两次发布版本之间的差异就会越少。因此,如果你每次修改都会被发布,那么风险仅仅局限于这一次变更。持续部署是一个可以减少发布风险的好办法。

也许最重要的是,持续部署迫使你做正确的事儿(正如Fitz在他的博客中所说的那样)。没有完整的自动化构建、部署、测试和发布流程,你无法做到持续部署。没有全面且可靠的自动化测试集合,你也无法做到持续部署。没有在类生产环境中运行的系统测试,你同样做不到持续部署。这就是为什么尽管你无法真正地做到每次的修改通过测试后就发布,也应该创建一个自动化流程——当你想这么做时,你就有能力这么做了。

部署流水线就是为了创建一个可重复的、可靠的自动化系统,把修改的代码尽快放到生产环境中。这就是用最高质量的流程创建最高质量的软件。顺着这个思路和方向,可以大大地减少发布流程中的风险。持续部署把这种方法作为它的必然结论。一定要认真对待这件事,因为它代表了软件的方式交付模式的转变。尽管你有很好的理由说:“我不需要每次修改都要发布一个版本”,但其实这样的理由要比你想象的少得多,而且你也应该像真的每次都要发布版本那样要求自己。

持续发布用户自行安装的软件
将一个应用程序的新版本发布到由你控制的生产环境中是一回事,发布用户自行安装到其自己环境中的软件(客户安装的软件)的一个新版本就是另一回事了。此时需要考虑下列事情。

  • 管理升级的历程。
  • 迁移二进制包、数据和配置信息。
  • 测试升级流程。
  • 从用户那里收集问题报告。

对于客户自行安装的软件来说,一个重要问题是:随着时间的推移,如何管理已经发布的众多版本。它很可能引发技术支持的恶梦:为了调试某个问题,你要将版本回滚到相应的版本上,努力回忆当时开发与这个问题相关的某个特性的情形。理想情况下,希望大家都用同一个版本,即最新的稳定版本。为了达到这一点,就要尽可能做到无痛苦的版本升级。

客户端处理升级有如下几种方式。

  • (1) 让软件自己检查是否有新版本,并提示用户下载并升级到最新版本。这是最容易实现的,但用起来也是最痛苦的。没人想看着一个下载进度条一点一点地向前走。
  • (2) 在后台下载,并提醒用户安装。在这种模式中,软件需要周期性地检查更新,在运行的同时悄悄地下载。当下载成功后,不断地提醒用户升级到最新版本。
  • (3) 在后台下载并在应用程序下次启动时悄悄升级。应用程序可能也会提示你立即重新启动(Firefox就是这么做的)。

如果你比较保守的话,选项(1)和(2)可能看起来更有吸引力。然而,在大多数情况下,这是一个错误的选择。作为应该程序的开发人员,你希望让用户有更多的选择。可是,对于升级这件事而言,用户可能并不了解为什么他需要推迟升级。如果你没有提供什么有意义的信息,还让他们考虑是否需要升级的话,其结果通常是用户选择不升级,仅仅因为升级可能会引起问题。

实际上,同样的思考方式也会呈现在开发团队的头脑中。升级可能会引起问题,既然开发团队自己也这么想,那么我们就应该给用户这样的选项。但是,如果升级过程的确很不稳定,那么用户选择不升级是正确的。而如果升级过程非常稳定,那么就没有必要给用户这样的选择:升级就应该是自动发生的。所以,实际上给用户选择就是告诉他们开发团队对升级过程没有信心。

正确的解决方案是升级过程已通过“防弹测试”(bullet proof)了,而且静默升级。特别是,当升级过程失败时,应用程序应该能够自动回滚到原来的版本并把失败报告给开发团队。开发团队就能修复这个问题,然后再次发布一个新版本(并希望)正确升级。所有这些都应该悄悄发生,无须用户知道。需要提示用户的唯一理由就是需要用户采取一些纠正措施。

当然,你可能还会有其他一些理由不想让软件悄悄升级。也许你不希望有人向你家里打电话,或者你只是某个企业中运维团队中的一员,而该企业规定应用程序的新版本只能在彻底测试且经过批准后,才允许部署,以确保万无一失。这两个情况都是合理的,可以用一个配置选项关闭自动升级。

为了提供一个坚如磐石的升级体验,你需要处理二进制包、数据和配置信息的迁移工作。无论哪种情况,升级过程应该保留一份旧版本的副本,直至完全确信升级已经成功。如果升级失败,应该悄悄地恢复二进制包、数据和配置信息。一种比较容易的方法是在安装目录中让一个文件夹包含当前版本的所有信息,并创建一个新文件夹用于保存新版本的所有信息。然后只要通过重命名目录或创建新版本的一个引用就可以了(在UNIX系统中,通常是使用符号链接做到这一点)。

应用程序应该能够从任意一个版本升级到另外一个版本。为了做到这一点,要对数据存储和配置文件都进行版本管理。每次改变数据库存储的模式或配置信息时,就要创建一个脚本将它们从一个版本升级到下一个版本。如果你想支持降级的话,还要有一个脚本让这些内容从高版本恢复到低版本。当升级脚本运行时,由它自动检查并识别数据存储和配置信息的当前版本,并利用相应的脚本把它们迁移到最新版本上。这种技术在第12章有更加详细的描述。

应该把对升级过程的测试也作为部署流水线的一部分。可以在部署流水线为这个目的专门设置一个阶段。在该阶段中,脚本选择基于真实数据和配置信息的初始状态(这些真实数据和配置信息来自于那些非常友好的用户),运行升级过程达到最新版本。这些活动应该在具有代表性的目标环境中自动完成。

最后,对于客户自行安装的软件来说,关键是能够把错误报告发回给开发团队。在Timothy Fitz关于客户自行安装软件的持续部署的博文中[amYycv], 它描述了用户软件遇到的很多不友好的事件,比如“硬件坏掉了、内存溢出条件、其他语言的操作系统、随机的DLL、别的进程向你的进程中插入了代码、在系统崩溃事件中打头阵的驱动器,以及其他更诡异和无法预期的集成问题。”

因此,一个崩溃报告框架是非常必要的。Google把一个Windows平台上的C++框架开源了,当需要时,.Net从其内部就可以调用该框架。关于如何更好地做崩溃报告以及什么样的度量项有利于报告结果依赖于你所使用的技术栈,这些问题超出了本书的讨论范围。作为起点,Fitz的博客中有一些非常有用的讨论。

10.7 小贴士和窍门

10.7.1 真正执行部署操作的人应该参与部署过程的创建

我们常常要求部署团队去部署那些他们从未接触过的系统。他们拿到一个CD和一些含糊的指南,比如“安装SQL Sever”。

这是运维与开发团队之间关系很差的一种信号,而且可以肯定的是,当真要部署到生产环境时,这个过程会非常痛苦,而且会出现很多指责和坏脾气

在一个项目启动时,开发人员首先要做的一件事情就是非正式地找到运维人员,让他们也参与到开发过程中。这样,运维人员从项目一开始就已经参与了软件开发,双方都知道在发布之前发生了什么,因此,这就会像新生儿的屁屁一样光滑。

10.7.2 记录部署活动

如果部署过程没有完全自动化(包括环境的准备工作),记录哪些文件在自动化部署过程中复制和创建,这是非常重要的。这样做之后,很容易对发生的问题进行跟踪调试,因为很清楚在哪里能找到配置信息、日志和二进制包。

同样重要的是,在每个环境的部署过程中,记录每个改动过的硬件清单和实际部署的日志。

10.7.3 不要删除旧文件,而是移动到别的位置

当做部署操作时,确保已保留了旧版本的一份副本。然后,在部署新版本之前清除旧版本的所有文件。如果旧版本的某个文件被遗忘在了最新部署版本的环境当中,出现问题后就很难追查了。更糟糕的是,如果旧版本的管理接口页面还留在那儿,那么很可能引起错误数据。

在UNIX环境中,一个最佳实践是:把应用程序的每个版本部署在一个单独目录中,用一个符号链接指向当前版本。版本的部署和回滚就只是改一下符号链接这么简单。对于网络版,可以把不同的版本放在不同的服务器上,或者在同一服务器上使用不同的端口。如10.4.3节中所说的,通过代理切换的方式在它们之间切换。

10.7.4 部署是整个团队的责任

“构建和部署专家”的存在是一种反模式。团队中的每个成员都应该知道如何部署,如何维护部署脚本。通过每次部署软件(即使是在开发机器上)都使用真正的部署脚本,就可以达到这一点。

如果部署脚本有问题,构建就应该失败。

10.7.5 服务器应用程序不应该有 GUI

在过去,有GUI的服务器应用程序是很常见的。尤其是PowerBuilder和Visual Basic构建的应用程序更常见。这类应用程序经常存在之前提到过的问题,比如配置信息没有脚本化、应用程序对安装在什么位置非常敏感,等等。然而,最主要问题是:为了能够正常工作,该机器必须有一个用户登录并显示一个界面。也就是说,系统重启(比如由于突发事件或正常升级)都会令该用户登出,而服务器也就停止了。之后,维护工程师就不得不登录到那台机器上,手工启动这个服务了。

10.7.6 为新部署留预热期

不要在预热时激活eBay-killer网站。当这样的网站在官方发布时,它应该已经运行了一段时间,足以让应用服务器和数据库建立好它们的缓存,准备好所有的连接,并完成了“预热”。

对于网站来说,可以通过金丝雀发布达到这个目标。新服务器和新的发布在开始时可以服务于一小部分请求。然后,当环境无异常并被证明行之有效后,你就可以将更多的负载切换到这个新系统上。

许多应用程序在部署时都会急于建立内部缓存。在缓存完成之前,应用程序的响应时间往往较长,甚至可能会失败。如果应用程序行为的确如此的话,请确保在部署计划中考虑到了这件事,包括重建缓存所需的时间(当然是在一个类生产环境中测试)。

10.7.7 快速失败

部署脚本也应该被纳入测试之中,以确保部署成功。这些测试也应该作为部署的一部分来运行。然而它们不应该是全面的单元测试,而是简单的冒烟测试,确保被部署的内容可以工作。

理想情况下,系统在启动初始化时也应该执行这些检查,一旦遇到了问题,就应该让系统无法启动。

10.7.8 不要直接对生产环境进行修改

大多数生产环境的停机是由于那些未受控的修改。生产环境应该是被完全锁定的,这样只有部署流水线可以对其进行改变,包括从环境配置信息到部署在其中的应用程序和相关数据。很多组织有严格的访问管理流程。我们曾看到过,某个组织管理生产环境访问方式是使用由审批流程和两阶段验证系统生成的有限有效期的密码,在使用这个验证系统时需要输入一个由RSA fob产生的代码。在某个组织中,对生产系统的变更可能只能在一个带有闭路电视监控摄像机的房间的某个终端进行操作。

这类授权过程也应该放在部署流水线中。这样做会得到相当大的好处,它意味着有一个系统来记录对生产环境的每一次变更。没有比确切记录谁、什么时候对生产环境做了哪些修改更好的审计跟踪方式了。而部署流水线正好提供了这种便利。

10.8 小结

部署流水线中比较靠后的几个阶段都是关注于测试环境和生产环境的部署。这些阶段与前几个阶段的区别在于它们并没有运行自动化测试作为后面几个阶段的一部分。也就是说,这些阶段很难说是“成功”还是“失败”。只要权限正确的话,部署流水线应该能够通过“单击按钮”就能将任意一个已通过前面几个阶段的构建版本部署到任意一种环境中。还应该让团队中的每个人都明确地看到哪个构建版本被部署到了哪个环境中,该构建版本包含哪些修改。

当然,降低发布风险的最佳方法是真正地做发布演练。越频繁地将应用程序发布到不同的测试环境中越好。尤其是,你越频繁地将应用程序发布到新的测试环境上,这个过程就越可靠,从而在生产环境上发布时遇到问题的可能性就越小。自动化部署系统应该既能够从无到有建立一个新的运行环境,也可以升级已有环境。

然而,无论系统的大小和复杂性,生产环境中的首次发布一定是个重要时刻。至关重要的是,要仔细考虑整个过程,做好充分地计划,使它尽可能地简单直接。然而,为了让团队敏捷起来,发布策略并不是在软件项目发布前的最后一刻(几天或几个迭代)才制定的。这应该是计划活动里的一部分,至少其中一部分会在项目早期就影响到开发决策。该发布策略将会(而且应该)随着时间的推移而变化,当到首次发布时,它会变成更准确、更加详细的方法。

发布计划最关键的部分是将来自组织各部门参与交付的代表组织起来:构建、基础设施、运维团队、开发团队、测试团队、DBA和技术支持团队。在整个项目周期中,这些人应该不断地交流,持续合作,从而使交付过程更加高效。

你可能感兴趣的:(#,devops,运维,数据库,持续集成)