常见 Jenkins 性能瓶颈及修复方法
Jenkins 实例运行缓慢?本指南深入探讨常见性能瓶颈,包括内存泄漏、磁盘空间问题和过度日志记录。学习如何识别症状、理解根本原因,并实施 JVM 调优、智能构建历史管理、日志优化和高效流水线编码等可行解决方案。发现必要的监控工具和最佳实践,确保 CI/CD 流水线平稳运行,实现更快的构建、响应式 UI 和更高效的软件交付流程。
常见 Jenkins 性能瓶颈及修复方法
一个缓慢的 Jenkins 实例通常不是由单一原因造成的。UI 响应迟钝,构建在队列中等待,代理断开连接,日志打开耗时漫长,有人会说“Jenkins 又挂了”。在这种抱怨背后,问题通常源于几个常见的瓶颈:控制器堆压力、磁盘速度慢、代理过载、插件问题、不良的流水线行为,以及到源代码控制和工件系统的网络延迟。
修复 Jenkins 性能最快的方法是将控制器问题与构建问题分开。如果即使没有构建运行,Jenkins UI、队列和任务页面也很慢,请从控制器开始排查。如果 UI 正常但构建耗时过长,请从代理、工作区、缓存和外部系统开始排查。
控制器内存与垃圾回收
Jenkins 控制器是一个 Java 进程。它需要足够的堆内存来处理任务配置、插件、构建元数据、队列状态和 Web 请求。当堆内存过小时,控制器会花费过多时间进行垃圾回收。当某个插件泄漏内存或在内存中存储过多数据时,增加堆内存可能只会推迟下一次故障的发生。
症状包括 UI 响应缓慢、长时间暂停、OutOfMemoryError、频繁的代理断开连接,或与可用执行器数量不匹配的构建队列延迟。
首先检查进程和日志:
ps -o pid,rss,vsz,etime,cmd -C java
journalctl -u jenkins --since "2 hours ago" | grep -Ei 'OutOfMemory|GC overhead|heap|killed'
对于中等规模的控制器,2 到 4 GB 的堆内存可能就足够了。繁忙的安装可能需要更多。不要盲目地将堆内存设置为机器大部分 RAM。操作系统仍然需要内存用于文件系统缓存、进程开销和监控代理。
典型的服务选项如下所示:
JENKINS_JAVA_OPTS="-Xms1g -Xmx4g -XX:+UseG1GC"
更改 JVM 选项后,在维护窗口期间重启 Jenkins,并观察正常负载下的行为。如果内存连续数天稳步增长且从未稳定,请获取堆转储并检查最近更新或安装的插件。保持插件最新,但避免在没有回滚计划的情况下更新大量插件集。
磁盘空间与磁盘 I/O
Jenkins 持续使用磁盘。JENKINS_HOME 存储任务配置、构建记录、指纹、插件数据、密钥、日志,有时还有过多的工件。代理使用磁盘存储工作区、依赖缓存、Docker 层、测试报告和临时文件。
磁盘已满很明显。磁盘速度慢则更令人烦恼,因为看起来一切正常,但所有操作都在等待。
检查容量和延迟:
df -h
du -sh /var/lib/jenkins/* 2>/dev/null | sort -h | tail
iostat -xz 1
如果在构建期间 %util 和 await 时间很高,则磁盘是瓶颈。常见的修复方法包括将工作区迁移到更快的存储、清理旧的 Docker 层、减少工件保留时间,以及阻止任务在仅需要报告或包时归档整个目录。
在任务上设置构建丢弃策略:
options {
buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '10'))
}
在 JENKINS_HOME 中手动清理时要小心。不要在 Jenkins 运行时删除随机的 XML 文件或插件目录。请使用 Jenkins 的保留设置、特定插件的清理工具和备份。
控制器上工作过多
最具破坏性的配置之一是在控制器上运行构建。控制器不应进行编译、运行测试、构建 Docker 镜像或执行大型检出操作。
在大多数安装中,将控制器执行器设置为 0。将构建放在代理上。如果您已经在控制器上运行构建,请逐步迁移它们,并注意隐藏的假设,例如本地工具路径、凭据绑定或仅存在于控制器上的文件。
同时检查流水线中控制器密集型的 Groovy 代码。诸如 sh 之类的流水线步骤在代理上运行,但 Jenkinsfile 中的 Groovy 逻辑可能在控制器上运行。避免将大文件读入 Groovy 变量、构建大型映射或在流水线脚本中进行大型 JSON 处理。对于繁重的数据处理,请在代理上使用 shell、Python、jq 或您的构建工具。
代理过载或不匹配
如果某个标签的队列时间很长,添加通用执行器将无济于事。需要 linux && docker && large-memory 的任务需要精确匹配该容量。
查看队列原因和标签使用情况。然后检查代理操作系统:
uptime
free -h
mpstat 1
iostat -xz 1
docker system df
如果代理正在交换,请减少执行器或增加内存。如果 CPU 使用率很高且构建持续时间在繁忙时段增加,请减少并发或添加代理。如果 I/O 等待很高,请将缓存和工作区迁移到更快的存储,或降低该节点上的并发任务数。
对于 Kubernetes 代理,资源请求与 Jenkins 执行器计数同样重要。请求过少 CPU 或内存的 Pod 可能会被调度到已经繁忙的节点上,然后 Jenkins 会看到一个就绪的代理,但构建运行缓慢。对于一次性 Pod,每个 Pod 一个执行器通常比多个执行器共享同一个容器更容易管理。
插件问题
插件是 Jenkins 的优势之一,也是性能问题的常见来源。插件可能增加页面渲染成本、减慢任务加载速度、保留过多构建历史,或在正常 UI 操作期间进行外部调用。
当性能突然变化时,询问最近发生了什么变化:
- Jenkins 核心升级。
- 插件升级。
- 新插件安装。
- 新的全局配置。
- 新的流水线库版本。
使用“管理 Jenkins”健康信息、日志和插件变更日志。如果 UI 在插件更新后变慢,请在测试控制器中测试回滚(如果有)。在进行大型升级之前,请备份 JENKINS_HOME 和插件版本。
不要“以防万一”保留插件。每个安装的插件都会增加维护负担。在检查任务依赖关系后,删除未使用的插件。
SCM 和工件仓库延迟
许多“Jenkins 很慢”的报告实际上是 Git、包注册表、容器注册表或工件仓库的问题。
检查构建日志中重复的慢速步骤:
git fetch
mvn dependency:resolve
npm ci
docker pull
docker push
archiveArtifacts
如果每个任务都等待依赖下载,请添加附近的代理或缓存。如果 git fetch 很慢,请检查仓库大小、分支发现、浅克隆设置以及从代理到 Git 服务器的网络路径。如果 Docker 拉取在临时代理上很慢,请使用注册表镜像或 BuildKit 注册表缓存。
保持诊断诚实:Jenkins 调度工作,但它无法使遥远的包注册表变快。
日志和构建历史膨胀
大型控制台日志会减慢页面渲染速度并占用存储空间。在正常构建期间打印每个测试夹具、每个 HTTP 响应或完整调试日志的任务最终会使 Jenkins 难以使用。
首先修复任务。降低正常日志详细程度,并仅在需要时将详细日志作为压缩工件归档。保持控制台输出专注于进度和失败上下文。
然后设置保留策略:
options {
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '50'))
}
对于合规性要求高的环境,将长期工件和日志迁移到专为保留、搜索和生命周期策略设计的外部存储系统。
实用的事件处理路径
当 Jenkins 当前运行缓慢时,请按以下顺序操作:
- 检查控制器 UI 是否缓慢。
- 检查控制器 CPU、内存、GC 症状和磁盘。
- 检查队列原因以及哪些标签在等待。
- 检查最繁忙代理的 CPU、内存、磁盘和工作区增长情况。
- 比较最近的插件、任务和共享库更改。
- 阅读一个慢速构建日志,并识别重复的昂贵步骤。
该路径可防止随机调优。增加堆内存无法修复饱和的 Docker 代理。添加执行器无法修复已满的磁盘。清理工作区无法修复导致控制器暂停的插件。
保持 Jenkins 可维护性
健康的 Jenkins 安装具有一些常规习惯:控制器执行器设置为零,代理根据其工作负载调整大小,配置构建保留,依赖缓存有目的性,插件更新被跟踪,并将基本指标导出到 Prometheus、Grafana、CloudWatch 或团队已使用的任何监控系统。
最好的修复通常是微小且具体的。将 Docker 构建迁移到专用代理。减少嘈杂任务的日志输出。添加 Maven 代理。减少交换节点上的执行器。删除未使用的插件。为多年来保留所有构建的任务设置保留策略。
当您不再将 Jenkins 视为一个黑盒,而是开始跟踪工作流程时,Jenkins 性能就会得到改善:从队列到控制器,到代理,到文件系统,到网络依赖,再回到构建日志。
示例:构建慢但 Jenkins 正常
假设开发人员报告拉取请求检查需要 25 分钟。Jenkins UI 响应迅速。队列很短。代理在线。慢速日志显示:
git fetch: 20 seconds
npm ci: 9 minutes
unit tests: 4 minutes
docker build: 10 minutes
archive artifacts: 1 minute
这主要不是 Jenkins 控制器的问题。可能的修复方法是包缓存、Dockerfile 层排序、BuildKit 缓存,以及可能拆分测试。增加控制器堆内存不会改变任何东西。
示例:所有任务都在等待但代理空闲
如果任务在队列中而代理似乎空闲,请阅读队列原因。您可能会发现任务需要 linux && docker,而空闲代理只有 linux。或者任务可能被 disableConcurrentBuilds、可锁定资源或无法配置匹配代理的云插件阻塞。
这种瓶颈是配置问题,而不是原始容量问题。添加两个不匹配的代理无济于事。
示例:控制器每天下午变慢
如果 UI 在每天同一时间变差,请查找计划任务:分支索引、备份、大型工件清理、漏洞扫描或过早开始的夜间流水线。检查该时间段内的控制器 CPU、堆和磁盘 I/O。还要检查是否由于 cron 表达式(如 0 2 * * *)导致许多任务在同一分钟开始。
在 Jenkins 调度中,尽可能使用哈希时间:
H 2 * * *
这样可以分散任务,而不是让所有任务在整点开始。
良好的监控应回答的问题
至少,监控应回答以下问题,而无需登录服务器:
- 控制器进程是否存活且响应?
- 使用了多少堆内存,垃圾回收运行的频率如何?
- 任务在队列中按标签等待了多长时间?
- 哪些代理离线或反复重新连接?
- 控制器和代理磁盘是否接近满?
- 相同任务的构建是否随时间变慢?
您不需要一个完美的仪表板来开始。即使只有几个指标和针对磁盘、堆、队列长度和代理可用性的警报,也能在开发人员报告之前捕获许多故障。