「07」- 部署流水线原则与工具设计

  CREATED BY JENKINSBOT

部署流水线(deployment pipeline)是持续交付1.0的核心模式。它是对软件交付过程的一种可视化呈现方式,展现了从代码提交、构建、部署、测试到发布的整个过程,为团队提供状态可视化和即时反馈。部署流水线的设计受到软件架构、分支策略、团队结构以及产品形态的影响,因此每个产品的部署流水线均有所不同。

本章将重点介绍产品团队设计和使用部署流水线的基本原则,以及企业开发部署流水线平台工具链时,需要构建的平台能力要求,以及相关子系统的服务逻辑架构。

让我们先从一个简单的部署流水线案例开始吧!

7.1 简单的部署流水线(示例)

我们以2008年商业套装软件产品Cruise为例,来解释持续交付部署流水线的概念。该产品思想最早来源于开源软件CruiseControl的企业版本。Cruise自2008年第一个版本发布以后,每3个月发布一个商业化版本,供全球企业用户试用和购买。截至2010年,代码行数约为50000行,自动化单元测试和集成测试用例约为2350个,端到端功能测试用例约为140个。后来更名为「GoCD」,并将社区版开源后,放到了网站上。以下均以“GoCD”之名进行描述。

7.1.1 简单的产品研发流程

GoCD系统是典型的Server/Agent架构,服务器和客户端各自可独立启行。服务器本身曾是一个典型的巨石应用,包含关系型数据库和Java应用服务器。用户可以通过浏览器访问其Web服务,同时它也提供RESTful API接口,方便用户进行程序扩展,架构示意图如图所示:

服务器和代理的代码(包括自动化测试代码)全部保存于同一个代码仓库,版本控制软件使用的Mercural是(与Git类似的分布式版本管理工具),团队成员均有权修改代码库中的任何代码。产品研发团队的总人数保持在12人左右。在产品版本交付期中,迭代周期为一周。团队自身也使用该产品进行持续集成与持续交付实践。在每个迭代结束后,用最新版本替换团队自己正在使用的旧版本。每两个迭代将试用版本部署到公司内部的公用服务器上,供公司其他团队使用。若公司内部试用版本运行质量达标,一周后再将该版本交付给该产品的试用企业,进行外部企业用户早期体验,如图所示:

由于团队使用「测试驱动开发」方法,因此开发人员编写所有的自动化单元测试用例与功能自动化测试用例,并负责维护它们。单元测试的行覆盖率在75%~80%波动。

7.1.2 初始部署流水线

GoCD团队遵守「持续集成六步提交法」(参见第9章),任何人提交代码后,立即自动触发一次部署流水线实例化,该部署流水线如图所示:

第一个阶段是“提交构建”(自动触发,自动执行):包括7个并行自动化任务,分别是编译打包、代码规范静态扫描、5个不同的自动化单元/集成测试用例集合,使用自动触发机制。产品的自动化集成测试也使用单元测试框架编写,并在提交构建阶段与单元测试一起执行。由于GoCD支持多种操作系统,因此在这一阶段会同时构建生成对应不同操作系统的软件包,如deb文件、.exe文件、.zip文件等。这些安装文件生成以后,可供后续所有阶段使用。后续所有阶段不再重新编译打包。

第二个阶段是“次级构建”(自动触发,自动执行):包括两个并行自动化功能测试集任务,分别运行于两类环境,即Windows/IE和Linux/Firefox浏览器,并且两个环境中所用的测试用例集相同,使用自动触发机制。

第三个阶段开始,每个阶段都只有一个任务。

第三个阶段是“UAT部署”(手工触发):将软件包部署到手工UAT环境(用户验收环境,User Acceptance Environment)

第四个阶段是“UAT结果”(手工触发):测试人员“手工验证”完成后,将其标记为“验收通过”

第五个阶段是“性能测试”(手工触发):就是做自动化性能测试。

第六个阶段是“内部体验”:就是将Alpha版本部署到企业内部服务器,给内部其他团队试用。

第七个阶段是“外部体验”:就是将Beta版本发给外部的企业用户体验。

第八个阶段是“上传发布”:就是上传版本。将确定的商业发布版本上传到指定服务器,供用户登录产品网站自行下载。

每次提交代码后,部署流水线的「提交构建」都会被自动触发。「提交构建」成功后会自动触发「次级构建」。提交构建阶段的7个任务中,执行时间最长的是单元测试,其中JavaScriptFu和Java单元测试用例与集成测试用例共有2350个,被分成5个测试集,最长的一个测试集持续时间约为15分钟。「次级构建」的两个并行任务,端到端功能验收测试140多个,执行时间最长需要约30分钟。

第三个阶段(UAT部署)由测试工程师手工触发。测试工程师根据具体需求完成情况,在已经通过次级构建阶段的那些构建中,选择一个被测版本,向UAT环境部署软件包,用于手工验收测试。如果该版本通过了手工验收测试,则测试人员会手工触发第四个阶段,系统自动标记该版本为已测试通过版本。

第五个阶段的「性能测试」也是手工触发。

7.1.3 流水线执行状态解析

开发人员每次提交代码都会触发一次部署流水线。测试人员只从通过「次级构建」的那些版本中选择包含新功能的版本进行UAT部署,并进行手工测试。验收结束后,触发“UAT结果”。团队会定期触发性能测试。这个部署流水线的运行实例示意图:

团队开发工程师每人每天都会提交一次。因此,这个部署流水线每天都会启动多次。当然并不是每次提交的变更都会走到最后的「上传发布」。也不是每次提交都会走到「UAT部署」,因为开发人员并不是完成一个功能需求后才提交代码,而是只要做完一个开发任务,就可以提交。每个功能可能由多个开发任务组成,开发工程师需要确保即便提交了功能尚未开发完成的代码,也不会影响已开发完成的那些功能。

7.2 部署流水线的设计与使用

上面介绍的GoCD团队的部署流水线虽然是一个简单的部署流水线,但其设计及运作方式体现了团队使用部署流水线的设计原则与协作纪律。

7.2.1 流水线的设计原则

流水线的设计遵循以下原则:

	**# 1. 一次构建,多次使用 #**
	当某个部署流水线的一次运行实例构建出制品(如二进制软件包),如果需要,它就应该直接被用于该流水线后续阶段的构建过程,而不是在后续阶段中被再次重复构建。如果该部署流水线实例触发了下游流水线,并且下游流水线也使用该制品,那么,部署流水线工具应该确保它来自上游部署流水线的同一个实例。只有这样,我们对该制品的质量信心才能随着部署流水线的前进而增加。例如,在图7-4中,构建号为521的部署流水线实例中,其内部发布阶段所用的软件包就是同一构建号521的提交构建阶段生产出来的二进制产物。
	2008年GoCD的所有代码和构建安装脚本及配置信息都保存在同一个代码库中。每次触发部署流水线后,如果该实例后续各阶段需要前面阶段的构建产物,则均从构建产物仓库中取出,而非再次重新构建。
	即使有同一个部署流水线的多个实例正在同时运行,每个实例中后续各阶段所用的制品和源代码也应与同一部署流水线实例前面阶段的版本和出处保持一致。
	**# 2. 与业务逻辑松耦合 #**
	部署流水线工具应该与具体的部署构建业务相分离。我们不应该为了方便实现自动化,而将软件代码的构建和部署过程与所选择的部署流水线工具紧耦合。例如将一些软件部署时所用的脚本或所需信息由部署流水线平台保存。相反,我们应该提供单独的脚本,并将其放入该产品的代码仓库中。这样就可以轻松对这些脚本的修改进行跟踪和审核。也就是说,仅仅将部署流水线平台工具视为任务的调度者、执行者和记录者,它只需要知道部署流水线中各种任务触发与调度流程,而不必知道我们如何构建和部署软件。
	**# 3. 并行化原则 #**
	在部署流水线的设计中,我们也应该尽可能考虑并行化。在GoCD的部署流水线中,很多阶段都有并行任务。例如,提交构建阶段中有5个自动化测试任务,它们各自包含不同的测试用例,在不同的计算节点上运行。简而言之,应该尽早提供质量反馈信息,从而及时修正发现的问题。
	如果任何资源都是无限且免费使用的,那么我们希望每一次变更都会同时触发所有类型的测试,而且所有自动化测试用例都是并行执行。如此一来,整体的反馈时间就会大大缩短。
	**# 4. 快速反馈优先 #**
	在资源不足的情况下,部署流水线应该让那些提供快速反馈的任务尽早执行。例如GoCD的部署流水线中,单元测试放在了端到端功能自动化测试和性能自动化测试的前面。这是「反馈速度」与「反馈质量」之间的一种权衡。为了确保能够更快地得到反馈,我们可能会冒一些风险,优先执行那些运行速度快的自动化验证集合,而将那些运行较慢、消耗资源较多的自动化验证集合放在后面执行。
	**# 5. 重要反馈优先 #**
	对于反馈机制,不能只因其执行速度慢,就把它放在后面执行。这一条与前面看似矛盾,但在某些情况下却是必要的质量手段。
	例如,软件安装包的安装测试虽然运行速度比单元测试速度慢,但其反馈更加真实有价值,也应该放到流水线的前面阶段来执行,免所有的单元测试都通过以后才发现软件无法部署启动。

7.2.2 团队的协作纪律

团队协作有以下几条原则:

	**# 1. 立即暂停原则 #**
	「立即暂停原则」是指当部署流水线运行时,某个环节一旦出了问题导致执行失败,团队应该立即停下手中的任务,安排人员着手开始修复它,而不是放任不管。并且,在问题被修复之前,除因修复这个问题而提交代码以外,禁止其他人再向代码仓库提交新的代码变更。
	「立即暂停原则」是质量内建理念的具体体现,它借鉴丰田生产系统中的Stop the line原则。在丰田汽车生产线上,无论什么原因,只要操作者无法高质量地完成他的工作任务,他就可以拉下警示灯,让整个生产线停下来直到问题被解决(详见第4章的相关内容)。
	GoCD团队在实践部署流水线时,也采用了类似的做法。为了不妨碍团队其他成员提交代码,若提交构建阶段失败,提交者在10钟内无法修复问题的话,应该回滚代码。
	**# 2. 安全审计原则 #**
	角色协作时,如果要传递代码或软件包,那么它们应该来自「受控环境」。「受控环境」是指对该环境的一切操作均被审计,并且在该环境中的任何组件(如源代码、二进制代码包或者已安装的程序)均已通过审计。每个部署流水线实例(以唯一实例编号为标识)的任何环节均应使用部署流水线所提供的制品,其产生的任何产物也应该接受受控管理。
	例如,测试人员不应该私自拉取代码,自己手工构建软件包进行测试,也不应该接受开发人员通过各种方式(如即时通信工具)传递的软件包进行测试。每个角色对交付物进行验证时,都应该确保该交付物来自公共受信源,即统一的版本控制仓库或制品库。
	尽可能早地对部署流水线产物进行安全审计,包括在构建过程中所使用的第三方软件包以及企业内其他团队提供的类库或软件服务。

7.3 部署流水线平台的构成

企业或团队需要一个灵活且强大的工具平台,才能快速建立自己的部署流水线。那么,这个工具平台应该包含哪些部分,需要具备哪些能力,才能称为灵活且强大呢?接下来我们就讨论一下部署流水线平台工具链的主要组成部分,以及应当具备的基本能力。

7.3.1 工具链总体架构

部署流水线几乎贯穿于整个持续交付“8”字环中的验证环,涉及从代码提交到生产环境部署的整个流程。支撑部署流水线的平台通常由一系列工具组合而成。这个部署流水线工具体系主要分为3部分:

	第一部分是“唯一受信源”,它是部署流水线的基础,为部署流水线的运行提供原材料(即代码和第三方组件),也用于保存部署流水线运行过程中的产物。
	第二部分是“部署流水线工具”本身,负责各种任务的调度与结果统一展现,通过与其他专项工具或系统相互协作,完成整个交付流程。
	第三部分是“基础支撑服务层“,由多种专门工具组成,提供软件的构建、测试和部署等基础能力。

	**# 1. 唯一受信源 #**
	唯一受信源是团队日常工作过程中所需信息的权威仲裁者。在图中,底部的3个仓库(图中灰色方框)就是企业软件生产中的唯一受信源。当不同角色对某一信息产生质疑时,都应该能够追溯到唯一受信源,并以其为标准唯一受信源应该对信息之间的关联关系进行持久化。例如,制品库中的任意制品都可以在代码仓库源找到其对应的源代码。而代码仓库中的信息应该能在需求/缺陷管理平台中找到其对应的需求出处。
	对于任何环境中安装并运行的软件(甚至包括操作系统本身),我们都应能在制品库中找到与其对应的二进制安装包,以及它所依赖的其他二进制包。即使由于存储空间限制,也应该能够找到它的准确出处,如下载的URI。
	代码仓库中的代码也应该从需求/缺陷管理平台上找到相应的关联关系。也就是说,当不同角色对源代码是否满足需求验收条件产生分歧时,都应该可以在需求/缺陷管理平台上找到正确无二义性的答案
	**# 2. 部署流水线平台 #**
	根据产品团队在平台上对其自身产品流水线的定义,部署流水线平台通过一定的形式连接受信源与不同基础服务,并能够协调和调度不同任务,完成整个交付流程运作,并能够展示所有部署流水线进展与历史信息。
	**# 3. 基础支撑服务层 #**
	一个有历史的软件企业,很可能已经具备相应的基础服务(构建、测试、部署),并且根据原有职责的划分,这些基础服务已经分布于不同的职能部门,很可能还会有重复建设的问题。例如,测试部门管理着多套测试环境,运维部门则严格控制着生产环境。与此同时,每个职能部门都定义各自的验证体系与规范。例如,开发团队要求自测和代码规范;测试部门建立了自己使用的自动化测试体系,并建设自动化测试用例集,同时使用这种自动化测试体系来要求和验证开发团队的软件提测质量标准;运维部门也会建设运维内部的工具体系平台,达到提高运维效率、降低工作强度的目标。
	当我们的软件交付模式向持续交付模式改变时,要求这些服务管理能力必须与部署流水线形成连通,从而使持续交付模式的收益最大化。因此,我们需要对原有支撑平台进行一系列改造,以适应部署流水线模式。

7.3.2 平台应当具备的基本能力

部署流水线平台是团队多角色协作的中枢系统,其关注点是软件自身的价值流动效率,包含从代码提交到部署上线的全流程活动信息,能够谁确展现部署流水线各环节的状态,并在不增加团队负担的情况下自动收集各环节产生的衡量数据,并对价值流动的效率进行度量。例如,度量某一功能的开发周期时间(development cycle time),即从某一功能特性的第一行代码提交,到该功能特性发布到生产环境(或者交付到客户手中)的时间长度,如图所示:

平台要具备可追溯能力:

	一、对事件的追溯能力,即部署流水线中发生的任何事件都应该能够追溯,包括:什么人?在什么时间执行了什么操作?为什么执行?以及操作过程与相应的脚本是什么?只有这样,才能支持良好的安全审计工作。另外,也有利于快速定位和缺陷分析,或者帮助诊断线上问题。
	二、对部署流水线产物的追溯能力。这些信息包括部署流水线的任意产物、其对应的源,构建时的脚本与环境,以及其所依赖的其他组件及相应的版本信息等。

平台要具有对历史构建进行重建的能力。一个极端的场景就是当一个旧的软件版本出现了生产问题以后,即便在产品仓库中已经找不到对应的二进制安装包,只要「唯一受信源」的内容无损,部署流水线平台就可能马上找到当时的部署流水线配置,并再次重建这个旧版本。另外,我们经常会遇到需要重新执行部署流水线中个别环节的场景。例如,如果你怀疑某次的自动化测试失败是由于运行环境相关因素导致的,那么你可能就希望重新运行这个环节。

7.3.3 工具链建设策略

虽然「工具链总体架构」中以较高的抽象层次来描述持续交付部署流水线平台架构,但并不是说它是一个巨石架构。事实上,它由很多不同的工具与子系统整合而成。由于每个公司所使用的技术架构、开发语言、运维方式等都有所不同,因此所用的工具也会有所差异。

对创业公司或「小型公司」来说,由于团队人员规模小,业务场景相对单一,软件架构不是特别复杂,因此通过相关领域的开源工具拼装就可以建立适合自己团队的部署流水线平台。

对有一定历史的「中型公司」来说,遗留系统和代码增多,并有一定的工具基础,可能就需要自己开发一些工具实现一些定制化需求,从而解决某些领域的特定问题,以便提高管理效率。例如,当自动化测试用例数量较多时,就可能需要增加自动化测试用例的管理与分发系统。GoCD团队自动化测试用例较多后,就自行开发了一个自动化测试用例自动分组插件,由该插件自动将所有测试用例分配到不同任务里,并将这些任务分配到多个测试环境中并行执行。

但对「大型公司」来说,其软件产品的运行环境更加复杂,各产品组件之间的关联关系更加复杂,数量规模也比较大,因此定制化需求会更高。为了发挥持续交付的威力,各类支撑服务的云化管理也成了必选项。例如,亚马逊、谷歌、Facebook Facebook、Netflix公司等都开发了自己的持续交付部署流水线平台工具链,甚至将其中的部分工具贡献给了开源社区。

「部署流水线平台」本身只是用于软件部署流水线的定义与任务调度,以及当前状态的展现,具体任务的执行均应由基础支撑服务承担。而这些「基础支撑服务」之间也有相互的关联关系,一个系统的输入可能就是另一个系统的输出。下面让我们从部署流水线中流动的内容来理解这些系统之间的关联关系。

7.4 基础支撑服务的云化

业界领先互联网公司的服务端程序部署频率都非常快,如表所列。

这些公司都建立了自己的云化基础支撑服务,以便支持公司内的大规模持续交付实践,并鼓励使用统一平台和工具,在提升交付效率的同时,也提高资源利用率,降低管理成本。

有些公司认为,内部工具反正是给内部员工用的,只要能用就行,使用体验如何、是否需要统一都不重要。

然而,在持续交付模式下,不好用的工具会对效率产生很大的影响。亚马逊早在2006年就认识到了这一点,在功能性和易用性方面,其公司内部的支撑工具平台在业界也可以算数一数二,让亚马逊电商这个超级庞大复杂的网站流畅运行。其所有工具在全公司范围内统一使用,更新及时且统一,有专门的团队负责开发和维护。

7.4.1 基础支撑服务的协作过程解析

为了能够更好地了解各类基础支撑服务系统的定位与职责,让我们先以一个简单部署流水线(如图所示)的运行示例来说明持续交付部署流水线平台工具链中各种基础支撑服务之间的协作过程:

这个部署流水线只包含3个阶段,分别是“提交构建阶段”、“次级构建阶段”、“部署生产环境阶段”。其中,「提交构建阶段」包括构建打包、单元测试、代码规范检查;「次级构建阶段」包括端到端自动化测试;「部署生产环境阶段」就是直接将成功通过前两个阶段的代码部署到生产环境中。各阶段之间为自动触发关系,如图所示:

平台中所有相关系统的协作信息都会经部署流水线平台展示出来。但是,为了让示意图更简洁,图中没有画出部署流水线平台的调度操作。粗线箭头的方向就是部署流水线的推进方向,带圈的序号表示部署流水线中不同的阶段活动,“0”表示各种基础环境的初始准备活动。所有配置与描述信息及代码都来自代码仓库,二进制包在第一次构建生成后就被放入二进制管理库,并被后续两个环节重复利用,不必再次构建生成。

	第0步:环境准备。运维部门提供的**「基础环境管理服务」**从代码库中获取某产品基础环境要求后,自动为团队准备部署流水线运行所需要的基础环境。如,用于编译打包的构建环境、单元测试用的测试环境、手工验收测试的UAT环境,甚至生产环境。
	第1步:提交构建。提交构建阶段不但包括软件的编译打包,还包括基本的软件包验证,如单元测试、代码规范扫描、安装包验证测试等。因此,**「构建包管理服务」**从代码库中取出源代码,在构建环境中构建打包后,放入制品库。然后,「部署包管理服务」根据流水线的定义将编译好的产物放到测试环境中。若测试过程需要一些特殊配置,则同时从源代码库中拉取测试部署配置。部署成功后,执行流水线指定的测试任务,最终返回测试是否成功的信号。
	第2步:次级构建。**「部署包管理服务」**从制品库中取出第1步生成的二进制包,并从代码库中取出UAT部署配置信息,将二者结合后,部署到UAT环境,运行端到端自动化测试用例。结束后,返回是否成功的标记。
	第3步:部署生产环境。「部署包管理服务」从制品库中取出第1步生成的二进制包,并从代码库中取出生产部署配置信息,将二者结合后,部署到生产环境。

# 最简流水线 — MVU每日50次部署 #
IMVU是一个以3D人物为特性的陌生人社交与游戏应用创业公司,成立于2004年,截止到2009年,其开发工程师约为50人,但其每日生产部署次数达到50次。其部署流水线只有两个阶段,即“提交构建”和“生产环境部署”,并且都是自动触发。自动化测试套件在30~40台机器上并行执行,一共需要运行9分钟,生产环境部署需要6分钟。这两个步骤是连续进行的,这也意味着每9分钟就会向网站推送一次新的代码修订版本,即一个小时之内可以部署6次。平均每天部署50次之多。

现在,我们已经了解了各基础支撑服务之间的协作过程,接下来我们分别讲解不同系统的逻辑结构。

7.4.2 「构建管理服务」

「构建管理服务」包括构建的「任务管理服务」、「调度服务」、「构建集群管理」、「构建执行器」,如图所示:

「任务管理服务」包括两个子服务,一个是接收子服务,另一个是通知子服务:

	「接收服务」是指从开发者个人或者部署流水线(或持续集成服务器)向其发送构建任务请求,记录构建请求的相关信息(请求者、请求任务内容与类型),并将其加入待构建队列中。
	「通知服务」是指从已构建完成任务列表中取出相关信息,及时通知任务发起人有关构建任务的结果信息。

「调度服务负责」从待构建任务列表中,根据一定的调度算法选择构建任务,并将其发送到相应的构建机上执行构建,例如C++代码的构建任务应该发送到有对应C++编译环境的机器上。

「集群管理服务」负责对各类构建环境的管理,包括编译各类构建环境的建立与销毁、环境的状态管理(繁忙、空闲、失去连接)。

「执行器」是执行构建任务的代理,在集群中有很多个执行器,甚至一个计算节点可以有多个执行器。每个执行器需要根据接收到的信息从对应的代码仓库URI检出代码,并根据要求进行编译构建。当构建任务完成之后,根据任务信息将指定的产物放到构建描述中指定的位置(通常为企业的制品库),并向调度服务汇报执行结果。「执行器」本身并不真正执行任务,而是调用专门的构建工具来执行,例如对应Java语言项目的构建工具有Ant Maven Gradle等。

目前很多开源持续集成服务器(如nJenkins、GoCD)提供相应的调度管理功能,大部分情况下已能够满足中小企业需求。只有当企业比较大、构建任务比较多的情况下,才需要自己定制构建管理系统。您可能注意到,在上图中,构建请求既有来自部署流水线的请求,也有来自工程师的请求。这表示,该「构建管理服务」也支持工程师在未提交代码前,就利用它进行个人构建。这令工程师在本地编写代码期间,就可以利用这种强大的服务能力。在第16章的案例中,就使用了类似做法,用于提升持续集成六步提交法中个人构建的反馈速度。

事实上,每种基础支撑服务都应该支持这种工作模式,从而最大化利用资源,提升质量反馈速度。

7.4.3 「测试管理服务」

「测试管理服务」包括「测试任务管理服务」、「测试用例调度服务」、「测试集群管理服务」。对于有大量自动化测试用例的公司,可能还要有「用例健康管理自动化服务」。

「测试任务管理服务」与「构建任务管理服务」类似,也是用于接收任务和反馈任务执行结果,包括任务接收子服务和任务反馈子服务。

「测试用例调度服务」负责根据一定的调度算法从「任务管理服务」中选择测试任务执行。这个调度服务有两种工作模式:一种是顺序执行测试用例,即将所有测试用例分配到符合测试条件的一台测试设备上执行;另一种是并行执行测试用例,即将测试用例分成数个子集,将其分发到多台测试设备上执行。

「测试集群管理服务」与「构建管理服务」中的「集群管理服务」职责类似,包括测试环境的建立与销毁、测试环境的状态管理(繁忙、空闲、失去连接)。但是,其管理的集群类别会更多一些,如单元测试集群、功能测试集群、性能测试集群等。另外,由于每条产品线所需的测试环境可能存在差异,因此还需要按产品线再进行细分。

对执行大量自动化测试用例来说,测试用例本身不稳定也会成为一个比较严重的问题。一些公司会建立「测试用例健康管理服务」。例如,按照一定的规则来自动判断某个用例是否为不稳定测试用例。若被判定为不稳定用例,就将其从健康测试用例集中移出,放入不稳定用例池。那么,当下次正常调用原测试用例集时,该不稳定测试用例会被自动排除在外。

与此同时,「用例健康管理服务」还会对不稳定用例池中的用例以不同的策略进行检查(例如,将该用例在不同的网络节点、不同的节点资源的条件下,连续重复执行100次)如果仍旧有超过一定数量的失败次数,就认定该用例为非常不稳定用例,通知该测试用的归属团队进行处理,如图所示:

7.4.4 「部署管理服务」

我们常会遇到“软件在测试环境和预生产环境中的测试都没有问题,但是到了生产环境就会出错”的现象。其中一个主要原因就是:生产环境与测试环境的差异导致的。例如,十几年前,国内很多大型企业的生产环境中用的J2EE应用服务器都是商业软件。由于它们过于笨重,使得开发人员和测试人员在调试或测试期间都喜欢使用Tomcat作为服务器。而Tomcat对语法检查并不严格,但商业软件却常严格。因此,Web应用上线之后,总是有一些页面因Html标签不匹配而报错。这就是由于环境不一致导致的。

另外,运维部门与产品研发团队之间的责任冲突由来已久。运维部门负责生产环境的稳定性,产品研发团队负责开发新功能。他们之间的接口是一个正式产品仓库。产品研发团队将验收合格的软件包放入这个正式产品仓库,即算完成了研发任务。接下来由运维部门从这个生产仓库中取出该软件包,并根据研发团队提供的上线部署清单,将其部署到生产环境中,如图所示:

在持续交付工作模式下,所有人应该使用相同的工具集。任何人只要获得授权,他就可以一键发出部署指令,而「部署管理服务」接收到指令后,根据其中描述的不同环境部署配置信息,在指定的环境部署指定的软件包,这些环境包括开发测试环境、测试环境、预生产环境、生产环境。

此时,「部署管理服务」负责接受来自不同方的部署请求,分别从制品库中获得指定软件包,从代码仓库中获取部署脚本与配置文件,并根据其中的部署描述,将该软件包分发到指定运行的节点上,将其正确安装后,启动服务。目前市场上已有很多部署管理工具,如Puppet/Ansible/SaltStack等,能够与部署流水线平台协同工作,完成环境部署任务。

7.4.5 「基础环境管理服务」

「基础环境管理服务」为上面3种管理服务(构建管理、测试管理、部署管理)提供环境准备、管理、监控服务。它会接受来自「构建管理服务」、「测试管理服务」、「部署管理服务」的请求,根据请求描述为其准备相应的基础环境,如图所示。「基础环境管理服务」接到3个前端服务的请求后,根据相关的信息,从代码仓库、镜像仓库、软件包仓库获取所需内容,经过加工后得到所需环境。将其放到对应的集群中,并发出通知即可。

「基础环境管理服务」由技术运维团队负责,且不直接为研发团队提供服务。研发团队仅与构建服务、测试服务、部署服务打交道。

随着 Docker技术的成熟,越来越多的公司开始使用这一技术,使得环境准备工作大为简化,可以直接将软件应用、配置、基础环境构建为一个Dockerf镜像,在部署时直接拉取并启动已经生成的Docker镜像即可。

7.5 企业制品库的管理

「企业制品库」是部署流水线工具链中的企业「唯一受信源」之一,也是企业信息安全管理中很重要的一个节点。只有通过安全审计的二进制软件包才能被纳入「企业制品库」,而且安全审计部门应当定期对其中存储的内容进行安全扫描,及时清理存在安全隐患的二进制软件包。

7.5.1 制品库的分类

制品库(artifact repository)的类型如表所示:

	**# 1. 临时软件包库 A (企业内部) #**
	企业内部的「临时软件包库」用于存储企业内部团队开发的通过部署流水线生成代码的所有软件包。例如,每次触发构建后产生的二进制包。该仓库中的二进制包不能被直接部署到生产环境。如果存在存储空间的限制问题,则临时软件包库的内容可以被清理。
	**# 2. 正式软件包库 B (企业内部) #**
	正式软件包库用于存储那些经过部署流水线验证,被确认能够且将要被发布到生产环境(或用户手中)的软件包。一旦经过确认,这些软件包就应该从临时软件产物库移动到正式产品库中。正式软件包库中所存储的软件包应该被一直保存,**直至退市**。
	**# 3. 外部软件包库 #**
	外部软件包库是指该软件包的源代码不是由企业自身团队管理和维护,但是企业在研发自己的软件产品过程中所用到的软件制品。这些软件制品由企业从互联网或第三方机构获得,并保存到外部软件包库中。
	外部软件包库中的存储形式可能包括3种:第一种是直接以二进制形式保存;第二种是以源代码副本的方式保存;第三种是以外部链接地址的形式保存,即仅记录该外部软件包的互联网下载地址,当有人需要时,通过该外部链接地址获取。
	在企业内部建立统一管理的外部软件包库,其目的有3个:一是方便快捷,在企业内部保存这些软件包后,内部人员可以比较方便从内网获取,而不需要连接外网;二是统一审计,内部团队仅从该库中获取外部软件包,容易管理控制,避免同一软件的多种版本却有很多种不同来源,也避免内部人员违规使用不安全的版本;三是保证安全,由于所需的外部软件包全部在这里,因此企业可以对其进行安全检查,以确保企业自己的软件产品不会因不安全的外部包而出现问题,即使市场上发现某个外部软件包存在安全隐患,也能够立即采取措施,通知内部团队打补丁或替换。
	**# 4. 临时镜像库 C、正式镜像库 D、外部镜像库 Y #**
	这3个镜像库与上面的软件包库的职责类似,只是保存的内容有所不同。我们可以将其中保存的镜像看作是一种特殊形态的软件包,使用相同方式进行管理即可。

7.5.2 制品库的管理原则

在企业制品库中,每一个制品都应该有唯一标识,并且连同其来源、组成部件以及用途等,一起保存为该制品的元信息。

所有制品都可以追溯至源头,包括临时制品库中的制品。

无论何时何地,通过制品的唯一标识,任何人从制品库获取的制品都是相同的。

如果制品库中的制品本身被删除或丢失,那么企业可以根据其保留在制品库中的元信息描述,通过原有的部署流水线再次生成与原来相同的制品。

7.6 多种多样的部署流水线

由于软件产品所在行业不同,产品本身的形态不同,负责研发的团队人员组成不同,源代码的版本管理分支策略不同,使用的部署流水线形式也会各不相同。

7.6.1 多组件的部署流水线

如果一个软件产品由多个组件构建而成,每个组件均有独自的代码仓库,并且每个组件由一个单独的团队负责开发与维护,那么,整个产品的部署流水线的设计通常与图中相似。每个组件的部署流水线成功以后,都能触发下游的产品集成部署流水线。这个集成部署流水线的集成打包阶段将自动从企业软件包库中获取每个组件最近成功的软件包,对其进行产品集成打包,并触发集成部署流水线的后续阶段。

7.6.2 个人部署流水线

GoCD使用分布式版本管理工具Mecurial,每名工程师都创建了自己专属的部署流水线,用于个人在未推送代码到团队仓库之前的快速质量反馈。个人部署流水线并不会部署到团队共同拥有的环境中,而是仅覆盖个人开发环节,如图所示:

每名工程师均通过部署流水线提供的模板功能克隆一份团队部署流水线,将其他阶段全部删除,仅保留前面两个阶段,即“提交构建”和“次级构建”的内容。令这个部署流水线监听工程师自己的代码仓库代码变化,并自动化触发。每当开发人员提交代码到个人仓库时,都会自动触发其个人专属的部署流水线。

这样做的收益有以下3个:

	(1)该部署流水线与团队部署流水线共享构建和测试集群环境。由于这些环境是统一管理的,因此能够确保任何时刻,每个人的构建与自动化测试环境均与团队所属的环境一致。
	(2)保证每名工程师都能利用更强大的测试资源,加快个人验证的速度。
	(3)个人部署流水线运行的测试用例与团队部署流水线前两个阶段的验证集合相同,假如团队部署流水线出现构建失败,则容易定位问题,例如工程师遗漏文件未提交。

7.6.3 部署流水线的不断演进

如果你认为GoCD团队的部署流水线一直是本章开始介绍的那样,没有任何变化,你就错了。随时间的推移,部署流水线也应该随着产品的发展而演进。截止到2018年4月,GoCD的社区版本每个月发布一次正式版,其部署流水线设计也已经发生了很大的改变,如图所示:

在图中“构建Linux包”这个部署流水线中,包含两个阶段:

	第一个阶段是“build-no_server”。在这个阶段,在这个阶段一共39个任务并行执行,既并行构建组成Server所需的多个Jar包,也并行执行Java测试用例和JavaScript单元测试用例。这体现了部署流水线“尽量并行化”原则。
	第二个阶段是“build-server”,使用经第一个阶段已初步验证通过的多个Jar包组装成Server包。

在图中“Linux验收测试”这个部署流水线中,也包含两个阶段:

	第一个阶段是运行高优先级的功能测试;
	第二个阶段是对插件部分的自动化功能测试,这体现了部署流水线的“快速反馈优先”原则。

而在后续的各类测试(如验收测试、回归测试或者功能测试)中,被测试的二进制包均来自前面各部署流水线的产出物,而且确保其使用同一源代码版本。

在性能测试部署流水线中,共包含8个阶段它们分别是生成测试用脚本、准备测试环境、启动Server、启动Agent、配置用例、等待就绪、运行和停止。

7.7 为开发者构建自助式工具

在很多企业中,每个职能部门构建的工具都是为自己部门服务的。例如,测试团队开发的自动化测试集主要是为了减少测试人员的手工回归测试工作量,并且常常将其中一部分核心用例集作为提测的门槛,用于验收开发部门提交测试的软件包质量。

另外,还有下面这类场景。在某大型互联网公司中,测试部门开发的测试平台只能在浏览器表单中编写测试用例,而且必须手动提交保存,一不小心就会丢失刚刚写到一半的测试用例。这样差的易用性怎么可能让习惯于本地集成开发环境的开发人员喜欢使用它呢?又怎么能让开发人员喜欢运行这些测试呢?运维部门更是为了生产环境的稳定性,建立起复杂的审批流程,一步步设卡。其负责开发的生产环境配置管理中心非常适合生产环境的管理,可能还非常强大。但是,如果为了践行持续交付中的环境一致性原则,让开发工程师和测试工程师也来使用这套系统,那么你一定会遇到很多困难,因为这样的系统并不是为日常调试和测试环境使用的。

世界优秀的互联网公司却采用了另一种工具平台的设计理念,即“为开发工程师设计他们认为好用的工具”。在2006年,亚马逊公司的首席技术官 Werner VogelsXf对工程师提出“谁构建,谁运营”(you build it you run it)的工作指导原则。他说:

	无论从客户的角度还是从技术的角度来说,让开发工程师承担一部分技术运营的责任,都有利于大大提高服务质量。传统方式下,开发人员把软件隔“墙”(指部门墙)扔给运维工程师就完事儿不管了。在亚马逊,情况却不是这样的。谁构建,谁运营。这不但让开发人员每天都与他们自己的软件打交道,而且也让他们在日常工作中经常与客户联系。而客户反馈环对改进服务质量至关重要。

这种方式要求创建强大的工具平台,能够很好地支持开发工程师做产品服务的技术运营工作。亚马逊也的确投入了大量的人力和物力,创建了一整套支撑系统。同时,也要求改变工具建设的思路,即所有DevOps的工具除审查性服务以外,都应该提供自助式服务。

例如,在Facebook公司,开发人员可以通过他们内部平台看到自己的代码已经发布到哪个阶段,有多少用户在使用(如图7-16所示)。此时,开发工程师在不需要任何人帮助的情况下,就能够了解他的代码已经发布到哪个阶段了。

在互联网电商公司Etsy,开发工程师可以查看到自上次生产部署以后,每次的代码变更数量,并且非常方便地查找代码差异,如图所示:

综上所述,为了实现“谁构建,谁运营”,企业对于DevOpsI工具的建设,应该坚决从开发工程师的工作场景出发,为其构建强大的DevOps工具。不仅是生产环境的运维工具,而且是整个工作流程中的业务软件监控工程基础设施,它包括:

	基础的研发流程自助平台,如各类运行环境(构建、测试、生产)的自助平台
	数据自助平台(包括三层监测数据)
	用于业务快速试错的实验测量平台
	针对移动设备,建立用户触达平台

当我们以这种思路来建设基础工具平台,我们的组织才能成为由多个真正自驱动、自服务的业务导向型全功能团队的学习型组织。

7.8 小结

我们在本章中介绍了团队设计和使用部署流水线的原则,以及企业定制开发私有部署流水线工具链的设计要点和工具平台的能力要求。

同时,还对四大基础支撑服务(编译构建服务、自动化测试服务、部署管理服务及基础环境服务)的逻辑组件进行了简要介绍。

同时,还介绍了三大受信源(需求管理仓库、源代码仓库和制品库)之间的关联关系,以及对它们的管理要求。

本章中我们还列举了几个不同的产品场景,以及相应的部署流水线设计方案,供大家参考。要想让部署流水线发挥最大的作用,研发团队需要尽可能遵守以下5条原则:

	(1)任何软件包的取用皆须通过受控源,各角色之间禁止通过私有渠道(如电子邮件、即时通信工具等)获取。
	(2)尽可能将一切流程自动化,并持续优化执行时间。
	(3)每次提交都能够自动触发部署流水线。
	(4)尽可能地少用手动触发方式。
	(5)必须执行立即暂停原则(stop the line)