Jenkins 如何在两周内慢慢耗尽我们的 EFS Burst Credits
I’m happy to translate the article for you, but I’ll need the full text of the post (the body of the article) in order to do so. Could you please paste the article’s content here? Once I have the text, I’ll provide a Simplified‑Chinese translation while preserving the source line, formatting, markdown, and any code blocks or URLs unchanged.
TL;DR
我们的 Jenkins 在 1/26 开始出现故障,但根本原因始于 1/13。我们发现了三个相互叠加的问题:
- Shared Library cache 被禁用(已有问题)
- 切换到 disposable agents(1/13 的更改)
- Increased build frequency(新年效应)
结果: ≈ 50× 元数据 IOPS 增加 → EFS 突发信用在两周内耗尽。
为什么你应该在意
如果你在 EFS 上运行 Jenkins,可能会遇到这种情况。症状会突然出现,但根本原因往往在几周前就已经埋下。对指标进行时间序列分析至关重要。
谜团:症状 vs. 根本原因
之前,我写过 Jenkins 变慢、Git 克隆开始失败的情况。我们发现约 15 GB 的 Git 临时文件(tmp_pack_*)在 EFS 上累积,导致元数据 IOPS 用尽。
我们通过弹性吞吐量和清理任务解决了它。案子就此结束,对吧?
并非如此。
当我查看 EFS 突发信用余额 图表时,注意到了一件重要的事:
信用额度大约在 1 月 13 日开始下降,但症状却在 1 月 26 日出现。
时间线
| 日期 | 事件 |
|---|---|
| 1/13 | 信用额度开始下降 |
| 1/19 | 快速下降 |
| 1/26 | 信用额度降至底部 |
| 1/26‑27 | 症状出现 |
tmp_pack_* 的累积是一个 症状,而非根本原因。1 月 13 日 有所变化。
1月13日有什么变化?
老实说,这让我很困惑。我有一些想法,但没有确定的结论:
1. Agent 架构变更
大约在 1 月 13 日,我们更改了 Jenkins 代理策略:
| 之前(共享代理) | 之后(一次性代理) |
|---|---|
| EC2 type: c5.large, 等 | EC2 type: t3.small, 等 |
| 多个作业共享代理 | 每个作业使用一个代理,使用后销毁 |
| 工作区复用 | 每次完整 git clone |
使用 git pull 进行增量更新 | 每次使用 git clone 完整克隆 |
2. 新年后开发冲刺
团队在新年假期后加速开发,导致整体 Jenkins 负载增加。
数学计算:元数据 IOPS 提升 50 倍
Builds per day: 50 (estimated)
Files created per clone: 5,000
Shared‑agent approach:
Clone once = 5,000 metadata operations
Disposable‑agent approach:
50 builds × 5,000 files = 250,000 metadata operations/day
≈ 元数据 IOPS 提升约 50 倍。
再加上新年高峰,数字会更糟。
理解 Jenkins 中的 Git 缓存
在调查过程中,我注意到目录:
/mnt/efs/jenkins/caches/git-3e9b32912840757a720f39230c221f0e
这是 Jenkins Git 插件的裸仓库缓存。
Git 缓存工作原理
插件通过以下方式优化克隆:
- 将远程仓库以裸仓库的形式缓存到
/mnt/efs/jenkins/caches/git-{hash}/中。 - 使用
git clone --reference从该缓存克隆到作业工作区。 - 根据 仓库 URL + 分支 生成哈希值。
问题: 一次性代理可能无法受益于此缓存,因为它们在每次构建时都是全新的。
关键线索:tmp_pack_* 所在位置
我重新查看了 tmp_pack_* 文件的存放位置:
jobs/sample-job/jobs/sample-pipeline/builds/104/libs/
└── 335abf.../root/.git/objects/pack/
└── tmp_pack_WqmOyE ← 100‑300 MB
这些位于 每个构建的目录 中:
jobs/sample-job/jobs/sample-pipeline/
└── builds/
├── 104/
│ └── libs/.../tmp_pack_WqmOyE
├── 105/
│ └── libs/.../tmp_pack_XYZ123
└── 106/
└── libs/.../tmp_pack_ABC456
每次构建 都会重新检出流水线共享库,导致每次生成 tmp_pack_*。
问题: 为什么共享库会在每次构建时都被获取?
根本原因:缓存设置关闭
在 Jenkins 配置中我发现了关键线索:
共享库设置 “在控制器上缓存获取的版本以便快速检索” 未勾选。
后果:
- 共享库缓存完全被禁用。
- 每次构建都会从远程仓库完整拉取。
- 临时文件生成在
.git/objects/pack/中。 - 大量的元数据 IOPS 消耗。
修复方案:启用缓存
- 启用 “在控制器上缓存获取的版本以便快速检索”。
- 将 Refresh time in minutes 设置为 180 分钟。
选择刷新时间
| 刷新间隔 | 效果 |
|---|---|
| 60‑120 分钟 | 快速更新,适度降低 IOPS |
| 180 分钟 (3 小时) | 平衡 – 大约每天 8 次更新 |
| 360 分钟 (6 小时) | 稳定 – 大约每天 4 次更新 |
| 1440 分钟 (24 小时) | 最大 IOPS 降低 |
为什么选择 180 分钟?
- 更新检查约每天 8 次(上午 9 点、12 点、下午 3 点、6 点……)。
- 共享库的更改在半天内反映是可以接受的。
- 显著降低 IOPS(每 3 小时一次,而不是每次构建)。
- 紧急更改可以通过 “强制刷新” 功能强制执行。
我已在运行手册中记录此内容,以免忘记。
衡量影响
| 期间 | 预期观察 |
|---|---|
| 短期 (24‑48 h) | 没有新的 tmp_pack_* 文件;元数据 IOPS 下降 |
| 中期 (1 week) | 突发信用余额恢复趋势;构建性能稳定 |
| 长期 (1 month) | 信用保持稳定;未出现重复 |
Lessons Learned
1. 症状 ≠ 根本原因 时间线
- 症状出现时间: 1/26‑1/27
- 根本原因出现时间: 约 1/13
- 信用额度耗尽: 两周内逐渐耗尽
时间序列分析至关重要。 只解决可见症状会导致表面化的解决方案。
2. 架构变更隐藏成本
一次性代理的更换降低了 EC2 成本,但在其他地方产生了问题。
进行架构变更时:
- 事先评估性能影响。
- 建立合适的监控。
3. EFS 元数据 IOPS 特性
- 大量创建/删除小文件极其致命。
- 文件数量比存储容量更重要。
- 爆发模式需要信用额度管理。
- 信用额度耗尽是逐步发生的。
尤其是
.git/objects/中包含成千上万的小文件时,其行为与普通文件 I/O 完全不同。
4. 复合根本原因
此问题并非单一原因,而是三因素的组合:
- 共享库缓存被禁用(原有问题)
- 一次性代理切换(1/13)
- 构建次数增加(新年期间)
单独来看每个因素可能不足以导致严重问题,但它们叠加后超出了临界阈值。
未解答的问题
虽然我们启用了共享库缓存,但仍在使用一次性代理。
一次性代理能否有效利用代理端的 Git 缓存?
可能的方案:
- 在所有代理之间共享 EFS Git 缓存
- 稍微延长代理生命周期,以便在作业之间复用
- 将缓存存放在 S3 并在启动时同步
在成本与性能之间找到合适的平衡仍然是一个挑战。
我在博客中更多地写技术决策和工程实践。欢迎查看。