《持续交付》(三)部署流水线与构建脚本

引言

本文是《持续交付》一书学习总结的第三篇。主要内容涉及部署流线和部署脚本的设计实践。

《持续交付》(三)部署流水线与构建脚本_第1张图片

部署流水线(一)

要实现持续交付,一般会定义一条部署流水线(deployment pipeline)。我们之前讨论过的持续集成过程,就是这个流水线的一部分。总的来说,部署流水线就是一套将代码从版本控制工具最终交付到用户手中的自动化流程。我们在前面看到了自动化的持续集成,以及验收测试。除此之外,部署流水线还包含在流程各个阶段(开发、测试、发布等)对软件的部署以及发布流程的管理。可以说,《持续交付》整本书都在围绕部署流水线进行讨论。

部署流水线的第一个好处我们多次提到过,就是基于自动化的快速反馈,如下图。在部署流水线的每一个阶段都能形成及时的反馈,让团队合作解决问题。经过多次反馈,到发布阶段时的软件质量是很高的。

《持续交付》(三)部署流水线与构建脚本_第2张图片

快速反馈可以帮助我们避免很多问题。首先是所谓回归(regression)的bug。由于部署流水线会针对每一次提交的代码改动执行一次,如果出现回归问题,就会被及时发现。
另一类问题是关于不同环境的配置。由于在部署流水线中,会先后部署多种环境(test,staging和production等)。不同环境的配置通常不同,这就可能导致同一个build在不同环境部署时失败,而这些问题会被立即处理。

最后,自动化的部署流水线可以让我们很容易的回退代码(roll back)到上一个正常版本,这也就降低了频繁发布的风险。

下图展示了一个更加具体的部署流水线。该图的上方就是存放代码和配置的版本控制工具,大家应该很熟悉。下方是存放build结果的地方,成为artifact repository。持续集成过程就结束于此,而部署过程也从此开始,因此它是部署流水线上的中间节点。常用的artifact repository是JFrog公司的Artifactory,大家可以搜索它官网。

《持续交付》(三)部署流水线与构建脚本_第3张图片

部署流水线从提交阶段开始,经过构建、单元测试和代码扫描等持续集成的必要步骤,进入验收阶段。在这个阶段需要一次部署,需要的配置从版本控制工具获得,需要的二进制应用程序从artifact repository获得。部署成功后可以先进行冒烟测试,保证部署没有大的问题,然后进行验收测试。这个测试可以是自动化的,也可以是手动的。

测试人员也可以利用部署流水线来部署自己需要的另一套测试环境。验收阶段没有问题之后,可以继续部署后续的环境如staging和production等。在每次部署后都需要进行冒烟测试。最后,还要进行非功能性的测试以及用户验收测试(UAT)。所有这些测试通过后,才能进入发布阶段。发布阶段没有体现在图二中,我们在后续分享中会谈到。
接下来就是部署流水线中的一些实践建议。

部署流水线(二)

下面我们来看一些实现部署流水线的实践。你可以在书中找到更多的实践建议,这里仅讨论其中一部分。

  1. 在每个环境上采用相同的部署方式。这可以防止“在我机器上是好的”综合症,即由于环境的差异导致部署失败。使用相同的环境多次检验,也可以保证部署过程的正确性。
  2. 对部署进行冒烟测试。这一点不多说,每次部署后都要验证部署是否成功,这里面也包括验证依赖的服务是否部署成功。
  3. 部署一份生产环境的拷贝。这一点比较关键,即使我们有测试和staging环境,也不一定保证生产环境的部署成功,因为配置和基础架构可能不同。部署一套与生产环境完全一致的环境是很有必要的。
  4. 如果某部分失败,停止流水线。在部署流水线上的任何一步发生错误,都应该停下来,调查并解决问题,避免有问题的代码被部署。
  5. 设立验收测试质量门(Quality Gate)。这是指如果验收测试没有通过,整个流水线就视作失败,不能继续部署。

接下来说一说跟发布相关的事情,一般部署流水线也包括发布的流程。发布流程的设计原则主要是两点:
1. 自动化过程。2. 支持回退。

在发布过程中,我们肯定要修改生产环境,但任何生产环境的改动都要局限在自动化的流程中,不能脱离流水线去手动修改任何配置、软件依赖、网络拓扑等。回退机制的关键是保留上一个可用的部署版本。由于部署流水线是从源代码开始的,代码版本控制工具天然就保留了历史版本,因此这个不是问题。

在设计部署流水线时还要注意一些问题。首先流水线的设计和开发是个演进过程,不要试图一次性把所有过程弄好。另外,需要制定一些指标(metrics)来评价部署流水线。最常见的指标就是整个流水线的周期时长(cycle time),即新版本的软件从源代码到交付给用户需要的总时间。这个指标决定了我们多久能交付一次。

如果遇到回退情况,多久能恢复。为了控制这个时间,我们要考察流水线上的每一步,看看瓶颈在什么地方,然后针对瓶颈进行优化。出了这个大的指标,我们还会关注一些小的指标,如测试的覆盖率、代码扫描发现的问题数量、每天build次数、每天build失败的次数以及每天代码提交次数等等。这些指标都会为我们优化部署流水线提供依据。

部署流水线就介绍到这里。这本书的剩余部分就是围绕部署流水线上的各个步骤展开的,我们首先来学习构建和部署脚本。

构建和部署脚本(一)

下面我们来看实现构建(build)和部署(deploy)脚本时的一些实践。

先说build,我们在日常的项目中一定接触过各种各样的build工具,取决于你所用的平台和技术。比如MSBuild用于Windows平台项目,Maven和Gradle用于Java平台的项目等。本质上,构建工具是帮你建模一个依赖网络,如下图。图中所示的最终目的是运行单元测试,不过在这之前我们需要准备测试数据,编译源代码以及测试case,而这些准备工作的前提是项目的初始化操作,那么整个依赖网络就从项目初始化开始。在部署流水线中,上述网络中的所有步骤通常被抽象成一个构建阶段(build stage)。

《持续交付》(三)部署流水线与构建脚本_第4张图片

在实际工作中,构建的过程可以在物理机上完成,也可以在虚拟机上完成,当然也可以利用容器来实现。目前,持续集成和交付的一个趋势就是在容器中实现,常用的容器工具是Docker,常见的容器管理工具有Kubenetes(K8S)。使用这些工具代替物理机或者虚拟机的好处是,将工作关注点放在流水线的具体实现上,而无需关系计算资源的管理。并且,容器化的环境和任务可以重复稳定运行。

接下来我们再看看一些构建和部署脚本的实现。

  1. 为流水线上每个阶段写一个脚本。这个实践有些理想化,通常我们将部署流水线分成持续集成和部署两部分,也就形成两个大的脚本。以持续集成为例,我们通常不会给初始化、编译、提交测试、验收测试等过程单独撰写脚本。但当某个阶段的任务增多,脚本逐渐变大时,可以根据需要将脚本拆分成若干个。
  2. 对每个环境使用相同的部署脚本。这一点是说针对开发、测试、staging、production等环境使用相同的部署脚本,这样脚本的质量就会经过反复验证。对于不同环境的区别,可以放置到配置文件中,使用之前讲过的配置管理方法来实现。
  3. 使用操作系统的打包工具。这里的打包工具可以理解为安装包工具,比如Windows Installer。使用这类工具的好处是,可以将分散部署的文件以及注册表项以统一的方式组织起来,再经过安装过程完成部署。这已经是广泛使用的实践了。
  4. 保证部署过程是幂等的。所谓幂等性,就是保证多次对资源的操作与一次操作的效果是相同的。多次部署与一次部署的效果要一致,始终保持目标环境的正确状态。有一个方法是,每次部署都基于一个基线(baseline),这个基线保证环境中的配置和依赖都已经准备后,比如可以对虚拟机建好模板或者使用容器镜像。这样部署出来的环境就容易保持状态的一致。如果你的项目是组件化的,且每个组件可以独立部署,那么每次部署就不用从头来过,而是直接部署有改变的组件即可。当然,这种部署需要进行充分的测试,尤其是组件之间的交互。
  5. 增量地演化部署系统。罗马不是一天建成的,我们不要指望开始就写出完善的构建和部署脚本。我们可以先针对开发者的环境编写脚本,运行良好之后,再针对测试过程部署环境。最后,再逐渐改良适配生产环境。

后面我们再看看部署脚本的其他实践。

构建和部署脚本(二)

我们接着来看一些构建和部署脚本的实践和技巧。

  1. 分层部署和测试。这一点是说我们在部署应用系统或服务之前,要确认部署所需的每一层环境都是满足要求的,比如硬件、操作系统、系统中间件以及各种配置,如下图。目前流行的云计算厂商都会提供基础架构以及平台服务,硬件和系统之类的条件可以轻易满足和配置。

《持续交付》(三)部署流水线与构建脚本_第5张图片

  1. 测试环境的配置。我们在真正部署软件系统前,需要对部署的环境做一个冒烟测试,看看一些关键资源是否满足条件,如图二。比如我们利用Azure来部署微服务前,需要部署微服务集群Service Fabric Cluster。集群部署之后首先要测试集群中节点的连通性,确认没有问题才开始具体微服务的部署。
  2. 永远使用相对路径。绝对路径的缺点是让你的构建过程与具体的机器紧耦合,这些部署脚本可能会不可重用,因此我们要尽量使用相对路径。在开发软件安装包时,可以采用一些系统默认的或者常用的路径,而不是指定自定义的绝对路径。
  3. 追踪二进制文件的版本。这一点的目的是部署出问题时,可以尽快发现二进制文件的版本和对应的代码提交。比如在.NET项目中,可以在二进制文件(assembly)的元数据中,并将该配置(package.config)放入版本控制系统。
  4. 测试目标不要让构建失败。我们通常的做法是,如果发现某个测试case失败,就让整个部署流水线失败。这样有一个缺点是,我们无法知道后续的测试是否通过,直到前面的问题都被解决,比较浪费时间。正确的做法是让流水线继续执行,然后在最后总结失败的所有环节。
  5. 限制你的脚本执行。这里的限制是指,脚本应该能判断出当前部署的环境是否与自己的目标一致。比如一个部署生产环境的脚本,可以检测当前部署的环境是否确实是生产环境,比如可以确认硬件和操作系统、数据库版本等信息,如果不对则停止执行。

关于持续集成相关的环节我们先看到这里,下次我们来看软件发布的一些流程。

你可能感兴趣的:(CI/CD)