面试 Q&A — 事务化处置引擎与运维 Agent 闭环#
主题:AIOps 处置侧从”LLM 生成命令 + 字符串护栏”重构为”资源建模 + 事务化工具 + Saga 编排 + 闭环执行”的完整设计。代码在
app/remediation/(model/engine/saga/locks/regression/ closed_loop/policy/stats)+app/runtime/remediation_exec.py+app/remediation_worker.py。 安全评测数字来自benchmark/reports/remediation_safety_20260707-010133.json(真实跑批)。
一、为什么要事务化#
Q1: 处置执行为什么不能就是”LLM 生成命令 + 白名单校验”?#
旧方案(已删除的 command_guard.py)的本质问题:护栏对象是字符串,不是语义。
docker restart nginx 能过正则,docker restart $(cat /tmp/x) 也可能过——护栏和执行之间
存在解释鸿沟。重构后系统里不存在”命令字符串”概念:
- LLM 只输出结构化意图
{resource: "container://nginx", operation: "stop", params: {}}; model.py的 Operation Registry 是封闭集合:不在册的 (资源类型, 操作) 一律拒, 参数走正则/枚举/范围校验归一化为工具入参,没有自由拼装空间;- 执行侧 MCP 工具按归一化入参调 SDK/API,不经过 shell。
安全评测(scripts/eval_remediation_safety.py,17 条注入攻击 + 5 条合法操作):
拦截率 17/17 = 100%,误杀率 0/5 = 0%。典型攻击样本:
resource: "container://web-api; rm -rf /" 直接在 URI 解析层被拒(容器名字符集校验)。
Q2: “Tool = 完整事务”具体指什么?#
engine.run_transaction(app/remediation/engine.py)把每个写操作做成五段式事务:
① SNAPSHOT 观测当前态 (回滚依据 + CAS 基准)
② PRECHECK CAS 漂移检测 + 存在性 + 状态机前置条件
②' BACKUP 快照恢复型资源 (File/DB/Deployment) 落物理备份, 备份失败即中止
③ APPLY 执行状态迁移
④ VERIFY 后置条件轮询 + 稳定窗口 (抓 crash-loop 假成功)
⑤ SAGA 补偿 verify 失败 + 可逆 + 已授权 → 自动回滚plaintext七个终态:committed / rolled_back / rollback_failed / verify_failed / state_drift / precheck_failed / failed(+ plan-only 的 planned)。对调用方而言要么 committed、
要么已回滚、要么明确失败,不存在”执行了但不知道结果”的中间态。每步写 journal,
全程可审计(落 remediation_transactions 表)。
Q3: CAS 漂移检测防的是什么真实竞态?#
“批的是 A 态、执行时已是 B 态”。审批是异步的(解耦模式 TTL 24h),人批准时容器可能
已经被别人重启过。执行前比对”审批时快照的状态”与”当前状态”(File 域比 sha256),
不一致 → state_drift 零变更中止。对标 Terraform 的 plan/apply 一致性检查。
二、多步 Saga 与并发护栏(2026-07-07 新增)#
Q4: 单步事务都能自动回滚了,为什么还要 Saga orchestrator?#
单步事务只对自己负责。计划 = [停容器 A, 重启服务 B],第 2 步失败时它自己会回滚自己,
但第 1 步已经 committed 的变更没人管——这就是”多步计划的原子性错觉”。
saga.py 的 orchestrator 补这个缺口:
- 维护已 committed 步骤栈,某步失败时逆序执行各步的 recovery_plan;
- recovery_plan 由事务引擎在 committed 记录里回填(SNAPSHOT 型含真实 backup_ref), 已随原审批整体授权,补偿不再走审批;
- 两条不盲干红线:不可回滚步骤(recovery_plan=None)跳过并浮出
compensation_failed;备份占位未回填(<执行时自动备份后回填>)拒绝盲补偿; - 终态四种:
committed / failed(零变更遗留) / compensated / compensation_failed(需人工)。
对标:Saga orchestration 模式(补偿决策集中在编排器,而非 choreography 分散在各步)。
Q5: CAS 挡了状态漂移,还有什么并发问题?怎么解?#
CAS 挡不住两个处置流交错推进同一资源(各自 CAS 都可能通过)。locks.py 两道护栏:
- 资源 URI 级锁:执行入口互斥,同一
container://nginx同时只允许一个处置流。 内存后端默认(单 worker),Redis SET NX EX 可选(多 worker),后端故障自动降级内存并告警; - 幂等键:worker 崩溃重投 / 消息重复消费时,同一审批 ID 复见直接
duplicate_skipped——是claim_for_executionDB 原子认领之外的执行层第二道兜底。
再往前一层是 incident/资源级去重(approvals.find_inflight):同资源或同事件已有
进行中处置流(pending/approved/executing)时,新投递直接 duplicate_inflight 拒绝。
三层防重: 投递去重 → DB 原子认领 → 执行幂等键。
三、闭环执行链(2026-07-07 接线,默认开)#
Q6: 完整闭环长什么样?#
诊断(fast/deep) → 处置提案(意图校验+策略+verify_metric) → 审批中心(pending)
→ 飞书卡片(工具/影响面 + ✅同意/❌拒绝 交互按钮 + 审批页兜底链接) [T3.2 → 手机一键升级]
→ 手机点同意(飞书长连接回调) / 审批中心 API / 超时→催办+续等一轮→二次超时 deny [T0.1]
→ remediation_worker 认领
→ 闭环执行: 去重 → 幂等键 → 资源锁 → 事务(五段式) → 分级回归门禁 [T3.1]
→ 审计落库 + /api/v1/remediation/stats 可观测 [T3.3]
→ 经验回灌 wiki (成功/回滚教训/回归未通过) [T3.5]plaintext开关 REMEDIATION_CLOSED_LOOP=true(默认),关掉回退旧单步裸执行。手机一键审批与审批中心 API
共享同一份 apply_decision(app/runtime/approval_decide.py)——飞书回调和 HTTP 端点只是
“点击来源”不同,决策+图恢复逻辑一份代码,零漂移。见第五节。
Q7: 审批超时为什么不能简单等价 deny?现在怎么处理?#
对止损类操作,“超时=拒绝”可能比”执行”更危险(容器 crash-loop 打爆下游)。但自动放行
更不可接受——无人确认的写操作违背 HITL 底线。折中:escalate-then-deny
(approval_timeout.py):第一次超时 → 飞书催办卡 + expires_at 续期一轮;二次超时 →
deny(可逆操作的拒绝文案附 recovery plan 说明,提示可重新发起)。任何路径都不自动放行。
Q8: “verify 通过”和”事件解决”是一回事吗?#
不是,而且分了三个层次递进(regression.py 的 verify_gates 分级门禁,对标开源
verificationGates):
- engine.verify(事务内):容器是不是到了目标态 running + 稳定窗口抓 crash-loop;
- service_health 门禁:资源起来 ≠ 服务真在服务——按资源类型探真健康
(container 探
container_inspectrunning、service 探service_statusactive),带重试; - metric_recovery 门禁:触发处置的 Prom 指标是否回落到阈值内(见 Q13);
- alerts_resolved 门禁:查
prom_active_alerts,触发本次处置的告警是否不再 firing。
分级语义:required 级失败即整体失败并终止下游(下游没意义再查);探针/后端不可用记
inconclusive 不判失败(fail-open,不能因 Prometheus 挂了把成功处置误标失败);但资源态
观测不到判失败(资源态是处置的直接对象,不 fail-open)。任一门禁未过 → 事务结果保留但标
needs_attention。真机验证:手机批准 restart 后 service_health 门禁探到容器 running → 通过。
Q9: plan-only 预览解决什么问题?#
审批人在卡片上看到的”影响面”之前是提案时的静态描述。run_transaction(plan_only=True)
(对标 terraform plan)真实走 snapshot + precheck + CAS 但零变更,返回
pre_state + 预期 effect + 可逆性 + recovery_plan——审批人看到的是执行时刻的真实状态
和”将要发生什么”,预览同样报告 CAS 漂移。
Q10: 处置系统自己怎么被观测?#
/api/v1/remediation/stats(stats.py 纯函数聚合 + 审计表):事务成功率、回滚率、
needs_attention 率、CAS 拦截数、零变更中止数;审批通过率、超时率、平均决策等待。
逻辑:回滚率上涨 = 自动化在变危险,CAS 拦截变多 = 环境变更频繁审批时效不够——
这些是调整审批 TTL 和收紧策略的依据。
五、对齐开源与手机一键审批(2026-07-07 对标 itops-agent-platform)#
Q12: 对照开源项目(qinshihu/itops-agent-platform),你补齐了哪几块?取舍是什么?#
对照那个项目的诊断-修复闭环,补齐三块(都默认开关关,合并后零行为变化):
- 定时巡检(
inspection_scheduler.py):从纯被动(告警/人工触发)补上主动周期体检——到点 对声明目标各投一条低优先级诊断任务,复用现有入队链路。多副本重复触发靠 Redis SETNX 兜底; - 分级验证门禁(见 Q8/Q13):把 2 段验证升级成门禁列表,补 service_health / metric_recovery;
- 手机一键审批(见 Q14/Q15):把飞书通知型升级成长连接卡片内一键。
关键取舍——不为对齐而对齐:那个项目的 metric_recovery 用硬编码阈值(CPU 1.0 / mem 90%), 且它验证失败后走的也是回滚而非重新规划。所以我只补 service_health + metric_recovery 两级 (性价比最高),metric 阈值不硬编码而是让 planner 声明(Q13);baseline_comparison 不做。
Q13: metric_recovery 门禁为什么默认”需要 spec 才激活”,而不是像开源那样硬编码阈值?#
因为**“处置生效后哪个指标该回到多少”是场景相关的,硬编码等于臆测**。设计成:门禁开关默认开,
但只有处置提案带 verify_metric = {promql, threshold, op} 时才真正查指标,否则该级跳过
(inconclusive,不阻断)。这样既不臆测,又保留了”有明确指标时自动验证”的能力。
Q14: verify_metric 怎么从 planner 一路带到闭环门禁?幻觉了怎么办?#
一条端到端接线(deep 的 remediation_planner_node + fast 共用 _intent_from_parsed /
propose_transaction,一处改动覆盖两条路径):
LLM planner prompt 声明 verify_metric (可选, 拿不准就省略, 不臆造 PromQL)
→ _intent_from_parsed 携带 → propose_transaction/sanitize_verify_metric 消毒进提案
→ 落库 approval_requests.verify_metric JSONB (ADD COLUMN IF NOT EXISTS, 老库平滑升级)
→ worker _proposal_from_row 重建 → closed_loop._build_metric_gate 激活门禁 → prom_query 查plaintext幻觉降级:LLM 给了错的 PromQL → prom_query 查不到数据 → 门禁记 inconclusive(跳过),
绝不把成功处置误判失败。消毒层还挡掉:缺 promql / threshold 非数字 → 整个置 None;op 非法回落 lt。
这是”让 LLM 发挥但错了不致命”的典型设计。
Q15: 本机无公网,怎么实现”手机点飞书按钮就批准”?#
这是本轮最亮的一处。原本的顾虑是”飞书交互卡片回调需要公网可达的回调 URL,本机收不到”。 破局点:飞书长连接(WebSocket)模式——本地应用作为 WS 客户端主动外连飞书,卡片按钮 点击经这条长连接推回,无需公网 IP / 内网穿透。
链路(app/runtime/feishu_ws.py):卡片带 value={action, req_id} 的交互按钮 → 手机点击 →
飞书经长连接推 P2CardActionTrigger → handle_card_action 三道关(open_id 白名单 + 幂等 +
决策映射)→ apply_decision(与审批中心 API 同一份)→ 审批 approved → worker 执行。
三道安全关缺一不可:① FEISHU_APPROVER_OPEN_IDS 白名单空 = 不接受任何卡片决策(防任意群成员
点按钮就能批);② 幂等由 apply_decision 底层 WHERE status='pending' 兜底;③ 决策后卡片回执
更新防重复点击。真机验证:手机点✅同意 → 日志 decided_by=feishu:ou_... → 容器真 restart。
六、边界与取舍(诚实标注)#
Q16: 这套闭环离生产还差什么?#
主动画出边界(单机 OrbStack 演示环境):
- 多主机执行通道:操作对象是本机 docker/进程/文件,没有 SSH/agent 下发通道;
- 变更管控:没有变更窗口/冻结期/与 CMDB 工单联动;
- 锁的生产化:Redis 锁的 release 是 GET+DEL 非原子(演示可接受,生产应换 Lua);
- 回归验证的告警关联是保守宽匹配(资源 id 出现在告警任意字段),生产应走 告警指纹/标签精确关联;
- 飞书 WS owner 锁用 SETNX + 续期保证多 worker 只一条连接,续期 stall 时理论上可能双连 (幂等兜底正确性);生产可考虑专用单例进程持连接。
原来这里第 2 条是”飞书没做卡片内批准(需公网回调)“——本轮已用长连接破解(Q15), 从边界升级成了能力。这些不是”没想到”,是单机项目的合理边界——面试时主动讲比被问出来强。