大促前的压测档口,我们那个日均订单量 50 万+ 的电商订单系统,Jenkins 还跑在 2.303.1 LTS 版本上,构建一次后端 Java 服务平均耗时 23 分钟。大促期间并行构建 8 个服务时,Master 节点 CPU 直接飙到 98%,内存占用 14GB(服务器总内存 16GB),有 3 次直接把 Master 搞挂,导致测试环境部署中断了 40 分钟。
我排查了整整两天,发现老版本 Jenkins 的 Master 节点在并行调度任务时,Groovy 脚本解析和任务队列管理的开销特别大。当时我们用的还是静态 Agent,8 台 Agent 机器(每台 4 核 8G)常年空载率 60%,但构建高峰期又不够用,只能手动加机器,运维成本极高。
我们直接升级到 Jenkins LTS 2.440.1(2024 年 2 月 28 日发布的最新 LTS 版本),这个版本对分布式调度的线程池做了优化,还修复了之前 Master 节点内存泄漏的老 bug。升级过程没想象中复杂,先备份了 JENKINS_HOME 目录,然后替换 war 包重启,插件批量更新到兼容版本,整个过程停服 15 分钟。
升级后我们做了两组对比测试,都是同样的 8 个后端服务(每个服务代码量约 12 万行,Maven 构建),同样的 16 核 32G Master 节点配置:
| 指标 | 2.303.1 LTS | 2.440.1 LTS | 变化 |
|------|-------------|-------------|------|
| 单服务平均构建耗时 | 23 分钟 | 14 分钟 | 降低 39% |
| 8 服务并行构建总耗时 | 28 分钟 | 17 分钟 | 降低 39% |
| Master 节点峰值 CPU | 98% | 62% | 降低 36% |
| Master 节点峰值内存 | 14GB | 8.2GB | 降低 41% |
| Agent 资源利用率 | 40% | 78% | 提升 38% |
性能提升的核心原因有两个:一是 2.440.1 版本优化了 Pipeline 的 Groovy 引擎,之前每个构建任务启动时要加载 200+ 个 Groovy 类,现在做了懒加载,启动时间从 45 秒降到了 12 秒;二是新版本支持 Agent 的细粒度资源分配,我们之前静态 Agent 每个任务固定占 2 核 4G,现在可以按任务实际需求分配,比如单元测试任务只给 1 核 2G,构建任务给 2 核 4G,资源浪费少了很多。
我们有个商品搜索服务,之前构建时要跑 1200+ 个单元测试,老版本下这部分就占了 11 分钟。升级后我调整了 Jenkinsfile 里的测试并行配置,结合新版本的并行任务调度优化,测试阶段直接降到了 6 分钟。
这次升级也踩了个 Java 版本的坑:2.440.1 LTS 要求 Java 11 及以上,我们之前 Master 跑的是 Java 8,升级后 Jenkins 启动直接报错。后来把 Master 的 Java 换成 Java 17(Jenkins 官方推荐 LTS 版本搭配 Java 17),插件也更新到支持 Java 17 的版本才解决。如果你们要升级,一定要先检查 Java 版本,不然白忙活。
我们团队去年做技术选型时,争议特别大。当时业务日活已经突破 120 万,后端有 42 个微服务,前端 6 个项目,测试环境每天构建次数超过 200 次。一部分年轻开发想换 GitHub Actions,说配置简单、界面好看;我坚持用 Jenkins,最后用三个实际场景的数据说服了大家。
第一个场景是多环境部署的权限控制。我们有开发、测试、预发、生产四个环境,生产环境部署必须经过运维负责人审批。GitHub Actions 的权限体系是按仓库来的,要实现“开发只能触发测试环境部署,运维才能触发生产部署”,得给每个仓库配复杂的分支保护和 secret 权限,42 个仓库配下来要花 3 天,还容易配错。而 Jenkins 装个 Role-based Authorization Strategy 插件,10 分钟就能搞定:
第二个场景是存量构建脚本的迁移成本。我们之前 3 年积累了 120+ 个 Jenkinsfile,里面有很多自定义 Groovy 函数、Shared Libraries,还有和内部运维平台(比如自研的发布系统、监控平台)的集成脚本。如果换 GitHub Actions,这些脚本要全部改成 YAML 格式,还要重新对接内部系统,保守估计要 2 个开发做 1 个月。而 Jenkins 2.440.1 完全兼容这些旧脚本,迁移当天就全部跑通了。
第三个场景是构建资源的成本控制。我们业务高峰期(比如大促前)每天构建次数会涨到 500+ 次,GitHub Actions 的公开仓库免费额度是 2000 分钟/月,私有仓库只有 500 分钟/月,超出的部分按 $0.008/分钟 收费,一个月下来要多花近 $2000。而我们用 Jenkins 的静态 Agent + 动态 K8s Pod,服务器是之前采购的闲置物理机,边际成本几乎为 0。
当然 Jenkins 也有缺点,比如 UI 确实不如 GitHub Actions 好看,我们后来装了 Blue Ocean 插件(Jenkins 的现代化 UI 插件),流水线可视化效果好了很多,开发也能直观看到每个阶段的耗时。
还有个实际问题:之前有次线上接口突然变慢,排查下来发现是某次构建把测试环境的配置打到了预发环境。后来我在 Jenkins 里加了参数化构建的强制校验,生产环境部署必须手动输入“CONFIRM_PROD”参数,不然流水线直接终止,再也没出过这种低级错误。
现在我们的 Jenkins 跑了 4 个月,每天 200+ 次构建,稳定性 99.95%,没出过一次因为 CI 工具本身导致的部署故障。对于百万日活以上的业务,Jenkins 的成熟度、灵活性和可控性,确实比 GitHub Actions 更适合。
我们现在的测试环境构建,全用的是 K8s 动态 Pod 作为 Agent,不用再维护静态 Agent 机器了。之前静态 Agent 每次构建完都会残留 Maven 依赖包、构建产物,磁盘经常满,每周都要手动清理一次。换成动态 Pod 后,每个构建任务启动一个独立的 Pod,构建完自动销毁,磁盘占用直接降为 0。
我先在 Jenkins 2.440.1 里装了 Kubernetes Plugin,然后配置 K8s 云,核心是把 Jenkins Master 的地址和 K8s 集群的凭证配好。这里有个细节:Pod 的模板要提前定义好,比如 Maven 构建的 Pod 要包含 Maven 镜像、JDK 环境,还要挂载宿主机的 Docker sock,方便构建镜像。
下面是我们订单服务的完整声明式 Jenkinsfile,覆盖“拉代码 -> 单元测试 -> 构建镜像 -> 推镜像到 Harbor -> 部署到 K8s 测试环境”全流程,每个步骤都有实际场景的注释:
这个流水线跑下来,从拉代码到部署完成平均 8 分钟,比之前静态 Agent 快了 6 分钟。有个要注意的点:Maven 依赖的 PVC 要提前创建好,不然每次构建都重新下载依赖,光下载就要 5 分钟。我们用的 PVC 是 100GB 的 SSD 存储,Maven 依赖存一次后,后续构建直接复用,依赖下载阶段基本可以跳过。
之前遇到过 Pod 启动失败的问题,排查下来是 K8s 集群的资源不够,动态 Pod 抢不到 CPU 和内存。后来我给 Pod 模板加了资源限制,同时调整了 Jenkins 的 K8s 云配置,设置了 Pod 的最大并发数为 10,超过的任务排队等待,再也没出现过 Pod 启动失败的情况。
去年我们团队接手了一个单体架构的电商核心系统,Jenkins流水线随着业务迭代越来越臃肿。当时我们运行的是Jenkins LTS 2.401.3,每个微服务的Jenkinsfile里都塞满了重复的Docker构建、SonarQube扫描和钉钉通知逻辑。最夸张的一个文件超过了600行Groovy代码。在一次大促前的压测准备中,我发现仅仅是修改了一行代码,触发构建后竟然需要等待12分钟才能看到结果。我分析日志发现,Jenkins每次启动Pipeline时,都会重新解析这些冗长的脚本,且大量重复的sh步骤产生了极高的上下文切换开销。原因在于Jenkins的Pipeline引擎在运行Groovy脚本时,会将脚本编译成字节码,如果脚本逻辑复杂且重复,类加载和编译时间会显著增加。
解决方案是将通用逻辑抽离成Shared Libraries。Shared Libraries允许我们将流水线逻辑作为独立的代码仓库进行管理,Jenkins在运行时动态加载。我创建了一个名为company-pipeline-lib的Git仓库,将通用的构建、测试、部署方法封装成全局变量。
例如,我们将通用的Docker构建逻辑封装如下:
在Jenkinsfile中,我们不再写冗长的Shell,而是直接调用:
除了代码复用,我还针对Groovy脚本本身做了优化。之前我们在Jenkinsfile里大量使用readFile读取配置,然后解析JSON。我将其改为在Shared Library中使用@NonCPS注解的方法来处理非序列化对象,避免Jenkins在Pipeline步骤间保存状态时的开销。经过这一轮重构,我们那个订单服务的构建时间从平均12分钟降低到了8分钟左右,优化幅度达到了33%。这不仅仅是因为代码变少了,更是因为减少了Jenkins Master节点解析脚本的CPU消耗。如果不做这种拆分,随着服务数量增加到30个,Jenkins Master的CPU负载会直接飙升至90%以上,导致整个团队的构建队列堵塞。
今年年初,为了适配Spring Boot 3.0对于Java 17的最低要求,我们决定将Jenkins LTS从2.361.4(运行在Java 11上)升级到最新的2.440.1 LTS(发布于2024年2月28日),并同步将运行环境切换到Java 17。我原本以为这是一个平滑的过渡,毕竟Jenkins官方早已宣布支持Java 17。然而,升级重启后的场景让我冷汗直流:Jenkins启动成功,但所有的构建任务都无法执行,日志中疯狂抛出java.lang.reflect.InaccessibleObjectException。
原因在于Java 17引入了强封装特性,默认禁止了通过反射访问JDK内部API。而当时我们使用的Role-based Authorization Strategy插件版本较旧,它在初始化权限上下文时试图通过反射修改java.lang.Class的私有字段。在Java 11下这只是一个警告,但在Java 17下直接导致了插件加载失败,进而导致权限系统瘫痪,所有用户都无法触发构建。
排查过程非常痛苦。我首先检查了Jenkins的启动日志,发现插件加载顺序虽然正常,但在初始化RoleBasedAuthorizationStrategy时抛出了异常。我尝试回滚Java版本,虽然能恢复服务,但无法满足业务对Java 17的需求。解决方案是必须升级插件。我登录到Jenkins插件管理页面,发现Role-based Authorization Strategy有一个更新提示,版本从3.x升级到了4.x。更新日志明确写着“Added support for Java 17+ by removing internal reflection”。
升级插件后,我遇到了第二个问题:权限隔离失效。原本配置好的“开发组只能看开发项目”的规则全部失效,开发人员能看到所有Job。我检查了config.xml中的权限配置,发现新版本的插件对Global Roles和Item Roles的解析逻辑发生了变化。我不得不重新配置权限矩阵。
具体配置逻辑如下,我通过脚本化方式重新定义了权限,确保细粒度控制:
这次事故让我深刻意识到,在升级Jenkins核心版本尤其是跨越Java大版本时,不能只看核心兼容性,必须检查所有关键插件的兼容性。如果不做这一步,就像我们当时一样,整个CI/CD流水线会直接停摆,影响全公司的发版节奏。现在我的标准操作流程是:先在测试环境用Java 17跑一遍LTS 2.440.1,利用Jenkins的插件兼容性检查工具扫描一遍,确认没有InaccessibleObjectException风险后再动生产环境。
随着Kubernetes成为我们生产环境的标准基础设施,传统的Jenkins直接通过kubectl部署应用的方式暴露出了诸多问题。去年下半年,我们试图将Jenkins LTS 2.440.1与Argo CD结合,实现GitOps工作流。之前的模式是Jenkins构建完镜像后,直接SSH到跳板机执行kubectl set image,这种方式不仅难以追溯变更历史,还因为Jenkins Agent需要配置K8s集群凭证而存在巨大的安全风险。
原因在于Jenkins本质上是一个CI工具,它的强项在于构建和测试,而在CD(持续部署)的声明式管理和状态同步方面,它不如Argo CD这种专为Kubernetes设计的工具。解决方案是调整职责边界:Jenkins只负责CI部分(代码拉取、编译、镜像构建、推送),而CD部分完全交给Argo CD。
具体的落地流程是这样的:Jenkins在构建成功后,不再直接部署,而是更新Git仓库中的Kubernetes Manifest文件。我们维护了一个名为infra-gitops的仓库,里面存放着所有服务的Helm Chart或Kustomize配置。Jenkins通过git push修改其中的镜像Tag。
以下是我们在Jenkinsfile中实现的GitOps更新逻辑:
Jenkins完成这一步后,剩下的事情就交给Argo CD。Argo CD会监听infra-gitops仓库的变化,检测到values.yaml的更新后,自动同步到Kubernetes集群。
这种架构的优势在于解耦。有一次,我们的Jenkins Master因为磁盘满导致服务中断,但因为镜像已经构建完成并推送,且GitOps仓库已经更新,Argo CD依然在半小时后成功将新版本部署到了测试环境,没有造成阻塞。如果不采用这种分离模式,Jenkins挂掉意味着整个发布流程终止。
结合2024年的技术趋势,Jenkins正在向无状态、容器化方向演进。我们在配置Jenkins on Kubernetes时,使用了动态Agent(Pod Templates),构建任务结束后Pod自动销毁,资源利用率提升了40%以上。这种Jenkins做CI、Argo CD做CD的组合,是目前云原生场景下最稳健的落地方案之一,既利用了Jenkins强大的插件生态(如SonarQube、Jacoco),又享受了Argo CD带来的声明式部署和回滚能力。
去年双十一前,我负责一个日订单量在 50 万左右的电商交易系统。当时为了提升构建效率,我决定把 Jenkins 从单机迁移到 K8s 动态 Pod 上。
一开始挺顺利,构建队列确实快了。但上线第二天,半夜突然收到告警,Pod 疯狂重启。我爬起来查日志,发现是 JVM 内存配置 和 K8s 资源限制 打架了。Jenkins 的 Agent Pod 启动时会预加载 Maven 依赖,内存瞬间飙到 2G,但我在 YAML 里只给了 1.5G 的 limit。
排查过程很痛苦,我甚至在本地用 kubectl describe 盯着 Pod 状态看了两个小时。最后解决的办法其实很朴实:给 Maven 加了 -DskipTests 和更精细的 -Xmx 参数,同时在 Jenkinsfile 里把 Pod 的 activeDeadlineSeconds 设置好,防止僵尸进程占着资源不放。改完之后,构建时间从 20 分钟降到了 8 分钟,那晚我直接睡在了公司沙发上。
现在很多人问我是不是该直接上 GitHub Actions,我的看法很直接:
* 如果你的团队 在 20 人以内,业务没那么复杂,直接用 GitHub Actions 或 GitLab CI,省心。
* 如果你像我一样,面对的是那种有复杂权限管控、多环境发布、还要对接一堆内网工具(比如老旧的 Nexus 或自研平台)的场景,Jenkins 依然是最稳的选择。
我踩过的坑是:不要盲目追求全自动化。有些流水线为了炫技写得过于复杂,结果同事接手时根本看不懂,最后维护成本比手动部署还高。
学 Jenkins 千万别死记硬背那些 Groovy 语法。找个你手头正在跑的小项目,试着写一个能自动打包并推送到测试环境的流水线。遇到报错就去翻官方文档,比看一百篇教程都管用。配置这东西,手熟了,心就不慌了。