0%

自动化脚本的一些原则与实践

在实现持续集成前,有一项很重要的工作就是自动化构建。

自动化构建必须满足一个条件:人和计算机都能通过命令行自动执行应用的构建、测试以及部署过程。而自动化脚本就是将自动化构建脚本化的产物。

本文中,将介绍一些自动化脚本的原则和实践。

自动化脚本应当与业务代码放在同一个版本库中

自动化脚本和业务代码是同样重要的,应当有版本控制。放在同一个版本库中,能够让开发人员和运维人员更好地配合。推荐将所有的自动化脚本都放在auto目录下。

一个pipeline的最终形态并不是在软件开发一开始就能确定的,最开始的时候可能只需要单元测试和代码风格检查,随着软件的开发,会逐步增加build,smoke-test,deploy-to-production等步骤。pipeline的构建以及自动化脚本都是增量式演进的

为pipeline中的每一个阶段中的每一步创建自动化脚本

一个pipeline通常会定义多个阶段,比如test->build->deploy,阶段之间一定是串行的。而一个阶段中会有一步或者多步,每步之间可以是串行的也可以是并行的,比如,在test阶段,我们会执行单元测试、代码风格检查,这两步没有任何逻辑上的依赖,因此它们可以并行运行的。

应该为pipeline中的每一个阶段中的每一步都创建自动化脚本。这样做的好处:

  • 能够保证一个脚本只做一件事情。一个阶段中某一步已经确定了这个脚本的功能边界,因此该脚本只负责这一步的功能。
  • 自动化脚本的命名应当有意义。因为功能边界已经限制住,pipeline中的每一步我们都是明确知道是做什么的,因此而我们能够更好得对其命名。推荐脚本的命名方式是动词+名词的风格,比如脚本的功能是部署到QA环境的,那么脚本命名可以为deploy-to-qa。我们推荐脚本不加任何后缀,采用shebang机制。

在本地PC和CI上运行结果应当一致

在这一条实践中,有一个隐含的条件就是自动化脚本在本地应当是可执行的,即需要在本地PC上执行所编写的自动化脚本。需要保证在本地PC和CI上运行结果应当一致,比如,我们现在有一个单元测试的自动化脚本auto/test,我们在开发过程中使用该脚本运行该脚本可以帮助我们提前发现问题,该脚本在本地运行通过,那么在CI上运行也一定是过的。

但是当本地环境和CI的环境不一致时,脚本可能会执行失败,比如jq在Mac OS上和在Linux上是有区别的。这个时候可以考虑使用容器化的方式保证环境的一致性

使用同样的脚本向所有环境部署

在软件的开发过程中,我们可能会有测试环境、QA环境、类生产环境、生产环境等。这些环境之间的区别是资源配置信息不同。自动化脚本auto/deploy可以将应用部署到不同的环境上,在上面有提到要为pipeline中的每一步提供自动化脚本,而部署到测试环境和部署到生产环境明显是不同的两步,我们可以将测试环境相关的配置信息放入到auto/deploy-to-test脚本中去,将生产环境相关的配置信息放入到auto/deploy-to-production中,这两个脚本会去调auto/deploy去做部署。

确保部署流程是幂等的

无论开始部署时目标环境处于何种状态,部署流程应该总是令目标环境达到同样的状态,并以之为结束点。

同一个commit,无论触发多少次构建,最终的结果都应该是一致的。

自动化脚本应当对具体的CI工具解耦

在软件的部署过程中,我们需要给应用程序指定一个版本号,而CI工具正好提供一个Build number,比如Gitlab的CI_PIPELINE_ID, Buildkite的BUILDKITE_BUILD_NUMBER,因此在脚本中直接使用这些环境变量就能获取一个版本号。但是不推荐这样使用CI提供的环境变量,这样会造成你的自动化脚本与CI工具强耦合,以后切换CI工具的时候会很痛。

使用相对路径

使用绝对路径会让构建流程与某台特定的机器形成强依赖,从而很难被用于配置和维护其他服务器。

消除手工步骤

消除手工或者通过图形用户界面工具来部署软件。部署过程应当是通过自动化脚本自动完成的,而不是根据一个很详细的文档,这文档中包含了每一步做什么,执行什么命令。使用手工部署会增加部署成本,同时增加出错的可能性。

不要把任何生成物放到版本控制库

在一次部署的过程中,可能会生成一个Docker镜像、Java的War包或者AWS Lambda Code的zip包。这些生成物应该放入其对应的文件存储系统,而不是直接放回到版本库中。如果这些生成物丢失了或者需要重新生成,应该通过触发Pipeline的方式去重新生成。