# ADR-073: AIOps 飛輪完整盤點 — 問題根因 + 長期解決方案 **狀態**: Accepted — 等待統帥批准實作(細化步驟已完成) **日期**: 2026-04-12 (台北時間) **更新**: 2026-04-12 晚 — 加入架構轉型截圖定案 + 四階段細化實施步驟 **作者**: Claude Sonnet 4.6 + 首席架構師 **觸發**: 資料庫審計顯示飛輪從未真正運轉 **完整規格**: `docs/superpowers/specs/2026-04-12-aiops-complete-flywheel-repair-design.md`(§十一 細化步驟、§十二 防偏差守則) ### 架構轉型核心(2026-04-12 截圖定案) ``` 舊架構(中斷路徑): 告警 → LLM 瞎目(alertname=NULL)→ 目標不明 Unknown → KM 空白 新架構(飛輪 V2.2): 告警 → 分診工作站(decision_manager 入口) ├─ TYPE-1(資訊)→ 純文字通知,不進修復流程 ├─ TYPE-4D(Drift)→ Drift 卡片,不進修復流程 └─ TYPE-3(需修復)→ MCP 上下文 → LLM 推理 → 自動執行 → KM 向量化 ``` **關鍵洞察**:分診工作站從 telegram_gateway 前移至 decision_manager 入口,是防止「HostBackupFailed 產生 kubectl scale deployment unknown」的架構根治。 --- ## 一、飛輪設計圖(截圖定案版) ### 正常模式(自主運行) ``` 監控觀察 → 接入去重 → 環境診斷 → 推理匹配 → 執行燒橇 → 學習固化 ↑ ↓ └─────────────────── 飛輪閉環 ──────────────────────────────┘ ``` **六個節點定義**: | 節點 | 功能 | 對應程式碼 | |------|------|-----------| | 監控觀察 | 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 筆/小時 |