在使用 GitLab CI/CD 构建现代软件流水线时,我们经常面临一个核心挑战:如何让一个作业的输出成果(比如编译好的二进制文件、测试报告或打包好的镜像)在后续的作业中被复用?在 GitLab 中,这个问题的解决方案被称为“构建产物”。
简单来说,构建产物是在作业运行期间生成的文件或目录,它们会被 GitLab 保存并归档,以便在流水线的后续阶段中下载和使用。这是实现 CI/CD 流水线数据持久化和阶段间协作的关键机制。
在这篇文章中,我们将深入探讨如何将 GitLab 构建产物传递到另一个阶段。我们不仅会看到基础的操作步骤,还会学习如何利用高级特性(如条件传递、过期策略和依赖管理)来优化你的流水线性能和存储成本。无论你是刚接触 CI/CD 的新手,还是寻求最佳实践的老手,我相信这篇指南都能为你提供实用的见解。
为什么我们需要在阶段间传递产物?
在开始编码之前,让我们先理解一下“为什么”。想象一下典型的软件开发流程:
- 构建阶段:我们将源代码编译成可执行文件或构建包。
- 测试阶段:我们需要使用刚才构建出来的可执行文件运行集成测试。
- 部署阶段:我们需要将那个通过了测试的构建包部署到生产环境。
如果没有“构建产物”,每个作业都将是孤立的。测试作业无法访问构建作业生成的文件,除非你重新编译一遍,这极大地浪费了时间和资源,且无法保证测试的是完全相同的版本。通过传递产物,我们确保了整个流水线处理的是同一份代码和同一个构建输出。
准备工作:配置你的 GitLab 环境
为了演示如何传递产物,我们需要一个实际的场景。让我们一步步搭建环境。
步骤 1:初始化演示项目
首先,你需要在 GitLab 上创建一个新项目。如果你已经有了项目,当然也可以直接使用。为了练习,我们可以在本地创建一个简单的演示环境。
打开你的终端,执行以下命令来克隆项目(请替换为你实际的项目地址):
git clone https://gitlab.com/your-username/your-project-name.git
cd your-project-name
为了模拟真实的构建场景,我们可以在本地创建一些目录结构,作为未来的产物:
# 创建一个模拟的构建输出目录
mkdir -p build_output
# 向其中写入一些内容,模拟构建生成的文件
echo "Initial content for the artifact" > build_output/artifact.txt
echo "Another dependency file" > build_output/config.json
步骤 2:编写 .gitlab-ci.yml 配置文件
GitLab CI/CD 的核心在于 .gitlab-ci.yml 文件。这个文件定义了流水线的所有行为。让我们从零开始编写这个文件,看看它是如何工作的。
在你的项目根目录下创建一个名为 .gitlab-ci.yml 的文件。
#### 定义阶段
首先,我们需要声明流水线包含哪些阶段。这是一个良好的实践,让流程一目了然:
stages:
- build
- test
- deploy
#### 配置构建作业并生成产物
接下来,我们创建第一个作业 build_job。这个作业的任务是“构建”我们的项目,并指定哪些文件应该被保存为产物。
请注意 artifacts 关键字,这是传递产物的核心配置:
build_job:
stage: build
script:
- echo "正在编译项目..."
# 在实际场景中,这里可能是 make, npm run build, mvn package 等
- mkdir -p build_output
- echo "这是构建产物内容:版本 v1.0" > build_output/artifact.txt
- echo "构建完成!"
artifacts:
paths:
- build_output/ # 指定要保存的目录
expire_in: 1 week # 设置产物的过期时间
这段代码做了什么?
- script:在容器内执行命令,创建文件。
- artifacts:paths:告诉 GitLab Runner,在作业结束后,把
build_output/目录下的所有文件打包上传到 GitLab 服务器。 - expire_in:这是一个重要的优化。我们不需要永久保存这些文件,设置过期时间(如1周)可以节省存储空间。
#### 在后续作业中使用产物
这是最神奇的部分。你不需要在 test_job 中写任何下载代码。GitLab 会自动检测到依赖关系,并将上一阶段(或相关依赖)的产物下载到当前作业的工作目录中。
让我们配置测试和部署作业:
test_job:
stage: test
script:
- echo "正在运行测试,使用构建产物..."
# 直接读取构建阶段生成的文件,不需要任何额外配置!
- cat build_output/artifact.txt
- echo "测试通过!产物内容验证成功。"
deploy_job:
stage: deploy
script:
- echo "正在部署项目..."
- cat build_output/artifact.txt
- echo "部署脚本执行完毕。"
# 只有在 main 分支上才运行部署
only:
- main
步骤 3:提交并观察流水线运行
现在,我们将这个配置文件提交到 GitLab,触发流水线:
git add .
git commit -m "添加 CI 配置:演示产物传递"
git push origin main
推送到 GitLab 后,导航到你项目的 CI/CD -> Pipelines。你应该会看到一条新的流水线正在运行。点击进入作业日志,你会注意到:
- 在
build_job结束时,日志会显示“Creating artifacts…”或“Uploading artifacts…”。 - 在
test_job开始时,日志会显示“Downloading artifacts…”。
这证实了产物已经被成功传递。
进阶配置:掌握 Artifacts 语法
为了应对更复杂的开发需求,我们需要深入了解 artifacts 的详细语法。这不仅能帮你解决棘手的问题,还能提升流水线的专业性。
1. 精确控制路径 (paths)
你可以指定单个文件或整个目录。支持通配符。
artifacts:
paths:
- build/
- target/*.jar # 仅保存 JAR 文件
- config/**/*.xml # 保存 config 下任何层级的 xml 文件
2. 条件性上传 (when)
默认情况下,只有作业成功时,产物才会被上传。但在某些调试场景下,即使作业失败(例如测试失败),我们也想保留日志或截图。
run_tests:
stage: test
script:
- npm run test
artifacts:
paths:
- test/screenshots/
when: on_failure # 仅当测试失败时上传截图
expire_in: 1 day
可选值包括:
-
on_success(默认):作业成功时上传。 -
on_failure:作业失败时上传。 -
always:无论成功还是失败都上传。
3. 排除不需要的文件 (exclude)
为了避免上传不必要的临时文件或大文件(如 nodemodules),可以使用 INLINECODEb8d0dedc。这对于控制存储体积非常关键。
build_job:
script: make build
artifacts:
paths:
- build/
exclude:
- build/**/*.tmp # 排除临时文件
- build/debug.log # 排除日志文件
4. 跨阶段产物依赖 (dependencies)
这是 GitLab CI/CD 中一个非常重要且容易混淆的概念。
默认行为:GitLab 会自动将所有前置阶段的产物传递给当前作业。
潜在问题:如果 Build 阶段生成了 1GB 的产物,Test 阶段只需要 10MB 的产物,而 Deploy 阶段只需要 1GB 的产物,那么 Test 作业会浪费大量时间去下载不需要的文件。
优化方案:使用 dependencies 关键字来显式指定要下载哪个作业的产物,甚至禁用自动下载。
build_front:
stage: build
script: echo "Frontend artifacts" > front.txt
artifacts:
paths: [front.txt]
build_back:
stage: build
script: echo "Backend artifacts" > back.txt
artifacts:
paths: [back.txt]
test_front:
stage: test
# 只下载 build_front 的产物,不下载 build_back 的
dependencies:
- build_front
script:
- cat front.txt
如果你想完全跳过产物下载(例如在清理作业中),可以设置为空数组:
cleanup_job:
stage: deploy
dependencies: [] # 不下载任何产物
script: rm -rf ./tmp
实战案例解析
为了巩固我们的理解,让我们看几个具体的实战案例。
案例一:基础产物传递(复用构建文件)
这是一个最经典的前端项目示例。我们在 INLINECODEa0a140a1 阶段生成静态站点,然后在 INLINECODE25cd8ca0 阶段部署它。
stages:
- build
- deploy
build_site:
stage: build
image: node:16
script:
- npm install
- npm run build
artifacts:
paths:
- public/ # 假设构建输出在 public 目录
expire_in: 1 hour # 构建产物只需保留到部署完成即可
deploy_to_s3:
stage: deploy
image: python:latest
script:
# pip install awscli ... (省略配置)
- aws s3 sync ./public s3://my-bucket/
# 默认会下载 build_site 的 public 目录到 ./public
关键点:INLINECODEa2748a91 作业会自动获得 INLINECODEfaef035b 目录,就像 build_site 在同一台机器上运行过一样。
案例二:条件产物(仅在失败时收集调试信息)
在自动化测试中,最让人抓狂的是“本地运行通过,CI 上失败却看不到报错”。我们可以利用条件产物在失败时自动收集截图。
stages:
- test
ui_tests:
stage: test
image: node:16
script:
- npm install
- npm run test:ui
artifacts:
paths:
- tests/screenshots/
- tests/videos/
when: on_failure # 只有测试失败时才上传这些文件
expire_in: 7 days
关键点:如果测试通过,这些文件会被忽略,节省存储;如果测试挂掉,你可以直接在 GitLab 界面下载截图进行分析。
案例三:多项目产物传递
有时候,产物需要在不同的 GitLab 项目之间共享。这通常用于微服务架构,比如项目 A 的构建产物被项目 B 用来运行集成测试。
这需要触发其他项目的流水线并传递产物。
项目 A 的配置:
build_a:
stage: build
script: echo "Project A Binary" > app-a.jar
artifacts:
paths: [app-a.jar]
项目 B 的配置:
项目 B 需要使用 trigger 关键字来启动项目 A,或者项目 A 触发项目 B。这里我们展示一种通过 API 触发并传递产物的逻辑(简化版思路):
实际上,最直接的方法是在 GitLab UI 设置中配置触发器,但更现代的方式是使用 needs 关键字(这通常用于多项目流水线优化):
# 在 Project B 中
job_b:
stage: test
needs:
- project: group/project-a
job: build_a
ref: main
artifacts: true # 明确要求下载 Project A 的产物
script:
- ls -l # 你会看到这里下载了 Project A 的 app-a.jar
常见错误与最佳实践
在配置 GitLab Artifacts 时,我们经常会遇到一些坑。让我为你总结一下最常见的错误及其解决方案。
错误 1:找不到文件路径
错误信息:INLINECODEf95bf88d 或 INLINECODE13093d93。
原因:你在 artifacts:paths 中定义的路径在脚本运行结束后不存在。请检查你的脚本是否将文件输出到了正确的目录。记住,路径是相对于项目根目录的。
解决:使用 INLINECODE51a14060 和 INLINECODE4ae8d3c1 在脚本中调试当前目录结构。
错误 2:不同 Runner 之间的路径问题
原因:如果 INLINECODE95b30484 作业在 Shell Runner 上运行,而 INLINECODE2f6b876c 作业在 Docker Runner 上运行,路径处理方式可能不同(比如绝对路径 vs 相对路径)。
最佳实践:始终使用相对于项目仓库根目录(CIPROJECTDIR)的相对路径。不要假设 Runner 的文件系统结构。
错误 3:产物体积过大导致流水线缓慢
原因:如果你传递了 node_modules 或大量的 Docker 镜像层作为产物,下载和解压的时间会非常长。
最佳实践:
- 善用 INLINECODEd43617fa:排除不必要的文件(如 INLINECODE91283611,
node_modules)。 - 善用
dependencies:限制只有需要的作业才下载大文件。 - 使用 Cache 而非 Artifacts:如果你只是想加速依赖安装(如 npm install),请使用 INLINECODE83972ff4 关键字,而不是 INLINECODE1672e9d6。Cache 不会在流水线之间持久保存,主要用于加速同一 Runner 上的任务执行;而 Artifacts 用于数据传递和长期存储。
最佳实践总结
- 命名清晰:确保产物路径清晰(如 INLINECODE6f0204f1, INLINECODEf3a43454),避免顶层文件乱放。
- 设置过期时间:永远设置
expire_in。除非你有特殊的合规要求,否则不要永久保存调试用的二进制文件。 - 利用环境变量:你可以使用
$CI_COMMIT_REF_NAME等变量来动态命名产物路径,方便区分不同分支的构建结果。
性能优化建议
对于大型项目,产物传递可能成为性能瓶颈。
- 使用 INLINECODE3b423b4d 或 INLINECODE278df29c 压缩:GitLab 默认会压缩产物,如果你的脚本生成了大量文本日志,手动压缩它们可以减少网络传输时间。
- 拆分流水线:如果可能,利用 INLINECODE401266cd 或 INLINECODE29d6ae7e 将庞大的单体流水线拆分。下游流水线可以通过 API 只获取它真正需要的那个特定文件,而不是整个产物包。
- 使用 Artifacts Reports:对于像 JUnit 测试报告、代码覆盖率报告或安全扫描报告,使用 INLINECODE81837156 关键字(如 INLINECODE414c3d54)。GitLab 会自动解析这些文件并在 UI 中展示,避免你手动下载文件查看结果。
总结
在这篇文章中,我们全面探讨了如何在 GitLab CI/CD 中将构建产物传递到另一个阶段。从最基本的 INLINECODE2772ca40 配置,到利用 INLINECODEf284c861 进行精细化的依赖控制,再到跨项目的产物共享,这些技巧构成了构建高效 CI/CD 流水线的基石。
通过合理使用产物,你可以:
- 避免重复劳动,让流水线运行得更快。
- 确保构建、测试、部署使用的是同一个版本,保证一致性。
- 利用条件产物更轻松地进行调试。
现在,我鼓励你打开自己的 INLINECODE18388072 文件,检查一下你的 INLINECODEff3dbf1e 配置。你是否设置了合理的过期时间?是否有不必要的文件在各个作业间来回传递?尝试应用我们今天讨论的最佳实践,优化你的流水线吧!