4、流水线配置入门(以GitLab CI为例)

好,我们正式开始配置第一条完整的 CI 流水线。很多新手在接触 GitLab CI 时,上来就复制别人的 .gitlab-ci.yml,结果跑起来各种报错。我个人建议:先理解它的骨架逻辑,再动手写。

说白了,GitLab CI 的配置文件就是一个 YAML 文件,名字固定叫 .gitlab-ci.yml,放在项目根目录。GitLab 检测到它后,每次推送代码就会触发流水线。

核心概念一句话总结:流水线 = 阶段(stages)+ 任务(jobs),任务运行在阶段中,阶段按顺序执行。

4.1 最简单的例子:先跑起来

我们从一个能跑的 demo 开始。假设你有一个 Python 项目,只想跑一下单元测试。

stages:
  - test

run_tests:
  stage: test
  script:
    - pip install -r requirements.txt
    - pytest

解释一下:

  • stages 定义所有阶段,这里只有一个 test。
  • run_tests 是任务名称,可以随便起,但最好有意义。
  • stage: test 指明这个任务属于哪个阶段。
  • script 是实际执行的 shell 命令。

简单吧?但别急着上线。我在项目里遇到过,有人把 script 拼写成了 scripts,结果管道跑了半天没动静——YAML 对大小写非常敏感。嗯,这一点要牢记。

4.2 多阶段流水线:从构建到部署

现实项目通常需要三个阶段:构建 → 测试 → 部署。举个例子,一个 Java Maven 项目:

stages:
  - build
  - test
  - deploy

build_jar:
  stage: build
  script:
    - mvn clean package -DskipTests
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 hour

run_unit_test:
  stage: test
  script:
    - mvn test
  needs: [build_jar]

deploy_staging:
  stage: deploy
  script:
    - scp target/*.jar user@staging-server:/app/
  only:
    - main

这里有三个知识点:

  • artifacts:把构建产物(jar 包)传给后续任务,默认会自动下载到后续任务的同路径下。
  • needs:明确依赖关系——测试任务必须等构建任务成功后才会开始,不然测试时 jar 包还没生成呢。
  • only:限定部署仅在 main 分支触发,防止其他分支把开发中的代码推到生产环境。

小技巧:expire_in 设置过期时间,避免 artifacts 挤爆服务器。我个人习惯设成 1 小时,因为大多数管道跑完后 10 分钟就不需要 jar 包了。如果你不设,GitLab 默认保留 30 天。

4.3 环境变量:让配置跑起来

代码里硬编码密码?那简直是定时炸弹。GitLab CI 支持在项目设置里配置变量,然后在 .gitlab-ci.yml 中引用:

deploy_prod:
  stage: deploy
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker push my-app:latest
  only:
    - tags

$CI_REGISTRY_USER 这类以 CI_ 开头的变量是 GitLab 预定义的。你还可以在 Settings → CI/CD → Variables 中自定义变量(如 SSH_PRIVATE_KEY),注意勾选 Masked 来脱敏。

⚠️ 踩坑记录:我曾经把数据库密码存在 Variables 里,但忘了加 Masked,结果打印日志时密码直接露出来了……幸好只是测试环境。

4.4 before_script 与 after_script

如果每个任务都要安装依赖或清理环境,重复写 script 太麻烦了。可以用 before_script 和 after_script 抽公共步骤:

default:
  before_script:
    - echo "开始时间: $(date)"
    - npm ci

stages:
  - lint
  - build

lint_code:
  stage: lint
  script:
    - eslint .

build_app:
  stage: build
  script:
    - npm run build
  after_script:
    - echo "构建完成,清理临时文件"
    - rm -rf node_modules

注意:default 块的 before_script 会作用于所有任务,除非某个任务单独覆盖。

4.5 流水线核心逻辑一览

光看文字可能不够直观。我画了一张图,把以上内容串起来——你可以一眼看清整个流程:

GitLab CI 流水线核心流程 代码推送 / MR Pipeline 启动 Stages build 编译 / 打包 test 单元 / 集成测试 deploy 部署到环境 编译任务 (job) 测试任务 (job) 部署任务 (job) artifacts (产物) needs (依赖) only/except (分支) 每个任务可独立配置 before_script、after_script、environment、variables

4.6 任务间的依赖控制

默认情况下,同一阶段的任务会并行执行。但有时候你需要控制先后顺序,比如先跑 lint 再跑单元测试,免得太大的代码问题后面才发现。这时可以这样:

lint:
  stage: test
  script:
    - flake8

unit:
  stage: test
  script:
    - pytest
  needs: [lint]

needs 不仅跨阶段,也适用于同阶段的不同任务。需要注意的是,如果使用了 needs,GitLab 不会自动等待该阶段所有任务完成,而是只等待你指定的任务完成。

秘诀:needs 可以大幅提升流水线速度。以前我试用 parallel 多任务时,发现测试 job 一直在空等,加上 needs 后构建完立即触发测试,节省了 30% 的时间。

4.7 实用表格:常用关键字速查

关键字 用途 示例
script 执行的命令列表 - echo Hello
before_script 任务前执行 - npm ci
after_script 任务后执行(失败也会执行) - rm -rf tmp
artifacts 保留产物供后续任务 paths: [dist/]
needs 声明依赖任务 needs: [build]
only/except 分支/标签过滤 only: - main
variables 自定义变量 KEY: value

4.8 常见陷阱与解决

我列出几个自己踩过的坑,希望能帮你少走弯路。

  • YAML 缩进错误:GitLab CI 用空格(别用 Tab),缩进不一致会直接报错。建议在本地用 yamllint 检查。
  • 任务名不能包含特殊字符:比如 run test 有空格,编译不过。用下划线 run_test 或驼峰 runTest。
  • artifacts 下载路径:后续任务的 script 执行时,工作目录是项目根目录,而 artifacts 会解压到当前目录,所以直接使用相对路径即可。我见过有同事在 needs 里指定任务却忘了写 artifacts: true——实际上 needs 默认会传递 artifacts,不需要额外声明,但容易混淆。
  • dependencies 与 needs 的差异:dependencies 是旧式语法,只在需要限制 artifacts 下载时才用。新项目建议统一用 needs。

4.9 个人经验:从小项目开始

如果你刚开始接触 GitLab CI,我强烈建议只配一个 test 阶段,跑通一个简单的 echo 或 pytest。别一上来就上五六个阶段。我在团队里见过很多次,新人信心满满写了个大配置文件,结果跑不通也不知道哪里错。先让流水线绿起来,再逐步加功能。

另外,.gitlab-ci.yml 的 CI Lint 工具很好用——在 GitLab 界面里找到 CI/CD → Pipelines → CI Lint,粘进去就能验证语法。嗯,别偷懒,每次改完先验证。