GitLab CI/CD 隐藏功能揭秘:你可能不知道的用法

devops进阶10 分钟阅读2026/9/14

GitLab CI/CD 隐藏功能揭秘:你可能不知道的用法

从一个真实痛点说起

去年年底,我们团队的流水线膨胀到了一个尴尬的地步:一次完整的 CI 跑下来要 23 分钟,而且其中大部分作业在 80% 的提交里根本不需要执行。更糟的是,有次一个同事在测试分支上误跑了部署作业,把预发环境的数据库覆盖了。那天晚上我在排查事故时翻 GitLab 文档,才发现我用了三年 GitLab CI/CD,居然有一半的功能从未碰过。

这篇文章就是我事后整理的一些“宝藏功能”,每一个都在我们仓库里真实落地了。我按“解决的问题”来组织,你可以对照自己团队的痛点来看。

一、rules + changes:让流水线只跑该跑的东西

我们最早用的是老式 only/except 关键字,规则散落在各处,很难读懂。后来全面迁到 rules,配合路径过滤,效果立竿见影。

lint-frontend:
  stage: test
  rules:
    - changes:
        - "frontend/**/*"
      when: on_success
    - when: never
  script:
    - cd frontend && npm run lint

这样前端 lint 只在前端代码变更时执行。我们的第一个坑:merge request 首次创建时 changes 的判断基准。默认它对比的是目标分支,这通常没问题,但如果你想更精确,可以加上:

rules:
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    changes:
      paths:
        - "frontend/**/*"
      compare_to: 'refs/heads/main'

迁移之后,日常改文档、改后端的提交,前端相关作业完全不再执行。平均流水线时间从 23 分钟降到了 11 分钟。

二、workflow:rules:在流水线级别做总闸门

上面是控制单个作业,但有时候你想控制整条流水线是否创建。比如我们遇到过一个坑:同事在本地打的 tag 推上去,触发了一整套测试流水线,浪费了大量 runner 资源。解决方法:

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - when: never

只有分支推送、MR 和定时任务会创建流水线,tag push 直接忽略。很多人不知道这个功能藏在顶层的 workflow 关键字里,而不是每个 job 里。

三、interruptible 和自动取消过时流水线

这是我个人的最爱。场景很常见:你推了三个 commit,前两个的流水线还在排队/运行,其实已经没有意义了。

build:
  stage: build
  interruptible: true
  script:
    - make build

test:
  stage: test
  interruptible: true
  script:
    - make test

然后在项目设置里勾选 Settings → CI/CD → General pipelines → Auto-cancel redundant pipelines,或者在较新版本中用:

workflow:
  auto_cancel:
    on_new_commit: conservative

注意一个坑:部署类作业千万不要设 interruptible: true,否则部署到一半被取消,环境就卡在中间状态了——我们真的踩过这个雷。

四、resource_group:一行代码防止并发部署

针对开头提到的事故,除了权限,我还加了这层保险:

deploy-staging:
  stage: deploy
  environment: staging
  resource_group: staging-deploy
  script:
    - ./deploy.sh staging

同一个 resource_group 的作业会自动串行执行——效果类似其他系统里的 mutex。它甚至会等正在运行的作业结束再启动下一个。这比自己在脚本里写文件锁优雅太多了。后来我给每个环境的部署作业都配了一个以其环境命名的 resource group,再也没出现过并发部署冲突。

五、!reference 标签:真正可维护的配置复用

很多人靠 YAML 锚点(& / *)复用配置,但锚点有个致命问题:include 引入的文件之间锚点不互通!reference 是 GitLab 自己的语法,跨文件有效:

# .common.yml
.default_before_script:
  before_script:
    - source scripts/setup-env.sh
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
# .gitlab-ci.yml
include:
  - local: .common.yml

build:
  !reference [.default_before_script, before_script]
  script:
    - make build

更好的是可以合并多个来源

before_script:
  - !reference [.default_before_script, before_script]
  - !reference [.security_before_script, before_script]
  - echo "extra step"

不过说实话,!reference 不支持合并嵌套过深的结构,复杂场景下我还是建议用 extends,两者搭配着用。

六、子流水线(Parent-Child Pipelines)与动态生成

这是让我最惊艳的功能。我们有个 monorepo,服务超过 15 个,顶层 YAML 已经接近千行。现在的做法是:一个轻量父流水线,根据变更路径动态生成子流水线:

# .gitlab-ci.yml
generate-children:
  stage: trigger
  rules:
    - if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
      when: never
    - when: on_success
  script:
    - python scripts/generate_pipeline.py > generated.yml
  artifacts:
    paths:
      - generated.yml

run-children:
  stage: trigger
  needs: [generate-children]
  trigger:
    include:
      - artifact: generated.yml
        job: generate-children
    strategy: depend

generate_pipeline.py 会检查哪些服务的目录有变更,只把对应服务的作业写进 generated.yml。子流水线在 UI 里是独立的一页,日志清晰得多。

七、一些小而美的细节

  • CI_MERGE_REQUEST_* 变量族:在 MR 流水线里可以直接拿到源分支、目标分支、MR 标题,我甚至用它做了“commit message 不符合规范就 fail”的检查。
  • retry + when
test:
  retry:
    max: 2
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

只对基础设施故障重试,代码失败不重试——避免掩盖真实的测试问题。

  • dotenv 报告:下游作业可以继承上游作业导出的变量:
build:
  script:
    - echo "IMAGE_TAG=$CI_COMMIT_SHORT_SHA" > build.env
  artifacts:
    reports:
      dotenv: build.env
  • timeout 设在作业级:别只依赖全局默认,慢测试单独放宽,其他作业收紧到 10 分钟,失败快速暴露。

实用建议与诚实的局限

建议:

  1. 迁移 only/exceptrules 值得专门开一个 MR 做,别混着用。
  2. 给部署作业加上 resource_groupenvironment,就两行配置,救过我不止一次。
  3. gitlab-ci-local 这类工具在本地预检查 YAML,别等推上去才发现语法错误。

局限,说实话:

  • 子流水线虽然好用,但 strategy: depend 会让父流水线阻塞等待,超长的子流水线会拖住整体状态;另外跨子流水线传递变量仍然只能靠 dotenv artifacts,不够顺手。
  • rules 里不能用 needs 那样的复用逻辑,复杂条件还是得在脚本里自己判断。
  • !reference 的报错信息比较隐晦,YAML 结构对不上时只会告诉你引用失败,排查基本靠猜。
  • 免费版 runner 的并发限制会抵消一部分流水线优化的收益——拆得再细,runner 不够也是白搭。

这些功能没有一个是花哨的技巧,核心思路只有一个:让每次流水线只做必要的事,并且出错时安全可控。如果你也想优化自己那台“越跑越慢”的 CI,我建议从第一节的 rules:changes 开始,那是投入产出比最高的一步。

相关 Agent

D

Docker

Docker 是一个容器化平台,允许开发者将应用及其依赖项打包成轻量级、可移植的容器。它简化了跨环境的开发、测试和部署过程。该描述强调它最适合容器化和开发环境,提供免费方案和团队方案,团队方案从每用户每月5美元起。

了解更多 →