4. 阶段、作业与依赖编排
在 GitLab CI/CD 中,.gitlab-ci.yml 默认按 stage 顺序串行推进:同一 stage 内的作业并行执行,只有当前 stage 全部成功,下一 stage 才会启动。这种“阶段闸门”模型简单可靠,但在大型研发体系中会带来明显等待——例如单元测试、静态扫描、镜像构建本可并行,却因被放进不同 stage 而被迫排队。要突破这一限制,就需要掌握 needs、dependencies、rules 与 only/except 这四组控制手段。
一、stage 是骨架,needs 是“抄近道”
stages 定义流水线的阶段顺序,每个作业通过 stage 关键字归属到某个阶段。默认依赖是“前一阶段全部完成”。而 needs 允许一个作业跳过阶段顺序,直接依赖指定的上游作业,只要这些作业完成即可启动,无需等待整个 stage 结束。这就是 DAG(有向无环图)流水线。
stages: [build, test, package, deploy]
build:app:
stage: build
script: ["./mvnw -q package -DskipTests"]
test:unit:
stage: test
needs: ["build:app"] # 不等 build 阶段其他作业
script: ["./mvnw -q test"]
scan:sast:
stage: test
needs: [] # 无依赖,流水线创建后立即运行
script: ["./run-sast.sh"]
package:image:
stage: package
needs: ["test:unit"] # 只等单测,不等 scan:sast
script: ["./build-image.sh"]
上例中,scan:sast 用 needs: [] 声明“无前置依赖”,可在流水线创建后立刻执行;package:image 只等 test:unit,不必等 scan:sast 结束。这样关键路径被显著缩短。需要注意:needs 引用的作业必须存在于同一流水线中,且不能形成环,否则配置校验会直接报错。
二、dependencies 与 needs 的区别
两者常被混淆。needs 控制的是作业何时可以开始(调度依赖);dependencies 控制的是从哪些上游作业下载 artifacts(产物依赖)。默认情况下,作业会下载所有前序 stage 的 artifacts,这既慢又可能引入无关文件。用 dependencies 精确指定,可以只取需要的产物。
test:unit:
stage: test
needs: ["build:app"]
dependencies: ["build:app"] # 只下载 build:app 的产物
script: ["./mvnw -q test"]
deploy:prod:
stage: deploy
needs: ["package:image"]
dependencies: [] # 不需要任何 artifacts
script: ["./deploy.sh"]
实践建议:凡是显式写了 needs 的作业,最好同时写 dependencies,把产物来源收敛到最小集合,避免“依赖了 A,却下载了 B、C、D 的产物”这类隐性开销。若某作业确实不需要任何产物,写 dependencies: [] 是最干净的做法。
三、rules:条件触发的主力
only/except 是早期语法,基于分支名、标签、事件做简单匹配,逻辑表达能力弱,官方已推荐用 rules 替代。rules 按顺序逐条求值,命中第一条即决定作业是否加入流水线,并可同时覆盖 when、变量等属性。
deploy:staging:
stage: deploy
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
when: on_success
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual
allow_failure: true
- when: never # 其余情况不创建该作业
deploy:prod:
stage: deploy
rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
when: manual
- when: never
这里 deploy:staging 在 develop 分支自动执行,在 MR 场景变为手动且允许失败,其他情况完全不创建;deploy:prod 仅在符合语义化版本标签(如 v1.4.0)时出现,且必须人工点击。这种“条件 + 手动门禁”的组合,是生产发布最常见的控制方式。
四、only/except 的定位与迁移
老项目中仍会见到 only: [main, tags]、except: [ schedules ] 这类写法。它适合极简场景,但无法表达“if + 变量 + when”的组合,也不支持 rules 那样的顺序短路。迁移时把握一条原则:把每个 only/except 条件翻译成一条 rules 的 if,最后补一条 when: never。例如 only: [main] 等价于 rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }, { when: never }]。
rules 与 only/except,GitLab 会报配置冲突。迁移应整作业替换,而非逐条叠加。五、编排关系总览
下面这张图把本节四组关键字的作用位置放在同一条流水线上:stage 决定纵向骨架,needs 画出横向捷径,dependencies 决定产物流向,rules 决定节点是否出现以及以何种 when 出现。
六、编排时的常见判断
- 关键路径优先:先找出从提交到部署的最长依赖链,用
needs把非关键作业从链上摘出去,缩短总时长。 - 产物最小化:每个作业只声明真正需要的
dependencies,减少下载与解压开销。 - 条件收敛:用
rules的when: never兜底,避免作业在非预期分支上意外创建。 - 手动门禁:生产部署用
when: manual,配合标签或保护分支,形成人工确认点。 - 避免环:
needs关系必须无环,否则流水线无法创建。
needs 后,作业不再受 stage 顺序保护。若上游作业尚未产出所需 artifacts,下游可能拿到空目录。务必让 needs 与 dependencies 指向同一批作业,保证调度与产物一致。把 stage 当作骨架、needs 当作捷径、dependencies 当作产物管道、rules 当作开关,四者配合就能在保持可读性的同时,把大型流水线的等待时间压到最低。实际落地时,建议先在测试分支验证 DAG 结构,再逐步推广到主干与发布流程。