面试知识库

面试 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) 也可能过——护栏和执行之间 存在解释鸿沟。重构后系统里不存在”命令字符串”概念

  1. LLM 只输出结构化意图 {resource: "container://nginx", operation: "stop", params: {}}
  2. model.py 的 Operation Registry 是封闭集合:不在册的 (资源类型, 操作) 一律拒, 参数走正则/枚举/范围校验归一化为工具入参,没有自由拼装空间;
  3. 执行侧 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_transactionapp/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 两道护栏:

  1. 资源 URI 级锁:执行入口互斥,同一 container://nginx 同时只允许一个处置流。 内存后端默认(单 worker),Redis SET NX EX 可选(多 worker),后端故障自动降级内存并告警;
  2. 幂等键:worker 崩溃重投 / 消息重复消费时,同一审批 ID 复见直接 duplicate_skipped——是 claim_for_execution DB 原子认领之外的执行层第二道兜底。

再往前一层是 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_decisionapp/runtime/approval_decide.py)——飞书回调和 HTTP 端点只是 “点击来源”不同,决策+图恢复逻辑一份代码,零漂移。见第五节。

Q7: 审批超时为什么不能简单等价 deny?现在怎么处理?#

对止损类操作,“超时=拒绝”可能比”执行”更危险(容器 crash-loop 打爆下游)。但自动放行 更不可接受——无人确认的写操作违背 HITL 底线。折中:escalate-then-denyapproval_timeout.py):第一次超时 → 飞书催办卡 + expires_at 续期一轮;二次超时 → deny(可逆操作的拒绝文案附 recovery plan 说明,提示可重新发起)。任何路径都不自动放行

Q8: “verify 通过”和”事件解决”是一回事吗?#

不是,而且分了三个层次递进regression.pyverify_gates 分级门禁,对标开源 verificationGates):

  1. engine.verify(事务内):容器是不是到了目标态 running + 稳定窗口抓 crash-loop;
  2. service_health 门禁:资源起来 ≠ 服务真在服务——按资源类型探真健康 (container 探 container_inspect running、service 探 service_status active),带重试;
  3. metric_recovery 门禁:触发处置的 Prom 指标是否回落到阈值内(见 Q13);
  4. 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/statsstats.py 纯函数聚合 + 审计表):事务成功率、回滚率、 needs_attention 率、CAS 拦截数、零变更中止数;审批通过率、超时率、平均决策等待。 逻辑:回滚率上涨 = 自动化在变危险,CAS 拦截变多 = 环境变更频繁审批时效不够—— 这些是调整审批 TTL 和收紧策略的依据。


五、对齐开源与手机一键审批(2026-07-07 对标 itops-agent-platform)#

Q12: 对照开源项目(qinshihu/itops-agent-platform),你补齐了哪几块?取舍是什么?#

对照那个项目的诊断-修复闭环,补齐三块(都默认开关关,合并后零行为变化):

  1. 定时巡检inspection_scheduler.py):从纯被动(告警/人工触发)补上主动周期体检——到点 对声明目标各投一条低优先级诊断任务,复用现有入队链路。多副本重复触发靠 Redis SETNX 兜底;
  2. 分级验证门禁(见 Q8/Q13):把 2 段验证升级成门禁列表,补 service_health / metric_recovery;
  3. 手机一键审批(见 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} 的交互按钮 → 手机点击 → 飞书经长连接推 P2CardActionTriggerhandle_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 演示环境):

  1. 多主机执行通道:操作对象是本机 docker/进程/文件,没有 SSH/agent 下发通道;
  2. 变更管控:没有变更窗口/冻结期/与 CMDB 工单联动;
  3. 锁的生产化:Redis 锁的 release 是 GET+DEL 非原子(演示可接受,生产应换 Lua);
  4. 回归验证的告警关联是保守宽匹配(资源 id 出现在告警任意字段),生产应走 告警指纹/标签精确关联;
  5. 飞书 WS owner 锁用 SETNX + 续期保证多 worker 只一条连接,续期 stall 时理论上可能双连 (幂等兜底正确性);生产可考虑专用单例进程持连接。

原来这里第 2 条是”飞书没做卡片内批准(需公网回调)“——本轮已用长连接破解(Q15), 从边界升级成了能力。这些不是”没想到”,是单机项目的合理边界——面试时主动讲比被问出来强。