持续集成与发布 CI/CD
文档用途
这篇文档用于介绍 GitLab CI/CD, 并记录实践中的细节.
详见官方文档 GitLab CI/CD.
写在前面
什么是 CI/CD
CI 的全称是 Continuous Integration (持续集成).
CD 包括两个概念: Continuous Delivery (持续交付) 和 Continuous Deployment (持续部署).
所以 CI/CD 从字面上讲就是 "持续集成, 交付与部署".
为何采用 CI/CD
- 能早早地发现问题, 有效避免新写的代码建立在 bug 之上.
通过自动检测每个 Merge Request 的测试用例能否全部通过, 能否成功构建. - 避免在重复且繁琐的 构建, 测试, 部署 等操作中浪费时间.
因为一切自动完成, 工作量仅来源于对 CI/CD 的配置与维护. - 可以不选择在开发所用的设备上构建, 从而能够避免打断开发工作.
- 确保构建环境的稳定. 避免 "代码相同构建结果却不同".
GitLab Runner
INFO
执行 GitLab CI/CD Pipeline 的设备通常和 GitLab Server 所运行的设备不是同一个.
为此需要有个中介, 使得 GitLab Server 和 CI/CD 的执行设备能交流, 这个中介就是 GitLab Runner.
参考官方文档 Install Gitlab Runner.
安装与更新
在不同的操作系统中安装或更新 gitlab-runner:
scoop install gitlab-runner # 安装
scoop update gitlab-runner # 更新
gitlab-runner restart # 更新后重启服务 (需要管理员身份)brew install gitlab-runner # 安装
brew upgrade gitlab-runner # 更新
brew services restart gitlab-runner # 更新后重启服务# 为 apt 命令添加第三方软件源 (由 GitLab 官方维护)
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install -y gitlab-runner # 安装
sudo apt upgrade -y gitlab-runner # 更新
sudo gitlab-runner restart # 更新后重启服务TIP
gitlab-runner 的版本最好和 gitlab 版本对应.
考虑到 gitlab 可能不会经常更新, 因此也应当避免 gitlab-runner 被意外更新.
为此可以利用软件包管理器的 pin 特性, 例如 HomeBrew 禁用软件包的更新.
注册与启动
我们需要为 gitlab-runner 选择一个恰当的路径 (例如 ~/gitlab-runner).
其内部 config.toml 文件用于配置, 之后运行 CI/CD Job 产生的文件也会缓存在这里.
mkdir ~/gitlab-runner
cd ~/gitlab-runner
# 注册新的 gitlab-runner, 需要提供重要信息 (包括 url, token 等).
# 会在当前路径生成 config.toml 配置文件, 之后可以进一步修改.
gitlab-runner register如何获取 token
可以为单个代码仓库注册私有的 gitlab-runner 实例, 也可以注册共享的实例.
这里选择注册共享实例: 在 Admin Area 中找到 CI/CD > Runners,
然后点击右上角的的 New instance runner 按钮:

选择恰当的配置, 建议勾选下图中的 Run untagged jobs:

在此页面中, 将会显示 token (记得保存, 离开此页面就再也看不到了).

需要输入信息示例
# Runtime platform arch=amd64 os=windows pid=23624 revision=f5da3c5a version=16.6.1
# Enter the GitLab instance URL (for example, https://gitlab.com/):
# [在这里输入自己搭建的 GitLab 的 URL]
# Enter the registration token:
# [在这里输入 token]
# Verifying runner... is valid runner=b4rVd7NMm
# Enter a name for the runner. This is stored only in the local config.toml file:
# [xxx]: [给这个 runner 取个名字, 可以直接回车使用默认名称]
# Enter an executor: instance, kubernetes, docker, shell, ssh, virtualbox, docker+machine, custom, docker-windows, parallels, docker-autoscaler:
# [在什么环境下执行 CI/CD 管线, 建议选择 docker]
# Enter the default Docker image (for example, ruby:2.7):
# [默认的 docker 镜像名称, 例如 debian:12-slim]
# Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
# Configuration (with the authentication token) was saved in "C:\\Users\\yusong\\gitlab-runner\\config.toml"完成以上步骤, 就在 GitLab 上注册了 runner, 并且在本地创建了配置文件 config.toml.
接下来, 我们需要在本地运行 runner. 为此, 我们需要先在操作系统中创建守护进程, 并启动进程.
# 需要使用管理员权限才能执行以下命令.
# 也就是说, 需要以管理员权限运行 Terminal, 才能执行以下命令.
# 根据 config.toml 中的配置创建守护进程
gitlab-runner install
# 启动守护进程
gitlab-runner start
# 查看状态
gitlab-runner status# 建议使用 brew 来管理 github-runner 的守护进程
brew services start gitlab-runner
# 查看包括 gitlab-runner 在内的所有由 HomeBrew 托管的守护进程状态
brew servicesTIP
可以通过多次执行 gitlab-runner register 命令来创建多个 runner 实例.
这么做的动机可能是: 需要多种类型的 executor (例如 docker 和 shell).
也可能是: 打算为每个代码仓库注册私有的 runner, 而不选择采用共享的 runner.
config.toml
INFO
config.toml 是 GitLab Runner 的配置文件, 在首次 gitlab-runner register 时自动生成.
自动生成的配置基本可以满足需求, 不过笔者建议为 config.toml 添加以下设置:
# 并发执行的 CI/CD Job 最大数量 (默认为 1).
concurrent = 8
# ...
[[runners]]
# FF_USE_FASTZIP 会让压缩与解压缩更快, 代价是占用更多硬盘空间.
environment = ["FF_USE_FASTZIP=1"]
# ...另外根据不同的 Executor 类型, 可以做这些配置:
# ...
[[runners]]
# 执行 CI/CD Job 时, 克隆代码仓库的根路径 (需要提供绝对路径).
builds_dir = "xxx/gitlab-runner/builds"
# 缓存文件路径, 用于加速 CI/CD Job 的执行 (需要提供绝对路径).
cache_dir = "xxx/gitlab-runner/cache"
# ...# ...
[runners.docker]
# 允许所有在这列出的 pull policy (默认只允许 "always").
allowed_pull_policies = ["always", "if-not-present"]
# ...TIP
修改 config.toml 后, 无需重启服务, 配置会自动生效.
因为 runner 的守护进程会每 3 秒检测配置文件是否有改动.
网页配置
在 GitLab Runner 的网页配置中, 最重要的是 "标签".
在 CI/CD Job 执行时, 会根据 Job 所需的标签与 runner 的标签来做筛选.
只有 "满足 Job 所需的所有标签" 的 runner 才会被选中.
如下图所示, 修改 gitlab-runner 实例的标签.
标签设置思路: 操作系统, Executor 类型, 设备的名称.

Executor 类型
Shell
INFO
GitLab 所支持的 shell 类型主要包括 bash, sh, pwsh. 详见官方文档 Supported Shells.
多数情况下 不建议 直接在操作系统的 shell 中执行 CI/CD,
但当工具链无法运行在容器环境中时, 那就没得选了 (例如构建 iOS App).
TIP
如果在 Windows 系统中部署 gitlab-runner, 但又不想使用 PowerShell (为了兼容性).
此时可以考虑使用 Git Bash (安装 Git 就自动安装好了). 记得修改 config.toml 中的设置:
# config.toml
[[runners]]
name = "shell executor"
executor = "shell"
shell = "pwsh"
shell = "bash"
# ...Docker
建议优先考虑使用 Docker 作为 CI/CD 的运行环境.
因为考虑到:
- 构建与测试环境有更好的隔离性和一致性.
- 如果自动的流程出问题, 方便在本地测试.
- 默认会利用 docker 的缓存机制加快构建.
# config.toml
[[runners]]
name = "docker executor"
executor = "docker"
# ...TIP
同一个 pipeline 中, 可以通过 tags 来 筛选 runner, 为任意 job 灵活地选取执行环境.
例如让部分 job 在 docker executor 中执行, 部分在 shell executor 中执行.
WARNING
系统自带的杀毒软件 (例如 Windows Security) 可能会影响性能.
解决方案请参考 运维笔记 - 避免杀毒软件影响性能 部分.
.gitlab-ci.yml
INFO
GitLab CI/CD Pipeline 由位于代码仓库根目录的 .gitlab-ci.yml 来描述.
这里只简单介绍, 更详细的内容写在 .gitlab-ci.yml 文档中.
重要概念
| 术语 | 含义 |
|---|---|
Pipeline | 整个 CI/CD 的流程, 由一系列 Job 组成 |
Job | 执行具体任务的单元, 组成 Pipeline |
Stage | 每个 Job 都处于某个 Stage |
Artifact | 会被自动上传 GitLab Server, 常用于保存编译结果 |
Cache | 缓存在本地 (也可以选择上传), 常用于加速编译 |
属于同一个 Stage 的多个 Job 可以并行执行.
最大并行数量由 config.toml 中的 concurrent 字段决定.
如果某个 Stage 的所有的 Job 都成功执行, 那么 Pipeline 就会进入到下一个 Stage.
如果任意一个 Job 执行失败, 那么 Pipeline 则 不会 进入下一个 Stage, 而是就此结束.
例外情况是: 如果 Job 通过 needs 字段指定了依赖关系, 那么执行顺序将会忽略 stage.
简单示例
为了配置 CI/CD 过程中具体要做什么, 我们需要在代码仓库的根目录添加一个文件.
名称为 .gitlab-ci.yml, 需要确保文件名称完全正确, 并在其中添加以下内容:
# 需要配合 executor 为 shell 的 runner 实例.
# shell 类型的 executor 将在操作系统中执行 script.
build-job:
stage: build
script:
- echo "Hello, $GITLAB_USER_LOGIN!"
test-job:
stage: test
script:
- echo "0 errors, 0 warnings."
deploy-prod:
stage: deploy
script:
- echo "This job deploys something from the $CI_COMMIT_BRANCH branch."# 需要配合 executor 为 docker 的 runner 实例.
# docker 类型的 executor 将在容器中执行 script.
build-job:
stage: build
image: alpine
script:
- echo "Hello, $GITLAB_USER_LOGIN!"
test-job:
stage: test
image: alpine
script:
- echo "0 errors, 0 warnings."
deploy-prod:
stage: deploy
image: alpine
script:
- echo "This job deploys something from the $CI_COMMIT_BRANCH branch."对文件内容的说明:
- 整个
.gitlab-ci.yml文件描述了整个 CI/CD 的流程, 也就是Pipeline. Job就是具体的任务, 以上共有 3 个任务, 分别是 build-job, test-job, deploy-prod.stage是指阶段. 前一个阶段所有的任务通过后, 才会开始执行下一个阶段的任务.- 在
test-job中, 我们调用了 Unity 的命令行工具, 使其打印出 Unity 的版本. image用于指定执行 job 的容器镜像.script是需要具体执行的命令.
在 shell 类型的 executor 中直接在操作系统内执行;
在 docker 类型的 executor 中, 在容器中执行.
TIP
有时候 script 中需要执行的命令很多, 此时可以考虑:
把命令写在 shell 脚本中, 然后通过 sh ./my-scirpt.sh 来执行.
编辑技巧
由于在本地编辑时没有语法提示, 因此很有可能出错. 为此可以这么做:
- 在代码编辑器中编写时, 使用
yaml语法检测插件 (例如在 VSCode 中添加 YAML). - 选择直接在 GitLab 网页中编辑该文件 (并 commit). 因为在网页里编辑时有语法提示.
- 打开 GitLab 具体工程的页面, 左侧点击 CI/CD > Pipelines, 然后在页面的右上角点击
CI Lint按钮, 可以打开一个专门用于检测.gitlab-ci.yml语法的页面.
触发方式
通常 CI/CD 管线会在 git push 时触发, 也会在 merge request 有新的 commit 时触发.
若希望手动触发, 则可以在代码仓库的 Build / Pipelines 页面的右上角点击 Run Pipeline.

另外 Build / Pipeline Schedules 页面可以用于周期性触发 CI/CD 管线.
Schedule 在创建好之后, 也可以点击按钮手动执行, 可以立即检测是否存在配置问题.

TIP
如果想构建基础容器镜像 (添加了一些调试工具), 作为其他镜像的基础, 则适合周期性构建.
另外对于不频繁更新的项目, 周期性触发的管线可用于确保工具链的健康 (避免 "落灰").
环境变量
预定义
GitLab 为我们提供了预定义的环境变量, 这里列出一些常用的.
更多详见官方文档 Predefined CI/CD variables reference.
| 环境变量 | 举例 |
|---|---|
| CI_COMMIT_SHA | "1f1686552bc15d60f37530adc4c65ff2e55689a5" |
| CI_COMMIT_SHORT_SHA | "1f168655" |
| CI_COMMIT_MESSAGE | "Awesome commit message" |
| CI_COMMIT_TIMESTAMP | "2023-12-15T22:27:53+08:00" |
| CI_COMMIT_AUTHOR | "yusong <xxx@gmail.com>" |
TIP
直接使用 export 命令, 可以打印当前 shell 会话中所有的环境变量.
因此可以借助 export 命令, 在 CI/CD Job 中列出所有环境变量和它们的值.
自定义
可以借助预先配置好的 GitLab CI/CD Variables, 并在 CI/CD Job 中作为环境变量使用.Variables 可以在单个代码仓库中配置, 也可以通过 Admin 共享配置.
共享配置的菜单路径为: Admin Area / Settings / CI/CD / Variables.

敏感数据
有些时候我们需要在 CI/CD 中使用敏感数据 (例如密码), 此时肯定不能明文记录在代码仓库里.
但即使自定义 Variable 也可能会泄露 (例如 CI/CD Job 的日志), 此时应该这么做:
在编辑 Variable 时, 勾选 Mask variable 选项.

FAQ
macOS 路径权限
macOS 中的程序访问敏感路径时需要用户手动授权.
当 gitlab-runner 尝试访问某个敏感路径时, 会触发系统弹窗, 此时应点击 Allow 按钮.
单独授权各个路径会比较麻烦, 可以考虑一次性授权所有路径.
如果 gitlab-runner 更新, 则需要重新授权.

可执行文件路径藏得比较深, 可能不好找, 为此可以使用以下命令:
# 会输出 gitlab-runner 的 symlink 路径
which gitlab-runner
# 复制 symlink 路径, 使用 realpath 找到真正的路径
realpath [symlink-path]WARNING
授权所有路径的访问权限比较危险.
即使 gitlab-runner 值得信任, 也可能因为开发者不恰当编写 .gitlab-ci.yml 而引发可怕的后果.
Docker-in-Docker
参考 Use Docker to build Docker images - GitLab Docs.
gitlab-runner 的配置文件 config.toml 需要做如下修改:
# ...
[runners.docker]
# 必须要将 privileged 设置为 true.
privileged = true
# "/certs/client" 应该被添加进 volumes.
volumes = ["/certs/client", "/cache"]
# ...对应的 .gitlab-ci.yml 文件举例:
variables:
# Specify to Docker where to create the certificates.
# Docker creates them automatically on boot, and creates
# `/certs/client` to share between the service and job
# container, thanks to volume mount from config.toml.
DOCKER_TLS_CERTDIR: "/certs"
build:
stage: build
image: docker
# services 也可以考虑写在全局的 default 字段中.
services:
# dind 标签代表 docker-in-docker.
- docker:dind
before_script:
# 如果向构建多平台镜像, 则需要执行以下两行命令
- docker context create builder
- docker buildx create builder
--use --driver docker-container
--name multiarch
script:
- docker build -t --quiet
my-docker-image .
--builder multiarch
--platform linux/amd64,linux/arm64