.gitlab-ci.yml是 GitLab CI/CD 的核心配置文件,它告诉 GitLab Runner 具体要执行什么操作。
.gitlab-ci.yml 简介
.gitlab-ci.yml 是一个使用 YAML 语法编写的文件,通常存放在项目的根目录下。
- 作用:定义自动化的流水线(Pipeline)。
- 触发机制:当你推送代码(push)、创建合并请求(Merge Request)或打标签(Tag)时,GitLab 会自动检测该文件并触发流水线。
- 结构:由一系列的 Jobs(作业)组成,这些 Job 被分配到不同的 Stages(阶段)中按顺序或并发执行。
核心关键字速查表
下表总结了最常用和关键的配置项:
| 关键字 | 说明 | 典型用途 |
|---|---|---|
stages |
定义流水线的阶段顺序 | [build, test, deploy],决定了 Job 的执行大顺序。 |
stage |
定义当前 Job 属于哪个阶段 | 将 Job 归类,如 stage: build。 |
script |
(必填) Job 真正执行的 Shell 命令 | npm install、docker build、pytest 等。 |
image |
指定执行环境的 Docker 镜像 | node:16-alpine、python:3.9。 |
default |
设置全局默认值 | 如果大部分 Job 都用同一个 image 或 tag,可在此统一定义。 |
variables |
定义环境变量 | 数据库连接串、版本号等(避免硬编码)。 |
rules |
现代化的条件判断逻辑 (替代 only/except) |
决定 Job 何时运行(如:只在 Tag 时、只在 MR 时)。 |
extends |
继承模版(复用配置) | 减少重复代码,继承隐藏作业 (.hidden-job) 的配置。 |
artifacts |
定义产物(Job 跑完后保留的文件) | 保存编译后的二进制文件、测试报告,供后续 Stage 下载。 |
dependencies |
限制下载哪些 Job 的产物 | 默认后续 Stage 会下载前面所有产物,用此可指定只下载特定的。 |
needs |
定义依赖关系(DAG 模式) | 高级:允许跨 Stage 并行,不按严格顺序,只等待特定的 Job 完成。 |
allow_failure |
是否允许失败 | 设为 true 时,即使该 Job 报错,流水线也会继续(常用于非阻塞的测试)。 |
before_script |
脚本执行前的准备命令 | 安装依赖、打印环境信息等。 |
cache |
定义缓存 | 缓存 node_modules 等,加速下一次构建。 |
environment |
定义部署环境 | 用于 GitLab UI 记录部署历史(如 Production, Staging)。 |
tags |
指定由哪个 Runner 执行 | 对应 GitLab Runner 配置的 tag(如 gpu, linux)。 |
环境变量解释
- CI_JOB_TOKEN 权限主要是查看API相关的
GIT_STRATEGY
- 环境变量 GIT_STRATEGY 用于控制 GitLab Runner 如何获取(拉取)源代码,它决定了在作业(Job)开始执行脚本之前,Runner 是应该重用之前的代码、重新克隆整个仓库,还是根本不需要代码。
| 策略值 | 行为描述 | 速度 | 纯净度 | 推荐场景 |
|---|---|---|---|---|
| fetch (默认) | 复用本地目录,拉取更新 | 快 🚀 | 低 (可能有残留) | 日常开发、测试、CI 流程 |
| clone | 删除目录,重新克隆 | 慢 🐢 | 高 (完全纯净) | 正式发布、排查环境问题 |
| none | 不拉取代码 | 最快 ⚡️ | - | 仅依赖 Artifacts 的部署、通知任务 |
GIT_DEPTH
当使用 fetch 或 clone 时,为了进一步加快速度,通常会配合 GIT_DEPTH 变量使用(浅克隆):
variables:
GIT_STRATEGY: fetch
GIT_DEPTH: "1" # 只下载最近的一次 commit,不下载历史记录,速度极快
## 深度解析:复杂流程完整实例
**场景假设**:
我们要构建一个 Node.js 应用。
1. **全局配置**:默认使用 Node 镜像,设置重试机制。
2. **Lint 阶段**:代码风格检查,允许失败(不阻塞流程)。
3. **Build 阶段**:编译代码,生成 `dist/` 目录作为产物。
4. **Test 阶段**:
- 单元测试:需要 `dist/` 文件。
- 安全扫描:使用特定 Docker 镜像,耗时较长。
5. **Deploy 阶段**:
- **Staging**:当合并到 `main` 分支时自动部署。
- **Production**:只有打 `v*` 格式的 Tag 时才部署,且需要人工点击确认。
### 完整的 `.gitlab-ci.yml` 内容
```yaml
# 1. 定义整个流水线的阶段顺序
stages:
- check # 代码检查
- build # 构建
- test # 测试
- deploy # 部署
# 2. 全局默认配置 (Default)
# 所有 Job 如果没有单独指定,都会继承这里的配置
default:
image: node:16-alpine # 默认使用 Node 16 镜像
retry: 1 # 如果 Job 失败,自动重试 1 次
tags: # 指定默认由带 'docker' 标签的 Runner 执行
- docker
# 全局变量
variables:
NPM_CONFIG_CACHE: '$CI_PROJECT_DIR/.npm'
# 3. 缓存配置:在 Job 之间共享 node_modules,加快速度
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
# 4. 模版作业 (Hidden Job / Template)
# 以 '.' 开头,不会直接运行,专门用于被 extends 继承
.deploy_template:
stage: deploy
script:
- echo "Deploying to $DEPLOY_ENV..."
- echo "Simulate deployment script here"
# 假设这里有真实部署命令,如 scp 或 kubectl
dependencies:
- build_app # 明确只下载 build_app 的产物
# =======================
# Stage: CHECK
# =======================
lint_code:
stage: check
script:
- npm ci
- npm run lint
# 关键字: allow_failure
# 即使代码风格检查失败,流水线也会显示橙色警告,但继续执行后续 Build
allow_failure: true
# =======================
# Stage: BUILD
# =======================
build_app:
stage: build
script:
- npm ci
- npm run build
# 关键字: artifacts
# 定义产物,build 完成后,GitLab 会把 dist 目录压缩保存
artifacts:
name: 'dist-$CI_COMMIT_REF_NAME'
expire_in: 1 week # 产物保留1周
paths:
- dist/ # 指定要保留的文件夹
# =======================
# Stage: TEST
# =======================
unit_test:
stage: test
script:
- npm run test
# 关键字: dependencies
# 显式声明依赖 build_app,GitLab 会自动把 build_app 的 artifacts 下载并解压到当前目录
dependencies:
- build_app
security_scan:
stage: test
# 关键字: image (覆盖全局 default)
# 这个 Job 需要特殊的安全工具镜像,不使用默认的 Node 镜像
image: aquasec/trivy:latest
script:
- trivy filesystem .
# 关键字: rules (逻辑控制)
# 仅在 main 分支 或 Tag 时运行,Merge Request 中不运行以节省资源
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG
# =======================
# Stage: DEPLOY
# =======================
# 部署到测试环境
deploy_staging:
# 关键字: extends
# 继承上面定义的 .deploy_template,复用 script 和 dependencies
extends: .deploy_template
variables:
DEPLOY_ENV: 'Staging'
environment:
name: staging
url: https://staging.example.com
# 规则:只有当推送到 main 分支时执行
rules:
- if: $CI_COMMIT_BRANCH == "main"
# 部署到生产环境
deploy_production:
extends: .deploy_template
variables:
DEPLOY_ENV: 'Production'
environment:
name: production
url: https://example.com
# 规则:只有打 Tag (例如 v1.0.0) 时才执行
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual # 关键:手动触发。流水线会暂停,等待人工点击`播放`按钮
# 生产环境通常不允许失败
allow_failure: false详细代码解读
让我们把上面例子中体现的关键技术点拆解一下:
stages (流程编排)
GitLab CI 默认有 .pre, build, test, deploy, .post 这几个阶段
stages:
- check
- build
- test
- deployGitLab 会按照这个顺序执行。如果 check 阶段的所有 Job 没成功(除非 allow_failure: true),就不会进入 build。同一个 Stage 下的 Job(如 unit_test 和 security_scan)是并行运行的。
extends (继承/复用)
.deploy_template:
script: ...
deploy_staging:
extends: .deploy_template这是保持 YAML 简洁的最佳实践。.deploy_template 就像父类,定义了通用的部署脚本。deploy_staging 和 deploy_production 继承它,只需要修改变量 (variables) 和规则 (rules)。
artifacts 与 dependencies (文件传递)
- Build 阶段:
artifacts告诉 GitLab:“我生成了dist/目录,请打包上传到服务器”。 - Test/Deploy 阶段:默认情况下,后续阶段会自动下载所有前序阶段的 artifacts。
- Dependencies:在
deploy_staging中,如果我们只想下载build_app的产物(不想下载unit_test可能产生的测试报告),使用dependencies: ["build_app"]可以显式指定,既安全又加快下载速度。
rules (强大的逻辑控制)
这是现代 GitLab CI 最核心的部分,取代了老旧的 only/except。
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual这段逻辑表示:
- IF: 当前提交是一个 Tag,且 Tag 名字匹配版本号正则(如
v1.0.0)。 - THEN: 将 Job 状态设为
manual(手动)。在 UI 界面上,这个 Job 会显示一个播放按钮,只有点击后才会真正部署。这对生产环境至关重要。
allow_failure (容错)
在 lint_code 中设置了 true。这意味着,如果 ESLint 发现代码格式有问题报错了,Pipeline 会显示一个黄色的感叹号(Warning),但不会通过红色的 X(Error)来停止流水线,后续的 build 依然会继续。
default 与 image
- 我们在顶部
default里定义了node:16-alpine。大部分 Job 不需要写 image,直接用这个。 - 但是在
security_scan中,我们需要运行安全扫描工具trivy,所以用image: aquasec/trivy:latest覆盖了默认值。
这个例子涵盖了你在实际工作中 90% 会用到的场景,从基础构建到复杂的生产环境门禁控制。
模板
在 GitLab CI/CD(包括 CI/CD Catalog)中,无论是一个 Job(任务)名称还是一个 文件名称,只要是以 .(英文句号/点号)开头,它代表的核心含义就是:隐藏的(Hidden) 或 抽象的基础模板(Base Template)。
以 . 开头的 Job(隐藏任务/抽象基类)
概念:
任何以 . 开头的 Job 都不会被 GitLab Runner 执行。它相当于面向对象编程中的“抽象类”,专门用来被其他 Job 继承(extends) 或 引用(!reference)。
在 Catalog 中的作用:消除重复代码 (DRY) 组件的作者通常会把公共的配置(比如通用的镜像、缓存设置、前置脚本)写在一个隐藏的 Job 中,然后让真正要执行的 Job 去继承它。
示例解析:
假设你在 Catalog 看到这样一个组件源码(templates/build.yml):
spec:
inputs:
image_version:
default: 'latest'
---
# 这是一个【隐藏 Job】,它以 . 开头。GitLab 不会单独运行它。
.go_base_setup:
image: golang:$[[ inputs.image_version ]]
before_script:
- echo "Setting up Go environment..."
- go mod download
# 这是一个【真正的 Job】。用户引入组件后,实际运行的是这个。
build_api:
extends: .go_base_setup # 继承上面隐藏 Job 的所有配置
stage: build
script:
- go build -o api_server ./cmd/api
# 这是另一个【真正的 Job】,它复用了相同的环境配置
build_worker:
extends: .go_base_setup # 再次继承
stage: build
script:
- go build -o worker ./cmd/worker好处:当用户 include 这个组件时,他们最终的流水线里只会多出 build_api 和 build_worker 这两个任务,而 .go_base_setup 默默在后台提供了基础配置,保持了代码的整洁。
以 . 开头的 YAML 文件(隐藏文件/私有模块)
在 CI/CD Catalog 的目录结构中,你可能也会看到以 . 开头的文件,比如 templates/.helper.yml。
概念:
GitLab CI/CD Catalog 在解析你的项目并生成可供他人搜索的“组件列表”时,会自动忽略所有以 . 开头的文件和目录。
在 Catalog 中的作用:私有内部逻辑 有时候一个 Catalog 组件的逻辑非常复杂,写在一个文件里太长了。组件作者会把逻辑拆分到多个文件里,但又不希望这些拆分出来的子文件被当成独立的组件暴露给最终用户。
示例解析:
my-catalog-project/
├── templates/
│ ├── .internal-scripts.yml # 以 . 开头,对外隐藏(私有辅助文件)
│ └── deploy.yml # 正常的组件,对外暴露(公共组件)在 deploy.yml 内部,作者可以通过本地引入的方式使用这个隐藏文件:
# deploy.yml
spec:
inputs:
env:
default: 'prod'
---
# 引入同目录下的隐藏文件
include:
- local: '/templates/.internal-scripts.yml'
deploy_job:
stage: deploy
script:
- !reference [.deploy_scripts, run] # 调用隐藏文件里的逻辑对外部用户而言,他们只能在 Catalog 页面上看到并使用 deploy 组件,完全感知不到内部 .internal-scripts.yml 的存在,这实现了良好的封装性。
针对 main 分支的 Push(推送)事件
# 方法一:只有在代码推送到 main 分支(包含 MR 合并和直接 push)时才触发整个流水线
workflow:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# 方法二:Job 级别生效,只有合并到 main 或直接 push 到 main 时才会触发的构建和部署
build_job:
stage: build
script:
- echo "正在构建 main 分支专属代码..."
rules:
- if: '$CI_COMMIT_BRANCH == "main"'pipefail 机制
.gitlab-ci.yml script:GitLab Runner 默认会开启类似于 set -eo pipefail 的机制。在 script 数组中,只要有任何一行命令失败(Exit Code 非 0),整个 Job 就会立即中止并标记为 Failed。如果需要允许某条命令失败而不中断 Job,通常需要特殊处理(例如 command || true 或者配置 allow_failure)。
gitlab ci 执行下面的脚步不生效:
if tar -tf "xx.tar.gz" | grep -q "abc"; then
fi️ 根本原因分析
-
GitLab CI 的默认行为 (
set -eo pipefail) GitLab CI 在执行你的脚本前,默认会自动注入set -e和set -o pipefail。set -o pipefail的作用是:只要管道符|连接的命令中有一个执行失败(退出码非 0),整个管道的退出码就是非 0。
-
grep -q的“提前退出”机制-q(quiet) 参数的作用不仅是不输出内容,更重要的是:一旦找到第一个匹配项,它就会立即退出并返回 0 (Success),不需要继续往下读。 -
tar遭遇SIGPIPE报错 (141) 当tar -tf正在源源不断地输出包含成百上千个文件名的列表时:grep -q如果在列表的前面(比如第 10 行)就匹配到了share/docs,它会立刻以状态码0退出。- 此时管道(pipe)被关闭了,但
tar命令还在尝试往管道里写数据。 - 操作系统发现没人接盘了,就会向
tar命令发送一个SIGPIPE信号,强制终止tar。 - 因此,
tar会异常退出,退出码通常为141(128 + SIGPIPE)。
-
if条件判定为假 (False) 虽然grep成功找到了字符串(退出码0),但因为tar被强制杀死了(退出码141),在set -o pipefail的规则下,整个管道tar ... | grep ...的最终退出码变成了141。if语句一看退出码是非 0,就认为条件不成立,直接跳过了then里面的逻辑,进入了下一次循环!
解决方法:
- 不适用
grep -q:if tar -tf "xx.tar.gzl" | grep "abcc" > /dev/null; then ... fi set +o pipefail# 临时关闭 pipefail
其他
Job 失败时执行特定的动作
为了在 Job 失败时执行特定的动作,可以利用 GitLab Runner 提供的内置环境变量 $CI_JOB_STATUS,在 after_script 中编写条件判断语句。
$CI_JOB_STATUS 在 after_script 中会有以下三种常见的取值:
success:表示script成功执行。failed:表示script执行失败。canceled:表示任务被手动取消。
.gitlab-ci.yml:
test_job:
stage: test
script:
- echo "运行测试脚本..."
- exit 1 # 模拟脚本执行失败
after_script:
- echo "当前 Job 的状态是 $CI_JOB_STATUS"
- >
if [ "$CI_JOB_STATUS" = "failed" ]; then
echo "检测到 Job 失败!开始执行失败后的补救/通知动作..."
elif [ "$CI_JOB_STATUS" = "success" ]; then
echo "Job 执行成功,执行成功后的动作..."
else
echo "Job 状态为 $CI_JOB_STATUS"
fi传递动态生成的环境变量
stages:
- build
- deploy
# 上游作业:生成变量
generate-data:
stage: build
script:
- echo "DYNAMIC_VERSION=v1.0.0-$(date +%s)" >> claude.env
- echo "DEPLOY_TARGET=production" >> claude.env
artifacts:
reports:
dotenv: claude.env
# 下游作业:直接使用变量
deploy-job:
stage: deploy
needs: ["generate-data"] # 必须声明依赖上游作业
script:
- echo "正在部署版本:$DYNAMIC_VERSION" # GitLab 自动注入了这个变量
- echo "部署目标环境:$DEPLOY_TARGET"debug 模式
CI Debug 追踪模式环境变量:/CD -> Pipelines -> Run Pipeline -> Variables 新增 CI_DEBUG_TRACE: "true"
my_job:
stage: build
variables:
CI_DEBUG_TRACE: "true" # 开启 Debug 模式
script:
- echo "Hello World"
- ls -al参考
- https://docs.gitlab.com/ci/yaml/
- https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates