GitLab CI 优化器
分析 GitLab CI/CD 流水线配置以找到速度、成本和可靠性改进。检查 .gitlab-ci.yml 以寻找缓存缺口、缺失的并行性、效率低下的作业排序、臃肿的 Docker 镜像、冗余工作和配置错误的运行器。产生一个具体的优化计划,估计时间节省。使用时: "加速我们的 CI"、 "流水线太长"、 "优化 GitLab CI"、 "审查我们的 .gitlab-ci.yml"、 "减少构建成本"、 "修复不稳定的流水线",或当流水线配置需要改进时。
分析步骤
读取
.gitlab-ci.yml 和任何包含的文件以构建一个完整的图景:
# 找到主 CI 配置
cat .gitlab-ci.yml
# 找到所有包含的 CI 文件
grep -r "include:" .gitlab-ci.yml
find . -name "
.gitlab-ci.yml" -o -name ".gitlab-ci.yml" | head -20
find . -path "
/.gitlab/ci/.yml" | head -20
# 检查 CI/CD 变量是否在文件中定义
grep -E "variables:" .gitlab-ci.yml
# 列出所有作业及其阶段
grep -E "^[a-zA-Z_][a-zA-Z0-9_-]
:" .gitlab-ci.yml | grep -v "^#" | grep -v "stage:" | grep -v "variables:" | grep -v "include:" | grep -v "default:" | grep -v "workflow:"
对于每个作业,提取:名称、阶段、运行器标签、Docker 镜像、需要/依赖项、缓存/构件配置、规则/条件、预估持续时间以及是否在每次提交或仅在特定分支上运行。映射执行流程以找到瓶颈:
阶段 1:构建 [作业-a:3 分钟] [作业-b:5 分钟]
↓
阶段 2:测试 [作业-c:8 分钟,需要:作业-a] [作业-d:2 分钟,需要:作业-b]
↓
阶段 3:部署 [作业-e:1 分钟,需要:作业-c,作业-d]
关键路径:作业-b(5 分钟)→ 作业-d(2 分钟)→ 作业-e(1 分钟)= 8 分钟
或者:作业-a(3 分钟)→ 作业-c(8 分钟)→ 作业-e(1 分钟)= 12 分钟 ← 瓶颈
总墙时间:12 分钟(受最长路径限制)
总计算时间:3 + 5 + 8 + 2 + 1 = 19 分钟(您需要支付的费用)
关键问题:哪个作业在关键路径上最长?(首先优化这个)
是否有作业顺序执行可以并行执行?
是否有作业在同一阶段没有实际依赖关系?检查这些常见的缓存问题:
完全缺失缓存:
# 错误:每次运行都下载所有依赖项
install:
script:
- npm ci
# 好:在运行之间缓存 node_modules
install:
script:
- npm ci
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
policy:pull-push
缓存密钥分析:
密钥策略 何时使用 无效化
$CI_COMMIT_REF_SLUG 分支特定缓存 新分支 = 冷启动 files:[package-lock.json] 依赖缓存 仅当锁文件更改时 $CI_JOB_NAME 作业特定缓存 从不(手动清除) prefix:$CI_COMMIT_REF_SLUG + files:两全其美 分支 + 锁文件更改
缓存策略优化:
仅读取缓存的作业应使用 policy:pull(节省上传时间)。
仅 install 作业应使用 policy:pull-push。
缓存与构件的决定:
缓存 = 尽力而为,速度加快重复运行,可能不可用
构件 = 保证,通过同一流水线的作业传递文件
规则:使用构件来传递下游作业需要的构建输出。
使用缓存来重新下载昂贵的依赖项。问题:使用大型基础镜像
# 错误:1.2 GB 镜像,需要 45 秒才能拉取
build:
image:node:18
# 更好:180 MB 镜像,需要 5 秒才能拉取
build:
image:node:18-alpine
# 最好:预构建镜像,包含您依赖的镜像
build:
image:registry.gitlab.com/my-org/ci-images/node:18
当 before_script 需要 >30s 或您每次运行都安装系统包时,创建自定义 CI 镜像。
使用 GitLab 的依赖代理 (${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:18-alpine)来缓存 Docker Hub 镜像并避免速率限制。用 needs:替换基于阶段的排序以实现并行执行:
# 没有 DAG:test-frontend 等待所有构建作业(包括 build-backend)
# 有 DAG:test-frontend 在 build-frontend 完成后立即开始
test-frontend:
stage:test
needs:[build-frontend]
# 在 build-frontend 完成后立即开始
test-backend:
stage:test
needs:[build-backend]
# 在 build-backend 完成后立即开始
deploy:
stage:deploy
needs:[test-frontend, test-backend]
# 在两个测试都通过后开始
影响:一个有 5 个作业的流水线(3+5+8+2+1 分钟)从 19 分钟的墙时间减少到 12 分钟(37% 更快)。使用 parallel:拆分测试:
test:
stage:test
parallel:4
script:
- TOTAL=$CI_NODE_TOTAL
- INDEX=$CI_NODE_INDEX
- | TEST_FILES=$(find tests/ -name ".test.js" | sort | awk "NR % $TOTAL == $INDEX")
npx jest $TEST_FILES
artifacts:
reports:
junit:junit.xml
使用 parallel:matrix 进行多环境测试(例如,NODE_VERSION:["16", "18")。