面试知识库

对齐实施方案 (2026-07-07)#

背景: 对照开源项目 qinshihu/itops-agent-platform 的诊断-修复闭环, 补齐三块能力。 状态: 已实现 (分支 feat/alignment-20260707, C→A→B 三 commit, 全套 356 passed)。

  • C 定时巡检 5e2818e; A 分级门禁 6ea3207; B 飞书一键审批 110b28f
  • 三块新能力默认开关全关 (INSPECTION_ENABLED / FEISHU_WS_ENABLED=false; 门禁工具缺失自动跳过), 合并后零行为变化。 基线: 治理闭环批次已在本地 main (05a2f15)。

三块 (相互独立, 可任意顺序单独落地):

  • A. 闭环验证门禁分级 —— 2 段验证 → 分级门禁 (补 service_health / metric_recovery)
  • B. 手机 IM 一键审批 —— 飞书通知型 → 飞书长连接卡片内一键 (无需公网)
  • C. 定时巡检 —— 纯被动触发 → 主动周期巡检入队

A. 闭环验证门禁分级#

现状锚点#

  • app/remediation/regression.py::verify_regression —— 现在只有 2 个 check: resource_state + alerts_resolved, 无重试。
  • app/remediation/closed_loop.py::_run_regression —— committed 后调用, 只注入 fetch_active_alerts
  • 事务引擎 app/remediation/engine.pyverify 已覆盖”资源态到目标态 + stabilization 稳定窗口”。

差距 (对照 verificationGates 五级)#

门禁级现状动作
command_success✅ engine.apply不动
service_health🟡 只看资源态新增探针: 端口/HTTP/进程存活, 带重试
metric_recovery🟡 只看告警 firing新增探针: 查 Prom 指标值 vs 阈值, 带重试
baseline_comparison不做 (性价比低)
impact_assessment可选 (二期)

设计 (注入式, 不改事务引擎)#

verify_regression 内部改成门禁列表驱动, 每级一个描述符:

@dataclass
class Gate:
    name: str
    required: bool = True
    max_retries: int = 0
    retry_interval_sec: float = 0.0
    check: Callable[[], Awaitable[dict]]   # -> {"ok": bool, "detail": str, ...}

async def verify_gates(gates: list[Gate], *, settle_sec: float, sleep=asyncio.sleep) -> RegressionResult:
    # 逐级跑; required 且重试用尽仍失败 → 整体 fail 并终止后续级;
    # 非 required 失败 → 记录跳过不判失败 (沿用现有 inconclusive 语义)
python

现有两个 check 平移成两个 Gate (resource_state/alerts_resolved), 对外 RegressionResult 结构不变, closed_loop 调用方零改动即可回落旧行为。

新增探针 (注入回调, 复用现有只读工具)#

  • probe_service_health(resource_uri) —— 复用 MCP network_ping / host_* / container_* 读工具; container 资源探端口, service 资源探 systemctl is-active + 端口。
  • fetch_metric(promql, threshold, op) —— 复用 Prometheus 查询工具 (Loki/Prom 已接), 判 value op threshold

closed_loop.py::_build_alerts_fetcher 旁加 _build_health_prober / _build_metric_fetcher, 从 tools_by_name 懒加载, 工具缺失则该 Gate 降级为 inconclusive (不阻断)。

配置 (沿用命名风格)#

REMEDIATION_GATE_SERVICE_HEALTH_ENABLED=true
REMEDIATION_GATE_SERVICE_HEALTH_RETRIES=3
REMEDIATION_GATE_SERVICE_HEALTH_INTERVAL_SEC=10
REMEDIATION_GATE_METRIC_RECOVERY_ENABLED=true
REMEDIATION_GATE_METRIC_RECOVERY_RETRIES=2
REMEDIATION_GATE_METRIC_RECOVERY_INTERVAL_SEC=30
plaintext

默认全开但工具缺失自动降级; 关掉即回退现状 2 段。

文件#

  • app/remediation/regression.py (加 Gate/verify_gates, 旧函数保留为薄封装)
  • app/remediation/closed_loop.py (加两个 prober builder + 组装 gates)
  • app/config.py / .env.example (6 个配置项)
  • tests/test_regression_gates.py (离线 fake 探针: 通过/重试后通过/重试用尽失败/工具缺失降级)

工作量 / 风险#

中偏小。风险低 —— 纯增强, 默认可降级, RegressionResult 契约不变, 旧路径可关开关回退。


B. 手机 IM 一键审批 (飞书长连接)#

现状锚点#

  • app/runtime/approval_notifier.py —— 裸 httpx 发通知卡片 (一个”去审批中心处理”跳转按钮), 无回调。
  • app/api/v1/approvals.py:79 decide() —— 审批决策入口: approval_repository.decide()claim_resume()resume_diagnosis_graph()
  • app/runtime/approvals.py::ApprovalRepository —— decide / claim_resume / find_inflight 等已齐。
  • app/main.py:46 lifespan —— checkpointer 在此挂载/关闭 (长连接常驻挂同处)。

关键洞察: 无公网也能收卡片回调#

原设计放弃”卡片内一键”的理由是本机无公网收不到回调。飞书长连接 (WebSocket) 模式让本地应用主动连飞书, 卡片按钮点击经长连接推回, 无需公网 IP / 内网穿透。这是原报告未考虑的路径。

设计#

  1. 卡片升级: approval_notifier.send_card 的 action 从单个跳转按钮 → 两个交互按钮”✅ 同意 / ❌ 拒绝”, 每个带 value={"action":"approve|deny","req_id":...}; 保留”去审批中心”跳转按钮作为兜底。
  2. 长连接常驻: 新增 app/runtime/feishu_ws.py, 用 lark-oapilark.ws.Client + EventDispatcherHandler 注册 card action callback handler; 在 main.py lifespan 里 create_task 起后台常驻, 关闭时取消。凭据复用 app/core/connectors/feishu.py 的 app_id/app_secret。
  3. 回调 handler (核心, 三道关):
    • 身份校验: 回调带 open_id/user_id, 校验在审批人白名单 (FEISHU_APPROVER_OPEN_IDS) 内, 否则拒绝并回卡片提示。
    • 幂等: 复用 ApprovalRepository.decideWHERE status='pending' 原子性 —— 重复点击/已决策直接短路。
    • 落决策: 调与 decide() 端点共享的内部函数 (把 approvals.py 的 decide 逻辑抽成 app/runtime/approval_decide.py::apply_decision(req_id, decision, decided_by), 端点和 WS handler 都调它, 避免逻辑二写)。
  4. 卡片回执更新: 决策后用飞书 PATCH message 把卡片更新为”已由 xxx 同意/拒绝”, 防重复点击 + 留痕。

与现有超时降级的衔接#

app/runtime/approval_timeout.py 的 escalate-then-deny 不变: 手机点了 → decide 写 pending→终态 → wait_once 立即返回非 timeout; 没点 → 照常催办→二次超时 deny。零冲突

依赖 / 配置#

requirements.txt: + lark-oapi
FEISHU_WS_ENABLED=false          # 默认关, 开了才起长连接 (灰度)
FEISHU_APPROVER_OPEN_IDS=        # 逗号分隔审批人 open_id 白名单; 空=不接受卡片决策(只通知)
plaintext

FEISHU_WS_ENABLED=false 时行为 = 现状 (只通知+跳转), 完全不引入风险。

文件#

  • app/runtime/feishu_ws.py (长连接客户端 + card action handler)
  • app/runtime/approval_decide.py (抽出的共享 apply_decision)
  • app/api/v1/approvals.py::decide (改调 apply_decision, 行为不变)
  • app/runtime/approval_notifier.py (卡片加同意/拒绝按钮 + 回执更新)
  • app/main.py (lifespan 挂长连接)
  • app/config.py / .env.example / requirements.txt
  • tests/test_feishu_ws_handler.py (离线: 伪造 card action 事件 → 身份校验/幂等/非授权拒绝, mock 掉真实 WS 与飞书 API)

工作量 / 风险#

中。风险点: ① lark-oapi 新依赖 (锁版本); ② 长连接断线重连 (SDK 自带, 需验证); ③ 身份白名单必须先配, 否则任何人点按钮都能批 —— 默认关 + 白名单空=不接受决策 双保险。回退: FEISHU_WS_ENABLED=false


C. 定时巡检#

现状锚点#

  • 纯被动: POST /diagnose/submit / Alertmanager webhook 触发。
  • app/queue/redis_streams.py:128 enqueue_task —— 复用其入队。
  • 无 scheduler。

设计#

新增轻量周期调度器 (不引 APScheduler, 复用 asyncio + Redis 防多 worker 重复触发):

  1. app/runtime/inspection_scheduler.py —— lifespan 起后台 loop; 每个巡检项 {name, interval_sec, query/target, mode}; 到点用 Redis SETNX + TTL 抢占 (多 worker 只有一个真触发), 然后 enqueue_task 入队一条诊断任务 (低优先级)。
  2. 巡检项配置来源: 先用 .env / 配置文件静态定义 (如”每 5min 巡检核心容器健康”); 二期可做成 DB 表 + API 管理。
  3. 巡检结果走现有诊断链路 → 落库 → 有问题才走处置审批 (复用现有闭环, 不新造)。

配置#

INSPECTION_ENABLED=false                  # 默认关
INSPECTION_INTERVAL_SEC=300
INSPECTION_TARGETS=container:core-*,host:cpu   # 简单声明式, 二期转 DB
plaintext

文件#

  • app/runtime/inspection_scheduler.py
  • app/main.py (lifespan 起调度 loop, 仅当 INSPECTION_ENABLED)
  • app/config.py / .env.example
  • tests/test_inspection_scheduler.py (离线: fake clock + fake enqueue, 验证 SETNX 去重/到点触发/关开关不触发)

工作量 / 风险#

小。风险低 —— 默认关, 复用现有队列与诊断链路, 不改核心路径。多 worker 重复触发由 Redis SETNX 兜底。


建议实施顺序与依赖#

顺序理由依赖
1C 定时巡检最小、风险最低、体感最明显, 且不碰核心
2A 门禁分级中小, 纯增强, 提升闭环可信度无 (但需确认可用的 Prom/health 读工具名)
3B 手机一键审批最有”闭环感”但引新依赖, 放最后稳一点需先配飞书 app 凭据 + 审批人 open_id

三块互不依赖, 也可只挑其一。每块都遵循本项目惯例: 新能力默认开关关/可降级、单测全离线、不动 .env 只加 .env.example 占位。

统一验收口径 (三块共用)#

  • 每块独立分支 + 独立 commit, 离线 pytest 全绿再合。
  • 涉及 .env 的开关默认值 = 关闭 / 现状行为, 保证合并后零行为变化, 需显式开启才生效。
  • B、C 的后台常驻任务必须在 lifespan 关闭钩子里可靠取消 (对齐 checkpointer 的挂载/关闭对称)。