把代码提交和构建流水线绑在一起,最省心的做法是让 Gerrit 当"发令枪"、Jenkins 当"执行手"——开发往 Gerrit 推一个 patchset,Jenkins 自动拉取对应版本编译、跑测试,再把结果以 Verified 标签回写到那个 change 上,通过就 +1,失败就 -1。这样一来,评审的人在页面上就能直接看到这次改动到底能不能编过、测试跑没跑通,不用再靠人工问一句"你本地编过了吗"。整套链路涉及 Gerrit 的 SSH 事件流、refs/changes 引用规则、Jenkins 的凭据管理、Gerrit Trigger 插件的触发与回写配置,每一样都有坑,尤其是权限和 refspec 这两块,配错一个字符就是"提交了但没反应"。
这篇内容就是把这套东西从头到尾捋一遍:从环境准备、插件选型、Gerrit 账号与权限配置,到 Jenkins 全局配置、流水线写法、Verified 回写、钉钉通知,最后把我在实际搭建中踩过的十几个坑整理成速查表。适合正准备给团队搭 CI 的同学,也适合已经搭了一半、卡在"触发不生效"或"权限不足"上的朋友。下面按实际搭建顺序来,你可以照着一步步抄。
1. 整体方案设计与链路选型思路
1.1 为什么让 Gerrit 主动推事件,而不是 Jenkins 定时轮询
很多人第一反应是让 Jenkins 用 SCM 轮询(poll SCM)去扫仓库变化,配置简单,五分钟就能跑起来。但这套方案在 Gerrit 场景下很快会暴露三个问题:一是轮询只能看分支,看不到 patchset。Gerrit 的代码改动是挂在refs/changes/下的临时引用,不在refs/heads/里,轮询根本扫不到;二是延迟不可控,轮询间隔设短了浪费资源,设长了开发提交完得干等;三是没法精确对应"哪一次改动触发了哪一次构建",回写 Verified 标签时容易张冠李戴。
所以正经做法是走事件驱动:Gerrit 在 patchset 创建、草稿发布、评论添加、ref 更新这些时机,通过 SSH 的 event stream 把事件推出来,Jenkins 侧监听到之后立刻触发构建,并且事件里自带 change number、patchset number、refspec、项目名这些信息,构建脚本直接拿来用。这套机制的好处是明确、实时、可追溯,代价是需要在 Gerrit 侧开一个长期连接的账号并配好权限。
1.2 组件拆解与网络关系梳理
整套链路其实只有三个角色:Gerrit 服务端、Jenkins 服务端、构建执行节点。Gerrit 需要开放两个入口,一个是 SSH(默认 29418),给 Jenkins 监听事件和回写标签用;另一个是 HTTP(默认 8080),给 Gerrit Trigger 插件调 REST API 补充信息用。Jenkins 这边需要一个能访问 Gerrit 29418 和 8080 的网络路径,以及一个专门用于对接的 Gerrit 账号。
我一般会画一张简单的表来对齐这些信息,避免配置的时候端口写错:
| 组件 | 用途 | 默认端口/路径 | 配置位置 |
|---|---|---|---|
| Gerrit SSH | 事件流监听、review 回写 | 29418 | Jenkins 全局 Gerrit Trigger 配置 |
| Gerrit HTTP | REST API 查询 change 详情 | 8080(或反向代理 80/443) | Gerrit Trigger 的 Gerrit REST API 段 |
| Git 仓库 | 拉取源码 | 通常复用 29418 或独立 9418/HTTP | 任务里的 SCM 配置 |
| Gerrit 网页 | 人工查看 Verified 标签 | 8080 | 无需配置,用于验证结果 |
这里有个容易被忽略的点:Jenkins 里配置 SCM 拉代码时,用的 Git URL 和 Gerrit Trigger 用的 SSH 地址是两回事。前者是ssh://ci@gerrit.example.com:29418/project.git,后者在插件里只填主机名和端口。很多人第一次配的时候把带路径的 URL 全塞进插件的 Host 字段,结果连接直接失败。
1.3 三种触发链路对比与选型建议
实际可选的路子有三条,我列个表对比一下,你自己按团队规模挑:
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Gerrit Trigger 插件 | Jenkins 装插件,长连 Gerrit SSH 监听事件 | 开箱即用,自动回写 Verified,支持动态触发 | 插件与 Jenkins 版本有兼容要求,事件多了连接易断 | 中小团队,标准用法 |
| Gerrit Webhook + 通用 Webhook 插件 | Gerrit 配置 webhook 回调 Jenkins | 解耦,Jenkins 重启不影响 Gerrit | 需要额外处理认证与回写逻辑 | 已有 webhook 基础设施的团队 |
| 自建 SSH 监听脚本 | 自己写脚本接gerrit stream-events,再调 Jenkins API | 完全可控,灵活 | 维护成本高,回写要自己实现 | 有特殊定制需求的团队 |
我的建议是:除非有硬性约束,否则直接用 Gerrit Trigger 插件。它的Verified回写、动态触发(按文件路径、分支、项目过滤)这些能力都是现成的,自建脚本省下来的那点灵活性,维护成本早就吃回去了。后面章节也全部按插件方案展开。
2. Jenkins 环境准备与安装配置要点
2.1 系统资源规划与端口预留
Jenkins 本身不挑机器,但一旦跑起编译任务,资源消耗会陡增。我一般给的单机建议是:4 核 CPU、8G 内存起步,如果构建 Java Web 项目、还要在容器里跑集成测试,直接上 8 核 16G,磁盘至少留 100G 给工作空间和历史构建。工作空间目录(JENKINS_HOME/workspace)的磁盘增长非常快,尤其是每次构建都要拉全量代码的场景,建议单独挂一块盘。
端口方面,Jenkins 默认 8080,如果 Gerrit 也在同一台机器上(不推荐,但测试环境常见),必须把其中一个改掉。改 Jenkins 端口最稳妥的方式不是改启动参数,而是改/etc/default/jenkins或 systemd unit 里的HTTP_PORT,这样重启服务不会丢配置。
注意:不要用 root 直接跑 Jenkins 主进程。用专用的 jenkins 用户,工作空间目录的属主也交给它,否则构建脚本里读文件会出现莫名其妙的权限拒绝。
2.2 安装方式选择与初始化避坑
安装有四种路子:包管理器(apt/yum)、WAR 包手动部署、Docker 容器、离线安装包。我按场景说:
- 内网环境、无法访问外部仓库 → 用 WAR 包或离线安装包,把
jenkins.war和插件目录一起打包带进去 - 需要快速起测试环境 → Docker,注意把
JENKINS_HOME挂出来,否则容器一删全没了 - 长期稳定运行 → 包管理器安装,配 systemd 管理
初始化阶段最容易卡的是插件下载慢。首次启动会问你要装哪套插件,直接跳过,进主页之后去Manage Jenkins → Plugins → Advanced改升级站点地址。国内可以用清华或华为的镜像,把https://updates.jenkins.io/update-center.json换成镜像地址,速度能从几 KB 提到几 MB。
离线安装的话,插件是.hpi文件,从有网机器上Manage Jenkins → Plugins → Advanced → Download下载好,拷到$JENKINS_HOME/plugins/目录,重启生效。注意插件之间有依赖关系,单独装 Gerrit Trigger 往往不够,会提示缺structs、ssh-credentials、credentials这些,建议在有网环境把依赖一起下全。
提示:修改升级站点后,记得点
Check now刷新一次,再回插件列表看可用更新。有时候缓存不刷新,你会以为镜像没生效。
2.3 必备插件清单与安装顺序
对接 Gerrit 的最小插件集合是这几个:
| 插件 | 作用 | 是否必需 |
|---|---|---|
| Gerrit Trigger | 监听事件、触发构建、回写 Verified | 必需 |
| Git plugin | 拉取 Gerrit 仓库代码 | 必需 |
| Credentials Binding | 在流水线里安全引用凭据 | 必需 |
| Pipeline | 写 Jenkinsfile | 强烈建议 |
| SSH Agent / SSH Credentials | SSH 密钥类凭据支持 | 必需 |
| DingTalk(或 Generic Webhook) | 构建结果通知 | 可选 |
| Workspace Cleanup | 构建前后清理工作空间 | 可选但推荐 |
安装顺序上,先装依赖插件再装 Gerrit Trigger,能少踩一次重启。装完之后Manage Jenkins → Global Tool Configuration里把 JDK、Maven、Git 的可执行路径配好,或者用自动安装。老版本 Jenkins 的JDK自动安装经常因为下载源问题失败,我一般直接填服务器上已经装好的路径,稳。
3. Gerrit 侧配置:账号、密钥与权限
3.1 创建 Jenkins 专用账号与 SSH 密钥
第一步是给 Jenkins 建一个独立的 Gerrit 账号,不要复用任何人的账号。原因是这个账号会有Label Verified的回写权限,混用会导致审计困难,而且人员离职时容易误删。
账号建好之后,用这个账号登录 Gerrit 网页,进入Settings → SSH Keys,把 Jenkins 侧的 SSH 公钥加进去。公钥在 Jenkins 服务器上这样生成:
ssh-keygen -t rsa -b 4096 -C "jenkins-ci@example.com" -f /var/lib/jenkins/.ssh/gerrit_ci -N ""-N ""表示不设密码,因为 Jenkins 非交互式调用没法输密码。生成完把gerrit_ci.pub的内容粘到 Gerrit。私钥gerrit_ci的内容后面要填进 Jenkins 的凭据里,注意是全文包括头尾的-----BEGIN/END-----行。
验证连通性,在 Jenkins 机器上执行:
ssh -p 29418 -i /var/lib/jenkins/.ssh/gerrit_ci ci-bot@gerrit.example.com gerrit version能打印出版本号就说明密钥和账号都对上了。这一步千万别跳,后面插件连不上,你回头查会花十倍时间。
3.2 项目权限配置与 Verified 标签授权
权限是 Gerrit 最绕的部分。核心思路是:CI 账号需要"读代码"和"给 Verified 打分"两种能力,其他一律不给。配置在项目的Access页面,或者直接编辑project.config。
先在All-Projects层面确认有Verified这个 label。如果是新装的 Gerrit,默认 label 只有Code-Review,需要手动加。编辑All-Projects的project.config:
[label "Verified"] function = MaxWithBlock value = -1 Fails value = 0 No score value = +1 Verifiedfunction = MaxWithBlock的含义是"取所有投票里的最大值,且 -1 会直接阻塞提交"。这个设置对 CI 很关键:构建失败给 -1,整个 change 就无法合入,形成硬门禁。
然后给 CI 账号授权。在项目 Access 里添加:
refs/*的Read权限,给 CI 账号(读代码)refs/heads/*的Label Verified,范围设为-1..+1,给 CI 账号refs/for/refs/*的Push,给开发者(这个本来就有)refs/heads/*的Submit,给评审人或有权限的人
注意:
Label Verified的范围如果只给0..+1,那构建失败时 CI 就没法打 -1,门禁就形同虚设。一定要给到-1..+1。
3.3 事件流机制与触发时机选择
Gerrit 的事件是通过gerrit stream-events命令输出的 JSON 流,插件在后台维持这个 SSH 连接。理解事件类型,配置触发条件时才有底:
| 事件类型 | 触发时机 | 典型用途 |
|---|---|---|
| patchset-created | 新 patchset 上传 | 每次提交自动构建,最常用 |
| draft-published | 草稿 change 发布 | 团队用草稿流程时 |
| comment-added | 有人在 change 上评论 | 用评论关键词触发重跑 |
| ref-updated | 分支引用更新 | 合并后构建主干 |
| change-merged | change 被合并 | 触发部署流水线 |
最常见的组合是patchset-created+comment-added(关键词比如recheck)。前者保证每次提交都跑一遍,后者给开发一个"手动重试"的入口,避免因为环境抖动导致的偶发失败必须重新推一个 patchset。
配置comment-added触发时,正则要写严一点,比如(?i)^recheck$,只认整行就一个 recheck 的评论。写松了会导致随便一句评论都触发构建,队列瞬间爆炸。
4. Jenkins 侧对接 Gerrit 完整实操
4.1 Gerrit Trigger 全局配置逐项填写
进入Manage Jenkins → Gerrit Trigger → Add New Server,这是整个对接的核心。字段含义和填写要点如下:
| 字段 | 填写内容 | 说明 |
|---|---|---|
| Name | gerrit-main | 自定义,任务里会引用这个名字 |
| Gerrit Host | gerrit.example.com | 只填主机名,不带协议和端口 |
| Gerrit SSH Server Port | 29418 | 默认值,改过就填实际值 |
| Gerrit SSH Server Username | ci-bot | 3.1 建的账号 |
| SSH Private Key File | 上传的凭据 ID | 选 SSH Username with private key 类型 |
| Gerrit REST API URL | http://gerrit.example.com:8080 | 用于查询 change 详情 |
| Gerrit HTTP Username | ci-bot | REST API 认证 |
| Gerrit HTTP Password | HTTP 密码 | 在 Gerrit Settings → HTTP Password 生成 |
提示:Gerrit 的 HTTP 密码不是登录密码,要去 Gerrit 网页的
Settings → HTTP Password → Generate new password生成。很多人填了登录密码,结果 REST API 一直 401。
配完之后点Test Connection,两个按钮分别测 SSH 和 REST。SSH 通了、REST 通了,全局配置才算完。如果 SSH 报认证失败,八成是私钥格式问题,把私钥重新导出一次,确保没有多余空格。
4.2 凭据配置的三种类型与踩坑
Jenkins 里的凭据分三种,对接 Gerrit 会用到其中两种:
- SSH Username with private key:给 Gerrit Trigger 和 Git 拉代码用。Username 填
ci-bot,Private Key 选Enter directly粘贴私钥全文,或者选From a file on Jenkins master指定路径。 - Username with password:给 REST API 用,用户名
ci-bot,密码是 HTTP 密码。 - Secret text:给钉钉 webhook token 之类的用。
踩坑最多的是 SSH 私钥格式。如果你是从 Windows 用 PuTTY 生成的.ppk文件,Jenkins 不认,必须转成 OpenSSH 格式再上传。转换用:
puttygen key.ppk -O private-openssh -o id_rsa_openssh另一个坑是 Git 拉代码时用的凭据和 Gerrit Trigger 用的可以不同。我习惯统一用同一个 SSH 密钥,减少管理成本,但如果团队规定 CI 账号和拉代码账号分离,那就各配各的。
4.3 任务触发配置与动态过滤
新建任务,类型选 Pipeline 或 Freestyle。以 Pipeline 为例,在Build Triggers里勾选Gerrit event,然后配置触发器:
- Trigger On:勾
Patchset Created、勾Comment Added并填正则 - Gerrit Project:填
Plain模式,值写项目的project.config里的 name,比如platform/web-portal - Branches:可以填
Plain精确匹配,或者Path模式用正则,比如refs/heads/(master|release/.*) - File Paths:可选,按改动文件过滤,比如只改动
src/main/**才触发
这里有个非常实用的功能叫Dynamic Trigger,开启后可以在同级目录里维护一个配置文件,把项目、分支、触发条件的映射写在文件里,不用改任务配置。项目多了之后(比如几十个仓库共用一个 CI 任务),这个配置方式能省下大量重复劳动。
注意:
Gerrit Project字段必须和 Gerrit 里的项目全名完全一致,大小写敏感。写错了不会报错,就是永远不触发,排查起来很费劲。
4.4 Verified 回写配置与失败门禁
回写是插件自动完成的,但要配几个地方。在 Gerrit Trigger 全局配置的Advanced里:
- Gerrit Reporting Values:配置成功时
Verified +1、失败时Verified -1的默认消息 - Gerrit Verified Commands:插件内部通过
gerrit review命令回写,可以自定义命令模板
如果构建过程中途被中断(比如超时、节点掉线),插件会走unstable或notbuilt分支,对应打分可以单独设。我一般把超时也设成-1,因为超时本身就是问题,不该放过。
流水线里也可以手动调用回写,用于更精细的控制:
stage('Report') { steps { script { if (currentBuild.currentResult == 'SUCCESS') { gerritReview labels: ['Verified': 1], message: '构建通过' } else { gerritReview labels: ['Verified': -1], message: '构建失败,请查看日志' } } } }5. 流水线与构建脚本实战
5.1 Jenkinsfile 整体结构设计
一个标准的 Gerrit CI 流水线,我一般拆成五段:准备、编译、测试、归档、回写。下面是一个可直接复用的骨架,语言标注为 groovy:
pipeline { agent any options { timeout(time: 30, unit: 'MINUTES') buildDiscarder(logRotator(numToKeepStr: '30')) skipDefaultCheckout(true) } stages { stage('Checkout') { steps { checkout([$class: 'GitSCM', branches: [[name: env.GERRIT_REFSPEC ?: 'refs/heads/master']], userRemoteConfigs: [[ url: 'ssh://ci-bot@gerrit.example.com:29418/platform/web-portal.git', credentialsId: 'gerrit-ci-ssh', refspec: '+refs/changes/*:refs/changes/*' ]] ]) } } stage('Build') { steps { sh 'mvn -B -DskipTests clean package' } } stage('Test') { steps { sh 'mvn -B test' } } stage('Archive') { steps { archiveArtifacts artifacts: 'target/*.jar', fingerprint: true junit 'target/surefire-reports/*.xml' } } } post { success { gerritReview labels: ['Verified': 1] } failure { gerritReview labels: ['Verified': -1], message: '构建失败' } } }skipDefaultCheckout(true)很重要,它会跳过 Jenkins 自动生成的 checkout 阶段,改由我们自己控制拉取逻辑,这样才能精确拉到指定的 patchset。
5.2 refspec 机制:怎么拉到指定的那一次提交
Gerrit 的 change 不在分支上,而是挂在refs/changes/XX/YYYY/Z这个引用下。三个数字的含义是:
XX:change number 的后两位,不足两位前面补零。比如 change number 是 7,就写07YYYY:完整的 change numberZ:patchset 序号,从 1 开始
举例,change number 为1234,第 3 个 patchset,引用就是refs/changes/34/1234/3。
Jenkins 触发时,环境变量GERRIT_REFSPEC就是这个完整引用,GERRIT_BRANCH是目标分支(如master),GERRIT_CHANGE_NUMBER是 change 号。所以流水线里直接用env.GERRIT_REFSPEC作为拉取目标,就能精确取到触发的那一次改动。
要在 URL 里配refspec: '+refs/changes/*:refs/changes/*',否则 Git 不会把远端这些临时引用同步下来,checkout 就会失败报Couldn't find remote ref。
提示:
GERRIT_REFSPEC变量只在由 Gerrit 触发时才有值。手动点"立即构建"时它是空的,所以骨架里写了?: 'refs/heads/master'兜底,否则手动构建会因为空 ref 直接报错。
5.3 构建产物归档与测试报告聚合
归档不只是留个文件,它和评审体验直接相关。把target/*.jar用archiveArtifacts归档后,评审人可以直接从构建页面下载产物做验证,不用自己编译。fingerprint: true会记录文件指纹,将来追溯"这个包是哪次提交产出的"时非常有用。
单元测试报告用junit步骤聚合,Jenkins 会把target/surefire-reports/*.xml解析成趋势图,挂在任务首页。构建不稳定(有测试失败但不阻塞)时,currentBuild.currentResult会是UNSTABLE,这时候回写策略要单独想清楚:我一般把 UNSTABLE 也当成 -1,因为不准的测试比没有测试更危险。
日志量大是常态,尤其是 Java 项目跑集成测试。建议在流水线里加日志截断,只保留关键行,或者在logRotator里限制保留份数,不然磁盘一个月就满了。
5.4 构建结果通知到钉钉
通知不是必需品,但团队协作少不了。钉钉自定义机器人的做法是:群里添加机器人,拿到 webhook 地址和一个 access_token,然后用 curl 发消息。流水线里这样写:
post { always { script { def status = currentBuild.currentResult def msg = "【${env.JOB_NAME}】${status}\nchange: ${env.GERRIT_CHANGE_NUMBER}\n地址: ${env.BUILD_URL}" sh """ curl -s -H 'Content-Type: application/json' \ -d '{"msgtype":"text","text":{"content":"${msg}"}}' \ https://oapi.dingtalk.com/robot/send?access_token=你的token """ } } }安全上加两层:一是机器人设置里开启"自定义关键词",消息里必须包含该关键词才会发出,比如让所有消息都带项目名;二是用加签方式,把 timestamp 和 sign 拼进 URL,这样 token 泄露了别人也没法伪造。token 本身放 Jenkins 凭据里,别硬编码在 Jenkinsfile 中。
s和sh的引号嵌套容易出错,上面的写法用双引号包 shell、双引号包 JSON 是不对的,实际要改成单引号包 shell 脚本,或者用 Groovy 的writeFile先生成 JSON 文件再用curl -d @file。这个细节不注意,消息发出去就是格式错误。
5.5 多仓库共用流水线的组织方式
项目一多,每个仓库建一个 Jenkins 任务会累死运维。两种解法:
一种是多分支流水线 + 共享库。把公共逻辑(拉取、编译、回写、通知)抽到 Shared Library,各仓库的 Jenkinsfile 只写差异部分,比如用哪个 Maven profile、跑哪些测试。Jenkins 配置里指定共享库的 Git 地址和版本,任务里@Library('ci-lib') _引入。
另一种是单任务 + 动态触发。一个 Jenkins 任务绑定 Gerrit 的多个项目,用 Dynamic Trigger 的配置文件控制每个项目的触发条件,然后在流水线里根据GERRIT_PROJECT变量走不同的构建分支。这种方式任务数量最少,但 Jenkinsfile 里的分支逻辑会变复杂。
我的经验是:仓库数量在 10 个以内,用第一种,清晰;超过 20 个且构建流程高度相似,用第二种,好维护。两种也可以混用,核心仓库单独建任务,边缘仓库走通用任务。
6. 常见问题与排查技巧实录
6.1 触发不生效的排查路径
"提交了代码,Jenkins 没反应"是对接阶段最高频的问题。按这个顺序查:
- 看 Gerrit Trigger 全局配置的
Test Connection是不是都通过,SSH 和 REST 有一个不通就不行 - 看 Jenkins 任务的
Gerrit event触发条件,项目名和分支是否和实际提交完全一致 - 在 Gerrit 网页看事件是否产生,
People → Events里能看到最近的事件记录 - 看 Jenkins 系统日志,
Manage Jenkins → System Log加一个com.sonyericsson.hudson.plugins.gerrit的日志记录器,级别调到 FINE,能看到插件收到的事件详情 - 看 CI 账号在 Gerrit 里的
Read权限有没有覆盖到目标项目的目标分支
第 5 条最隐蔽。很多团队给 CI 账号只配了refs/heads/master的读权限,结果 release 分支的提交完全不触发,还以为是插件坏了。
| 现象 | 高频原因 | 处理方式 |
|---|---|---|
| 完全无触发 | CI 账号读权限不足 | 补refs/*的 Read 权限 |
| 部分分支不触发 | 分支匹配模式写错 | 检查 Branches 字段模式 |
| 触发但拉不到代码 | refspec 未配置 | 补+refs/changes/*:refs/changes/* |
| 事件时有时无 | SSH 连接被防火墙断开 | 调大 keepalive 或检查网络设备会话超时 |
6.2 权限与 SSH 报错的典型表现
Permission denied (publickey)是最常见的一类,原因无非三种:密钥没加到 Gerrit、私钥格式不对、账号用户名写错。逐条排除即可。
还有一种报错是missing Change-Id in commit message footer,这个是开发提交时没装 commit-msg hook。让开发执行:
curl -Lo .git/hooks/commit-msg http://gerrit.example.com:8080/tools/hooks/commit-msg chmod +x .git/hooks/commit-msg回写失败报not permitted或label Verified is restricted,说明 CI 账号没有Label Verified的-1..+1权限,回到 3.2 检查授权范围。
6.3 构建阶段的报错处理
容器化构建时经常遇到镜像拉取失败,报错长这样:
docker: error response from daemon: get "https://registry-1.docker.io/v2/": net/http: request canceled这是拉取公共镜像慢或超时。配置镜像加速即可,编辑/etc/docker/daemon.json:
{ "registry-mirrors": ["https://你的加速地址"], "max-concurrent-downloads": 5 }改完systemctl daemon-reload && systemctl restart docker。另外max-concurrent-downloads调小反而更稳,因为并发太高容易触发限流。
工作空间残留导致的构建失败也很典型,比如上次构建留下的target目录里有个被锁的文件。在流水线开头加清理:
stage('Clean') { steps { cleanWs() } }cleanWs()比deleteDir()更彻底,它会把工作空间和相关的元数据一起清掉。
6.4 离线环境与升级站点问题
内网环境装插件,走离线包。流程是:外网机器装一个同版本 Jenkins,把需要的插件在插件管理里下载.hpi,连同依赖一起拷到内网机的$JENKINS_HOME/plugins/,重启。常见坑是版本不匹配,插件要求的 Jenkins 核心版本高于当前版本,装上直接启动失败。解决办法是先在插件页面看清楚要求的核心版本,必要时升级 Jenkins 主程序。
升级站点慢的问题,除了换镜像,还可以直接用离线 update-center 文件:把update-center.json下载到本地,改配http://内网地址/update-center.json,插件列表从本地读,速度就上去了。
注意:升级 Jenkins 主版本前务必备份
$JENKINS_HOME整个目录,至少备份config.xml、jobs/、plugins/、credentials.xml这几项。主版本跨度过大时插件兼容问题会成片出现,回滚是常态。
7. 实际搭建中的经验与优化建议
7.1 构建速度与并发控制
流水线跑得慢,八成慢在依赖下载和全量编译。几个实操手段:Maven 本地仓库做成持久化卷,不要每次构建都重新下;用mvn -o走离线模式,前提是依赖已经缓存过;把测试拆成单元测试和集成测试两段,单元测试必跑,集成测试只在特定分支或加标签的 change 上跑。
并发方面,同一个 change 的多次 patchset 会排队。我的做法是开启 Jenkins 的"同一任务并发执行"但给每个 change 加锁,用lock步骤按 change number 加锁,保证同一个 change 不会有两个构建同时跑,不同 change 之间互不影响。
7.2 稳定运行与长期维护要点
Jenkins 和 Gerrit 的 SSH 长连接容易因为网络设备的会话超时被断开,表现是"用了一段时间后突然不触发了"。解决办法有两个:一是在 Jenkins 全局配置里调大连接重试和 keepalive 参数;二是加一个定时任务,每隔几分钟检查一次 Gerrit Trigger 的状态页,发现断连就发告警。
另外,构建历史会持续膨胀,buildDiscarder(logRotator(numToKeepStr: '30'))这类策略要在每个任务上都配上,别指望全局默认值。归档产物也设个大小上限,大文件归档几次就能把磁盘吃满。
日志方面,建议把 Gerrit Trigger 的日志级别平时设成WARNING,出问题临时调到FINE,查完调回去。常年开着FINE会让日志文件以肉眼可见的速度增长。
我自己维护这套流程两年多,最大的体会是:配置本身不难,难的是把每个环节的"隐性前提"明确下来——CI 账号的权限范围、refspec 的同步规则、事件类型和触发时机的对应关系。把这些写进团队文档,新人接手时就不会在"为什么不触发"上反复折腾。最后再分享一个小习惯:每次改动触发配置或权限配置后,拿一个测试项目做一次端到端验证,从提交到看到 Verified 标签走完一遍,比事后排查便宜太多。