.gitlab-ci.yml的官方文档列出了四十多个参数。但真正让你把 CI 配置写好的,不是记住这些参数,而是理解 stage 依赖、产物传递、配置复用和条件触发这四条主线。本文以具体场景串起关键参数,从能用的配置到好维护的配置。
一、核心概念:Pipeline → Stage → Job
GitLab CI 的层次关系:
Pipeline (一次提交触发)
└── Stage: build
│ ├── Job: compile-js ─┐
│ └── Job: compile-css ─┤ 同 stage 内并行
└── Stage: test
├── Job: unit-test ─┐
└── Job: lint ─┘ 等 build 全部成功后才开始
- Job 是最小执行单元,每个 job 至少有一个
script指令 - Stage 是 job 的分组,决定执行顺序
- Pipeline 是一次完整的运行
一个 job 最少这样写就能跑:
say-hello:
script:
- echo "Hello, CI!"
没有指定 stage 的 job 默认属于 test 阶段。没有显式声明 stages 列表时,GitLab 使用内置的三个阶段:build、test、deploy。
一个重要细节:同一个 stage 内的所有 job 是并行执行的,但前提是 runner 有空闲的并发槽位——默认情况下 concurrent = 1,意味着即使配置了并行,单个 runner 也只能一次跑一个 job。真正的并行需要配置 runner 的并发数。
二、Job 之间的依赖:artifacts 与 dependencies
2.1 artifacts:跨 stage 传递文件
构建阶段的产物如何在测试阶段使用?答案是通过 artifacts:
stages:
- build
- test
build:
stage: build
script: npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test:
stage: test
script: npm test
test job 启动时,GitLab 自动把之前所有 job 的 artifacts 下载到工作目录。这就是为什么 test 能用到 build 产出的 dist/ —— 跨 stage 的文件传递不需要手动声明依赖。
2.2 dependencies:按需选择产物
默认行为是所有前一 stage 的 artifacts 都会下载。当 pipeline 有很多 job 时,不需要的 artifacts 会拖慢执行:
deploy-to-staging:
stage: deploy
script: scp -r dist/ user@staging:/app
dependencies:
- build # 只下载 build job 的 artifacts,忽略 test 的
dependencies 同时做了两件事:限定了只下载哪些 job 的产物;如果依赖的 job 没有 artifacts,不会报错,只是什么都不下。
2.3 cache:跨 pipeline 的加速
cache 和 artifacts 容易混淆:
| 概念 | 传递范围 | 用途 | 典型场景 |
|---|---|---|---|
| artifacts | 同 pipeline 内不同 stage | 传递构建结果 | dist/ 给 deploy 用 |
| cache | 跨 pipeline | 加速重复安装 | node_modules 下次不用重装 |
build:
script: npm ci
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
key 决定了缓存如何匹配——用分支名做 key,同分支的下次 pipeline 就能命中缓存。注意 .gitlab-ci.yml 中 yarn 和 npm 的缓存默认路径不同,用错路径缓存就白配了。
三、触发条件:only/except 与 rules
3.1 only/except:限定触发分支
deploy:
script: echo "deploying"
only:
- master
- /^release-.*$/
except:
- schedules
only 和 except 同时出现时是「交集」关系:必须满足 only 且不满足 except 才触发。这是最常见的误解——不是 only 或 except 二选一,而是两者都生效。
特殊关键字:
| 关键字 | 含义 |
|---|---|
branches |
任何分支 push |
tags |
tag push |
merge_requests |
MR 创建/更新 |
schedules |
定时触发 |
web |
手动在 UI 上点跑 |
3.2 changes:只对文件变更敏感
当只有特定文件变更时才需要跑对应的 job——比如 Dockerfile 变了才构建镜像:
docker-build:
script: docker build -t app:latest .
only:
changes:
- Dockerfile
- docker/**/*
changes 判断的是本次 push 相对于上次的变更。需要注意:对于新分支的第一次 push,changes 永远为 true——GitLab 没有参考基线,无法判断哪些文件是新变更的。
3.3 rules:only/except 的替代者
GitLab 12.3 引入了 rules,并在 14.0 开始推广它替代 only/except。rules 的优先级更高、表达能力更强:
deploy:
script: echo "deploying"
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
when: manual
- when: never
三条规则的逻辑:master 分支自动部署 → tag 推标签时手动确认 → 其他情况不触发。rules 中的 when 除了 on_success/always/manual/never,还可以用 delayed 实现延时执行。
四、消除重复:extends 与 include 与锚点
一个项目中的几十个 job 往往共享大量配置。三种复用机制各有用处。
4.1 YAML 锚点:同文件内复用
.docker-cache: &docker-cache
image: docker:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-image:
<<: *docker-cache
script: docker build -t app .
锚点是纯 YAML 特性,GitLab 不感知。优点是不依赖 GitLab 版本;缺点是只能同文件复用,跨项目跨文件无法引用。
4.2 extends:继承模板 job
.template:
image: node:16
before_script:
- npm ci
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
lint:
extends: .template
script: npm run lint
test:
extends: .template
script: npm test
extends 实现的是合并:子 job 的字段会覆盖父模板的同名字段,但 script 是例外——如果父模板和子 job 都定义了 script,只会执行子 job 的 script,不会合并。如果需要同时执行,应该在子 job 的 before_script 中加入额外的命令。
4.3 include:从外部文件引入
当 .gitlab-ci.yml 超过 500 行,就该拆分了:
include:
- local: '/ci/build.yml'
- local: '/ci/test.yml'
- local: '/ci/deploy.yml'
- template: 'Security/SAST.gitlab-ci.yml'
include 支持四种来源:local(同仓库)、remote(HTTP URL)、template(GitLab 官方模板)、project(其他 GitLab 项目)。include 仅仅是文件内容的拼接,被引入文件中的 extends 引用和 !reference 标签都正常生效。
五、variables:变量的优先级与作用域
5.1 变量的定义来源
GitLab CI 中同一变量名可能在 6 个地方被定义,优先级从高到低:
- 手动触发 pipeline 时传入的变量(最高)
- Job 级
variables定义 - Pipeline 级
variables定义 - 项目 Settings → CI/CD → Variables
- Group 级 Variables
- GitLab 预定义变量(如
$CI_COMMIT_REF_NAME)(最低)
一个常见的坑:在项目 Settings 中定义 NODE_ENV=production,在 .gitlab-ci.yml 中写 variables: NODE_ENV: development,两者的预期是谁生效?答案是 yml 中的 pipeline 级定义优先。
5.2 变量在 job 间传递
默认情况,一个 job 中 export 的变量不会传递到下一个 job。需要显式声明:
build:
script:
- export BUILD_VERSION=$(cat version.txt)
artifacts:
reports:
dotenv: build.env
artifacts:reports:dotenv 是 GitLab 13.1 引入的特性——它把指定文件中 KEY=VALUE 格式的变量注入到后续 job 的环境变量中。比在 script 中 echo "VAR=$var" >> build.env 然后让下游 job source build.env 的方式更标准化。
六、Pipeline 优化
6.1 needs:打破 stage 顺序
默认情况下,job 必须等前一 stage 全部成功才能开始。但有些 job 不需要:
stages:
- build
- test
- deploy
compile-js:
stage: build
script: npm run build:js
compile-css:
stage: build
script: npm run build:css
test-js:
stage: test
script: npm run test:js
needs: [compile-js] # 不等 compile-css,编译完 js 立刻开始测试
deploy:
stage: deploy
script: deploy.sh
needs:
- test-js
- job: compile-css
artifacts: true # 需要 compile-css 的 artifacts
needs 把 pipeline 从严格串行的 waterfall 变成了 DAG(有向无环图)。副作用是 needs 声明的依赖关系会覆盖默认的 stage 顺序——如果 deploy 配置了 needs: [compile-js],它会在 compile-js 完成后立即执行,不会等 test-js。
6.2 retry:自动重试
网络抖动导致的临时失败不应该让整个 pipeline 红灯:
deploy:
script: deploy.sh
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
when 指定哪些类型的失败才重试——runner 宕机或超时可以重试,但代码编译错误不应该重试,避免浪费时间。
6.3 并行化
test:
script: jest --shard=$CI_NODE_INDEX --shard-count=$CI_NODE_TOTAL
parallel: 4
parallel: 4 会创建 4 个相同配置的 job,分别注入 $CI_NODE_INDEX(1-4)和 $CI_NODE_TOTAL(4)。测试框架利用这两个变量对测试用例分片。
七、配置的可维护性策略
7.1 什么时候该拆分
一个判断标准:当你需要滚动超过 3 屏才能看完 .gitlab-ci.yml 时,就该用 include 拆分了。典型拆分策略是按功能模块:
.gitlab-ci.yml # 入口,包含 stages 定义和 include
ci/
build.yml # 构建相关 job
test.yml # 测试相关 job
deploy-staging.yml
deploy-prod.yml
security.yml # SAST、依赖扫描等
7.2 模板化的通用 Job
把重复的部分抽成模板 job,让每个具体 job 只写自己差异化的部分:
.node-job:
image: node:16
before_script:
- npm ci --cache .npm --prefer-offline
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- node_modules/
- .npm/
lint:
extends: .node-job
stage: test
script: npm run lint
unit-test:
extends: .node-job
stage: test
script:
- npm test -- --coverage
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
重点:before_script 用 --prefer-offline 避免每次都向 npm registry 请求元数据;.npm/ 缓存让 npm ci 真正从本地拷贝而非网络下载。
7.3 关键性能指标
| 指标 | 如何诊断 | 优化方向 |
|---|---|---|
| 安装依赖耗时高 | chart 中 before_script 占用大 | 配置 cache、锁定 lockfile |
| 安装依赖重复 | 每个 job 都有 npm ci | 抽成模板 + extends |
| 构建产物传递慢 | artifacts 体积大 | 只上传必要的文件,设置合理的 expire_in |
| 同 stage job 未并行 | 配置了但实际串行 | 配置 runner 的 concurrent 参数 |
总结
GitLab CI 的配置能力远超一个「构建-测试-部署」的线性流水线。理解阶段依赖、artifacts 的传递边界、变量优先级和配置复用机制后,.gitlab-ci.yml 可以成为一个可维护的工程文件,而不是一个越滚越大的命令脚本。