Files
awoooi/docs/proposals/INTEGRATION_ARCHITECTURE_MASTER.md
OG T 89e05e6ea2 docs: ADR-037 + 監控架構提案 + Runbooks
- ADR-037 監控增強架構
- MONITORING_MASTER_PLAN 主計畫
- MASTER_EXECUTION_SCHEDULE 執行排程
- Phase D/E/Worker HPA Runbooks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-29 16:04:08 +08:00

571 lines
27 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.
# AWOOOI 整體整合架構統合設計
> **文件類型**: 統合架構設計Single Source of Truth for Integration
> **優先級**: 🔴 統帥最高指令
> **建立**: 2026-03-29 13:27 (台北)
> **核心命題**: 所有節點必須在同一座大腦的神經網路中協同運作,不允許孤島。
---
## 第一部分:現況誠實盤點(精確)
### 已確認:比稽核報告更樂觀的部分
| 項目 | 稽核報告誤判 | 真實現況 |
|------|------------|---------|
| Worker SIGTERM 處理 | 報告說「缺失」 | ✅ **已實作**`signal_worker.py:450-455`)— `signal.signal(SIGTERM)` + `shutdown_event` |
| Worker 優雅關機流程 | 報告說「需要實作」| ✅ **已實作**`stop()` 方法,有心跳機制) |
| SignOz Webhook 路由 | 報告說「未部署」| ✅ **已路由**`main.py:419`|
### 已確認:比稽核報告更嚴峻的部分
| 項目 | 稽核報告版本 | 真實缺口 |
|------|------------|---------|
| Worker stop() timeout | 未提及 | ❌ **只有 5 秒**AI 分析 30-60 秒會被強殺 |
| K8s terminationGracePeriodSeconds | 未提及 | ❌ **未設定**K8s 預設 30 秒不夠用 |
| ESLint i18n 強制 | 說「CI 攔截」| ❌ **只有 TODO 注解**`.eslintrc.js:20-22`),未實際安裝 plugin |
| Visual Regression 跨平台 | 說「截圖比對」| ❌ **Mac 與 CI Linux 字體渲染不同**baseline 不能在 Mac 產生 |
| PostgreSQL HA | 說「Streaming Replication」| ❌ **無切換機制**,主庫掛了需要人工介入 |
| Redis HA | 完全未提及 | ❌ **無 Sentinel**Redis 單點故障 |
| SSE Event Sourcing | 只設計了事件類型 | ❌ **F5 刷新後 GenUI 卡片全消失** |
| Kali 整合 | Cronjob 被動 | 🟡 層次太低,應升格為 SecurityAgent |
---
## 第二部分:完整整合地圖
### 2.1 系統神經網路拓撲
```
外部事件輸入
┌─────────────────────────────────────────────────────────────────┐
│ Alertmanager :9093 Sentry :9000 SignOz :3301 │
│ GitHub Actions Kali Scanner K8s Events │
└──────────┬──────────────┬──────────────┬──────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────────────┐
│ AWOOOI API (K3s :32334) │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌─────────────┐ │
│ │Alertmanager│ │Sentry │ │SignOz │ │Kali(未來) │ │
│ │Webhook │ │Webhook │ │Webhook │ │Webhook │ │
│ └────┬─────┘ └────┬─────┘ └─────┬─────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Signal Worker (Redis XREADGROUP) │ │
│ │ ← 消費 awoooi:signals stream │ │
│ │ → IncidentEngine (聚合 / GraphRAG / 持久化) │ │
│ └──────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ OpenClaw (192.168.0.188:8089) │ │
│ │ 決策引擎: RCA → Blast Radius → Risk → Action │ │
│ │ 工具: kubectl / SSH / Prometheus / SigNoz │ │
│ └──────┬─────────────────────┬────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ Telegram Bot Approval DB (PostgreSQL) │
│ 統帥通知 審核佇列 │
└──────────────────────────────────────────────────────────────────┘
統帥批准 / 拒絕
┌────────────▼────────────┐
│ Auto-Repair Actions │
│ restart/scale/rollback │
└─────────────────────────┘
```
### 2.2 前端整合地圖
```
AWOOOI Web (K3s :32335 / Next.js)
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌── Dashboard (/) ─────────────────────────────────────────┐ │
│ │ AutonomyIndexPanel ← GET /api/v1/stats/autonomy │ │
│ │ SystemPulseRow ← GET /api/v1/stats/overview │ │
│ │ DecisionZone ← SSE /api/v1/approvals/stream │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Omni-Terminal ─────────────────────────────────────────┐ │
│ │ Input Area → POST /api/v1/terminal/command │ │
│ │ ThinkingStream ← SSE /api/v1/terminal/stream/{id} │ │
│ │ GenUI Renderer ← event: render_ui │ │
│ │ Event Replay ← Redis List (Last-Event-ID) │ ← ❌ 缺失
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Knowledge Base (/knowledge-base) ─────────────────────┐ │
│ │ ❌ 空白頁面,缺後端 API │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ [深度調查跳脫入口] │
│ → SigNoz: http://192.168.0.188:3301 (新分頁) │
│ → Grafana: http://192.168.0.188:3000 (新分頁) │
│ → Sentry: http://192.168.0.110:9000 (新分頁) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 第三部分:六大缺口的系統整合修復方案
### 缺口 1Worker terminationGracePeriodSeconds 不足
**問題根源**`signal_worker.py``stop()` 等 5 秒,但 AI 分析任務最長 60 秒。K8s 預設 `terminationGracePeriodSeconds: 30`。兩個值都不夠,且彼此沒有對齊。
**與整體整合的依賴**
- Worker 縮容影響HPA 縮容時 → K8s 發 SIGTERM → `stop()` 被調用 → 5 秒後強殺
- 上游依賴Sentry 分析任務、Alertmanager 分析任務都在 Worker background task 中執行
- 下游影響PostgreSQL 寫入可能不完整Incident 狀態 Dirty
**修復:三層數值對齊**
```yaml
# k8s/awoooi-prod/08-deployment-worker.yaml
# 修改 terminationGracePeriodSeconds
spec:
template:
spec:
terminationGracePeriodSeconds: 90 # 給 Worker 足夠時間完成當前任務
containers:
- name: awoooi-worker
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # 給 K8s 時間發 SIGTERM
```
```python
# apps/api/src/workers/signal_worker.py
# 修改 stop() 的 timeout 與 K8s terminationGracePeriodSeconds 對齊
async def stop(self) -> None:
if not self._running:
return
self._running = False
if self._task:
try:
# 從 5 秒改為 75 秒(比 terminationGracePeriodSeconds=90 少 15 秒緩衝)
await asyncio.wait_for(self._task, timeout=75.0)
except TimeoutError:
logger.warning("signal_worker_stop_timeout_forcekill")
self._task.cancel()
except asyncio.CancelledError:
pass
logger.info("signal_worker_stopped")
```
**整合驗證指令**
```bash
# 模擬 K8s 縮容,確認 Worker 優雅關機
kubectl scale deployment awoooi-worker -n awoooi-prod --replicas=0
kubectl logs -n awoooi-prod $(kubectl get pod -n awoooi-prod -l app=awoooi-worker -o name) --tail=20
# 預期看到:
# shutdown_signal_received signal=15
# signal_worker_shutting_down
# signal_worker_shutdown_complete
# (整個流程在 90 秒內完成)
```
---
### 缺口 2ESLint i18n 強制攔截eslint-plugin-i18next
**問題根源**`.eslintrc.js:20-22` 只有 TODO 注解,未安裝 `eslint-plugin-i18next`
**與整體整合的依賴**
- 這是 i18n 清零後的**防護層**清零是清過去的債ESLint 是防未來的債
- 需在 `pnpm build``pnpm lint` CI 步驟中阻擋
- 影響所有前端開發流程AI 生成代碼也必須通過)
**修復:安裝並啟用 Plugin**
```bash
# Step 1: 安裝
cd apps/web
pnpm add -D eslint-plugin-i18next
```
```javascript
// apps/web/.eslintrc.js 修改後
module.exports = {
extends: [
'@awoooi/eslint-config/react',
'next/core-web-vitals',
'plugin:i18next/recommended', // ← 新增
],
plugins: ['i18next'], // ← 新增
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
'@next/next/no-html-link-for-pages': 'off',
'no-console': 'off',
// 🚨 i18n 鐵律:所有 JSX 文字必須透過 t() 函式
// 違反此規則 = PR 阻擋error 級別)
'i18next/no-literal-string': ['error', {
markupOnly: true, // 只攔截 JSX 文字節點(非 JS 字串)
ignoreAttribute: [ // 技術屬性不攔截
'className', 'id', 'href', 'src', 'type', 'key',
'data-testid', 'aria-label', 'placeholder'
],
}],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': 'warn',
'no-constant-condition': 'warn',
},
ignorePatterns: [
'node_modules', '.next', 'out', 'dist', 'test-results',
'*.config.js', '*.config.ts',
],
}
```
**與 CI 整合(必須加入 cd.yaml**
```yaml
# .github/workflows/cd.yaml 在 Build 之前加入 Lint 步驟
- name: 🔍 ESLint i18n 強制檢查
run: |
cd apps/web
pnpm lint
# 失敗 = 有硬編碼字串 = 直接阻擋部署
```
**⚠️ 重要**:第一次啟用 `eslint-plugin-i18next` 後,**現有的 40+ 違規會立刻全部報錯**。因此必須先完成 i18n 清零,再啟用此 Rule。**正確順序**
1. i18n 清零(一次性修復 40+ 違規)
2. 安裝 eslint-plugin-i18next啟用防護
3. 加入 CI Lint 步驟
---
### 缺口 3Visual Regression 跨平台渲染問題
**問題根源**MacM1/M2/M3vs GitHub ActionsLinux Ubuntu的字體渲染引擎不同CoreText vs FreeType導致截圖像素不吻合。
**與整體整合的依賴**
- Baseline 快照必須統一來源CI Docker 環境)
- 每次更新 Baseline 必須是可審計的(透過 PR不能在本機靜默更新
**修復Docker 強制基準線更新 + threshold 調整**
```json
// apps/web/package.json 新增 scripts
{
"scripts": {
"test:visual:update": "docker run --rm -v $(pwd):/work -w /work -p 3000:3000 mcr.microsoft.com/playwright:v1.44.0-jammy pnpm exec playwright test --update-snapshots --project=chromium --grep @visual",
"test:visual": "pnpm exec playwright test --project=chromium --grep @visual"
}
}
```
```typescript
// apps/web/playwright.config.ts 修改截圖比對設定
export default defineConfig({
expect: {
toHaveScreenshot: {
threshold: 0.05, // 允許 5% 差異(吸收跨平台微小差)
maxDiffPixelRatio: 0.05,
// 強制使用 CI 環境的字體設定
},
},
use: {
// 截圖時的視窗大小固定,避免不同螢幕 DPI 差異
viewport: { width: 1280, height: 720 },
deviceScaleFactor: 1, // 強制 1x避免 Retina 差異
},
});
```
**強制規範(加入 .awoooi-agent-rules.md 條款)**
```markdown
## 條款 21Visual Regression Baseline 更新規範
🚨 絕對禁止在本機 Mac 環境執行 `--update-snapshots`
✅ 更新 Baseline 必須透過以下流程:
1. 在本機執行:`pnpm test:visual:update`Docker 環境)
2. Docker 生成的 .png 截圖自動存入 tests/e2e/__snapshots__/
3. 提 PR標注 📸 VISUAL_UPDATE
4. 統帥視覺審核截圖後方可合併
```
---
### 缺口 4PostgreSQL HAPatroni / CloudNativePG
**問題根源**PostgreSQL 在 .188 上是單一 Docker 容器K3s 的 Datastore 也依賴它ADR-033。資料庫掛掉 = K3s 控制面 + AWOOOI 資料同時失效。
**與整體整合的依賴**
- PostgreSQL 是 AWOOOI 的 Episodic MemoryIncidents、Approvals、Audit Logs
- PostgreSQL 也是 K3s 的 HA Datastore120/121 節點的 K3s 元數據)
- Auto-Repair 對 PostgreSQL 執行 `docker restart` 是**危險的**(可能 Dirty Page
**修復路線圖(三階段)**
```
Phase DB-A1週低風險
監控補強
├── 啟用 PG slow query log (log_min_duration_statement = 2000ms)
├── 加入 pg_stat_statements extension 並接入 Prometheus
└── 關閉 auto_repair.postgres.restart防止 Dirty Page
Phase DB-B1月中風險
備份策略
├── Velero + PostgreSQL Volume Snapshot已有 Velero需加 Volume 備份)
└── 確認 WAL archiving 到 MinIOWAL-E/WAL-G
Phase DB-CQ2需評估
HA 策略評估
├── 方案 ACloudNativePGK8s 原生 PostgreSQL Operator
│ → 在 K3s 中部署 CloudNativePG主從自動切換
├── 方案 BPatroni + keepalivedVM 層 HA
│ → 在 .188 和備用機上部署 Patroni
└── 方案 CCitus分片過於複雜暫不考慮
推薦:方案 A (CloudNativePG),與 K3s 最整合
```
**立即可執行的防護措施**
```yaml
# k8s/awoooi-prod/manual-remediation/postgres-recovery.yaml
# 建立 PostgreSQL 緊急修復 Playbook人工操作
# 事件PostgreSQL 掛了
# 動作:
# 1. OpenClaw 發告警 + Telegram
# 2. AlterManager 生成 CRITICAL Approval不自動修復
# 3. 統帥核准後,執行以下指令:
# ssh root@192.168.0.188 'docker restart awoooi-postgres'
# kubectl rollout restart deployment/awoooi-api -n awoooi-prod
# kubectl rollout restart deployment/awoooi-worker -n awoooi-prod
```
---
### 缺口 5Redis HASentinel 模式)
**問題根源**Redis 在 .188 上是單一容器port 6380無備援。Redis 同時承載:
- Working MemoryIncident 聚合狀態)
- SSE Terminal Event Store未來的 Event Source
- Sentry Dedup Cache10分鐘去重 TTL
- Anomaly CounterADR-037 核心數據)
**與整體整合的依賴**
- Redis 掛掉 = Signal Worker 無法消費事件 = 整個告警鏈路中斷
- AOF 啟用對性能有影響,需要評估
**修復路線圖**
```
Phase Redis-A立即0風險
確認 AOF 配置
├── ssh root@192.168.0.188 'docker exec awoooi-redis redis-cli CONFIG GET appendonly'
└── 確認 appendonly yes否則重啟後 Working Memory 歸零)
Phase Redis-B1月中等工程量
Redis Sentinel 部署(在 .110 上部署 Sentinel + Replica
├── .188Master現有
├── .110Replica + Sentinel
└── OpenClaw 使用 redis-sentinel:// URI自動發現 Master
配置變更:
# AWOOOI API 連線改用 Sentinel
REDIS_URL=redis-sentinel://sentinel1:26379/awoooi-master
```
**立即可執行的防護措施**
```bash
# 確認 Redis AOF 狀態
ssh root@192.168.0.188 'docker exec awoooi-redis redis-cli CONFIG GET appendonly; \
docker exec awoooi-redis redis-cli CONFIG GET appendfsync; \
docker exec awoooi-redis redis-cli INFO persistence | grep aof'
# 若 appendonly = no立即啟用需重啟 Redis
ssh root@192.168.0.188 'docker exec awoooi-redis redis-cli CONFIG SET appendonly yes'
# 注意CONFIG SET 是即時生效的,不需要重啟
```
---
### 缺口 6SSE Event SourcingTerminal 狀態不丟失)
**問題根源**Omni-Terminal 的 SSE 串流是無狀態的F5 刷新後所有 `render_ui` GenUI 卡片消失。
**與整體整合的依賴**
- 這是 Agentic Workspace 用戶體驗的底層設施
- 依賴 Redis List 作為 Event Store如果 Redis 無 AOF重啟後也丟
- 必須與 SSE 三種事件類型設計同步建立
**修復:三層機制**
```
Layer 1: 後端 Event StoreRedis List
terminal.py → 每個 SSE 事件同步寫入 Redis List
Key: terminal:events:{command_id}
TTL: 3600 秒1小時
Layer 2: 前端 ReconnectLast-Event-ID
useTerminalSSE → EventSource 自動帶 Last-Event-ID
後端收到後:從 Redis 撈出錯過的事件 → Replay → 接上即時 Stream
Layer 3: 本地 Zustand 持久化
useTerminalStore → 用 zustand/middleware/persist 持久化到 sessionStorage
F5 刷新 → 從 sessionStorage 恢復 GenUI 卡片UI 層快速恢復)
同時 → SSE 重連補齊 Server 端新事件
```
**後端實作關鍵代碼**
```python
# apps/api/src/api/v1/terminal.py 補充 Event Store 機制
import json
from src.core.redis_client import get_redis
async def stream_with_persistence(command_id: str, event_type: str, data: dict):
"""
SSE 事件輸出 + 同步寫入 Redis Event Store
確保 F5 刷新後可以 Replay
"""
redis = get_redis()
event_payload = {
"type": event_type,
"data": data,
"timestamp": now_taipei_iso()
}
# 寫入 Redis ListRPUSH append to right
key = f"terminal:events:{command_id}"
await redis.rpush(key, json.dumps(event_payload))
await redis.expire(key, 3600) # 1 小時後自動清理
# 返回 SSE 格式字串
return f"id: {redis.llen(key)}\nevent: {event_type}\ndata: {json.dumps(data)}\n\n"
@router.get("/stream/{command_id}/replay")
async def replay_terminal_events(command_id: str, last_event_id: int = 0):
"""
從指定 ID 開始 Replay 錯過的事件(用於 F5 重連)
"""
redis = get_redis()
key = f"terminal:events:{command_id}"
# 取出 last_event_id 之後的所有事件
events = await redis.lrange(key, last_event_id, -1)
async def generate():
for i, event_json in enumerate(events):
event = json.loads(event_json)
yield f"id: {last_event_id + i + 1}\nevent: {event['type']}\ndata: {json.dumps(event['data'])}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
```
---
## 第四部分:整合依賴關係圖(執行順序)
```
┌─────────────────────────────────────────────────────────────────────┐
│ 整合執行優先序 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ 🔴 P0 立即執行(本週,阻塞後續工作) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. 確認 Redis AOF 狀態5min │ │
│ │ 2. Worker terminationGracePeriodSeconds 修正1h │ │
│ │ 3. i18n 清零4h← 必須先於 ESLint Plugin 安裝 │ │
│ │ 4. ESLint i18n Plugin 安裝並啟用1h │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓(完成後解鎖) │
│ │
│ 🟠 P1 短期2-3週
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 5. Sentry Comment Token 配置2h │ │
│ │ 6. SignOz 告警規則部署到 .1882h │ │
│ │ 7. Worker HPA YAML 部署30min │ │
│ │ 8. E2E CI Weekly 排程30min │ │
│ │ 9. Visual Regression Docker 基準線建立2h │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓(完成後解鎖) │
│ │
│ 🟡 P2 中期Month 2
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 10. Omni-Terminal SSE Event Sourcing8h │ │
│ │ 11. Storybook 10 心組件8h │ │
│ │ 12. Nexus AI 自治率 UI8h │ │
│ │ 13. FinOps Dashboard UI8h │ │
│ │ 14. Redis Sentinel 部署1天 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓(完成後解鎖) │
│ │
│ ⚪ P3 長期Q2-Q3
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 15. CloudNativePG 評估與導入2天+ │ │
│ │ 16. Kali SecurityAgentMCP Tool 化) │ │
│ │ 17. Knowledge Base 後端全建 │ │
│ │ 18. Phase 4 視覺靈魂注入 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 第五部分:整合風險矩陣
| 風險 | 可能性 | 影響 | 緩解措施 |
|------|--------|------|---------|
| **ESLint 啟用後大量報錯** | 100%(有 40+ 違規) | CI 完全阻塞 | 先清零再啟用,正確順序執行 |
| **Worker timeout 修改引發 Pod 啟動異常** | 低 | 服務中斷 | 先在 Dev namespace 測試 |
| **Redis AOF 啟用影響性能** | 中 | 延遲微增 | 使用 `appendfsync everysec`(非 `always`|
| **Visual Regression Docker 第一次 Baseline** | — | 需要 1-2h 產生基準線 | 排在非尖峰時段執行 |
| **PostgreSQL 無 HA 期間主庫故障** | 低 | 完全停機 | 備份策略Velero+ Playbook 就位 |
| **SSE Event Sourcing Redis 依賴** | — | Redis 故障時 Event 丟失 | 先解決 Redis AOF再實作 Event Sourcing |
---
## 第六部分:監控機制與前端的整合設計原則
(承接 `MONITORING_ARCHITECTURE_DEEP_DIVE.md` 的「三義分離原則」)
**整合設計的核心約束**
```
1. 監控數據 → 後台靜默消化,不直接呈現給統帥
└─ 99%Prometheus/SigNoz/Sentry 原始數據
└─ 1%AI 無法自動處理 → 浮現為 ApprovalCard
2. 前端不直接查詢 Prometheus/SigNoz
└─ 所有監控數據透過 AWOOOI API 統一封裝
└─ API 層:/api/v1/stats/overview, /api/v1/slo, /api/v1/finops
3. 深度調查只能透過「智能跳脫」(新分頁)
└─ GenUI 卡片提供 ExternalLinks 按鈕
└─ 絕對禁止 iframe 嵌入 Grafana/SigNoz
4. AI 自治率指數是前端唯一的「監控摘要」入口
└─ Dashboard (/) 最頂部AutonomyIndexPanel
└─ 用一個數字替代所有圖表
```
---
*「整合不是把所有工具接在一起,而是讓所有工具服務同一個大腦。」* 🦞