前言
CI 持续集成(Continuous Integration) ,是我们的开发工程流里非常重要的一环。
在进行多人协作的日常工作中,为了保证整个团队的代码质量维持一个比较高的水平,我们往往会在 pre-commit 钩子里配置 lint 校验或者在 CI 中执行校验,两者的最大不同就是本地运行的 git hooks 可以被手动跳过(--no-verify 大法),且校验未通过内容只有当前分支的开发者本人可见,并伴随着较长的校验运行等待时间,无法在根本上保障代码质量。
同时我们也希望在合码到 master 之前执行一些单元测试和 E2E 测试,这种时候 CI 就成为了最佳选择。
本文会介绍业界比较流行的 Github Actions 和 Gitlab CI ,带大家对比一下不同 CI 工具使用差别;同时带大家快速上手 CI 工具 ,学习如何自己设计 CI 流程和 Actions 脚本编写。
基础概念
CI 通用组成
Events
决定我们需要设置 CI 流程在何时触发(git merge / push 等)
Pipeline
通过在目标目录下创建 YAML 文件,即可配置一个 Pipeline。
如果我们的项目下拥有多个 YAML 配置文件,即被认为拥有多个 Pipeline。
Pipeline - Jobs - Steps 的三级结构为 CI 配置文件的核心结构。
Job
一个 Pipeline 包含若干个 Job,一般来说每一个 Job 各自会被分发到不同的机器上并行执行,每个 job 都有独立的上下文 (context)。
当一个 Job 执行结束就会丢失上下文,如果不同的 Job 之间需要进行信息传递可以使用两种方案,即Jobs 间的文件可以使用 Artifact( Pipeline 内的产物传递,在获得数据时上传,在需要消费时下载文件) 和 Cahce(Pipelines 间的缓存复用)。
Step
一个 Step 包含
- 若干命令或一个 Action(Github Actions / Codebase CI)
- 若干命令(Gitlab CI)
一个 Job 中的 Step 都在同一个机器中,按配置语法逻辑依次执行,共享同一份机器资源,共享同一个工作目录,环境不一样时(比如使用不同的镜像)并非所有目录均共享。
Runners
我们 CI 逻辑在底层其实就是一个钩子函数触发跑一段固定脚本,不管以何种方式执行 CI,我们的脚本都需要有特定的服务器、虚拟机或容器来提供运行环境。
- 可以使用自带的预置 Runners,也可以手动搭建(Github Actions)
- 可以使用公司内部搭建的共享 Runners,也可以手动搭建(Gitlab CI)
举例
Github Actions
前方指路 🫱 官方文档
下面是一个简单的例子:
name: learn-github-actions #流程名称
on: [push] #触发时间
jobs:
check-bats-version: #定义一个名为 check-bats-version 的 job
runs-on: ubuntu-latest #运行在最新版本的 Ubuntu Linux 运行程序
steps:
- uses: actions/checkout@v3 #执行Actions:把当前代码库 checkout 到 runner 上
- uses: actions/setup-node@v3 #执行Actions:安装 Node.js
with:
node-version: '14' #指定版本为14
- run: npm install -g bats #执行命令:下载 bats
- run: bats -v #输出 bats 版本
具体YAML每条语句执行流程的含义在注释中进行了解释,其中的每条 uses 都代表使用了不同的 Actions 能力,下面我会详细讲解一下 Actions。
Actions
Actions 可以算是 Github Actions 最有特色的功能,Github 所有的 Actions 都可以在这里找到。
Actions 可以被理解为 CI 流程中可复用的 原子化 step 结点。 试想,如果我们想做实现一个代码行数检查的 CI 节点,整个脚本设计也许很顺利,但是这种基础的功能可能有很多场景需要,其他业务线也有类似的能力想要实现,没有 Actions 的话只能做机械的代码拷贝工作。
在使用 Github 的 CI 能力后,我们就可以把想要封装的能力设计为一个 Actions,发布后其他人也可以使用同一个 Actions 而避免重复开发。
下面是一个 Actions 的 action.yml
配置文件,其实就是这个 Actions 的说明书,它规定了我们自定义 Actions 的两个入参(file-path & current-time)和输出结果(results-file)以及运行环境(node16)等重要内容:
name: "actions/example" # Actions 的名称,一般以 actions/ 开头
description: "Receives file and generates output" # Actions 的描述
inputs:
file-path: # 需要输入的内容
description: "Path to test script" # 输入内容描述
required: true # 为必须输入
default: "test-file.js" #默认输入
current-time: # 需要输入的内容
description: "current-time" # 输入内容描述
required: false # 不是必须输入
outputs:
results-file: # Actions 输出的内容
description: "Path to results file" # 输出内容描述
runs:
using: 'node16' # 运行环境
main: 'dist/index.js' # 入口函数
我们可以把 Actions 抽象理解为一个活跃在 CI 流水线上的云函数,我们设定好固定的输入输出和执行环境后,用户只需要把云函数需要的参数传递过去,在脚本正常执行的情况下,云函数就能生成预期的计算结果。
在编写好 action.yml
后,我们就可以着手具体脚本逻辑的实现了。
下面的示例中我们引入了两个库@actions/core
和 @actions/github
为我们提供 Actions 的核心能力,其中@actions/core
可以帮助我们接收输入信息和输出信息,@actions/github
可以帮我们进行 GIthub 仓库的权限校验和获取 Actions 执行的上下文信息。
const core = require('@actions/core');
const github = require('@actions/github');
try {
// `current-time` input defined in action.yml file
const time = core.getInput('current-time');
console.log(`start time is ${time}!`);
const time = (new Date()).toTimeString();
core.setOutput("Now time", time);
const payload = JSON.stringify(github.context.payload, undefined, 2)
console.log(`The event payload: ${payload}`);
} catch (error) {
core.setFailed(error.message);
}
除了这些,我们的工具库还有其他更加强大和复杂的能力,供开发者在需要时组合使用。
学会了以上内容,我们就可以着手加亿点细节来开发自己的 Actions 了,其实想象空间还是非常大的。并且拥有很好的可复用性,对于开源项目来说非常方便,且不需要搭建自己的 Runner 就可以开箱即用,特别适合在 Github 上的自建小轮子。
其实关于 Github Actions 还有很多有意思的内容,希望深入学习的同学可以去官网文档进一步了解。
Gitlab CI
前方指路 🫱 官方文档
下面也是一个简单 E2E 测试的例子 .gitlab-ci.yml
:
image: node:8.10 #镜像为8.10版本的 Node.js
stages: # job 分为两个阶段,两个阶段下属的 steps 会同步执行,两个阶段会按分别顺序进行,需要体现 steps 前后依赖关系时需要设置 stages
- deploy #deploy_terraform:
- confidence-check #e2e:firefox & e2e:chrome
deploy_terraform:
stage: deploy #所属 stage
script:
# Your Review App deployment scripts - for a working example please check https://gitlab.com/Flockademic/Flockademic/blob/5a45f1c2412e93810fab50e2dab8949e2d0633c7/.gitlab-ci.yml#L315
- echo
environment: production
e2e:firefox:
stage: confidence-check #所属 stage
services:
- selenium/standalone-firefox #当前 step 所需镜像
script:
- npm run confidence-check --host=selenium__standalone-firefox
e2e:chrome:
stage: confidence-check #所属 stage
services:
- selenium/standalone-chrome #当前 step 所需镜像
script:
- npm run confidence-check --host=selenium__standalone-chrome
从配置语言来看其实和 Github Actions 也是比较相似的,最明显的就是.gitlab-ci.yml
在语法定义上有点差别,但是镜像、名称、Pipeline - Jobs - Steps 的三级结构还是比较一致的。
Script
需要被运行的脚本,是以数组形式进行配置。我们还可以使用 before_script,它可以配置流水线任务中的 script 执行之前需要执行的脚本。
我们所有需要运行的逻辑都需要在这里执行,所以如果使用 Gitlab CI,我们的一些 CI 脚本逻辑需要写在项目内部。如果想提取出一些可复用的逻辑,就必须发一个独立 Package 来给其他项目的 CI 流程使用,或者创建 Docker 镜像来执行。个人感觉在灵活度上不如 Github Actions,对于使用 Node.js 编写脚本的前端研发不是特别友好。
pipeline_job:
......
script:
- cd <文件夹目录路径>
- npm install
- npm build
......
Template
为了降低用户的使用门槛,对于一些不同语言的项目的 CI 流程,我们可以使用不同的模板来配置。用户不需要自己编写主流程结构和镜像设置等,只需要考虑自定义的输入参数和 Jobs、Steps 的具体逻辑内容。
差异和迁移
如何在 gitlab ci 中使用 github actions?
总结
- Github 开源项目使用 Github Actions 是非常方便的,也不需要再去安装其他的 CI App,Actions 用起来体验很好。
- 本文没有介绍其他的业界优秀 CI 工具如 Travis CI,Circle CI,但其实原理和流程基本是相通的,如果对这些感兴趣的同学也可以自行深入了解。