背景
在 CI/CD 流水线中,某些 job 会对共享资源(如对象存储、数据库、文件系统)执行写操作。如果多个 pipeline 实例同时触发此类 job,可能导致:
- 数据竞争:两个 job 同时写入同一路径,产生不一致或损坏的数据
- 事务冲突:如
ossutil sync --delete并发执行时,后完成的 job 可能覆盖先完成的 job 的变更
实际场景
我们有一个制品同步项目,包含两个 job:
sync-rpm:同步 RPM 包到 OSS 的 RPM 仓库目录sync-deb:同步 DEB 包到 OSS 的 DEB 仓库目录
需求如下:
| 场景 | 是否允许并发 |
|---|---|
同一 pipeline 的 sync-rpm 与 sync-deb |
|
不同 pipeline 的 sync-rpm |
|
不同 pipeline 的 sync-deb |
解决方案:resource_group
GitLab 提供了 resource_group 关键字,用于限制同一时间只有一个 job 在运行。当多个 job 属于同一个 resource_group 时,GitLab Runner 会串行执行它们,后续的 job 显示 “waiting for resource” 直到锁被释放。
官方文档:Resource group
使用方法
在 .gitlab-ci.yml 的 job 定义中添加 resource_group 字段:
sync-rpm:
stage: sync
resource_group: sync-rpm-lock # 👈 添加 resource_group
image: ...
script: ...
sync-deb:
stage: sync
resource_group: sync-deb-lock # 👈 添加 resource_group
image: ...
script: ...
关键设计:使用不同的 resource_group 名称
| Job | resource_group | 效果 |
|---|---|---|
sync-rpm |
sync-rpm-lock |
跨 pipeline 串行 RPM 同步 |
sync-deb |
sync-deb-lock |
跨 pipeline 串行 DEB 同步 |
为什么不用同一个 resource_group?
如果把两者都设为 sync-lock,同一 pipeline 内的 sync-rpm 和 sync-deb 也会串行执行,但这两个 job 写入的 OSS 目录不同,没有并发冲突,串行会白白增加总耗时。
使用不同的 resource_group 则实现了细粒度的控制:
Pipeline #1:
sync-rpm (sync-rpm-lock) ──────────────┐
sync-deb (sync-deb-lock) ──────────────┤ 并行 ✅
│
Pipeline #2: │
sync-rpm (sync-rpm-lock) ── 等待 ───────┘ 串行 ⏳
sync-deb (sync-deb-lock) ────────────── 并行 ✅(deb lock 空闲)
锁的粒度选择
| 策略 | 配置 | 适用场景 |
|---|---|---|
| 粗粒度锁 | 所有 job 共用同一个 resource_group |
所有 job 写入同一资源,或简单场景不太关心效率 |
| 细粒度锁 | 按资源路径拆分不同 resource_group |
不同 job 操作不同资源,希望最大化并行度 |
| 动态锁 | 使用变量作为 resource_group 名 | 按分支/环境/参数动态隔离 |
动态 resource_group 示例
如果需要按环境或参数进一步细化锁的粒度,可以使用变量:
sync-rpm:
resource_group: sync-rpm-$ENVIRONMENT-lock
# 开发环境和生产环境各自独立锁,互不阻塞
其他并发控制方式
interruptible — 取消旧 pipeline
如果希望新 pipeline 触发时直接取消正在运行的旧 job(而非等待):
sync-rpm:
interruptible: true
resource_group: sync-rpm-lock
当新 pipeline 触发且 resource 被旧 job 占用时,GitLab 会尝试取消旧 job,立即启动新 job。适合"最新的构建结果才重要"的场景。
不同 Stage 串行
如果只关心同一 pipeline 内的顺序,直接使用 stage 即可:
stages:
- sync-rpm
- sync-deb
但这不解决跨 pipeline 并发问题,因此仍需配合 resource_group 使用。
对比总结
| 机制 | 同一 pipeline 内 | 跨 pipeline | 行为 |
|---|---|---|---|
| 不同 stage | 串行 | 按 stage 顺序执行 | |
resource_group |
按需控制 | 同组 job 排队 | |
resource_group + interruptible |
按需控制 | 只保留最新执行 |
我们的最终配置
stages:
- sync
variables:
PROXY_URL: https://gh-proxy.org
BUCKET_NAME: mirrors-xuxiaowei
LANG: C.UTF-8
sync-rpm:
stage: sync
resource_group: sync-rpm-lock # 👈 RPM 同步跨 pipeline 排队
image: $SYNC_RPM_IMAGE
# ... (其他配置不变)
sync-deb:
stage: sync
resource_group: sync-deb-lock # 👈 DEB 同步跨 pipeline 排队
image: $SYNC_DEB_IMAGE
# ... (其他配置不变)
效果:
- 同一 pipeline 中
sync-rpm和sync-deb并行运行(操作不同 OSS 目录) - 不同 pipeline 的同类型 job 排队执行(保护同一 OSS 目录不被并发写入)
- 不同 pipeline 的不同类型 job 互不影响(各自独立的锁)
总结
resource_group 是 GitLab CI/CD 中控制 job 并发执行的简洁有效手段。关键设计要点:
- 按资源粒度拆分 resource_group,而非所有 job 共用一个锁,以实现最大并行度
- 配合
interruptible: true可在新 pipeline 触发时取消旧的等待/运行中的 job - 可以使用变量动态构造 resource_group 名称,实现按环境/分支的隔离
本文对应的配置修改 commit:b4fe3ec