GitLab CI 缓存完全指南:加速构建的终极实践
- 一、缓存的核心价值与工作原理
- 1.1 🟢 什么是缓存?
- 1.2 🔵 缓存 vs 制品:本质区别
- 二、缓存配置基础语法
- 2.1 🟡 cache 关键字基本结构
- 2.2 🟠 缓存键(key)策略
- 2.3 🔴 缓存策略(policy)
- 三、多语言缓存实践
- 3.1 🟣 JavaScript/Node.js 缓存
- 3.2 🔵 Python 缓存
- 3.3 🟡 Ruby 缓存
- 3.4 🟠 Go 缓存
- 四、高级缓存策略
- 4.1 多缓存与回退键
- 4.2 全局回退键
- 4.3 禁用缓存或按作业覆盖
- 4.4 分布式缓存
- 五、完整实践示例
- 六、总结
🌺The Begin🌺点点关注,收藏不迷路🌺 ⬇ ⬇ 底部 ⬇ ⬇ |
⚡ 在 CI/CD 流程中,每次构建都重新下载依赖项是效率最低的行为之一。GitLab CI 的缓存机制通过复用之前下载的内容,能显著缩短流水线执行时间。本文将全面解析缓存的工作原理、配置方法及多语言实践方案。
一、缓存的核心价值与工作原理
1.1 🟢 什么是缓存?
在 GitLab CI 中,缓存是作业下载并保存的一个或多个文件。使用相同缓存的后续作业无需再次下载这些文件,因此运行速度更快 。
💡核心原则:缓存是为了加速你的作业,但它可能不存在,所以不要依赖它来传递必要的构建结果 。缓存是一个优化手段,而非必需的数据传递机制。
1.2 🔵 缓存 vs 制品:本质区别
理解缓存和制品的区别,是正确使用两者的前提:
| 对比维度 | 🟡 缓存(Cache) | 🔴 制品(Artifacts) |
|---|---|---|
| 用途 | 存储从互联网下载的依赖项 | 在阶段间传递中间构建结果 |
| 存储位置 | Runner 本地或分布式缓存(S3) | GitLab 服务器 |
| 跨流水线 | ✅ 后续流水线可复用 | ❌ 同一流水线结束后通常过期 |
| 生命周期 | 长期存在,按 key 复用 | 默认 30 天过期 |
| 依赖关系 | 可选,不存在时重新下载即可 | 必需,后续阶段依赖其结果 |
一句话总结:
缓存是为了"加快速度"但可以不存在,制品是为了"传递结果"所以必须存在。
二、缓存配置基础语法
2.1 🟡 cache 关键字基本结构
在.gitlab-ci.yml中,使用cache关键字定义缓存:
job:cache:key:"$CI_COMMIT_REF_SLUG"# 缓存的唯一标识paths:# 需要缓存的目录或文件-vendor/-node_modules/policy:pull-push# 缓存策略(拉取/推送)核心字段说明:
| 字段 | 作用 | 示例 |
|---|---|---|
key | 缓存的唯一标识,决定不同作业/分支是否共享缓存 | $CI_COMMIT_REF_SLUG |
paths | 需要缓存的文件或目录路径(相对于项目根目录) | - node_modules/ |
policy | 缓存策略:pull(只拉取)、push(只推送)、pull-push(默认) | policy: pull |
2.2 🟠 缓存键(key)策略
缓存键是决定缓存能否被复用的核心。不同的 key 会生成不同的缓存,互不影响。
按分支隔离(推荐):
cache:key:$CI_COMMIT_REF_SLUG# 每个分支独立缓存paths:-node_modules/此配置可防止不同分支意外覆盖缓存 。
按作业和分支隔离:
cache:key:"$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"# 每个作业+分支独立缓存paths:-vendor/按依赖文件计算键(精准失效):
cache:key:files:# 根据文件内容生成哈希作为缓存键-package-lock.jsonpaths:-node_modules/当package-lock.json变化时,缓存自动失效,确保依赖变更时重新下载 。
分支间共享缓存(谨慎使用):
cache:key:"global-cache"# 所有分支共享同一个缓存paths:-shared/2.3 🔴 缓存策略(policy)
policy控制作业如何与缓存交互:
| 策略 | 行为 | 适用场景 |
|---|---|---|
pull-push(默认) | 拉取已有缓存,执行后推送更新 | 大多数场景 |
pull | 只拉取不推送 | 只读作业(如测试),避免不必要的缓存写入 |
push | 不拉取,执行后推送 | 负责生成缓存的专用作业 |
实用示例:条件化缓存策略
根据分支动态调整策略,减少默认分支的缓存写入开销 :
conditional-policy:rules:-if:$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHvariables:POLICY:pull-push-if:$CI_COMMIT_BRANCH!=$CI_DEFAULT_BRANCHvariables:POLICY:pullcache:key:gemspolicy:$POLICYpaths:-vendor/bundlescript:-echo "缓存策略根据分支动态变化"三、多语言缓存实践
3.1 🟣 JavaScript/Node.js 缓存
npm 项目:将 npm 缓存目录放在项目内,按分支缓存 :
default:image:node:latestcache:key:$CI_COMMIT_REF_SLUGpaths:-.npm/before_script:-npm ci--cache .npm--prefer-offline# 使用缓存离线安装基于锁文件计算键(推荐):
cache:key:files:-package-lock.jsonpaths:-node_modules/Yarn 项目:使用yarn-offline-mirror缓存压缩包 :
cache:key:files:-yarn.lockpaths:-.yarn-cache/3.2 🔵 Python 缓存
pip 项目:设置PIP_CACHE_DIR到项目内目录 :
variables:PIP_CACHE_DIR:"$CI_PROJECT_DIR/.cache/pip"cache:key:files:-requirements.txtpaths:-.cache/pip-venv/3.3 🟡 Ruby 缓存
Bundler 项目:将 gem 安装到项目内目录并缓存 :
cache:key:files:-Gemfile.lockprefix:$CI_JOB_NAME# 不同作业使用不同缓存前缀paths:-vendor/rubybefore_script:-bundle config set--local path 'vendor/ruby'-bundle install-j $(nproc)3.4 🟠 Go 缓存
Go Modules:设置GOPATH到项目内目录 :
variables:GOPATH:$CI_PROJECT_DIR/.gocache:paths:-.go/pkg/mod/# 缓存 Go 模块四、高级缓存策略
4.1 多缓存与回退键
使用多个缓存(每个作业最多4个):
test-job:cache:-key:files:-Gemfile.lockpaths:-vendor/ruby-key:files:-yarn.lockpaths:-.yarn-cache/回退缓存键:当找不到指定缓存时,尝试使用回退缓存 :
test-job:cache:-key:cache-$CI_COMMIT_REF_SLUGfallback_keys:-cache-$CI_DEFAULT_BRANCH# 主分支缓存作为回退-cache-default# 最终回退paths:-vendor/ruby这确保了新分支能复用主分支的缓存,加速首次构建。
4.2 全局回退键
使用CACHE_FALLBACK_KEY变量指定全局回退缓存 :
variables:CACHE_FALLBACK_KEY:global-fallbackjob:cache:key:"$CI_COMMIT_REF_SLUG"paths:-binaries/4.3 禁用缓存或按作业覆盖
完全禁用缓存:
job:cache:[]# 空列表表示该作业不使用缓存继承全局配置并覆盖特定设置(使用 YAML 锚点):
default:cache:&global_cachekey:$CI_COMMIT_REF_SLUGpaths:-node_modules/policy:pull-pushjob:cache:<<:*global_cache# 继承所有设置policy:pull# 仅覆盖 policy4.4 分布式缓存
如果使用多个 Runner,需要启用分布式缓存以确保在不同 Runner 间共享缓存 :
在config.toml中配置 S3 作为共享缓存后端:
[[runners]] [runners.cache] Type = "s3" Path = "bucket/path/prefix" Shared = true [runners.cache.s3] BucketName = "foobar" AccessKey = "<changeme>" SecretKey = "<changeme>"GitLab.com 的共享 Runner 即采用分布式缓存方式 。
五、完整实践示例
以下是一个综合配置示例,结合了多语言缓存和最佳实践:
stages:-deps-test-builddefault:image:node:latestvariables:PIP_CACHE_DIR:"$CI_PROJECT_DIR/.cache/pip"GOPATH:$CI_PROJECT_DIR/.go# 安装依赖的专用作业(推送缓存)install deps:stage:depsscript:-npm install jest-junitcache:-key:$CI_COMMIT_REF_SLUGpaths:-node_modules/-key:files:-requirements.txtpaths:-.cache/pippolicy:push# 测试作业(只拉取缓存)test:stage:testbefore_script:-npm install-g jestscript:-jest binarysearch.test.jscache:key:$CI_COMMIT_REF_SLUGpaths:-node_modules/policy:pull# Python 测试(独立缓存)python-test:stage:testimage:python:latestcache:key:files:-requirements.txtpaths:-.cache/pipbefore_script:-pip install-r requirements.txt--cache-dir .cache/pipscript:-pytest .六、总结
| 实践要点 | 说明 |
|---|---|
| 🟢缓存定位 | 缓存是为了加速构建,不是必需的传递机制 |
| 🔵键设计 | 使用$CI_COMMIT_REF_SLUG按分支隔离,或用files基于依赖文件生成键 |
| 🟡多缓存 | 每个作业最多4个缓存,可分别缓存不同依赖 |
| 🟠策略选择 | 用pull策略避免不必要的缓存写入 |
| 🔴分布式 | 多 Runner 环境下启用 S3 分布式缓存 |
| 🟣回退键 | 通过fallback_keys让新分支复用主分支缓存 |
🔑核心启示:GitLab CI 缓存的本质是用空间换时间——通过复用依赖文件换取更快的构建速度。合理的缓存键设计是成功的关键:过于宽泛会导致缓存污染,过于精细则命中率低。推荐使用
files结合锁文件的方式,在依赖变更时自动失效,在依赖不变时稳定命中。
🌺The End🌺点点关注,收藏不迷路🌺 ⬆ ⬆ 顶部 ⬆ ⬆ |