# MeshSafe Diagnostics Kit v1.0 方案 > 版本: v1.0 > 日期: 2026-10-28 > 状态: **待审核** > 作者: MeshSafe Team > 优先级: 高(P0) --- ## 一、定位 > **目标**:让 MeshSafe 运维人员在产品内部就能看到"系统健康状况",遇到问题能一键打包带出来给开发方分析。 > > **边界**: > - ❌ 不联网、不依赖外部服务、不做 LLM、不自动上传 > - ✅ 所有分析跑在客户容器里(在线定时) > - ✅ 导出诊断包(手动触发,U 盘带出) > - ✅ Docker 能力自动检测 + 三级降级 > - ✅ 全部定时器周期 + 规则阈值配置化 > - ✅ 总开关可关闭整个诊断套件(只留手动导出) --- ## 二、核心设计决策 | # | 决策 | 理由 | |---|------|------| | 1 | **在线层:只跑 grep 模式匹配,不收集完整日志** | 光闸内网,日志可能带客户敏感信息;完整日志只在**用户手动导出**时打包 | | 2 | **三层定时器**(错误类 30s / DB 聚合 60s / 容器类 300s)| 拦截日志要快出快分析;容器/配置类变化慢 | | 3 | **页面请求时必跑轻量规则**(DB 聚合 + 引擎健康 + 文件 stat) | ~430ms 用户无感,确保页面显示的是最新数据 | | 4 | **日志匹配 + docker inspect 跑后台定时器** | 涉及 subprocess + IO,耗时 ~2.5s 不阻塞前台 | | 5 | **Docker 能力三档降级**(full / partial / none) | 客户环境可能挂不了 docker.sock;降级后页面明确告诉用户哪些规则没跑 | | 6 | **总开关 enabled** | 有的客户不需要/不想跑诊断,一键关掉只留手动导出 | | 7 | **全部定时器周期 + 规则阈值配置化** | 光闸环境差异大,硬编码不合适 | --- ## 三、功能清单 ### 3.1 控制台新页面:诊断面板 | # | 模块 | 展示内容 | 数据源 | |---|------|---------|--------| | A1 | 实时健康概览 | 4 张卡片:总扫描量、恶意拦截、**错误比例**(高就红)、引擎在线 | DB ScanTask + scanner.health() | | A2 | 引擎健康详情 | 每个引擎状态、ClamAV 病毒库日期、YARA 规则数、最近检测耗时 | scanner.health() + 配置快照 | | A3 | 容器运行状态 | 5 个容器 status / restart_count / Docker 能力检测结果 | docker inspect(或降级跳过)| | A4 | 异常检测面板 | 规则引擎跑出来的问题列表(每条带严重度、消息、证据、检测时间戳)| 实时计算 + Redis 缓存 | | A5 | 最近 20 条错误 | 从 DB 查 verdict='error' 的 ScanTask | DB | | A6 | **一键导出按钮** | 生成 `diagnostics.tar.gz` → 浏览器下载 | 后端打包 | **页面顶部信息栏**: ``` 最后刷新: 10:29:15 🧩 Docker 能力: ✅ 完整(日志检测 + 容器状态) 或 ⚠️ 部分 / ℹ️ 未启用 或 ⛔ 诊断套件未启用 ``` **每条规则结果带时间戳**(不同规则可不同,因来自不同运行通道): ``` 🔴 R009 ClamAV 被 OOM Kill (检测于 10:29:15) ← 页面请求时跑 🟡 R002 ClamAV 病毒库 14 天未更新 (检测于 10:29:15) ← 页面请求时跑 🟢 R004 所有引擎健康 (检测于 10:28:02) ← 定时器跑 ``` **未启用 Docker 时**:跳过 R009-R015,对应规则行显示 `ℹ️ Docker 能力不可用,本条规则未运行`。 **诊断套件总开关关闭时**:页面显示大横幅 `⛔ 诊断套件未启用`,下面规则列表隐藏,只保留手动导出按钮。 ### 3.2 规则清单(15 条) #### 页面请求必跑(轻量,~430ms,每次打开都算) | 规则 | 类型 | 数据源 | 耗时 | |------|------|--------|------| | R001 error 比例 | DB SQL | ScanTask GROUP BY verdict | ~20ms | | R005 24h 无扫描 | DB SQL | ScanTask MAX(created_at) | ~20ms | | R002 ClamAV 病毒库日期 | 引擎协议 | clamd SIGINFO | ~100ms | | R004 引擎健康 | 引擎协议 | scanner.health() | ~300ms | | R003 YARA 规则 mtime | 文件 stat | os.path.getmtime | <1ms | #### 定时器组 A:错误/拦截类(30s 周期) | 规则 | 依赖 | 异常信号 | 匹配模式 | |------|------|---------|---------| | R009 ClamAV OOM/crash | docker logs clamd | clamd 被杀/崩 | `Killed` / `exit 137` / `segfault` / `oom-killed` | | R010 Postgres FATAL | docker logs postgres | 连接池爆 | `FATAL` / `too many connections` / `PANIC` | | R011 Redis OOM | docker logs redis | 内存满 | `OOM command not allowed` / `out of memory` | | R012 磁盘满 | 5 个容器 docker logs | 磁盘爆 | `No space left on device` / `ENOSPC` | | R013 Nginx upstream 失败 | docker logs nginx | api 挂了 | `Connection refused` / `upstream timed out` | | R014 API 连接异常 | docker logs api | 依赖断了 | `Redis connection refused` / `Postgres connection refused` | #### 定时器组 B:DB 聚合类(60s 周期) | 规则 | 数据源 | |------|--------| | R006 suspicious > malicious | ScanTask GROUP BY verdict | | R007 reject 比例 | ScanTask COUNT + verdict | | R008 同 SHA256 重复 | ScanTask GROUP BY sha256 HAVING COUNT > 1 | #### 定时器组 C:容器/引擎类(300s 周期) | 规则 | 依赖 | |------|------| | R015 容器重启次数 | docker inspect × 5(任一 restart_count > 3)| ### 3.3 每条规则输出格式 ```json { "id": "R009", "severity": "high", // high / medium / low / info "message": "ClamAV 被 OOM Kill(近 24h 重启 3 次)", "hint": "给 clamav 容器加内存限制 512m → 1g", "evidence": [ "meshsafe-clamav 最近日志:Killed (exit 137)", "meshsafe-clamav restart_count = 3" ], "run_at": "2026-10-28T10:29:15+08:00", "run_by": "page_request", // page_request / timer:error_scan / export "skipped": false, "skip_reason": null } ``` ### 3.4 一键导出 **端点**:`GET /api/v1/telemetry/dump`(返回文件下载) **导出包结构**: ``` diagnostics-20261028-103045.tar.gz ├── rules_report.json ← 导出这一刻跑一遍规则引擎的完整结果(带证据 + 时间戳) ├── containers/ │ ├── meshsafe-api.log ← docker logs --tail 2000(导出时抓完整) │ ├── meshsafe-api.meta.json ← restart_count / started_at / 镜像 tag │ ├── meshsafe-clamav.log │ ├── meshsafe-clamav.meta.json │ ├── meshsafe-postgres.log │ ├── meshsafe-postgres.meta.json │ ├── meshsafe-redis.log │ ├── meshsafe-redis.meta.json │ ├── meshsafe-nginx.log │ └── meshsafe-nginx.meta.json ├── scan_tasks.json ← 最近 500 条 ScanTask(脱敏) ├── scan_findings.json ← 对应 ScanFinding(rule name / severity / engine) ├── engine_health.json ← scanner.health() 快照 ├── policy_snapshot.json ← 策略版本号 + YARA 规则文件列表(**不含规则原文**) ├── docker_detect.json ← 启动时 Docker 能力检测结果 ├── compose_state.json ← docker compose ps 输出(容器运行状态表) └── meta.json ← 打包时间 / 生成人 / MeshSafe 版本 / 规则版本 ``` **数据脱敏策略**: | 字段 | 处理方式 | |------|---------| | 原始文件名 | 保留扩展名 + SHA256 前 8 位 | | 文件路径 | 截断为相对路径 | | 用户名 | `u***n` | | IP | `10.0.1.*`(保留最后一段)| | 策略 YAML | 默认只传版本号 + 规则列表;加开关可选全量 | ### 3.5 Docker 能力检测 启动时自动运行(`@app.on_event("startup")`),三档降级: | 检测结果 | 条件 | 影响 | 页面显示 | |---------|------|------|---------| | **full** | docker.sock 挂载 + docker CLI 可用 + `docker logs/inspect meshsafe-api` 成功 | R009-R015 全部正常运行 | ✅ Docker 能力完整 | | **partial** | docker CLI 有但 socket 权限不够 | R009-R015 可能部分失败 | ⚠️ Docker 访问受限,部分容器日志规则未运行 | | **none** | socket 未挂 / CLI 未装 | R009-R015 **自动跳过** | ℹ️ 未启用 Docker 日志检测 | 检测结果写 `docker_detect.json`(导出包),同时存 Redis 供页面查询。 --- ## 四、定时器运行架构 ### 4.1 三层定时器 + 页面必跑 ``` MeshSafe API 进程 │ ├── FastAPI app │ └── startup event │ ├── ① DIAG_ENABLED? → false 直接 return(全部定时器不启动) │ ├── ② Docker 能力检测 → 存内存 dict + Redis │ ├── ③ asyncio.create_task(_timer_loop("error_scan", 30s, R001/R005/R009-R014)) │ ├── ④ asyncio.create_task(_timer_loop("db_aggregate", 60s, R006/R007/R008)) │ └── ⑤ asyncio.create_task(_timer_loop("container", 300s, R002/R003/R004/R015)) │ ├── 页面请求 GET /console/diagnostics │ ├── 同步跑 R001/R002/R003/R004/R005(~430ms,用户无感) │ ├── 读 Redis 缓存拿 R006-R015 │ └── 合并 + 渲染(每条带时间戳) │ └── 手动导出 GET /api/v1/telemetry/dump ├── 阻塞跑完整规则引擎(含实时 + 日志 + 容器) ├── 抓 docker logs --tail 2000(全容器) ├── 打包 tar.gz └── 用后台线程跑,超时 60 秒 ``` ### 4.2 单次运行耗时预期 | 通道 | 规则 | 耗时 | 周期 | 并发安全 | |------|------|------|------|---------| | 页面必跑 | R001/R002/R003/R004/R005 | ~430ms | 每次页面加载 | 同步 await,不阻塞 worker | | 定时器 A | R001/R005/R009-R014 | ~2.5s | 30s | asyncio.create_subprocess_exec | | 定时器 B | R006/R007/R008 | ~60ms | 60s | 单次 SQL 查询 | | 定时器 C | R002/R003/R004/R015 | ~1.5s | 300s | clamd TCP + docker inspect × 5 | | 导出 | 全部 15 条 + docker logs 全量 | ~5-8s | 用户手动 | 后台线程,60s 超时 | ### 4.3 Redis 缓存结构 ``` ms:diag:latest ← Hash: { rules: [...], generated_at: 1730406555, docker_status: "full", enabled: true } ms:diag:cache:R001 ← String: { "result": {...}, "ts": 1730406555, "run_by": "timer:error_scan" } ms:diag:cache:R009 ← 同上 ... ms:diag:docker_status ← String: "full" / "partial" / "none" ms:diag:enabled ← String: "true" / "false" ``` **TTL**:单条规则缓存 6 分钟;`latest` 整个包 10 分钟。 ### 4.4 定时器异常保护 ```python async def _timer_loop(name, interval, rules, docker_status): """通用定时器循环。""" while True: try: await asyncio.sleep(interval) results = await _run_rule_group(rules, docker_status) await _write_redis(results, name) except asyncio.CancelledError: logger.info(f"定时器 {name} 被取消") break except Exception as e: logger.exception(f"定时器 {name} 异常,{interval}s 后重试: {e}") # 继续 while True,下次 sleep 后重试 ``` --- ## 五、后端接口清单 | 方法 | 路径 | 说明 | 响应 | |------|------|------|------| | GET | `/console/diagnostics` | 诊断面板 HTML | 页面(含 DIAG_ENABLED=false 横幅)| | GET | `/api/v1/telemetry/summary` | 实时面板数据(页面调用)| JSON | | GET | `/api/v1/telemetry/engine-health` | 引擎健康快照 | JSON | | GET | `/api/v1/telemetry/errors?limit=20` | 最近错误列表 | JSON | | GET | `/api/v1/telemetry/docker-status` | Docker 能力检测结果 | JSON | | GET | `/api/v1/telemetry/rules` | **所有规则最新结果**(带时间戳 + 运行来源)| JSON | | GET | `/api/v1/telemetry/dump` | **一键导出 tar.gz** | 文件下载 | | GET | `/api/v1/telemetry/config` | 当前配置快照(周期、阈值、Docker 模式)| JSON | --- ## 六、后端代码结构 ``` app/ ├── diagnostic/ ← 新增目录 │ ├── __init__.py │ ├── config.py ← 读取 diagnostics.yaml + 环境变量覆盖 │ ├── engine.py ← DiagnosticEngine(跑规则集) │ ├── rules_db.py ← DB 聚合类规则实现(R001/R005-R008) │ ├── rules_engine.py ← 引擎健康类规则实现(R002-R004) │ ├── rules_log.py ← 日志匹配类规则实现(R009-R014) │ ├── rules_container.py ← 容器状态类规则实现(R015) │ ├── docker_probe.py ← 启动时 Docker 能力检测 │ ├── collector.py ← DB + scanner.health() 原始数据拉取 │ ├── dumper.py ← 打包 tar.gz + 脱敏 │ └── scheduler.py ← 三层定时器循环 ├── console.py │ └── 新增 1 个页面路由 + 7 个 API 路由 ├── main.py │ └── startup event 加 DIAG_ENABLED 检查 + scheduler 启动 ├── config/ │ └── diagnostics.yaml ← 新增配置文件 ├── templates/ │ ├── diagnostics.html ← 新增诊断面板模板 │ └── _nav.html ← nav_active='diagnostics' 入口 └── Dockerfile └── 加 `RUN apt-get install docker.io-cli` ``` --- ## 七、配置文件完整内容 **文件**:`app/config/diagnostics.yaml` ```yaml # =============================================================== # MeshSafe Diagnostics Kit v1.0 配置 # =============================================================== # 环境变量可覆盖: # MESHSAFE_DIAG_ENABLED=true|false # MESHSAFE_DIAG_DOCKER=auto|true|false # MESHSAFE_DIAG_ERROR_INTERVAL=30 # MESHSAFE_DIAG_CONTAINER_INTERVAL=300 # MESHSAFE_DIAG_DB_INTERVAL=60 # =============================================================== diagnostics: # ---------- 总开关 ---------- # true = 启动定时器、检测 Docker、运行规则引擎 # false = 只保留手动导出功能,所有自动诊断关闭 enabled: true # ---------- Docker 日志/容器状态能力 ---------- # auto = 启动时自动检测 docker.sock 挂载 # true = 强制启用,连不上启动报错 # false = 强制关闭 docker: enabled: auto # ---------- 后台定时器周期(秒,0 = 不跑)---------- timers: error_scan_interval: 30 # 错误/拦截类日志(R001/R005/R009-R014) container_check_interval: 300 # 容器/引擎类(R002/R003/R004/R015) db_aggregate_interval: 60 # DB 聚合类(R006/R007/R008) # ---------- 日志行数控制 ---------- docker_log_tail: 500 # 定时器 grep 最近多少行 export_log_tail: 2000 # 导出时完整抓多少行 # ---------- 规则阈值 ---------- rules: error_ratio_threshold: 0.05 # R001: error > 5% 告警 clamav_db_stale_days: 7 # R002: 病毒库 > 7 天告警 reject_ratio_threshold: 0.20 # R007: reject > 20% 告警 suspicious_exceeds_malicious: true # R006 开关 ``` --- ## 八、页面设计 ### 8.1 诊断面板布局 ``` ┌────────────────────────────────────────────────────────────────────┐ │ Nav: 概览 | 策略配置 | 任务历史 | 测试靶场 | 🔧 诊断 ← active │ │ 最后刷新: 10:29:15 🧩 Docker: ✅ 完整 │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─ 实时健康概览 ────────────────────────────────────────────────┐ │ │ │ 扫描总量 恶意拦截 可疑待核 错误比例 引擎在线 │ │ │ │ 12,472 117 47 10.0% 🔴 5/5 ✅ │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ ⚠️ 异常检测(本地规则引擎)───────────────────────────────────┐ │ │ │ 🔴 R001 错误比例偏高(10.0% > 阈值 5%) (10:29:15) │ │ │ │ 近 100 次扫描 12 次 error │ │ │ │ 💡 建议检查 ClamAV / Redis / Postgres 引擎 │ │ │ │ │ │ │ │ 🔴 R009 ClamAV 被 OOM Kill(重启 3 次) (10:29:15) │ │ │ │ 证据: meshsafe-clamav stderr: Killed (exit 137) │ │ │ │ 💡 建议给 clamav 容器加内存 512m → 1g │ │ │ │ │ │ │ │ 🟡 R002 ClamAV 病毒库 14 天未更新 (10:29:15) │ │ │ │ 💡 在线更新失败 → U 盘离线导入 │ │ │ │ │ │ │ │ 🟢 所有其他规则通过 │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ 🧩 容器运行状态 ──────────────────────────────────────────────┐ │ │ │ API ✅ healthy restart: 0 启动: 2h 前 │ │ │ │ Postgres ✅ healthy restart: 0 │ │ │ │ Redis ✅ healthy restart: 0 │ │ │ │ ClamAV ✅ running restart: 3 ⚠️ 近 24h 不稳定 │ │ │ │ Nginx ✅ healthy restart: 0 │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ 🧪 引擎状态 ─────────────────────────────────────────────────┐ │ │ │ 🦠 ClamAV ✅ 病毒库: 2026-10-14 (14天前) │ │ │ │ 🎯 YARA ✅ 规则: 205 条 │ │ │ │ 📄 Document ✅ │ │ │ │ 🔑 Keywords ✅ │ │ │ │ ⚙️ Binary ✅ │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ ❌ 最近 20 条错误 ────────────────────────────────────────────┐ │ │ │ [table: 时间 | 文件名 | 错误原因 | 引擎 | 分数] │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ [📦 导出诊断包] 按钮 │ └────────────────────────────────────────────────────────────────────┘ ``` ### 8.2 总开关关闭时 ``` ┌────────────────────────────────────────────────────────────────────┐ │ ⛔ 诊断套件未启用 │ │ diagnostics.yaml: diagnostics.enabled = false │ │ │ │ 诊断套件提供实时健康检测、容器运行状态监控、一键诊断包导出。 │ │ 关闭后仅保留手动导出功能,所有自动规则引擎将停止运行。 │ │ │ │ 启用方式: │ │ 1. 修改 app/config/diagnostics.yaml: enabled → true │ │ 2. 或设置环境变量:MESHSAFE_DIAG_ENABLED=true │ │ 3. 重启 api 容器 │ │ │ │ [📦 手动导出诊断包(离线收集)] │ └────────────────────────────────────────────────────────────────────┘ ``` --- ## 九、实施计划 ``` Phase 1(3-4 天)— 核心框架 ├── app/diagnostic/ 目录结构 + 配置读取 ├── DiagnosticEngine 框架 + 规则基类 ├── DB 聚合类规则(R001/R005-R008) ├── 引擎协议类规则(R002/R003/R004) └── Docker 能力检测 Phase 2(2-3 天)— 定时器 + 页面 ├── 三层定时器 + Redis 缓存 ├── 页面必跑规则通道 ├── 诊断面板 HTML 模板(含 Docker 状态 + 总开关横幅) ├── 异常列表 + 时间戳显示 └── docker-compose.yml api 服务加 docker.sock 挂载 Phase 3(2 天)— 日志匹配 + 导出 ├── 日志匹配类规则(R009-R014) ├── docker inspect 类规则(R015) ├── 一键导出 tar.gz + 脱敏 ├── Dockerfile 加 docker CLI └── 导出包结构完成 Phase 4(1 天)— 测试 + 验证 ├── docker build + 重启 + 功能验证 ├── 模拟无 Docker 环境(不挂 socket)→ 验证降级 ├── 关闭总开关 → 验证横幅 + 手动导出仍可用 └── 全接口自测 ``` --- ## 十、后续演进(不在 v1.0) | 版本 | 功能 | |------|------| | v1.1 | 导出包可选"含完整策略 YAML";规则阈值页面可配(不用改 yaml)| | v1.1 | 导出包可选"上传到指定 S3 桶"(如果客户有内网 S3)| | v2.0 | 云端收集服务(MeshSafe Pulse):多客户聚合 + dashboard + 规则引擎云端版 | | v2.0 | LLM 辅助分析:把 diagnostics.tar.gz 喂给 LLM 生成结构化报告 | | v3.0 | AI 辅助修复闭环(仅在你自己的 sandbox 里做,不碰客户环境)| --- ## 附录 A:Dockerfile 改动 ```dockerfile # ===== 新增:装 docker CLI ===== # api 容器需要 docker CLI 才能通过 docker.sock 访问宿主机 Docker RUN apt-get update && apt-get install -y --no-install-recommends \ docker.io-cli \ && rm -rf /var/lib/apt/lists/* ``` ## 附录 B:docker-compose.yml 改动 ```yaml api: volumes: # ... 原有 volume ... - /var/run/docker.sock:/var/run/docker.sock:ro # ← 新增:只读挂载 ``` ## 附录 C:快速降级指引 | 问题 | 处理 | |------|------| | 启动报 `ModuleNotFoundError: docker` | 忘装 docker CLI,跑 `apt-get install docker.io-cli` | | 启动后页面显示 `ℹ️ Docker 能力未启用` | 没挂 docker.sock;compose api volumes 加一行 + rebuild | | 想完全关掉诊断 | `diagnostics.enabled: false` 或环境变量 | | 定时器吃 CPU | 三个定时器周期都可以调大;或设为 0 不跑某个 | | 导出包太大 | `export_log_tail` 调小;或只导出 `scan_tasks.json` | --- **文档结束** — 待审核 → 确认后开始编码