diff --git a/docs/adr/ADR-073-flywheel-full-audit.md b/docs/adr/ADR-073-flywheel-full-audit.md new file mode 100644 index 00000000..c47f8136 --- /dev/null +++ b/docs/adr/ADR-073-flywheel-full-audit.md @@ -0,0 +1,288 @@ +# ADR-073: AIOps 飛輪完整盤點 — 問題根因 + 長期解決方案 + +**狀態**: Accepted — 等待實作批准 +**日期**: 2026-04-12 (台北時間) +**作者**: Claude Sonnet 4.6 + 首席架構師 +**觸發**: 資料庫審計顯示飛輪從未真正運轉 + +--- + +## 一、飛輪設計圖(截圖定案版) + +### 正常模式(自主運行) + +``` +監控觀察 → 接入去重 → 環境診斷 → 推理匹配 → 執行燒橇 → 學習固化 + ↑ ↓ + └─────────────────── 飛輪閉環 ──────────────────────────────┘ +``` + +**六個節點定義**: + +| 節點 | 功能 | 對應程式碼 | +|------|------|-----------| +| 監控觀察 | Prometheus/Alertmanager 接收告警,Fingerprint 去重 | `webhooks.py` alertmanager_webhook | +| 接入去重 | Debounce 30 分鐘,相同 fingerprint 只建一個 Incident | `webhooks.py` fingerprint + debounce | +| 環境診斷 | MCP 收集真實環境狀態(SSH/K8s/Prometheus)| `decision_manager._collect_mcp_context()` | +| 推理匹配 | 查 KM Playbook → 有匹配直接執行;無匹配呼叫 LLM 推理 | `decision_manager._dual_engine_analyze()` | +| 執行燒橇 | 透過 MCP Provider 執行修復動作 | `decision_manager._auto_execute()` | +| 學習固化 | 成功→生成 Playbook;失敗→生成 Anti-Pattern;寫入 KM | `decision_manager._generate_playbook_draft_if_new()` | + +### 人工介入模式(需人工路徑) + +``` +推理匹配 + ├→ TYPE-3(高風險審核): AI 找到方案但動作風險高 → 人工批准 → 執行 → 學習固化 + └→ TYPE-4(根因確認): AI 無法判斷 → 人工手動處理 → 記錄手動步驟 → 生成新 Playbook +``` + +**人工介入只發生在兩種情況**: +1. `risk_level = critical` 或 DESTRUCTIVE_PATTERNS 命中 → TYPE-3 卡片 +2. `confidence < 0.5` 或 MCP 全失敗 → TYPE-4 卡片,人工確認後轉 Playbook + +--- + +## 二、資料庫實際數據(2026-04-12 統計) + +### Incidents(132 筆) + +| 欄位 | 數值 | 正常應有 | +|------|------|---------| +| severity P3 | 108(82%) | P3 應是少數 | +| severity P2 | 18(14%) | | +| severity P0 | 6(4%) | | +| status INVESTIGATING | 87(66%) | 應該接近 0 | +| status RESOLVED | 45(34%) | | +| alertname | **全部 NULL** | 應有 alertname | +| alert_category | **全部 NULL** | 應有分類 | +| notification_type | **全部 NULL** | 應記錄卡片類型 | +| vectorized | **全部 False** | 應 True(已向量化進 KM)| +| outcome | **全部 null** | 應記錄修復結果 | + +### Approvals(380 筆) + +| status | 數量 | 說明 | +|--------|------|------| +| EXECUTION_FAILED | 211(55%) | 超過一半執行失敗 | +| APPROVED | 132(35%) | 已核准,未執行或執行中 | +| REJECTED | 30(8%) | 人工拒絕 | +| EXPIRED | 5 | 超時未處理 | +| **EXECUTION_SUCCESS** | **2(0.5%)** | **實際成功修復只有 2 次** | + +### Top Actions(失敗根因) + +| action | 數量 | 問題 | +|--------|------|------| +| 未知操作 \|(空) | 93 | action 解析失敗,完全不知道要做什麼 | +| 重新啟動 postgres-primary-0 | 30 | 同一問題反覆重啟,未解決根因 | +| 重新啟動 postgresql-native | 25 | 同上 | +| 重新啟動 harbor-core-7d4b8c9f5-xk2m3 | 21 | Pod hash 寫死,每次不同 | +| OBSERVE | 16 | LLM 判斷無法修復,只觀察 | + +### Playbooks = **0** +### Knowledge Entries(699 筆) +- INCIDENT_CASE: 690(有案例,**但全部未向量化**,無法 RAG 查詢) +- RUNBOOK: 7 +- BEST_PRACTICE: 2 + +--- + +## 三、飛輪每個節點的實際狀態 vs 設計目標 + +### 節點 1:監控觀察 ✅ 部分正常 +- Prometheus → Alertmanager → webhooks.py 接收 ✅ +- **問題**:alertname 存入 signals JSONB,但 `signals->0->>'alertname'` 全為 NULL,代表 signals 結構不對 + +### 節點 2:接入去重 ⚠️ 部分異常 +- Fingerprint 機制存在 ✅ +- **問題**:debounce window 只有 5 分鐘,Prometheus 每 1 分鐘 fire 一次,5 分鐘後重新建 Incident,導致同一問題產生多個 Incident,反覆發 Telegram + +### 節點 3:環境診斷 ❌ 未運作 +- `_collect_mcp_context()` 已實作(commit c439277) +- **問題**:`8be87b0` 的修復程式碼 CD 失敗未部署,目前 Pod 是舊 image(`a86ecf3`),該 image 沒有 `_collect_mcp_context()` +- MCP Providers 雖然 enabled,但沒有在分析前被呼叫 + +### 節點 4:推理匹配 ❌ 嚴重問題 +- LLM 分析有在跑(Nemotron NIM)✅ +- **問題 A**:Playbooks = 0,`evaluate_auto_repair()` 在 Playbook 匹配步驟永遠失敗,**100% 走人工審核** +- **問題 B**:`signals` JSONB 裡 alertname = NULL,LLM 拿到的是空告警名稱 +- **問題 C**:target_resource 解析失敗(DockerContainerUnhealthy → target = alertname or unknown) +- **問題 D**:LLM 習慣性回傳 `risk_level = high`,但 YAML 規則的 risk 沒有覆蓋 LLM 結果 + +### 節點 5:執行燒橇 ❌ 從未成功(0.5%) +- `_auto_execute()` 三層安全守衛存在 ✅ +- **問題 A**:93 次 `未知操作|` → action 解析失敗,LLM 回傳的 action 包含 `|` 分隔符,解析邏輯出錯 +- **問題 B**:`target = unknown` 或 `target = alertname` → 安全守衛攔截 → 發 `❌ 自動修復失敗`(而非 TYPE-4 卡片) +- **問題 C**:K8s deployment 名稱含 hash(`harbor-core-7d4b8c9f5-xk2m3`),K8s 驗證失敗 +- **問題 D**:Docker/Host 層告警(DockerContainerUnhealthy / HostBackupFailed)走 K8s 路徑,根本路徑就錯 + +### 節點 6:學習固化 ❌ 完全失效 +- `_generate_playbook_draft_if_new()` 已實作(commit 7eb49f9)✅ +- **問題 A**:Playbooks 仍為 0,代表觸發條件從未成立,或有 exception 被靜默吞掉 +- **問題 B**:690 筆 INCIDENT_CASE 全部 `vectorized = False`,RAG 查詢永遠找不到歷史案例 +- **問題 C**:outcome / target_resource / alert_category 全為 NULL,KM 記錄的案例沒有分類,無法被有效查詢 +- **問題 D**:重複發生的告警(postgres-primary-0 被重啟 30 次)沒有任何學習,每次重新走全流程 + +--- + +## 四、與設計定案的落差對照表 + +| 設計定案要求 | 實際狀況 | 落差等級 | +|------------|---------|---------| +| 告警 → 自動修復閉環 | Playbooks=0,100% 人工審核 | 🔴 P0 | +| 重複告警不重複觸發 | 5 分鐘 debounce,每次重建 | 🔴 P0 | +| 失敗 → 學習 → 建立規則 | 690 案例未向量化,Playbook 從未生成 | 🔴 P0 | +| KM 三段資料完整 | outcome/vectorized 全 NULL | 🔴 P0 | +| target 動態解析(Docker/Host)| target = unknown/alertname | 🔴 P0 | +| NO_ACTION 告警不顯示按鈕 | HostBackupFailed 仍有六個按鈕 | 🟠 P1 | +| 通知類型正確分類 | notification_type 全 NULL | 🟠 P1 | +| severity/risk_level 正確 | P3 佔 82%,risk 被 LLM 覆蓋 | 🟠 P1 | +| 人工修復後轉 Playbook | 無 handle_manual_fix_done 呼叫 | 🟠 P1 | +| 前端顯示飛輪狀態 | 截圖顯示 Playbook=0,飛輪靜止 | 🟡 P2 | + +--- + +## 五、長期解決方案(按優先順序) + +### P0-A:修復 CD + 部署新 image +**問題**:`8be87b0` 的三大修復未上線 +**解法**:確認 Harbor 有 `8be87b0` 的 web image;若沒有,重新觸發 build +**驗收**:Pod image = 8be87b0,`_collect_mcp_context` 存在於 /app/src + +### P0-B:修復 signals 結構 — alertname 存入正確欄位 +**問題**:`signals->0->>'alertname'` = NULL +**解法**:`webhooks.py` 在建 Incident 時,確認 signal 的 alertname 正確寫入 JSONB;或新增獨立欄位 `alertname VARCHAR(100)` +**驗收**:新 Incident 的 alertname 不為 NULL + +### P0-C:debounce window 延長到 30 分鐘 +**問題**:5 分鐘 debounce → 同一問題每 5 分鐘重新建 Incident + 發 Telegram +**解法**:`webhooks.py` fingerprint debounce window 從 5 分鐘 → 30 分鐘 +**驗收**:同一告警 30 分鐘內只有 1 個 INVESTIGATING Incident + +### P0-D:冷啟動 Playbook 生成 +**問題**:Playbooks = 0,自動修復永遠失敗 +**解法**:一次性腳本,讀取 approval_records 裡 EXECUTION_SUCCESS 的 2 筆 + APPROVED 的 top 10,用 LLM 生成初始 Playbook 草稿,寫入 DB 標記 APPROVED +**驗收**:Playbooks >= 10,evaluate_auto_repair 至少有匹配 + +### P0-E:690 筆 INCIDENT_CASE 向量化 +**問題**:vectorized = False,RAG 永遠找不到歷史案例 +**解法**:一次性腳本,批次向量化所有 `vectorized=False` 的 knowledge_entries +**驗收**:vectorized = True,RAG 查詢返回相關案例 + +### P0-F:Docker/Host 層告警走 SSH MCP 路徑 +**問題**:DockerContainerUnhealthy → K8s 路徑 → deployment 不存在 → 失敗 +**解法**:`_auto_execute()` 判斷 alertname prefix(Docker*/Host*)→ 走 SSH MCP `ssh_docker_restart`;不走 K8s 路徑 +**驗收**:DockerContainerUnhealthy 觸發 → SSH MCP 執行 → 成功 + +### P1-A:action 解析修復(93 筆「未知操作|」) +**問題**:LLM 回傳含 `|` 的 action,解析後為空 +**解法**:`_push_decision_to_telegram()` 的 action 佔位符替換邏輯,加 `|` 分隔符清理 +**驗收**:新的 approval_records action 不出現「未知操作|」 + +### P1-B:NO_ACTION → TYPE-1(無按鈕) +**問題**:HostBackupFailed 仍有六個按鈕 +**解法**:`classify_notification()` 加判斷:`suggested_action == "NO_ACTION"` → TYPE-1 +**驗收**:HostBackupFailed Telegram 卡片無操作按鈕 + +### P1-C:outcome / alert_category / notification_type 寫入 +**問題**:三個欄位全 NULL,KM 無法分類 +**解法**: +- `_auto_execute()` 完成後寫 `incident.outcome` +- `webhooks.py` 建 Incident 時寫 `alert_category`(從 YAML 規則取) +- `_push_decision_to_telegram()` 發完寫 `notification_type` + +### P1-D:risk_level YAML 優先覆蓋 LLM +**問題**:LLM 習慣性回傳 high,YAML 規則的 risk 沒有覆蓋 +**解法**:`_dual_engine_analyze()` 中,若 YAML 規則有 risk 值,優先用 YAML 的,不採用 LLM 的 +**驗收**:HostHighCpuLoad(YAML risk=medium)→ Telegram 顯示 MEDIUM + +### P2-A:前端飛輪狀態即時顯示 +**問題**:截圖顯示飛輪靜止,Playbook=0,成功率 0.5%,需人工介入 +**解法**:前端飛輪元件(已有)連接以下 API: +- `GET /api/v1/stats/flywheel` → 返回六節點狀態(活躍/靜止/錯誤) +- `GET /api/v1/stats/summary` → 返回 Playbook 數量、成功率、今日處理數 +- WebSocket 推送:每次節點狀態變化即時更新 +**驗收**:飛輪動畫跟隨真實告警流轉動;Playbook 數量即時更新 + +### P2-B:前端顯示人工介入路徑 +**問題**:截圖第二張「需人工介入」時,飛輪顯示 TYPE-4(根因確認)和 TYPE-3(高風險審核)路徑 +**解法**:飛輪元件增加「顯示人工干預路徑」toggle(已有),觸發時: +- 紅色虛線顯示從「推理匹配」到「人工處理中心」的路徑 +- TYPE-3 / TYPE-4 標籤顯示在對應節點旁邊 + +--- + +## 六、實作優先順序與批准等待 + +> 以下所有變更等待首席架構師批准後才開始實作 + +### Sprint ADR-073-A(本週):讓飛輪真的動起來 + +| # | 任務 | 風險等級 | 估計工時 | +|---|------|---------|---------| +| 1 | CD 修復 + 8be87b0 部署確認 | Tier 2 | 30 分鐘 | +| 2 | signals alertname NULL 修復 | Tier 2 | 1 小時 | +| 3 | debounce 5→30 分鐘 | Tier 2 | 30 分鐘 | +| 4 | 冷啟動 Playbook 生成腳本(一次性)| Tier 2 | 2 小時 | +| 5 | 690 筆 KM 向量化腳本(一次性)| Tier 1 | 1 小時 | + +### Sprint ADR-073-B(下週):修復路由 + 通知品質 + +| # | 任務 | 風險等級 | 估計工時 | +|---|------|---------|---------| +| 6 | Docker/Host 告警 → SSH MCP 路徑 | Tier 3 | 3 小時 | +| 7 | action 解析「未知操作|」修復 | Tier 2 | 1 小時 | +| 8 | NO_ACTION → TYPE-1 無按鈕 | Tier 2 | 1 小時 | +| 9 | outcome/alert_category/notification_type 寫入 | Tier 3 | 2 小時 | +| 10 | risk_level YAML 優先 | Tier 2 | 1 小時 | + +### Sprint ADR-073-C(再下週):前端飛輪即時化 + +| # | 任務 | 風險等級 | 估計工時 | +|---|------|---------|---------| +| 11 | `/api/v1/stats/flywheel` API | Tier 1 | 2 小時 | +| 12 | 前端飛輪元件連接真實 API | Tier 2 | 3 小時 | +| 13 | WebSocket 即時推送 | Tier 2 | 2 小時 | + +--- + +### Sprint ADR-073-B 補充(整合 ADR-071 工作序,2026-04-12 更新) + +| # | 任務 | 風險等級 | ADR-071 對應 | +|---|------|---------|-------------| +| B-DB | DB Migration: incidents +9 欄位 | Tier 1 | ADR-071-A | +| B1 | 檢傷分類站前移(decision_manager 入口)| Tier 3 | ADR-071-A0+B | +| B2 | TYPE-1 純資訊卡片 | Tier 2 | ADR-071-C | +| B3 | KMConversionService(RESOLVED→KM→向量化)| Tier 2 | ADR-071-G | +| B4 | TYPE-4 手動修復記錄 | Tier 2 | ADR-071-H | + +### Sprint ADR-074(監控補全,依賴 ADR-073-A/B 完成後) + +| # | 任務 | 告警名稱 | 嚴重度 | +|---|------|---------|-------| +| M1 | 飛輪健康度 Prometheus Exporter + 告警規則 | `FlywheelPlaybookZero` / `FlywheelExecutionSuccessLow` | P0 Critical | +| M2 | 主機間網路 Blackbox probe | `HostNetworkPartition` | P0 Critical | +| M3 | CoreDNS 解析失敗監控 | `CoreDNSResolutionFailed` | P0 Critical | +| M4 | Gitea CI/CD 管線失敗 webhook | `GiteaCIPipelineFailed` | P0 Critical | +| M5 | 備份還原週排程測試 | `BackupRestoreTestFailed` | P0 Critical | +| M6 | Docker 188 容器詳細健康 | `DockerContainerUnhealthyDetailed` | P1 Warning | +| M7 | Redis Streams 積壓 | `RedisStreamBacklogHigh` | P1 Warning | +| M8 | PostgreSQL 磁碟增長率 | `PostgreSQLDiskGrowthRate` | P1 Warning | +| M9 | Gemini API 錯誤率 | `GeminiAPIErrorRateHigh` | P1 Warning | + +> **完整規格**: `docs/superpowers/specs/2026-04-12-aiops-complete-flywheel-repair-design.md` + +--- + +## 七、驗收標準(飛輪真正運轉的指標) + +完成後 7 天內,以下指標必須達到: + +| 指標 | 當前 | 目標 | +|------|------|------| +| Playbooks 數量 | 0 | ≥ 20 | +| EXECUTION_SUCCESS 率 | 0.5% | ≥ 30% | +| 重複告警同 Incident 率 | < 10% | ≥ 70% | +| KM vectorized | 0% | ≥ 90% | +| alertname NULL | 100% | 0% | +| 飛輪節點有活動的告警 | 0 | ≥ 1 筆/小時 |