.gitlab-ci.yml 简介

发布时间: 更新时间: 总字数:4918 阅读时间:10m 作者:IP:上海 网址

.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 installdocker buildpytest 等。
image 指定执行环境的 Docker 镜像 node:16-alpinepython: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)。

环境变量解释

GIT_STRATEGY

  • 环境变量 GIT_STRATEGY 用于控制 GitLab Runner 如何获取(拉取)源代码,它决定了在作业(Job)开始执行脚本之前,Runner 是应该重用之前的代码、重新克隆整个仓库,还是根本不需要代码。
策略值 行为描述 速度 纯净度 推荐场景
fetch (默认) 复用本地目录,拉取更新 快 🚀 低 (可能有残留) 日常开发、测试、CI 流程
clone 删除目录,重新克隆 慢 🐢 高 (完全纯净) 正式发布、排查环境问题
none 不拉取代码 最快 ⚡️ - 仅依赖 Artifacts 的部署、通知任务

GIT_DEPTH

当使用 fetchclone 时,为了进一步加快速度,通常会配合 GIT_DEPTH 变量使用(浅克隆):

yaml
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 这几个阶段

yaml
stages:
  - check
  - build
  - test
  - deploy

GitLab 会按照这个顺序执行。如果 check 阶段的所有 Job 没成功(除非 allow_failure: true),就不会进入 build。同一个 Stage 下的 Job(如 unit_testsecurity_scan)是并行运行的。

extends (继承/复用)

yaml
.deploy_template:
  script: ...

deploy_staging:
  extends: .deploy_template

这是保持 YAML 简洁的最佳实践。.deploy_template 就像父类,定义了通用的部署脚本。deploy_stagingdeploy_production 继承它,只需要修改变量 (variables) 和规则 (rules)。

artifactsdependencies (文件传递)

  • Build 阶段artifacts 告诉 GitLab:“我生成了 dist/ 目录,请打包上传到服务器”。
  • Test/Deploy 阶段:默认情况下,后续阶段会自动下载所有前序阶段的 artifacts。
  • Dependencies:在 deploy_staging 中,如果我们只想下载 build_app 的产物(不想下载 unit_test 可能产生的测试报告),使用 dependencies: ["build_app"] 可以显式指定,既安全又加快下载速度。

rules (强大的逻辑控制)

这是现代 GitLab CI 最核心的部分,取代了老旧的 only/except

yaml
rules:
  - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
    when: manual

这段逻辑表示:

  1. IF: 当前提交是一个 Tag,且 Tag 名字匹配版本号正则(如 v1.0.0)。
  2. THEN: 将 Job 状态设为 manual(手动)。在 UI 界面上,这个 Job 会显示一个播放按钮,只有点击后才会真正部署。这对生产环境至关重要。

allow_failure (容错)

lint_code 中设置了 true。这意味着,如果 ESLint 发现代码格式有问题报错了,Pipeline 会显示一个黄色的感叹号(Warning),但不会通过红色的 X(Error)来停止流水线,后续的 build 依然会继续。

defaultimage

  • 我们在顶部 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):

yaml
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_apibuild_worker 这两个任务,而 .go_base_setup 默默在后台提供了基础配置,保持了代码的整洁。

. 开头的 YAML 文件(隐藏文件/私有模块)

在 CI/CD Catalog 的目录结构中,你可能也会看到以 . 开头的文件,比如 templates/.helper.yml

概念: GitLab CI/CD Catalog 在解析你的项目并生成可供他人搜索的“组件列表”时,会自动忽略所有以 . 开头的文件和目录

在 Catalog 中的作用:私有内部逻辑 有时候一个 Catalog 组件的逻辑非常复杂,写在一个文件里太长了。组件作者会把逻辑拆分到多个文件里,但又不希望这些拆分出来的子文件被当成独立的组件暴露给最终用户。

示例解析:

text
my-catalog-project/
├── templates/
│   ├── .internal-scripts.yml   # 以 . 开头,对外隐藏(私有辅助文件)
│   └── deploy.yml              # 正常的组件,对外暴露(公共组件)

deploy.yml 内部,作者可以通过本地引入的方式使用这个隐藏文件:

yaml
# 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

️ 根本原因分析

  1. GitLab CI 的默认行为 (set -eo pipefail) GitLab CI 在执行你的脚本前,默认会自动注入 set -eset -o pipefail

    • set -o pipefail 的作用是:只要管道符 | 连接的命令中有一个执行失败(退出码非 0),整个管道的退出码就是非 0。
  2. grep -q 的“提前退出”机制 -q (quiet) 参数的作用不仅是不输出内容,更重要的是:一旦找到第一个匹配项,它就会立即退出并返回 0 (Success),不需要继续往下读。

  3. tar 遭遇 SIGPIPE 报错 (141)tar -tf 正在源源不断地输出包含成百上千个文件名的列表时:

    • grep -q 如果在列表的前面(比如第 10 行)就匹配到了 share/docs,它会立刻以状态码 0 退出。
    • 此时管道(pipe)被关闭了,但 tar 命令还在尝试往管道里写数据。
    • 操作系统发现没人接盘了,就会向 tar 命令发送一个 SIGPIPE 信号,强制终止 tar
    • 因此,tar 会异常退出,退出码通常为 141 (128 + SIGPIPE)。
  4. if 条件判定为假 (False) 虽然 grep 成功找到了字符串(退出码 0),但因为 tar 被强制杀死了(退出码 141),在 set -o pipefail 的规则下,整个管道 tar ... | grep ... 的最终退出码变成了 141if 语句一看退出码是非 0,就认为条件不成立,直接跳过了 then 里面的逻辑,进入了下一次循环!

解决方法:

  • 不适用 grep -qif 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_STATUSafter_script 中会有以下三种常见的取值:

  • success:表示 script 成功执行。
  • failed:表示 script 执行失败。
  • canceled:表示任务被手动取消。

.gitlab-ci.yml

yaml
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

参考

  1. https://docs.gitlab.com/ci/yaml/
  2. https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates