fix(deploy): 故障告警按 invocation 分会话 —— 崩溃循环时告警不再被护栏挡下
# 问题(实测)
服务反复崩溃时,故障告警**送不出去**。
链路:告警邮件带 `relay:"summary"`(走免预算通道),而服务端按
`AutoAliasFor(收件人, 主题)` 派生会话别名 —— **主题相同即同一条会话**。于是崩溃
循环下多封告警全落进同一条会话,而网关对会话内**连续**的中继邮件有
`maxRelayHops=5` 的硬上限(防两个 Agent 互相唤醒的正当护栏)→ 第 6 封起返回 403:
POST /api/v1/mail/send ... - 403 245B (网关 NRestarts=0,一直在正常服务)
报告只能落 spool,等下次 ExecStartPost `--flush` 才补投。实测 dsh 崩溃循环
(我自己的部署缺陷所致)时的六条告警全部 spooled —— 而**服务反复崩溃正是最需要
告警送达的时刻**。
# 修法
主题带上本次启动的 `INVOCATION_ID` 短号:`[dsh] 桥服务异常终止 #5ce6a845`。
主题变了,别名与会话随之分开,每次崩溃各自可投递。
**护栏不削弱**:它针对的是 agent↔agent 互相唤醒,不是同一个人收多条故障通知。
副作用是崩溃循环产生多条会话而非一条线程 —— 对"服务在崩"这件事,分开计数比合并成
一条更容易发现问题。
`INVOCATION_ID` 缺失时退回 `Date.now()+randomUUID()` 的哈希:宁可每次不同,
也不能因为拿不到标识而退回"主题相同 → 告警又被挡下"。
# 验证(三条,含两个反向对照)
不同 invocation → 主题各不相同(每次崩溃各自成会话)
无 invocation(兜底) → 每次仍各不相同(不会退回同主题)
同一 invocation 重报 → 主题相同(一次崩溃仍是一条会话,不刷屏)
注:第二次验证第一次跑时"失败",是因为**我的测试假设错了** —— 父环境里本来就继承了
`INVOCATION_ID`,两次跑用的是同一个 id。用 `env -u INVOCATION_ID` 才真正走到兜底分支。
This commit is contained in:
@ -58,6 +58,21 @@ function redact(value, env) {
|
||||
.slice(0, 1_500);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本次服务启动的短标识,用于让每封故障通知各自成会话(见 subject 处的注释)。
|
||||
*
|
||||
* 优先用 systemd 的 `INVOCATION_ID`(每次启动唯一)。它缺失时退回随机值 ——
|
||||
* 宁可每次不同,也不要因为拿不到标识而退回"主题相同 → 告警被护栏挡下"。
|
||||
*/
|
||||
function invocationTag(env) {
|
||||
const invocation = String(env.INVOCATION_ID || '').trim();
|
||||
if (invocation) return invocation.replace(/[^A-Za-z0-9]/g, '').slice(0, 8);
|
||||
return createHash('sha256')
|
||||
.update(`${Date.now()}:${randomUUID()}`)
|
||||
.digest('hex')
|
||||
.slice(0, 8);
|
||||
}
|
||||
|
||||
function relayKeyFor(env, serviceName) {
|
||||
const invocation = String(env.INVOCATION_ID || '').trim();
|
||||
if (invocation) return `service-failure:${invocation}`;
|
||||
@ -97,7 +112,23 @@ export function buildFailurePayload({
|
||||
|
||||
return {
|
||||
to: String(env.AGENTMAIL_FAILURE_RECIPIENT || DEFAULT_RECIPIENT),
|
||||
subject: `[${agentName}] 桥服务异常终止`,
|
||||
/*
|
||||
* 主题带上本次启动的 invocation 短号。
|
||||
*
|
||||
* 服务端按 `AutoAliasFor(收件人, 主题)` 派生会话别名,所以**主题相同 = 同一条会话**。
|
||||
* 崩溃循环下这一点会咬人:多封告警全落进同一条会话,而网关对会话内**连续**的中继
|
||||
* 邮件有 `maxRelayHops=5` 的硬上限(防两个 Agent 互相唤醒的正当护栏)→ 第 6 封起
|
||||
* 返回 403,报告只能落 spool,等下次 ExecStartPost `--flush` 才补投。
|
||||
*
|
||||
* 实测就是这样:dsh 崩溃循环时的六条告警全部 spooled,而**服务反复崩溃正是最需要
|
||||
* 告警送达的时刻**。
|
||||
*
|
||||
* 带 invocation 让每次崩溃各自成会话,因而每次都能投出去 —— 护栏不受影响
|
||||
* (它针对的是 agent↔agent 的互相唤醒,不是同一个人收多条故障通知)。
|
||||
* 副作用是崩溃循环会产生多条会话而不是一条线程;对"服务在崩"这件事,
|
||||
* 分开计数比合并成一条更容易发现问题。
|
||||
*/
|
||||
subject: `[${agentName}] 桥服务异常终止 #${invocationTag(env)}`,
|
||||
body: lines.join('\n'),
|
||||
relay: 'summary',
|
||||
relay_key: relayKeyFor(env, service),
|
||||
|
||||
Reference in New Issue
Block a user