📝 摘要 :一条告警说某个构建跑了 23 天。可打开 Jenkins------
0 of 2 executors busy、队列是空的、模块没一个显示「构建中」、上次构建 2 秒就成功了,四个地方都说「没有构建在跑」 ,我一度判定「指标坏了」,这个判断是错的 。它藏在 Maven 模块页,连构建丢弃策略都放过了它,理由是「it is still running」:靠「还在运行」躲过清理,又靠躲过清理继续「运行」。而红叉点下去纹丝不动,往下每层标准做法也依次失效。本文有完整追查、从容器日志挖出的根因,和真正收得掉它的办法。

前情:一条告警,指向一片空气
给 Jenkins 补监控时我写了 11 条告警规则,其中一条是「构建运行超过 2 小时」。规则加载上去,它立刻就命中了------怎么配这套监控是另一篇的事(《Jenkins 监控一条龙:接入、看板 9964、11 条告警规则直接抄走》),本文只讲这条告警响了之后发生了什么。
两天后再看,它还在烧,数字还在涨:

图:Value 570.2 小时 ≈ 23.8 天 。注意别把两个时间搞混------Active Since 2d 20h 是告警 烧了多久,Value 570.2 才是构建跑了多久。
⚠️ 这张是后来补截的 ,那会儿我已经把这条规则的 description 改过一版了------所以图上那句「去
groupId:artifactId对应的模块页看构建历史 」是我事后加的 。当时真正响的是下面这段,它可没这么好心:
text
任务 order-sync/com.example:order-sync-api 已运行 556.6 小时。
若同期 executor 并未被占用,多半是状态没收干净的僵尸构建,需手动 abort
那就去 abort。 然后我卡了半天------因为打开 Jenkins,什么都没有。
一、四个地方都说「没有构建在跑」
先看任务列表:

图:任务列表全绿,构建队列是空的,0 of 2 executors busy。
点进告警里那个 job,还是一样:

图:order-sync-api 上次成功是 1 月 8 天前的 #37 ,只跑了 2 秒 ,上次失败「无」。左下角构建历史里也只有 #37。
把四条摆一起:
| 我看的地方 | 它说什么 |
|---|---|
| 构建执行状态 | 0 of 2 executors busy------没有执行器在忙 |
| 构建队列 | 队列中没有构建任务 |
| 模块状态 | 三个模块,两个绿勾、第三个是个没见过的图标------但没有一个显示「构建中」(那个怪图标第三节揭晓) |
| 上次构建 | #37,1 月 8 天前,只跑了 2 秒 |
四个独立来源全都说没有构建在跑。只有那一条指标坚持说,有个东西已经跑了 23 天。
1.1 ⚠️ 我在这里判断错了一次
四比一,最省事的结论是「指标坏了」------我当时就是这么想的,而且差点照着这个结论写下去。
会这么想是因为我心里有一条推理:
如果它真在跑,必然占着一个 executor。executor 是 0,所以没有构建在跑。
这句话读起来天经地义。它是错的 ,第三节会说清为什么。而正是这句话,让我差点把一个真实存在的问题当成指标 bug 处理掉。
⇒ 一条通用的教训:当「多个来源」和「一个来源」打架时,先别按票数投票,先问「这几个来源是不是在回答同一个问题」。 后面会看到,那四个来源和那一条指标,回答的根本不是同一个问题。
二、它在第三层:模块自己的页面
回头盯着那个 job 名看:
text
order-sync/com.example:order-sync-api
└──────┬──────┘
groupId:artifactId
com.example:order-sync-api 是 Maven 的 groupId:artifactId ------它不是一个普通任务,是 order-sync 这个 Maven 项目下的一个模块。而模块有它自己的页面,我前面根本没打开过:

图:找到了。左下角 #39 的进度条还在走,旁边那个红叉就是 abort 按钮。
⚠️ 再留意一眼 #39 最左边------那也是个绿勾 。一个进度条还在走的构建,图标却显示成功。这个矛盾是整件事的钥匙,第六节揭晓。
真正说明问题的是右边那两行,必须放在一起读:
text
最近一次构建 (#39),23 天之前 ← 开始了
最近完成的构建 (#37),1 月 8 天之前 ← 但「完成」的还停在上一个
#39 开始过,然后再也没有「完成」。 它就卡在这两行之间的缝里,所以既不算成功也不算失败,两列都不会动它。
(后面第五节会看到,它其实比这更微妙------结果早就判完了,只是没走完收尾。)
这也解释了第一节那张图里「上次成功 #37、上次失败无」为什么一动不动:这两列压根不描述一个还没结束的构建。
三、把时间线还原出来
Jenkins 界面上看不到 #39 经历了什么,但指标里的信息足够拼出来:
| 时间 | 发生了什么 | 依据(指标) |
|---|---|---|
| 2026-07-28 | #37 正常跑完,三个模块全成功,order-sync-api 用了 2 秒 |
last_build_start_time + last_build_duration = 2000 + last_build_result_ordinal = 0(SUCCESS) |
| 2026-08-12 11:06:32 | #39 被触发 |
构建页显示的启动时刻 |
| 11:06:37(5 秒后) | 构建被 abort ,此时 order-sync-api 正走到写 jar 那一步 |
容器日志 order-sync #39 aborted(第五节) |
| 同一刻 | order-sync-api 判完 SUCCESS 就再没走完收尾 (详见 6.2);order-sync-server 没轮上,结果是 NOT_BUILT |
running_build_duration 从这一刻起按墙钟涨;-server 的 last_build_result_ordinal = 3(NOT_BUILT)------第一节那张图里它那个不是绿勾的图标就是这个 |
| 之后 | #39 的父构建记录被清掉了,模块记录却删不掉 |
父任务 discard_active = 1、available_builds_count = 1(只剩 #37);日志里那句 Unable to delete ... because it is still running 是直接证据(5.1) |
「按墙钟涨」是可以验的------这条查询在正常情况下应该约等于 30:
bash
curl -s --get http://prometheus.example.com:9090/api/v1/query \
--data-urlencode 'query=delta(default_jenkins_builds_running_build_duration_milliseconds[30m])/60000'
# → 30.25 墙钟过了 30 分钟,它涨了 30.25 分钟,一秒不差
3.1 三个指标各说各话,但没有一个在撒谎
| 指标 | 它其实在回答什么问题 | 答案 |
|---|---|---|
last_build_* |
最后一次跑完的构建怎么样? | #37,2 秒,成功 |
running_build_duration |
有还在 building 的构建吗?跑多久了? | #39,23 天,还在涨 |
executor_in_use |
有几个执行器被占着? | 0 |
插件给 running_build_duration 写的 HELP 是这么说的:
text
Indicates the runtime of the run currently building if there is a run currently building
「if there is a run currently building」------它只在有构建进行中时才存在。 换句话说,这条指标存在本身就是结论:Jenkins 认为有个 Run 还在 building。它没坏,它一直在准确回答自己那个问题。
⇒ 所以第一节那句推理错在哪,现在清楚了:
❌ 如果它真在跑,必然占着一个 executor。
这话对普通任务 成立。但 Maven 模块的构建跑在父任务的 executor 里,它不单独占号牌 。父构建早退出了,executor 自然归零------而模块那个 Run 对象还挂在那儿。
「四个来源 vs 一个来源」从来就不是四比一------它们问的根本不是同一个问题 。那四个各自回答的是「有没有执行器在忙」「队列里有没有排队的」「上次跑成功没有」,只有那一条指标问的是「有没有东西还在 building」。而这恰恰是当时唯一该问的那个问题。
四、为什么它能躺三周:层层遮蔽,每层都合理
| 你会去看的地方 | 为什么看不到它 |
|---|---|
| 任务列表 | 模块不出现在任务列表里,只有父任务 order-sync 在 |
| executor / 构建队列 | 模块构建不单独占 executor,父任务早退出了 → 显示 0 of 2 busy |
| 父任务的构建历史 | 构建丢弃策略把 #39 的父记录清掉了 → 历史里只剩 #37 |
三层里没有一层是 bug,全是正常设计。 (第五节还会补上第四层------连清理机制都放过了它。) 模块本来就不该出现在任务列表;模块构建本来就不该单独占执行器;构建丢弃策略本来就该清理旧记录。
它们叠在一起的效果,是这个东西在所有常规入口里都不存在------除了它自己的模块页,和那一条指标。
它不消耗资源,所以没人会因为「变慢」发现它;它也不报错,所以不会有任何通知。唯一还看得见那里的,就是那条告警。
五、它是怎么变成这样的:日志里的两行
到这一步我本来打算收尾了------「它是什么、为什么找不到」都清楚了,至于当初为什么会卡住,我以为查不到:父构建的记录早被丢弃策略清掉,UI 上是个 404。
但这台 Jenkins 已经 43 天没重启 过,docker logs 里还留着当时那段。
bash
docker logs jenkins 2>&1 | grep -nE "exit code|terminated unexpectedly|ChannelClosed|OutOfMemory|order-sync"
第一行就结案了:
text
2026-08-12 11:06:37 INFO hudson.model.Run#execute: order-sync #39 aborted
构建 11:06:32 启动、11:06:37 被 abort------只跑了 5 秒 。而那 5 秒里它正好走到 order-sync-api 的这一步:
text
[INFO] --- jar:3.1.1:jar (default-jar) @ order-sync-api ---
[INFO] Building jar: /var/jenkins_home/workspace/order-sync/order-sync-api/target/order-sync-api-1.0-SNAPSHOT.jar
日志到此为止 ------没有 BUILD SUCCESS、没有 Total time、没有 Finished:,也没有任何异常栈 。因为它不是崩的,是在这一刻被掐断的 :abort 切掉了 Maven 的 channel,模块的 result 已经判完(所以是 SUCCESS),后面三步收尾没人做了;第三个模块也就没轮上(NOT_BUILT)。
📌 我中途猜错过一次 :日志断得这么干净,我一度怀疑是 forked 出去的 Maven JVM 被 OOM-killer 干掉了,还专门去找
exit code 137。证据不支持 ------一个异常栈都没有,正因为 abort 是正常控制流,不是崩溃。
5.1 ⭐ 而它能躺 23 天,是因为清理机制被自己的症状挡住了
同一份日志里还有这么两行:
text
Also: java.io.IOException: Unable to delete order-sync/com.example:order-sync-api #39
because it is still running
jenkins.util.io.CompositeIOException: Failed to rotate logs for [order-sync #39]
构建丢弃策略来清理过。父记录删掉了(所以 UI 上 404),模块记录没删掉------理由是「it is still running」。
⇒ 唯一可能自动清掉它的机制,恰恰被它自己的症状挡住了:
它因为「还在运行」躲过了清理,又因为躲过了清理而继续「运行」。
⚠️ 顺带注意第二行------Failed to rotate logs for [order-sync #39]。它不只是「没被清掉」,它还把这个任务的日志轮转整个搞失败了。 所以「它安静地待着、谁也没影响」这个印象是不准确的:它一直在让一件后台维护工作报错,只是那个错没人看。
第四节说它被三层正常设计盖住,现在得补第四层------连清理机制都放过了它,而且是出于一个完全讲得通的理由。
5.2 不是孤例
text
2026-07-30 order-sync #38 aborted
2026-08-12 order-sync #39 aborted ← 留下残留的这次
2026-08-13 order-sync #40 aborted
这个任务被 abort 是常态,另外还有三次 hudson.remoting.ChannelClosedException(Channel to Maven ... channel is already closed)。
⇒ 每次 abort 一个 Maven 构建,都有留下这种残留的风险,这次只是恰好留下了一个。所以第七节那段扫描脚本值得定期跑,而不是出事才跑。
唯一没查到的是:谁 abort 的。 日志只记了 aborted,没记来源------人工点的、超时策略、还是上游触发,无从分辨。
六、⭐ abort 点了没用------一层比一层底层,才终于收得掉
找到 #39 之后,事情看起来就剩一步了:点那个红叉。
点完,它纹丝不动。 刷新页面,进度条还在走;等一个抓取周期,指标继续涨;/alerts 上那条告警还是 firing。
6.1 第一层(界面按钮):红叉是个空操作
红叉走的是 Run.doStop(),它做的事是去中断这个构建占用的 Executor 线程。
而这个构建的 executor 是 null------就是第三节那件事。没有线程可中断,doStop() 就是个空操作,UI 也不会报错,点了跟没点一样。
⚠️ 这是本文第二个「看着正常其实没生效」:按钮响应了、页面刷新了、没有任何报错------但什么都没发生。 和一条永远返回空的告警规则是同一种毛病。
6.2 只读诊断:它不是「没跑完」,是「跑完了没下班」
界面上问不出更多了,去 Script Console(http://jenkins.example.com:8080/script)问对象本身。
🔴 Script Console 以 Jenkins 主进程权限执行任意 Groovy ,等同于在那台机器上以 Jenkins 用户执行代码。只跑你看得懂的语句,一条条粘,别一次全贴。
groovy
def b = Jenkins.instance.getItemByFullName('order-sync/com.example:order-sync-api').getBuildByNumber(39)
println "class : ${b.getClass().name}"
println "isBuilding : ${b.isBuilding()}"
println "result : ${b.getResult()}"
println "executor : ${b.getExecutor()}"
println "duration : ${b.getDuration()}"
实际输出:
text
class : hudson.maven.MavenBuild
isBuilding : true
result : SUCCESS
executor : null
duration : 0
class 坐实了它是 Maven 模块构建,executor = null 坐实了红叉为什么无效。但真正让我改结论的是中间两行:
| 字段 | 值 | 说明 |
|---|---|---|
isBuilding |
true |
它自认还在构建 |
result |
SUCCESS |
可结果已经判完了,而且是成功 |
duration |
0 |
而耗时从没被写下来 |
一次构建的正常收尾是四步:判定 result → 写 duration → state 置为 COMPLETED → isBuilding 转 false。
它只走完了第一步。
⇒ 所以它不是「跑了 23 天没跑完」,是**「早就跑完了,但没下班」**------活干完了,结果也判了,卡在交接班那一步。
这也补上了第二节欠的那个解释------模块页那个绿勾是怎么回事 :它显示的不是 #37 的状态,#39 自己就是 SUCCESS。
(而「上次成功」之所以还停在 #37,第二节说过了:那一列只认已经完成的构建,#39 没走到 COMPLETED,不算数。)
6.3 第二层(公开 API):网上最常见的那个写法,也不行
搜「Jenkins stuck build」,给出的标准解法基本都是这一句:
groovy
b.finish(hudson.model.Result.SUCCESS, null) // 它本来就成功了,只是没收尾
b.save()
跑出来:
text
groovy.lang.MissingMethodException: No signature of method:
hudson.maven.MavenBuild.finish() is applicable for argument types:
(hudson.model.Result, null) values: [SUCCESS, null]
Run.finish() 是 protected,本来就不是给外部调的 。那些帖子当年能跑通,大概率是因为旧版 Groovy 对访问修饰符睁一只眼闭一只眼,而新版收紧了------这一层我没去考证 Groovy 的版本变更历史,只能说「在我这套版本上它不work」。
6.4 ⭐ 第三层(反射改私有字段):终于收得掉了
isBuilding() 判的是 Run 内部一个 state 字段(枚举 NOT_STARTED / BUILDING / POST_PRODUCTION / COMPLETED)。这个枚举是私有的,所以常量只能从字段类型里取:
groovy
import hudson.model.Run
def b = Jenkins.instance.getItemByFullName('order-sync/com.example:order-sync-api').getBuildByNumber(39)
def f = Run.class.getDeclaredField('state')
f.setAccessible(true)
println "state before : ${f.get(b)}"
println "isBuilding : ${b.isBuilding()}"
def COMPLETED = f.getType().getEnumConstants().find { it.name() == 'COMPLETED' }
f.set(b, COMPLETED)
b.save()
println "----- after -----"
println "state : ${f.get(b)}"
println "isBuilding : ${b.isBuilding()}"
println "result : ${b.getResult()}"
text
state before : BUILDING
isBuilding : true
----- after -----
state : COMPLETED
isBuilding : false
result : SUCCESS
⚠️ result 保持 SUCCESS 不动 ------它本来就成功了,标成 ABORTED 是篡改事实。我们要修的只是「没下班」这一件事。
💡 顺带一个冷知识 :
state是transient字段,不落盘 ------所以它只活在内存里,Jenkins 重启后会由加载逻辑重新判定。⚠️ 按这个机制推断「重启一下也能治好它」,但我没实测 (当时不想为这个重启一台大家在用的 Jenkins)。反过来倒是能确定一件事:这台 Jenkins 已经 43 天没重启过,而
#39是 23 天前的------它正是靠着「一直没重启」才躺得住。
6.5 ⚠️ 验证:改完别急着下结论,插件有采集缓存
改完我立刻去查指标,结果它还在,值还在涨------差点以为没生效。
text
default_jenkins_builds_running_build_duration_milliseconds{...} 2.077911804E9 ← 还在
是 prometheus 插件的采集缓存(默认约 120 秒)。等一个周期再看:
bash
# 这一行消失 = 真收干净了
curl -s http://jenkins.example.com:8080/prometheus/ | grep running_build_duration
# 更准的判据:直接问 Prometheus 那条序列还在不在
curl -s --get http://prometheus.example.com:9090/api/v1/query \
--data-urlencode 'query=default_jenkins_builds_running_build_duration_milliseconds'
# → {"data":{"result":[]}} 空 = 序列没了
两分钟后指标消失,告警随之全灭:

图:INACTIVE (11)------从 firing 到全灭,闭环合上了。
Jenkins 侧的指标也跟着正了过来:
text
last_build_start_time → 2026-08-12 (原来是 07-28 的 #37,现在是 #39)
last_build_result → 0 = SUCCESS
last_build_duration → 0 (那一步的原始数据当时就没落,永久是 0)
#39 从「未完成」变成了「最近完成的构建」,模块页那两行终于对齐。
6.6 ⇒ 回头改那条告警规则
这次追查最直接的产物,是原来那条规则的 description 写错了指引。
⚠️ 这条规则前后改过两版:前情那张截图拍到的是中间那版 ------已经加了「去模块页找」,但还写着「走 Script Console 强制 finish 」。而 finish() 恰恰是 6.3 里那个调不动的方法,所以那版也还是错的。下面给的是最终版:
yaml
# ❌ 原版:让人去点一个点了没用的按钮
description: "任务 {{ $labels.jenkins_job }} 已运行 {{ printf \"%.1f\" $value }} 小时。
若同期 executor 并未被占用,多半是状态没收干净的僵尸构建,需手动 abort"
# ✅ 改后:告诉未来的自己去哪儿找、以及按钮可能无效
description: "任务 {{ $labels.jenkins_job }} 已运行 {{ printf \"%.1f\" $value }} 小时。
若 executor 未被占用,多半是没收干净的残留构建;任务列表里通常找不到它,
去 groupId:artifactId 对应的模块页看构建历史。若 abort 按钮无效(executor 为 null),
走 Script Console 把 Run 的 state 反射改成 COMPLETED(finish() 是 protected,调不动)"
告警的价值不只是「响」,还包括「响了之后把人引到对的地方」。 前一版把我引到了一个空操作上,多花了半小时。
七、怎么把你环境里的同类残留一次扫出来
不用一个个点。同时满足「有构建在跑」和「没有执行器在忙」,就是可疑对象:
promql
# 跑了超过 2 小时、但全局执行器占用为 0
default_jenkins_builds_running_build_duration_milliseconds / 3600000 > 2
and on() (jenkins_executor_in_use_value == 0)
⚠️ and on() 那半边是全局执行器占用,所以这条查询在「确实有别的构建在跑」的时候会漏报。更钝但更稳的版本是只看时长、再人工过一遍:
promql
topk(10, default_jenkins_builds_running_build_duration_milliseconds / 3600000)
在 Jenkins 侧则可以直接问它自己(Script Console,只读):
groovy
// ⚠️ 只取每个 job 的「最后一次构建」,别用 job.getBuilds().findAll{...}
// getBuilds() 返回的是懒加载列表,对它做 findAll 会把该 job 的**全部历史构建**载入内存,
// 在跑了几年的 Jenkins 上足以把 controller 拖垮。
Jenkins.instance.getAllItems(hudson.model.Job.class).each { job ->
def b = job.getLastBuild()
if (b?.isBuilding()) {
def hours = (System.currentTimeMillis() - b.getStartTimeInMillis()) / 3600000
if (hours > 2) {
println "${job.fullName} #${b.number} 已跑 ${hours.round(1)}h executor=${b.getExecutor()}"
}
}
}
executor=null 那几行就是重点对象------它们「在跑」,但没有任何线程在为它们干活。
📌 这个写法的边界 :只查每个 job 的最后一次构建。本文这个案例正好命中(
#39就是order-sync-api的 lastBuild),但如果卡住的那个后面又跑过别的构建,这段就扫不到它。先用它快扫一遍,有嫌疑再去那个 job 的构建历史里翻。
八、不止 Maven 模块:卡住构建的几种常见形态
本文实测的是 Maven 模块这一种。按同样的判据(isBuilding() 为真 + 界面上找不到 / abort 无效),还有几类常被提到的形态,下面这三类我没有实测,是按机制推断列出来供对照的,你遇到时自己验:
| 形态 | 典型表现 | abort 有效吗 |
|---|---|---|
| ✅ Maven 模块残留(本文实测) | 任务列表 / executor / 父任务历史都看不到,只有模块页有 | ❌ 无效,executor 为 null |
⚠️ Pipeline 卡在 input |
executor 被占着,机器 CPU 却是闲的 | 一般有效 |
| ⚠️ agent 掉线后构建悬空 | 节点已离线,构建仍显示进行中 | 常无效,需从节点侧处理 |
| ⚠️ 节点被删但构建还挂着 | 构建指向一个不存在的节点 | 常无效 |
⇒ 通用判据只有一条:看 getExecutor() 是不是 null。 是 null,abort 按钮就基本别指望,直接走 Script Console。
九、这套能证明什么,不能证明什么
✅ 已经做到的:
- 根因查到了底 ,不是停在「它是什么」:从容器日志找到了
#39 aborted那一行、以及丢弃策略「因为它还在运行所以删不掉」那两行,完整因果链每一步都有日志佐证(第五节)。⚠️ 中途猜错过一次(一度怀疑 OOM-killer),正文把这次误判也留着; - 那条残留构建是真实发现 ,不是构造的例子,而且已经真的收掉了 ------
state改COMPLETED后指标消失、11 条规则全部INACTIVE(见 6.5 的截图); - 时间线先从指标逐条对出来 (
last_build_start_time/last_build_duration/last_build_result_ordinal/discard_active/available_builds_count),再由容器日志把精确时刻和因果坐实 (11:06:32启动 /11:06:37aborted)------两条独立证据链互相印证,不是猜的; - 「按墙钟增长」做过区间验证(30 分钟涨 30.25 分钟);
- 红叉无效是实测:点完之后指标继续涨、告警继续 firing;
- Script Console 三层全部实测 :只读诊断(
executor确为null、result已是SUCCESS而duration仍为0)、finish()报MissingMethodException、反射改state生效------每一条输出都是原样贴的。
❌ 还没做到的:
- 不知道是谁 abort 的那次构建。 容器日志只记了
order-sync #39 aborted,没记来源------人工点的、超时策略、还是上游触发,无从分辨; - 第八节那三类形态没有实测,按机制推断列出,已在表里标注;
- 「重启 Jenkins 也能治好它」没实测 ------按
state是transient推断,当时不想为这个重启一台大家在用的 Jenkins(6.4 已标注); - 没考证
finish()从哪个版本开始调不动了 ------只能确定在我这套版本上它报MissingMethodException(6.3 已标注)。
十、总结
一句话版本 :告警说有个构建跑了 23 天,而任务列表、构建队列、executor 里全都找不到它------因为几层完全正常的设计叠在一起,正好把它盖住了;根因是一次 5 秒就被 abort 的构建,abort 掐断了 Maven channel,收尾没做完。
五件带走的事:
-
「多个来源 vs 一个来源」不要按票数投票。 先问这几个来源是不是在回答同一个问题。本文里那四个界面来源答的都是别的问题(执行器忙不忙、队列空不空、上次跑成功没有),只有那条指标问的是「有没有东西还在 building」------而那才是当时唯一该问的。
-
⭐ Maven 模块的构建不单独占 executor。 所以「executor 是 0 ⇒ 没有构建在跑」这条推理,在 Maven 任务上不成立------我就是被这句话带偏的。
-
⭐ abort 点了没反应,不是 UI 卡了 ------
executor为null时doStop()本来就是空操作。而且往下每一层「标准做法」也会依次失效 :网上最常见的build.finish(...)在新版上报MissingMethodException(方法是protected),最后要反射改Run的私有state字段才收得掉。判据始终是那一个:getExecutor()是不是null。 -
⭐ 清理机制也可能被症状本身挡住。 构建丢弃策略本该清掉它,却因为「it is still running」删不动------它靠着「还在运行」躲过清理,又靠着躲过清理继续「运行」。设计自动清理的东西时,想一想「被清理对象处于异常态」这个分支。
-
告警不只要「响」,还要「把人引到对的地方」。 那条 description 让我去点一个点了没用的按钮,这是它的缺陷,改掉它和写出它一样重要。
最后回到那条构建。它不占 executor、不报错、没拖慢任何人的发版------所以三周里没有一个人发现它。但它并不是真的无害 :日志里那句 Failed to rotate logs 说明,它一直在让构建丢弃策略报错,只是那个错也没人看。
这类东西的麻烦不在于破坏力,在于它同时躲开了「性能变差」和「有人报错」这两个人类会注意到的信号。 而能发现它的唯一途径,是有一条告警替你盯着那个没人会去看的角落。
延伸阅读
| 想看 | 文章 |
|---|---|
| 本文那条告警是怎么来的:Jenkins 接入 + 看板 + 11 条规则 | 《Jenkins 监控一条龙》 |
流水线本身怎么写崩:17 个反模式(含 input 占 executor) |
《Jenkinsfile 反模式图鉴》 |
| Prometheus + Grafana + Alertmanager 怎么搭 | 《Prometheus + Grafana + AlertManager 监控体系搭建:Docker 一把梭》 |
| 插件暴露的全部指标清单(含每条 HELP) | prometheus-plugin · docs/metrics |
Jenkins Run 的 API(isBuilding / getExecutor / doStop) |
Run (Jenkins core javadoc) |
给读者的小问题:去 Script Console 跑一下第七节那段 Groovy ,看看你的 Jenkins 上有几个
executor=null的「在跑」构建。按我的经验,跑过两年以上的 Jenkins,很少是零。
🏷️ 标签 :Jenkins Prometheus 告警 排查 CI-CD Maven