极狐GitLab

每个作业都在传一遍产物,流水线慢的根子可能就在这

极狐GitLab
2026年10月29日
17300
分享:

每个作业都在传一遍产物,流水线慢的根子可能就在这

先明确一件事:作业产物(artifacts)是作业跑完后归档出来的文件或目录,比如编译产物、测试报告、构建出的镜像清单。它的默认行为常常被忽略——后面阶段的作业会把前面阶段所有作业的产物都下载一遍。官方文档写得很直白:默认情况下,后续作业会获取早期阶段作业的所有产物的副本。一个十几步的流水线,很容易变成几十次无意义的重复下载。

为什么流水线越优化越慢

很多团队优化流水线的顺序是反的:先换更大的 Runner,再加缓存,最后才想起看作业日志里那几十秒的「Downloading artifacts」。

三段真实代价:

  • 时间:每个作业开跑都要先解压一遍产物归档,这一步在慢存储或跨可用区时能占到作业时长的三成。

  • 存储:产物不是免费的,单次流水线产物动辄几百 MB,加上每个引用上保留的历史流水线,份额涨得比代码快得多。

  • 正确性:把产物当发布存储用,是官方明确劝阻的做法——保留「最近成功作业产物」的设置会忽略 expire_in,而最新产物的 URL 每次新流水线都会指向不同的文件,下游按 URL 取包迟早会取错版本。

换句话说,产物传递链没梳理清楚,前面所有优化都在给一条漏水的管子加压。

三步把产物传递链收窄

第一步:先找出谁在无脑下载

打开任意一次流水线的作业日志,搜索 Downloading artifacts。凡是明明只用到一两个文件的作业,却在下载整个归档,就是可优化点。判断标准很简单:这个作业的脚本里到底引用了哪些路径。

第二步:用 dependencies 或 needs:artifacts 精准指定来源

dependencies 和 needs:artifacts 都会改变默认行为——一旦使用,产物只从你点名的作业里取,不再全量拉取。

stages: [build, test, package]

build-app:
  stage: build
  script: ["./build.sh"]
  artifacts:
    paths:
      - dist/
    expire_in: 3 days

unit-test:
  stage: test
  needs:
    - job: build-app
      artifacts: true   # 只用 build-app 的产物
  script: ["./run-tests.sh"]

lint:
  stage: test
  dependencies: []      # 这个作业根本不需要产物,直接关掉
  script: ["./lint.sh"]

dependencies: [] 是最容易被忽视的一行。把不需要产物的作业全部显式关掉,往往能直接砍掉三成以上的下载时间。

第三步:把归档缩小,把过期时间定清楚

只列后续作业真正需要的路径,用 artifacts:exclude 剔除中间文件,再用 expire_in 兜底:

build-app:
  stage: build
  script: ["./build.sh"]
  artifacts:
    paths:
      - dist/
    exclude:
      - "dist/**/*.map"
      - "dist/tmp/**/*"
    expire_in: 3 days

如果完全没写 expire_in,用的是实例级的默认产物过期时间;写了之后,保留时长就按你的配置走。另外单次产物归档有大小上限,默认每 100 MB,实例、群组、项目都能单独调,改小一点反而能逼着团队把归档做干净。

产物不该承担发布职责

这是一个比性能更贵的问题。产物的生命周期跟着流水线走,发布资产的生命周期跟着版本走,两者混在一起,早晚出事。

官方文档专门提醒过:不要将作业产物用作发布存储。原因很实在——开启「保留最近成功作业的产物」后,这批产物会忽略 expire_in,而某个分支上「最新产物」的 URL 每次流水线都会指向不同的文件。下游如果有人拿这个 URL 去拉取,拿到的可能是三次流水线之前的包。

正确分工是这样的:

需求

该用什么

不该用什么

阶段间传编译结果

作业产物 + needs:artifacts

制品库

测试报告、覆盖率、SBOM

artifacts:reports

制品库(报告类产物会被流水线直接读取,不能挪走)

交付给下游的包

软件包仓库(通用包、Maven、npm 等)

作业产物

对外发布的版本

发布(Release)+ 软件包仓库

分支上「最新产物」的 URL

软件包仓库的容量量级和产物不是一个水平:通用软件包单个文件上限可以到 5 GB,而作业产物归档默认每个 100 MB。把该进制品库的东西挪过去,产物的存储曲线会立刻平下来。

改完之后怎么衡量

不要只凭感觉说「快了一点」。改之前先记三个数,改完对比:

指标

观察方式

合理预期

单次流水线「Downloading artifacts」总耗时

作业日志累加

明显下降,通常能砍掉一半以上

产物存储占用

项目设置里的用量视图

一周内趋于平稳,不再随流水线数量线性上涨

流水线端到端时长

流水线详情页

有改善,但幅度小于第一项——它受最慢作业限制

三个容易踩到的坑

坑一:exclude 不是递归的。 写 dist/tmp/ 并不会排除这个目录下的所有文件,必须显式匹配,比如 dist/tmp/**/*。这一点官方文档专门做过提示,也是最常见的配置错误。

坑二:expire_in 不等于「到点就删」。 每个引用上最近完成的流水线、最近成功的流水线的产物默认会被保留,任何 expire_in 都对它们不生效。所以在主分支上跑一次就指望产物自动消失,不会发生。

坑三:跨项目取产物要写全三要素。 用 needs:project 时,项目路径、作业名、ref 必须一起给,ref 写错就会拿到另一条分支上的旧产物,排查时极难看出问题。

常见问题

dependencies 和 needs 该用哪个? 项目里已经在用 needs 做 DAG 并行,就统一用 needs:artifacts,别两套混用;纯阶段模型的老项目用 dependencies 改动更小。

产物默认什么时候上传? 默认只在作业成功时上传(on_success)。失败也要留产物(比如测试报告、崩溃日志),显式写 artifacts:when: on_failure 或 always。

产物能给外部人下载吗? 可以限制,artifacts:access 控制谁能下载,敏感项目的产物别默认对全项目成员开放。

动手清单

  1. 在流水线日志里搜 Downloading artifacts,列出下载量最大的三个作业;

  2. 给不需要产物的作业加 dependencies: [];

  3. 给需要的作业改用 needs:artifacts 点名来源;

  4. 收敛 paths、补 exclude、统一 expire_in;

  5. 把产物大小上限下调一档,观察一周存储曲线。

需要对照官方语义细节,可以查 作业产物文档 与 CI/CD YAML 参考。想直接在自己的流水线上看一遍效果,可以在极狐GitLab 上开一个项目的免费试用,把上面几段配置贴进去跑两轮对比作业时长。

一句话收尾:流水线的性能问题,一半出在算力上,另一半出在「不必要的数据搬运」上。先把后者清干净,再去谈扩容。