Files
awoooi/docs/adr/ADR-064-alert-rule-engine-yaml-driven.md
OG T 99cc420429 docs(review): 首席架構師 Code Review 後 — ADR-064/067 + Skill 02 補全記錄
ADR-064: 補 I1 整合記錄(get_incident_type 三層降級、rule.id ≠ incident_type 設計決策)
ADR-067: 補 D1 集中化完成記錄(9 purpose keys 對應表)
Skill 02: 補 get_incident_type 使用規範 + Ollama D1 模型中央化禁令

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 21:35:25 +08:00

123 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-064: Alert Rule Engine — YAML 驅動規則匹配 + AI 自動學習
**狀態**: 已批准
**日期**: 2026-04-09
**作者**: ogt + Claude Sonnet 4.6
**審查**: 首席架構師2026-04-09
---
## 背景
`openclaw.py` 中的 `_generate_mock_response` 用硬編碼 if/elif 實作規則匹配,每次新增告警類型都要改 Python 代碼並重新部署。隨著監控目標增加,此模式不可持續。
## 決策
### D1: 規則外化為 YAML`apps/api/alert_rules.yaml`
規則引擎從 YAML 檔載入Pod 重啟即生效,不需要改代碼。
**規則結構**:
```yaml
rules:
- id: docker_container_unhealthy
priority: 10
match:
alertname: [DockerContainerUnhealthy]
message: [unhealthy]
response:
kubectl_command: "ssh {host} 'docker inspect {container}...'"
suggested_action: RESTART_DEPLOYMENT
risk: medium
responsibility: INFRA
```
**Priority 體系**:
| 範圍 | 用途 |
|------|------|
| 1499 | 手寫規則(高優先,不被 AI 覆蓋) |
| 500890 | AI 自動生成規則 |
| 999 | `generic_fallback` 通用兜底 |
### D2: AI 自動規則學習機制
當告警命中 `generic_fallback` 時,觸發 `auto_generate_rule()`
1. 呼叫 Ollama (deepseek-r1:14b) 生成 YAML 規則片段
2. Ollama 失敗 → Gemini 2.0 Flash 備援
3. 驗證格式id/match/response/kubectl_command 必填)
4. `textwrap.dedent()` 正規化縮排(防 LLM 輸出前置空格)
5. append 到 `alert_rules.yaml`
6. `lru_cache.cache_clear()` — 同 Pod 立即生效
### D3: 模組邊界
- `alert_rule_engine.py` = Service 層,只讀 YAML不直接存取 Redis/DB
- `auto_generate_rule()` 採用 DI 參數注入(`ollama_url`, `model`, `gemini_api_key`),不 import settings 全域單例
- asyncio 觸發在上層 async `_call_with_fallback()` 執行,不在 sync `_generate_mock_response()` 中操作 event loop
### D4: 匹配邏輯
優先順序alertname 完全匹配 > alert_type 部分匹配 > message 關鍵字
`generic_fallback``alertname: ["*"]`)在 `_matches()` 中永遠回傳 False`match_rule()` 的第二輪迴圈單獨選取,防止其 alert_type/message 關鍵字意外命中。
## 已知限制
### L1: 多 Pod 環境下規則可能重複生成
`_generating` set 是進程記憶體級去重,多 Pod 各自維護。同一告警可能在不同 Pod 同時觸發生成,產生重複規則 append。
**緩解**: `_rule_id_exists()` 提供二次去重,但有 lru_cache 的時間窗口 race condition。
**計劃**: 若未來 Pod 數 > 2需 Redis 分散式鎖。目前 prod 為 2 Pod可接受。
### L2: `lru_cache` 跨 Pod 不同步
新規則寫入後,只有寫入的 Pod 清除了 cache其他 Pod 需重啟才能載入新規則。這是已知行為,下次告警觸發時仍會走 `generic_fallback`,但不會再次生成(`_rule_id_exists` 讀 YAML 直接確認)。
## 測試策略
`auto_generate_rule()` 採 DI可在不啟動 FastAPI 的情況下單獨測試:
```python
await auto_generate_rule(
alert_context={"labels": {"alertname": "TestAlert"}},
ollama_url="http://mock",
model="test-model",
)
```
## I1 整合記錄 (2026-04-11, commit d77b2ad)
`get_incident_type(alertname)` 已整合,實作三層降級:
```
YAML rule.incident_typeLayer 1
→ ALERTNAME_TO_TYPE 靜態 dictLayer 2, src/constants/alert_types.py
→ "custom"Layer 3 兜底)
```
**關鍵決策**
- YAML `rule.id` 不等於 `incident_type`(命名空間不同,禁止混用)
- YAML 無 `incident_type` 欄位時一律 fall through不用 rule.id 代替
- `webhooks.py` 已改用 `get_incident_type()` 取代靜態 dict
**M3 常量遷移**`ALERTNAME_TO_TYPE`56 筆)從 webhooks.py 內聯遷移至 `src/constants/alert_types.py`
## 相關檔案
- `apps/api/alert_rules.yaml` — 規則定義
- `apps/api/src/services/alert_rule_engine.py` — 規則引擎 + `get_incident_type()`
- `apps/api/src/constants/alert_types.py` — ALERTNAME_TO_TYPE 靜態 dictLayer 2 fallback
- `apps/api/src/api/v1/webhooks.py` — 呼叫 `get_incident_type()`
- `apps/api/tests/test_get_incident_type.py` — 11 個整合測試
- `apps/api/src/services/openclaw.py``_generate_mock_response` + `_call_with_fallback` 整合點
- `apps/api/Dockerfile` — COPY alert_rules.yaml
## 參考
- ADR-006: AI 模型路由配置
- ADR-052: Phase 24 AIRouter
- `feedback_lewooogo_modular_enforcement.md`: 積木化 5 問