3. .gitlab-ci.yml 基础语法

在 GitLab CI/CD 中,.gitlab-ci.yml 是整条流水线的“源代码”。它决定了每次提交后,GitLab Runner 会执行哪些作业、按什么顺序执行、在什么条件下执行,以及执行时使用哪些变量和环境。理解这份文件的基础语法,是后续编写自动化测试与发布流水线的前提。本节围绕最核心的八个关键字展开:stages、jobs、script、before_script、after_script、variables、rules 与 only/except,并重点说明它们的执行顺序与优先级关系。

一、stages 与 jobs:流水线的骨架与执行单元

stages 定义流水线的阶段顺序,是一个顶层关键字,值为字符串数组。默认情况下,如果未声明 stages,GitLab 会使用 .pre、build、test、deploy、.post 五个阶段。阶段之间是串行关系:只有当前阶段的所有作业成功完成,下一阶段的作业才会开始。同一阶段内的多个作业默认并行执行。

作业(job)是流水线的最小执行单元,写在顶层,名字不能是保留关键字(如 stages、variables、before_script 等)。每个作业至少要包含 script,否则该作业不会被执行。作业通过 stage 关键字归属到某个阶段,未指定时默认属于 test 阶段。

stages:
  - build
  - test
  - deploy

build_app:
  stage: build
  script:
    - echo "编译应用"
    - make build

unit_test:
  stage: test
  script:
    - echo "运行单元测试"
    - make test

上面这段配置中,build_app 先执行,成功后 unit_test 才会启动。如果 build_app 失败,unit_test 不会运行,整个流水线标记为失败。

二、script、before_script、after_script:作业内的执行顺序

每个作业内部,命令的执行顺序是固定的:before_script → script → after_script。before_script 常用于准备环境,例如安装依赖、登录镜像仓库、设置环境变量;script 是作业的主体逻辑;after_script 用于清理工作,例如停止服务、删除临时文件、上传日志。

需要特别注意:after_script 无论作业成功还是失败都会执行,但它执行失败不会影响作业本身的最终状态。也就是说,即使 after_script 报错,只要 script 成功,作业仍算成功。此外,before_script 和 after_script 都可以定义在顶层作为全局默认值,作业内定义的同名关键字会覆盖全局值。

before_script:
  - echo "全局准备:安装依赖"
  - npm install

after_script:
  - echo "全局清理:删除临时目录"
  - rm -rf tmp/

test_job:
  stage: test
  before_script:
    - echo "本作业覆盖:只安装测试依赖"
    - npm install --only=dev
  script:
    - npm run test
  after_script:
    - echo "本作业清理:收集覆盖率报告"
    - cp coverage/lcov.info ./artifacts/

在这个例子中,test_job 实际执行的是作业内定义的 before_script,而不是顶层的全局版本。这就是“就近覆盖”原则:作业级配置优先于全局配置。

三、variables:变量定义与作用域

variables 用于定义变量,可以在顶层定义全局变量,也可以在作业内定义局部变量。变量在 script、before_script、after_script 中都可以通过 $变量名 或 ${变量名} 引用。作业级变量会覆盖同名的全局变量。

variables:
  APP_ENV: "staging"
  NODE_VERSION: "18"

build_job:
  stage: build
  variables:
    APP_ENV: "production"
  script:
    - echo "当前环境:$APP_ENV"
    - echo "Node 版本:$NODE_VERSION"

执行 build_job 时,APP_ENV 输出为 production,因为作业级变量覆盖了全局变量;而 NODE_VERSION 仍为 18,因为它只在全局定义。变量还可以在 GitLab 项目设置中预定义,优先级通常为:作业级变量 > 项目/组级变量 > 全局变量。

四、rules 与 only/except:控制作业是否执行

rules 和 only/except 都用于控制作业在什么条件下被加入流水线,但两者不能在同一作业中混用。only/except 是较早期的语法,基于分支名、标签、事件类型进行简单匹配;rules 是推荐的新语法,支持更细粒度的条件判断,包括分支、事件、变量、文件变更等,并且可以按顺序匹配,一旦命中就停止后续判断。

deploy_job:
  stage: deploy
  script:
    - echo "部署到生产环境"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
    - if: '$CI_COMMIT_BRANCH =~ /^release\/.*/'
      when: on_success
    - when: never

这段配置的含义是:当提交到 main 分支时,deploy_job 以手动方式触发;当提交到 release/ 开头的分支时,自动执行;其他情况一律不执行。rules 按从上到下的顺序匹配,第一条命中的规则决定作业行为,因此最后一条 when: never 起到了兜底作用。

相比之下,only/except 的写法更简单,但表达能力有限:

deploy_job:
  stage: deploy
  script:
    - echo "部署到生产环境"
  only:
    - main
    - /^release\/.*/
  except:
    - schedules

它表示只在 main 分支或 release/ 开头的分支上执行,但定时任务触发的流水线除外。由于 rules 更灵活且是官方推荐方向,新项目应优先使用 rules。

五、优先级与执行顺序总览

把上述关键字放在一起,可以形成一条清晰的判断链:首先由 rules 或 only/except 决定作业是否进入流水线;进入后,按 stages 定义的阶段顺序串行推进;同一阶段内作业并行;每个作业内部按 before_script → script → after_script 执行;变量则按“作业级覆盖全局级”的规则生效。下面这张图概括了从配置解析到作业执行的完整关系。

.gitlab-ci.yml 关键字关系与执行优先级 rules / only-except 决定作业是否进入流水线(按顺序匹配,命中即停) 命中后进入 stages:阶段串行推进 build test deploy 同阶段内作业并行 阶段内调度 单个 job 内部执行顺序 before_script 准备环境 / 安装依赖 script 作业主体逻辑(必需) after_script 清理 / 收集报告(必执行) script 失败 → 作业失败,after_script 仍执行 variables:作用域与覆盖 全局变量(顶层) ← 被覆盖 作业级变量(优先) 就近覆盖 优先级链:rules/only-except 筛选 → stages 排序 → 作业内 before_script → script → after_script → 变量就近覆盖 rules 与 only/except 不可混用;rules 按顺序匹配,命中即停

六、常见易错点与配置建议

第一,rules 与 only/except 不能同时出现在一个作业中,否则 GitLab 会报配置错误。第二,after_script 中不能使用 before_script 或 script 中定义的临时变量,因为它运行在独立的 shell 上下文中。第三,rules 的 when 值包括 on_success、on_failure、always、manual、delayed 和 never,其中 never 常用于排除特定条件。第四,如果作业没有 script,即使写了 rules 也不会执行,因为 GitLab 会直接忽略该作业。

在实际项目中,建议把全局的 before_script 只保留最通用的环境准备命令,把与具体作业强相关的依赖安装放到作业级 before_script 中,避免每个作业重复执行不必要的步骤。同时,用 rules 替代 only/except,可以让流水线的触发条件更清晰、更易维护。