新闻详情

新闻详情

首页 / 资讯中心 / 详情

Jenkins 对接 Gerrit:代码评审自动化与 Verified 投票

发布时间:2026/10/2 18:36:50来源:尧图网络
Jenkins 对接 Gerrit:代码评审自动化与 Verified 投票
我们团队最早做代码评审时流程是这样的开发在本地改完push 到评审分支评审人在网页上逐行看看完了在评论区写一句这里并发有问题改一下作者改完再推评审人再从头看一遍——他其实根本不知道这次改动会不会把编译搞挂、单测会不会红。后来我们把 jenkins 接进 gerritpatchset 一推上去几十秒之内 Gerrit 页面上就多出一个 Verified 的投票和一条带构建链接的留言评审人打开页面第一眼就知道这版是绿的注意力全放在设计逻辑上不用再替编译器干活。我这篇要写的就是这套jenkins 部署流程 对接 gerrit从零到跑通的完整过程。不是那种装个插件点两下就完事的攻略——真上手你会发现坑几乎都不在 Jenkins 的界面上而在 refspec 怎么写、HTTP 密码怎么配、Verified 标签的权限开在哪个 ref、构建失败到底是代码的问题还是拉取的问题。我会按我们实际落地的顺序讲先对齐两边的前置条件再打通事件通道然后讲 Job 里那几行真正关键的配置接着把投票回写的链路补上最后给一份我自己在用的排查顺序表和长期运行才会撞到的坑。适合已经会基本的 git 和 Linux 操作、手上有一台可以折腾的机器、准备把评审流程自动化起来的同学看纯新手也能跟着走我会解释每个字段为什么这么填。1. 先弄清楚为什么要在 Gerrit 前面架一层 Jenkins1.1 人工评审真正卡住的地方不是看不出来很多人以为代码评审慢是因为评审人看不出问题其实我们统计过自己项目的数据评审周期里占比最大的一块时间是来来回回的机械往返编译不过、单测挂了、代码风格检查报一堆、某个人忘了 pull 最新代码导致合并冲突。这些问题的共同点是——它们完全可以由机器判定而且判定成本极低但它们消耗的是评审人最宝贵的注意力。一个人一天能认真读完的 diff 大概也就几百行如果前两百行都在看这里少了个分号这个 import 没用上等他翻到核心逻辑那里耐心已经耗掉了。把 Jenkins 接在 Gerrit 前面本质上是把评审拆成两级第一级是机器评审只回答这版能不能编译、测试过不过、静态检查有没有新增问题输出是一个二值的 Verified 投票第二级才是人评审只回答这个设计合不合理、这个抽象是不是过度、这个边界条件考虑全了没有。机器的判断快、可重复、不带情绪人的判断慢但不可替代。分清这两级整个流程的瓶颈才会从机器能干的活转移到真正需要人的活。1.2 Gerrit 和 Jenkins 各自负责什么边界要划清楚我刚接手这套东西的时候犯过一个错误想让 Jenkins 干太多事既做构建验证又在评论里写代码风格建议还顺手把包推到制品库。结果是一个 Job 跑了二十分钟失败了没人知道是哪个环节的问题而且这个 Job 每次 patchset 上传都触发机器直接被压满。后来我把职责砍到只剩一条Jenkins 对某一个具体的 patchset 做验证然后给这个 patchset 投一个 Verified 票。环节由谁负责关键产物接收提交、生成评审单GerritChange-Id、change number、patchset number代码托管与版本对齐Gerrit含副本同步refs/changes/... 引用事件通知Gerritstream-eventspatchset-created 等事件流拉取指定 patchsetJenkins一个干净的工作区HEAD 指向该 patchset编译、单测、静态检查Jenkins退出码、测试报告投票与留言Jenkins 回写 GerritVerified ±1、构建链接最终合入Gerritrefs/heads/* 上的新提交这张表看着简单但边界这两个字是后面所有配置的依据。比如为什么 Jenkins 只投 Verified 不投 Code-Review因为 Code-Review 是人评审的语义机器投了会污染人的判断而且很多团队的权限配置里压根不允许 CI 账号投这个标签。再比如为什么最终合入必须回到 Gerrit 而不是 Jenkins 直接 push因为评审记录要留在 Change 上这是整个流程可追溯的基础。1.3 一次 patchset 从上传到投票中间到底发生了什么把链路完整走一遍后面遇到问题才知道该在哪一段下手。开发本地执行git push origin HEAD:refs/for/mainGerrit 收到后创建一个 Change这个 Change 有一个全局的 Change-Id 和递增的 change number本次推送的内容被标记为 patchset N如果之前推过就是 N1。这时候 Gerrit 会往事件流里扔一条类型为patchset-created的 JSON里面带着项目名、分支、change number、patchset number、patchset 对应的 commit SHA、以及这个 patchset 的 refspec。Jenkins 这边常驻一个监听者它通过 SSH 连到 Gerrit 的 29418 端口执行gerrit stream-events把这条事件读进来然后按你在 Job 上配的过滤条件决定要不要触发。触发之后Job 需要在工作区里把这个 patchset 的内容取下来——注意不是取分支最新而是取refs/changes/xx/xxxx/N这个引用。取到之后正常跑构建跑完根据结果调用 Gerrit 的接口给这个 change 的这个 patchset 写上 Verified 1 或者 -1顺便带一段构建日志的链接。整条链路里任何一环断掉表现都是没有投票但原因可能完全不同。这就是为什么我后面要单独用一整节讲排查顺序——不看事件流、不看 Jenkins 日志光盯着为什么没投票是猜不出来的。2. 两边的前置条件这些没对齐后面全是白折腾2.1 Jenkins 装在哪、用什么方式装直接决定你后面顺不顺先说安装方式的选择这一步我在不同环境里试过三种各有各的适用场景没有绝对好坏但选错了后面会难受。用 war 包直接跑java -jar jenkins.war --httpPort8080最轻适合先把流程跑通的验证阶段缺点是升级、开机自启、日志轮转都得自己管。用系统包管理器装Debian/Ubuntu 系apt装官方的源RHEL 系用 yum/dnf是最省心的它会帮你建jenkins用户、配好 systemd unit、日志进 journald我做生产环境基本都选这个因为它把Jenkins 是一个长期运行的服务这件事处理得最规范。用容器方式装看着时髦但你要想清楚一件事Jenkins 的构建过程本身经常需要 Docker容器里再套 Docker 要么挂载宿主机的 socket要么做 DinD前者有权限和路径映射的坑工作区路径在两边不一样挂载进去就对不上了后者性能和磁盘都会让你头疼。我的建议是Jenkins 本体不要跑在容器里构建节点可以是容器但 Jenkins 控制器老老实实跑在虚拟机上。我们现在的做法是一台 4C8G 的虚机跑控制器/var/lib/jenkins挂一块独立的数据盘构建任务分派到两台固定标签的构建节点上。这个结构后面讲并发和清理的时候你会看到它的好处。装完之后有两件事必须立刻做掉不做后面一定后悔。第一件是确认系统时间和 Gerrit 服务器是同步的用timedatectl看一眼有没有开 NTP 同步两边差几分钟会导致 SSL 校验、事件时间戳排序出各种诡异问题。第二件是把 Jenkins 的工作目录挪到数据盘上用JENKINS_HOME环境变量指过去因为默认位置在系统盘构建几次下来磁盘就满了而 Jenkins 磁盘满之后的表现是任务排队不执行特别难联想到磁盘。提示jenkins用户对工作目录必须有完整读写权限用 bind mount 挪目录之后很容易忘掉这一层表现是 Job 一跑就报无法创建工作区。挪完目录先切换到这个用户试着创建个文件别等出问题再回头查。2.2 插件清单装对比装多重要Jenkins 最容易被搞坏的方式就是见插件就装。我们踩过一次装了个版本管理相关的插件它自动把 git 插件降级了然后所有 Job 全挂。所以插件按最小可用集来装装完在做任何升级前先备份JENKINS_HOME。对接 Gerrit 场景下必需的是这么几个Git plugin拉代码的基础、Pipeline写 Jenkinsfile 用就算你一开始用自由风格 Job也建议装上早晚会用到、Credentials Binding管理密钥和密码、以及二选一的 Gerrit 侧插件——Gerrit Trigger或Gerrit Code Review。这两个的选择我在第 3 节展开讲这里先说结论新项目直接上Gerrit Code Review插件老项目如果已经在用 Gerrit Trigger 且跑得好没必要动。另外几个强烈建议装上的JUnit展示测试报告、Build Timeout防止某个 Job 卡死占着执行器不放、Workspace Cleanup收尾清理、AnsiColor让构建日志里的颜色能显示出来看测试输出舒服很多。装的时候注意 Jenkins 会提示插件之间的依赖关系如果提示某个插件需要更高版本的 Jenkins 本体先看清楚再点别硬装。2.3 Gerrit 侧的准备一个专门的服务账号Jenkins 连 Gerrit 需要身份我强烈建议为 CI 单独建一个账号不要用任何人的个人账号原因是这个账号需要一些不像人的权限比如能在refs/changes/*上打 Verified 标签混在个人账号里权限收不干净而且万一这个账号要废弃影响面清晰。建好账号之后要在它的 Settings 里生成一个HTTP 密码。这一步是新手最常卡住的地方Gerrit 的登录密码和 HTTP 密码是两回事网页上登录用的那个密码在很多配置下根本不能用于接口调用必须进 Settings → HTTP Credentials 里点生成拿到一串随机字符。用 SSH 方式通信的话则是另一套把 Jenkins 所在机器的公钥加到该账号的 SSH Public Keys 里。两种通信方式的取舍我列个表对比项SSH 方式HTTP 方式端口29418Gerrit SSH 端口80/443走反向代理凭证类型SSH 私钥用户名 HTTP 密码适合场景Jenkins 和 Gerrit 在同一内网、防火墙放行方便端口资源紧张、已经有统一网关事件流原生支持 stream-events新版插件走 REST 轮询/长连接常见故障私钥权限、known_hostsHTTP 密码未生成、反向代理路径不对我们的环境两种都在跑内网那套用 SSH因为 29418 直通很干净对外的那套走 HTTP因为统一网关只放 443。你选哪套取决于你们网络侧的限制功能上没有本质差别。2.4 Change-Id这一切的起点有个坑我见过太多次了——开发在本地提交的时候commit message 里没有Change-Id那一行git push直接被 Gerrit 拒绝。这不是 Jenkins 的问题但如果你负责推这套流程最好一开始就把 hook 装到开发者的仓库模板里别让每个人自己去配。Gerrit 提供了一个commit-msg钩子正确做法是把它放到 git 的模板目录里通常是/usr/share/git-core/templates/hooks/这样之后git clone出来的仓库都会自动带上新同事入职不用教。手工装的话是从 Gerrit 服务器上下这个文件放进.git/hooks/目录并加上可执行权限注意这个目录不会被提交误删了就没用了模板目录才是长久方案。为什么必须是 Change-Id 而不是靠分支名因为 Gerrit 的模型里一个 Change 是一个逻辑上的评审单可以有很多个 patchset。你第二次推送修改的时候Gerrit 是靠 commit message 里那行 Change-Id 认出这还是同一个评审单把它变成 patchset N1。没有这一行它就会新建一个 Change评审历史断掉Jenkins 也就会把它当成一个全新的提交来构建你会在页面上看到两个孤立的评审单非常难受。3. 打通事件通道插件配置里每个字段为什么这么填3.1 Gerrit Trigger 和 Gerrit Code Review到底选哪个这两个插件的定位差别值得花点时间说清楚因为它直接决定你后面写 Job 的方式。Gerrit Trigger是历史最久、用得最广的一个。它的工作方式是长连 Gerrit 的 stream-events把事件读进来之后按 Job 上配的触发器过滤然后决定触发谁。它的优点是配置界面成熟、网上资料多、对自由风格 Job 支持得很好、有过很多生产环境的验证。缺点是它的配置模型偏界面驱动Pipeline 支持是后加的用起来有点别扭而且它维护节奏这些年慢下来了。Gerrit Code Review是后来者思路更贴近现代的 Pipeline 用法它把与 Gerrit 的交互封装成一个个 Pipeline step比如检出、投票、查询你在 Jenkinsfile 里显式调用逻辑一目了然。它支持走 REST不依赖 29418 端口。缺点是对自由风格 Job 的支持不如前者而且因为你可以在 Pipeline 里自由组合写错了也更难定位。我的判断标准很简单新建的流水线用 Gerrit Code Review已经用 Gerrit Trigger 稳定跑了几年的自由风格 Job除非有明确理由别动。迁移是有成本的而且这两套的触发语义不完全一样迁过去之后你会发现有些边界行为变了。3.2 服务器配置里的字段逐个说清楚在 Jenkins 的全局配置里加一个 Gerrit 服务器看起来字段不多但每个填错都会在测试连接那里给你一个不太友好的报错。我按 SSH 方式逐个讲。服务器名称Name是你在 Job 里引用的标识起个有意义的名字比如gerrit-main别用默认值以后接第二台 Gerrit 的时候你分不清。前端地址Frontend URL / Gerrit URL填的是用户浏览器访问的那个地址比如https://gerrit.example.internal注意这个地址不是给 Jenkins 连的是拼在留言里给点的人用的所以它必须是从开发者浏览器能打开的那个不能填内网 IP 或者 29418 端口。这个字段填错的后果很隐蔽投票是成功的但留言里那个构建链接是打不开的开发者点了没反应还以为构建没跑。SSH 连接的字段包括主机名、端口通常 29418、用户名就是 2.3 节建的那个 CI 账号。然后是认证方式选私钥。这里有个细节私钥的格式。如果你是用ssh-keygen生成的默认是 OpenSSH 格式老版本的 Jenkins 插件只认 PEM 格式就是那种以-----BEGIN RSA PRIVATE KEY-----开头的新版本两种都认。遇到无法解析私钥的报错先用ssh-keygen -p -m PEM -f 私钥文件转一下格式再试。还有一个字段容易被忽略是否设置成为流事件监听者Enable Stream Events / Use Rest API。走 SSH 的时候就依赖 stream-events必须是开着的。如果这个 CI 账号同时被用在多个地方多个连接同时订阅事件流是可以的Gerrit 允许多个订阅者但你要注意事件重复消费的问题——如果你在 Jenkins 里配了两个服务器条目连的是同一台 Gerrit事件会被消费两次Job 就会跑两次。3.3 点测试连接之前先在命令行里试通这是个能省掉半小时来回的训练不要在 Jenkins 界面上调试连接。先在你自己的终端上用完全相同的凭证和参数把命令跑通再回界面填。具体怎么做把 Jenkins 用的那把私钥拷到本地一个临时目录权限设成 600权限不对 SSH 会直接拒绝使用而且报错信息很不明确然后执行类似这样的命令# 测试 SSH 连通性和账号身份 ssh -i /tmp/ci_key -p 29418 ci-botgerrit.example.internal gerrit version # 订阅十秒事件流看有没有输出验证 stream-events 权限 timeout 10 ssh -i /tmp/ci_key -p 29418 ci-botgerrit.example.internal gerrit stream-events第一条命令能返回版本号说明网络、端口、账号、密钥这四样都是通的。第二条命令的用途更大它验证的是这个账号有没有订阅事件流的权限。Gerrit 里这个权限叫Stream Events配置在 All-Projects 的全局能力Global Capabilities里默认情况下普通账号是没有的。你可以自己上传一堆测试提交来验证——如果命令跑起来一片安静你明明推了代码却没有输出那八成就是这个权限没开。这是事件收不到类问题里排名第一的原因我把它放在这里就是希望你别在这一步浪费一晚上。命令行跑通之后再回 Jenkins 界面填测试连接按钮一般就绿了。如果这时候还不通问题大概率在 Jenkins 侧的代理设置或者 JVM 的 DNS 解析上而不是 Gerrit 侧。3.4 事件过滤别让你的 Job 自己触发自己触发器的配置界面里有一堆勾选框我按重要性说。Patchset Created是必须勾的这是核心触发点。Draft Published看你们用不用草稿模式不用的话勾不勾无所谓。Comment Added这个要特别小心——很多人为了让开发能通过评论触重新构建会勾上它但如果你不加过滤条件就会掉进一个循环CI 投了 Verified 1这个投票本身也是一条评论也会产生一条 comment-added 事件如果过滤条件没把它排除掉Job 就又跑一遍又投一次票又产生一条评论……机器上就是无限循环跑构建。正确的做法是给 comment-added 加过滤只匹配特定的触发词比如评论内容里包含recheck或者retrigger才触发同时把投票类评论排除掉。我们的配置是comment-added 事件只在评论内容匹配recheck时触发其他一律忽略。开发想重跑在页面上评论一句recheck就行这个习惯教一次大家就会了。还有一个选项叫静默模式。它的意思是当 Gerrit 连接断开的时候不要触发新任务也不要因为连接问题导致任务失败报警。为什么需要这个因为 Gerrit 做维护重启的时候连接会短暂断开重连如果不静默这段时间里可能会有一批任务莫名其妙失败或者积压一堆无意义的重试。我们生产环境是开着的。4. Job 的核心refspec、环境变量和流水线骨架4.1 为什么直接配 git 拉分支在这里行不通不熟悉这块的人第一反应是Job 里配个 git 源拉main分支跑构建结束。这个配置在普通的定时构建场景里没问题但在 Gerrit 场景下是错的而且错得很隐蔽——它会构建成功投票也会成功但这个票投给的是一个根本没在评审的内容。原因是main分支上的最新提交和这个 patchset 的内容完全是两回事。patchset 的内容存在 Gerrit 的refs/changes/命名空间下具体格式是refs/changes/change number 的末两位/change number/patchset number。比如 change number 是 1234patchset 是 5引用就是refs/changes/34/1234/5。这个引用不在 normal 的分支列表里普通git fetch不带 refspec 是拉不到的。所以 Job 的第一件事必须是根据事件里的信息算出这个 patchset 的 refspec然后精确地把它拉下来。这件事的信息就来自 Gerrit 注入的环境变量。4.2 环境变量Job 里能直接用的那些Gerrit 插件触发 Job 的时候会往构建环境里注入一组环境变量。这张表我建议直接抄下来贴在工位上调试的时候非常有用变量名含义典型用途GERRIT_PROJECT项目名如platform/api多项目共用一个 Job 时判断GERRIT_BRANCH目标分支如main决定跑哪套测试GERRIT_CHANGE_NUMBER评审单编号拼 API 地址、写留言GERRIT_PATCHSET_NUMBER当前 patchset 序号判断是不是最新一版GERRIT_CHANGE_IDChange-Id带 I 前缀那串拼 API 地址、查状态GERRIT_PATCHSET_REVISION这个 patchset 对应的 commit SHA投票时指定 commitGERRIT_REFSPEC完整的 refs/changes 引用git fetch 用GERRIT_CHANGE_URL评审单的网页地址写进留言GERRIT_CHANGE_SUBJECT提交标题构建描述里显示GERRIT_EVENT_TYPE事件类型区分 patchset-created 和 comment-added这里有个必须提醒的点GERRIT_REFSPEC这个变量是 Gerrit Trigger 插件提供的。如果你用的是 Gerrit Code Review 插件它的检出方式不一样通常是调用插件提供的检出 step而不是自己拼 refspec。混用两套插件的写法是新手最常见的报错来源日志里会出现变量为空导致 fetch 了一个空引用。另外这些变量只在构建过程中有效而且它们不会被继承到 Jenkins 的全局配置里只能在 Job 内部的脚本中使用。写 Pipeline 的时候要注意作用域用env.GERRIT_REFSPEC来取。4.3 手写检出最不容易出错的方式如果你用的是 Gerrit Trigger 加 Pipeline或者你想完全掌控检出过程我推荐手写这几步不依赖插件的魔法。它多写三行但可控性和可调试性强很多。stage(Checkout Patchset) { steps { script { // 从事件注入的环境变量里取 refspec取不到就立刻失败 def refspec env.GERRIT_REFSPEC if (!refspec) { error(GERRIT_REFSPEC 为空说明触发方式不对停止构建) } echo 准备检出: ${refspec} (change ${env.GERRIT_CHANGE_NUMBER} patchset ${env.GERRIT_PATCHSET_NUMBER}) // 先拿到仓库的 refs 列表只拉引用不拉内容很快 sh git fetch --no-tags --prune origin refs/changes/*:refs/remotes/origin/changes/* // 再精确检出这个 patchset 对应的 commit sh git checkout -f ${env.GERRIT_PATCHSET_REVISION} } } }这几行里有两个细节值得说。第一行 fetch 用了--no-tags因为 Gerrit 仓库的 tag 可能非常多全拉下来纯属浪费时间和磁盘用了--prune是为了清掉已经被删除的引用避免工作区里残留过期内容。把refs/changes/*映射到refs/remotes/origin/changes/*是一个折中好处是你之后可以按需 checkout 任何一个 patchset坏处是引用列表会随着评审单增多而变长所以我在 Job 里配了定期清理工作区。检出用GERRIT_PATCHSET_REVISION也就是 commit SHA而不是用FETCH_HEAD是因为 SHA 是唯一的、可验证的。日志里直接打出来的一长串哈希跟 Gerrit 页面上显示的那串一模一样一旦发现构建的内容不对一眼就能对上。用checkout -f里的-f是强制覆盖本地修改因为工作区可能被上一个 Job 留下脏文件不清干净会构建出奇怪的结果。4.4 一份可以照着改的 Jenkinsfile 骨架下面这份是整个流程的主干我把投票和留言的部分也放进来了虽然细节在下一节展开讲但放在一起你能看到完整链条。pipeline { agent { label linux-build } // 用固定的构建节点标签别用 any options { timestamps() timeout(time: 30, unit: MINUTES) // 兜底防止卡死占住执行器 buildDiscarder(logRotator(numToKeepStr: 50)) } environment { GERRIT_HOST gerrit.example.internal GERRIT_PORT 29418 GERRIT_USER ci-bot } stages { stage(Checkout Patchset) { steps { script { def refspec env.GERRIT_REFSPEC if (!refspec) { error(GERRIT_REFSPEC 为空触发方式不对) } sh git fetch --no-tags --prune origin refs/changes/*:refs/remotes/origin/changes/* sh git checkout -f ${env.GERRIT_PATCHSET_REVISION} sh git log -1 --format%H %s } } } stage(Build Test) { steps { sh mvn -B -DskipTestsfalse clean verify } post { always { junit allowEmptyResults: true, testResults: **/target/surefire-reports/*.xml } } } } post { success { script { voteGerrit(1, 构建通过测试全部绿灯) } } failure { script { voteGerrit(-1, 构建失败详情见构建日志) } } aborted { echo 构建被取消不投票 } } }注意agent { label linux-build }这一行不要图省事写any。原因很实际接 Gerrit 的 Job 依赖一个已经配好的 git 环境、可能还有特定的 JDK 版本和 Maven 镜像配置any意味着它可能落到一个刚加进来、什么都还没装的节点上表现就是昨天还好好的今天全挂。用标签把节点绑定住是这类流水线最基本的一条纪律。另外aborted分支里只打日志不投票这个设计是有意的。如果构建是被取消的比如开发又推了一版你这时候投一个 -1 是不合适的会误导评审人。更细的处理在下一节讲。5. 把结果写回 Gerrit投票、留言和一个容易忽略的竞态5.1 Verified 标签的权限配在哪个 ref 上投票失败最常见的原因不是代码写错是权限没开。这一步的配置在 Gerrit 的服务端路径是进入All-Projects仓库找到refs/heads/*的访问权限给Label Verified加上 CI 账号的-1..1权限。这一步很多人做过但往往只做了这一半。还有一半在refs/changes/*上。为什么因为 Gerrit 在评估你能不能给这个评审单投票的时候会同时检查目标分支的权限和你对refs/changes/*的读权限。refs/changes/*默认对普通账号是只读之外还限制可见性的如果你的 CI 账号没有这个命名空间的权限表现就是投票时报一个含糊的not permitted或者干脆连 change 都查不到。我的做法是给 CI 账号单独建一个权限组然后在refs/heads/*和refs/changes/*上都加上 read 权限并在refs/heads/*上加Label Verified的-1..1。同时注意refs/meta/config这种特殊引用不用碰别手痒。注意权限改完需要让 CI 账号重新建立连接才会生效有些情况下 Gerrit 会缓存权限信息。改完权限发现还是不行先断开 Jenkins 与 Gerrit 的连接再重连一次别急着改代码。5.2 三种回写方式我为什么最后选了 SSH回写投票有三种常见方式我把它们的实际体验对比一下方式实现优点缺点插件内置 step在 Pipeline 里调gerritReview写法简洁参数有提示版本耦合升级插件可能改行为REST APIcurl调 review 接口通用任何语言都能用必须配 HTTP 密码反向代理容易踩坑SSH 命令ssh ... gerrit review复用已验证的 SSH 凭证最直接命令拼接要小心转义我们最后用的是 SSH不是因为另外两种不好而是因为 CI 账号的 SSH 通路已经跑通了复用它意味着少维护一套凭证少一个出错的地方。具体命令是这样# 给指定 commit 投 Verified 1并附一条留言 ssh -i /var/lib/jenkins/.ssh/gerrit_ci_key -p 29418 ci-botgerrit.example.internal \ gerrit review \ --label Verified1 \ --message 构建通过 | 详见: ${BUILD_URL} \ ${GERRIT_PATCHSET_REVISION}几个细节必须说。--label Verified1的写法在不同版本的 Gerrit 上略有差异老版本是--verified 1新版本推荐用--label形式建议你直接在你的 Gerrit 上执行ssh ... gerrit review --help看当前支持哪种别照抄网上的老命令。--message里的引号嵌套是最容易出错的地方外层已经有单引号了如果消息内容里还有变量和空格很容易拼错导致消息截断或者命令解析失败。我的办法是把消息先写到一个临时文件再用--message-file如果你用的版本支持或者干脆把整个命令放进脚本文件里执行而不是塞进一行 Shell。投票失败的时候不要只看失败了三个字SSH 的报错会告诉你原因如果是Permission denied是密钥或账号问题如果是not permitted是 5.1 节的权限问题如果是change not found那多半是 change number 或者 revision 取错了回头看看环境变量。5.3 留言怎么写决定这套东西会不会被开发接受技术上投票成功就算完成了但流程能不能被团队接受很大程度上取决于留言写得好不好。我见过最糟糕的留言就是一条光秃秃的-1什么信息都没有开发看到之后要在 Jenkins 里翻半天找到这次构建再从几千行日志里找到报错。这种体验用过两次大家就会开始绕过这套流程。好的留言至少包含三样东西结果是什么、去哪儿看详情、下一步该做什么。比如失败的时候写成这样构建失败单元测试OrderServiceTest有 2 个用例未通过详情见构建日志链接。作者排查时可以先在本地跑mvn test -DtestOrderServiceTest复现。 这句话里第一段说结果第二段给链接第三段给了一个可以直接执行的命令——最后这一段是最有价值的它把你自己去翻日志吧变成了你可以这么试。我在脚本里做了一个小优化把最关键的失败原因比如失败的第一个测试类名、编译错误的文件名和行号从日志里抽出来拼进留言里前 200 个字符。这样开发在 Gerrit 页面上不用点任何链接就能知道大概是什么问题。def voteGerrit(int score, String baseMsg) { def excerpt // 从构建日志末尾抓一段摘要最多 300 字符去掉容易破坏 shell 的字符 try { excerpt sh(script: tail -n 40 build.log | tr -d \\r | head -c 300 || true, returnStdout: true).trim() excerpt excerpt.replaceAll(, ).replaceAll(\, ) } catch (e) { excerpt } def msg baseMsg (excerpt ? ( | 关键输出: excerpt) : ) sh ssh -i /var/lib/jenkins/.ssh/gerrit_ci_key -p 29418 \ ${GERRIT_USER}${GERRIT_HOST} gerrit review \ --label Verified${score} \ --message ${msg} \ ${env.GERRIT_PATCHSET_REVISION} }注意抓取摘要那一步里我做了字符清理把引号删掉。这不是洁癖是因为留言内容最终要塞进远程执行的命令里一个单引号就能让整条命令崩掉而这个问题只在某些特定的失败信息里才会出现测试的时候很难覆盖到。宁可丢掉几个引号也不要让投票在这里失败。5.4 竞态新 patchset 来了旧构建还在跑这是整套流程里最需要动脑子处理的地方也是我认为区分能用和好用的分界线。场景是这样的开发推了 patchset 3Jenkins 开始构建。构建到一半他发现一个笔误马上推了 patchset 4。这时候 patchset 3 的构建跑完了如果它老老实实给 patchset 3 投了一个 Verified 1评审人打开页面看到的可能是 patchset 4 显示没有投票而 patchset 3 是绿的——评审人会以为最新的版本是绿的但实际上它根本没被构建过。这个错误非常危险因为它看起来一切正常。处理方式有几层。第一层插件层面通常有跳过过期 patchset 的投票这样的选项把它打开。原理是投票之前先查一下这个 change 当前最新的 patchset 是不是我构建的这一版不是就不投。Gerrit Trigger 有这个选项Gerrit Code Review 插件在投票 step 里会更智能地处理这一点但你要确认它是不是真的在检查。第二层自己在脚本里判断。投票之前查一次 change 的当前 patchset 号如果和GERRIT_PATCHSET_NUMBER不一致就跳过投票只在日志里记一笔。这个检查用 SSH 命令就能做成本很低我强烈建议加上因为它是唯一一个不依赖插件行为的保障。第三层是并发控制。如果同一时间有五个 patchset 在构建机器会同时跑五个任务谁先跑完不确定。这时候要限制每个 Job 的并发数量让同一分支或者同一个 change 的任务排队执行。Jenkins 的 Job 上有不允许并发执行的选项打开它最简单粗暴但有效新任务进来时如果前一个还在跑就排队等着。6. 排查链路从没有投票倒着往回查6.1 第一步永远是看 Gerrit 有没有发出事件遇到没有投票绝大多数人的第一反应是去翻 Jenkins 的构建历史翻半天发现压根没有触发过任务然后又不知道该看什么了。正确的顺序是从源头开始一步步往下走。第一步在任意一台能连 Gerrit 的机器上用 CI 账号订阅事件流ssh -p 29418 ci-botgerrit.example.internal gerrit stream-events然后另开一个窗口往 Gerrit 推一个测试提交。观察事件流窗口有没有输出对应的 JSON。这一步能干净地把问题分成两半有输出说明 Gerrit 侧一切正常问题在 Jenkins 的接收和过滤环节往第 2 步走。没有输出说明 Gerrit 根本没往外发事件。这时候检查两件事账号有没有Stream Events能力以及你推的是不是真的产生了一个新 change有时候推失败了你没注意。6.2 Jenkins 侧的日志看哪几个地方确认 Gerrit 有事件之后接下来看 Jenkins。有三个地方值得看顺序不能乱。第一个是 Jenkins 的系统日志Manage Jenkins → System Log里面会记录 Gerrit 连接的状态变化。你要找的是connecteddisconnectedreconnect这类关键词。如果看到反复的 connect / disconnect说明连接不稳定问题可能在网络或者 Gerrit 的负载上而不在配置上。这种情况构建任务会时有时无是那种明明配对了但就是偶尔不触发的罪魁祸首。第二个是 Gerrit 服务器配置页面上的连接状态指示它一般会显示当前连接是不是活的、最近一次心跳是什么时候。如果这里显示断开先别怀疑 Job 的配置先把连接修好。第三个才是 Job 的构建历史。这时候要看的是有没有触发而不是为什么失败。如果有触发但失败了那问题已经不在事件通道里了往下走。我整理过一个排查顺序表实际用下来能覆盖八九成的情况现象优先怀疑验证方式完全没触发事件流权限手动跑stream-events看有没有输出完全没触发连接断开看系统日志里的 connect/disconnect偶尔触发触发器过滤过严检查分支、项目名的匹配规则触发两次事件被重复消费检查是否配了两个服务器条目触发了但拉不到代码refspec 或 SHA 取错日志里打出 refspec 和 SHA 手工验拉到了但内容不对用了分支而不是 patchset对比git log -1和页面上的 SHA构建成功但没投票投票权限手工执行一次 review 命令投票报 not permittedVerified 权限或 refs/changes 读权限检查两个 ref 的权限配置6.3 权限类报错逐条拆开看权限类的报错信息通常很短很含糊但结合上下文基本能定位。我把我们实际遇到过的几种列出来。not permitted to see the change这种是读权限问题。CI 账号对目标项目或者refs/changes/*没有读权限。注意 Gerrit 的权限是按仓库继承的如果你的项目从All-Projects继承了默认配置而默认配置里对refs/changes/*是限制可见的那就要在项目层面单独给 CI 账号开。you are not allowed to label这种是标签权限没配或者配错了 ref。最常见的是权利给到了refs/heads/main但目标分支是refs/heads/release/*通配符没覆盖到。检查的时候要一条条看别只看第一条。invalid credential或者 HTTP 401是凭证本身的问题多半是用了登录密码而不是 HTTP 密码或者 HTTP 密码生成之后又改过账号设置导致失效了。这种情况重新生成一次 HTTP 密码就好。host key verification failed是 SSH 的 known_hosts 问题。Jenkins 运行 Job 的时候是以jenkins用户的身份SSH 会去读这个用户的~/.ssh/known_hosts。你的私钥在别的地方、known_hosts 里没有 Gerrit 的记录就会报这个。解决办法是把 Gerrit 的主机公钥加进去或者在 SSH 命令里显式指定-o UserKnownHostsFile指向一个预置好的文件。生产环境我更推荐后一种把 known_hosts 作为配置管理起来避免依赖运行时状态。6.4 构建失败不等于代码有问题这个我单独拿出来讲因为我们被坑过不止一次而且是那种看起来是代码问题实际是环境问题的坑最容易让开发产生不信任。最常见的是拉不到 patchset。表现是git fetch失败提示某个 ref 不存在。原因通常是 Gerrit 做了副本replication主节点已经返回了事件但从节点还没同步到这个 commitJenkins 拉的时候从节点上就没有。这种失败是瞬时的重试一次就好了。我们的做法是在检出环节加一个简单的重试失败后等 5 秒再试最多试 3 次。加了这三行之后这一类偶发失败基本消失了。另一种是依赖拉不下来。Maven 或者 npm 的仓库偶尔抽风构建就红了然后投了一个 -1开发一脸懵。这种也要走重试而且要在留言里明确写出来是依赖下载失败非代码问题避免开发真的去改代码。更好的做法是在构建节点上配本地缓存或者内网仓库把外部依赖的抖动隔离掉。还有一种是工作区脏。上一个构建留下的文件把这一次的结果污染了表现是本地能过、Jenkins 不过。我们的对策是在 Job 的最后一步固定清理工作区中不在版本控制里的文件以及在检出后用git clean -ffdx清一遍。清理工作区这件事一定要做不做的代价远大于它多花的那几秒。7. 跑上几个月之后你才会遇到的那些事7.1 构建节点和执行器的数量得算一下刚开始一两个项目的时候单节点跑得好好的。等接了十几个项目所有人都在同一个 Jenkins 上推代码你就会开始收到任务排队的抱怨。这时候要算一下账一个构建平均跑多久高峰期每分钟来多少提交乘一下就知道需要多少个执行器。我们的经验是留出至少 30% 的余量因为构建时间分布很不均匀一个跑十分钟的重活儿占住一个执行器后面的轻任务就全堵住了。更细的做法是按项目分节点。把构建重的、依赖多的项目放到专用的节点上轻量的放一起。用 Jenkins 的节点标签来做这个隔离在每个项目的 Job 上指定标签。这样做还有个附带好处某个节点的环境被搞坏了比如有人手贱升级了 JDK只影响用这个标签的项目不会全场趴窝。7.2 浅克隆能省多少什么时候不能用Gerrit 的仓库历史有时候会非常长一次全量克隆可能几百兆甚至上 G。如果你每个 Job 都做全量克隆磁盘和 IO 都会很快撑不住。优化手段是浅克隆用--depth1只拉最近一个提交。但这里有个坑对接 Gerrit 的场景下你要拉的引用是refs/changes/...浅克隆对这类引用的支持在不同 Git 版本上行为不一致而且有些构建需要看历史比如要生成 changelog、要做基于历史 diff 的增量测试浅克隆就没用了。我的做法是折中初次克隆用一个共享的引用仓库reference repository所有 Job 从这个本地仓库克隆速度非常快增量更新用 fetch 到refs/remotes/origin/changes/*的方式只拉变更的引用。这个配置一次设好后面基本不用管。7.3 拉不动镜像和依赖把外部依赖关在门外构建过程里如果需要拉 Docker 镜像或者从公网仓库下依赖那这台机器的对外依赖就成了整个流水线的薄弱环节。我们的做法是把所有外部依赖都换成本地来源。Docker 镜像的来源可以在构建节点上改/etc/docker/daemon.json加一个内网的镜像源地址然后重启 Docker 服务。写法大概是这样{ registry-mirrors: [https://your-internal-mirror.example.internal], insecure-registries: [your-internal-registry.example.internal:5000] }改完之后用docker info确认配置生效了再试着拉一个镜像验证速度。注意insecure-registries这一项只在用非加密连接访问内网仓库时才需要能用加密连接就不要加少一个配置少一个隐患。同样地Maven 的settings.xml、npm 的.npmrc、pip 的pip.conf都统一指向内网仓库。这件事做完之后有一个很明显的收益构建失败的原因从各种网络抖动收敛成了代码有问题和环境配置有问题两类排查时间直接降下来。如果你们的内网没有这样的仓库服务也可以用一台普通的机器搭一个缓存把常用的依赖和镜像在本地存一份。前期花一天时间搭后面省的时间是几十倍。7.4 离线安装和版本锁定Jenkins 的插件是从更新站点在线下载的生产环境如果机器不能出公网就得走离线安装。做法是在一台能上网的机器上下载.hpi文件上传到 Jenkins 里通过高级标签页上传安装或者直接放进JENKINS_HOME/plugins/目录后重启。离线场景下更需要版本锁定因为在线的时候顺手点了升级出问题还能回滚离线环境里往往没有备份的旧版本。我的建议是把所有插件的.hpi文件在一个目录里按版本号归档任何一个插件升级之前先把当前版本复制一份留底再装新的。这个习惯救过我们一次——某个插件的升级导致所有 Pipeline 的检出 step 行为变了我们花了十分钟回滚如果没留底就得从头找那个版本在哪下载。Jenkins 本体升级也一样升级前先确认插件兼容性Jenkins 在升级页面会列出升级后可能不兼容的插件清单一定要看。我们的节奏是先升级测试环境跑一个月没问题再升生产。7.5 监控别等开发来问为什么没投票这套流程一旦上线它就成了团队的工作依赖它挂了没人知道是最糟糕的状态。我们后来加了一个很简单的监控定时用 CI 账号查一次 Gerrit 连接状态再加上一条最近两小时有没有成功投票的检查异常就通过团队平时用的沟通工具发一条消息。不需要多复杂一个每分钟跑一次的脚本就够。监控的内容不要只看进程活着要看业务意义上的活。Jenkins 进程活着但 Gerrit 连接断了从进程角度看一切正常从用户角度看所有提交都没人投票。我个人在这套东西上投入的时间前后加起来大概一周多其中真正花在配置上的时间不到三分之一剩下三分之二都在处理事件通道、权限和竞态这些边角。但上线之后的效果很明显机械性的往返沟通基本消失了评审人打开页面看到绿色就直接看逻辑看到红色下面有具体行号就知道该改哪儿。如果你也在做这件事我最后再分享一个小经验——先拿一个不那么重要的项目试点跑顺了再铺开因为前期一定会有需要调优的地方在小项目上调整的成本比在所有项目上一起出问题低得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

公路落石检测实战:VOC转YOLO、小目标优化与边缘部署 2026/10/2 21:58:49

公路落石检测实战:VOC转YOLO、小目标优化与边缘部署

简介:本资源是一个面向计算机视觉初学者与目标检测实践者的公路落石检测专用数据集,聚焦于真实场景下的小目标识别任务,适用于YOLO系列模型训练、VOC格式迁移学习及标注工具实操练习。数据包共1019个文件,主体为282张JPEG图像、28…

阅读更多 →
基于Docker Swarm的Elasticsearch生产级集群部署与运维实践 2026/10/2 21:58:47

基于Docker Swarm的Elasticsearch生产级集群部署与运维实践

先把结论说在前头:这套方案不是我搭着玩的,是真的跑过一年线上环境的。三个 Elasticsearch 数据节点、三个 master 节点,全部跑在 Docker Swarm 之上,每天承载数亿条日志写入和搜索请求,期间还经历过两次机房节点故障、…

阅读更多 →
Keychain、无遥测与Socket隔离:Coucou的8个安全实践清单 2026/10/2 21:58:39

Keychain、无遥测与Socket隔离:Coucou的8个安全实践清单

Keychain、无遥测与Socket隔离:Coucou的8个安全实践清单 【免费下载链接】coucou A tiny friend that lives in your notch (macOS) or at the top of your screen (Windows, Linux) and keeps an eye on your coding agents: Claude Code, Gemini CLI, Antigravity…

阅读更多 →
频率域图像处理核心:傅里叶变换、频域滤波与同态滤波全解析 2026/10/2 21:58:12

频率域图像处理核心:傅里叶变换、频域滤波与同态滤波全解析

数字图像处理这门课,理论上讲,前几章再零碎,大家照着例题还是能把作业写出来的。但到了第四章频率域图像处理,大多数人的反应会突然慢下来:坐标系成了 u、v,图像变成了复数矩阵,之前积累的线性代…

阅读更多 →
YOLOv8垃圾分割检测系统:端到端实例分割实战指南 2026/10/2 21:58:12

YOLOv8垃圾分割检测系统:端到端实例分割实战指南

简介:YOLOv8垃圾分割检测系统是一套面向人工智能初学者与计算机视觉实践者的轻量级垃圾分类解决方案,聚焦图像识别与实例分割任务,适用于智能环卫、环保监测及课程设计等场景。资源包共41个文件,含15张JPG/PNG格式的样本图像、3个…

阅读更多 →
PowerShell禁止运行npm.ps1?一条命令解决Node.js脚本执行策略报错 2026/10/2 21:58:12

PowerShell禁止运行npm.ps1?一条命令解决Node.js脚本执行策略报错

在PowerShell里输入 npm -v ,回车,终端弹出一段红字:“npm : 无法加载文件 D:\nodejs\npm.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 about_Execution_Policies。”这句话我见过太多次了。毫不夸张地说&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉