# Skill 04: AWOOOI DevOps Commander
# 基礎設施指揮官

> **管轄範圍**: Docker, K3s, Nginx, NetworkPolicy, CI/CD
> **觸發條件**: 修改 `k8s/`, `Dockerfile`, `.github/workflows/`, `nginx`

---

## 文件資訊

| 欄位 | 值 |
|------|-----|
| **版本** | v2.4 |
| **建立日期** | 2026-03-20 (台北) |
| **建立者** | Claude Code |
| **最後修改** | 2026-03-31 12:45 (台北) |
| **修改者** | Claude Code

### 變更紀錄

| 版本 | 日期 | 執行者 | 變更內容 |
|------|------|--------|----------|
| v1.0 | 2026-03-20 | Claude Code | 初始建立 |
| v1.1 | 2026-03-23 | Claude Code | Worker NetworkPolicy 教訓 |
| v1.2 | 2026-03-25 | Claude Code | CI/CD 整合 + clawbot-redis → openclaw-redis |
| v1.3 | 2026-03-25 | Claude Code | 加入文件資訊區塊 |
| v1.4 | 2026-03-26 | Claude Code | 新增部署層級決策鐵律 |
| v1.5 | 2026-03-26 | Claude Code | **Phase 15 三層觀測架構 (Deep Linking)** |
| v1.6 | 2026-03-26 | Claude Code | **Runner 殭屍進程修復 + CI/CD cancel-in-progress: false** |
| v1.7 | 2026-03-28 | Claude Code | **K3s 生產級優化 (ADR-033 + Phase K0)** |
| v1.8 | 2026-03-28 | Claude Code | **可觀測性端點配置規範 (SignOz 121→188 修正)** |
| v1.9 | 2026-03-29 | Claude Code | **🔴 ADR-035 Telegram Secrets 自動注入鐵律** |
| v2.0 | 2026-03-29 | Claude Code | **🆕 ArgoCD Metrics + TLS 證書監控 (P1/P2 改進)** |
| v2.1 | 2026-03-30 | Claude Code | **🔴🔴🔴 前端內網 IP 禁令 + CD 安全修復** |
| v2.2 | 2026-03-31 | Claude Code | **📊 K3s 優化成效數據 (告警-100%, Pod 重啟-100%, 48h+穩定)** |
| v2.3 | 2026-03-31 | Claude Code | **📅 Phase 21 定期報告機制規劃 (Weekly/Daily E2E/K3s Report)** |
| v2.4 | 2026-03-31 | Claude Code | **🔧 OTEL gRPC vs HTTP 端點區分 (K8s:24317, CI/CD:24318)** |

---

## 📅 Phase 21 定期報告機制 (2026-03-31)

> **計畫文檔**: `project_periodic_reporting_plan.md`
> **狀態**: 📋 待統帥批准

### 現有元件復用

| 元件 | 位置 | 復用度 |
|------|------|--------|
| `DailySummaryMessage` | telegram_gateway.py | 100% |
| `RepairReportMessage` | telegram_gateway.py | 100% |
| `StatsService` | stats_service.py | 100% |
| E2E Workflow | .gitea/workflows/e2e-health.yaml | 95% |

### 三階段實施

| Phase | 名稱 | 工時 | 優先級 |
|-------|------|------|--------|
| 21.1 | Daily E2E Schedule | 15 min | P0 |
| 21.2 | K3s Telegram Report | 2h | P1 |
| 21.3 | Weekly Report | 2-4h | P2 |

### 依賴關係

```
Phase 18 (E2E Hardening) ✅
    └─► Phase 21.1 (Daily E2E)
            └─► Phase 21.2 (K3s Report)
                    └─► Phase 21.3 (Weekly Report)
```

---

## 🔴🔴🔴 前端內網 IP 禁令 (2026-03-30)

> **統帥指令**: CD Pipeline 建置 Web 映像時，`NEXT_PUBLIC_*` 禁止使用內網 IP
> **詳細文件**: `feedback_docker_nextjs_api_url.md` + `feedback_sentry_local_network.md`

### 血的教訓

| 問題 | 後果 | 根因 |
|------|------|------|
| 瀏覽器權限對話框 | UX 極差，API 請求失敗 | `NEXT_PUBLIC_API_URL` 使用內網 IP |
| SSE 連線失敗 | Dashboard 無法更新 | build-time 變數寫死內網 IP |

### CD Pipeline 建置規範

```yaml
# ❌ 絕對禁止
--build-arg NEXT_PUBLIC_API_URL=http://192.168.0.125:32334
--build-arg NEXT_PUBLIC_SENTRY_DSN=http://...@192.168.0.110:9000/2

# ✅ 必須使用公網域名
--build-arg NEXT_PUBLIC_API_URL=https://awoooi.wooo.work
```

### 技術原理

| 變數類型 | 注入時機 | K8s ConfigMap 有效? |
|----------|---------|---------------------|
| `NEXT_PUBLIC_*` | **Build-time** | ❌ 無效 (已打包進 JS) |
| 一般環境變數 | Runtime | ✅ 有效 |

### CD 安全規範

```yaml
# ❌ 禁止硬編碼密碼
echo "0936223270" | sudo -S kubectl ...

# ✅ 使用 Secret
echo "${{ secrets.SUDO_PASSWORD }}" | sudo -S kubectl ...
```

---

## 🔴🔴🔴 部署層級決策鐵律 (2026-03-26)

> **統帥指令**: 所有服務的部署位置 (主機/容器/K3s) 必須經過專業評估並明確確認
> **詳細文件**: `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_deployment_layer_decision.md`

### 血的教訓

| 問題 | 後果 | 根因 |
|------|------|------|
| 容器不見 | 服務中斷，資料遺失 | 沒有持久化 |
| K3s Pod 被移除 | 配置遺失，需重建 | 沒有 Git 版本化 |
| 修復時間長 | 業務影響嚴重 | 沒有備份/回滾機制 |

### 部署新服務前必須

```
1. ❌ 禁止: 直接 docker run / kubectl apply (不經評估)
2. ✅ 必須: 先提出「服務部署評估報告」
3. ✅ 必須: 等待統帥確認部署層級
4. ✅ 必須: 確認備份/回滾機制
```

### 三層部署選項

| 層級 | 適用場景 | 備份方式 | 範例 |
|------|---------|---------|------|
| **主機層** | 核心基礎設施、需持久化 | snapshot / rsync | Harbor, Runner, PostgreSQL |
| **容器層** | 獨立工具、非核心路徑 | compose.yml + volume 備份 | Sentry, Langfuse |
| **K3s 層** | 生產應用、需水平擴展 | Git YAML + PVC 備份 | AWOOOI API/Web/Worker |

### 評估報告必答問題

| # | 問題 | 為什麼重要 |
|---|------|-----------|
| 1 | 服務類型是什麼？ | 無狀態 vs 有狀態決定部署方式 |
| 2 | 需要持久化嗎？ | 決定是否需要 Volume/PVC |
| 3 | 可以接受中斷嗎？ | 決定 HA 需求 |
| 4 | 有備份機制嗎？ | 決定恢復策略 |
| 5 | 配置如何版本化？ | 決定 Git 管理方式 |
| 6 | 如何回滾？ | 決定部署策略 |

---

## 📊 K3s 優化成效 (2026-03-31 驗證)

> **詳細數據**: `memory/project_k3s_optimization_metrics.md`
> **首席架構師評分**: 198/200 (99%) EXCEPTIONAL

| 指標 | 優化前 | 優化後 | 改善 |
|------|--------|--------|------|
| **告警數** | 17 | 0 | **-100%** |
| **Pod 重啟** | 92+/天 | 0/天 | **-100%** |
| **孤立 RS** | 29 | 0 | **-100%** |
| **備份機制** | 0 | 3 層 | **+∞** |
| **穩定運行** | N/A | 48h+ | ✅ |

### 已啟用自動化

| 機制 | 配置 |
|------|------|
| HPA 自動擴展 | API/Web 2-6, Worker 1-3 |
| Kured 自動重啟 | 02:00-04:00 台北 |
| Descheduler | 每 2h CronJob |
| Velero 備份 | 每日 02:00, 153 items |
| etcd rsync | 每 6h → 188 |

---

## 五主機架構 (2026-03-28 K-HA 更新)

| 主機 | IP | 角色 | 部署內容 |
|------|-----|------|----------|
| DevOps | 192.168.0.110 | 金庫 | Harbor:5000, GitHub Runner, Sentry:9000, Langfuse:3100 |
| Security | 192.168.0.112 | 安全 | Kali Scanner:8080 |
| K3s Server #1 | 192.168.0.120 | 叢集 | Control-Plane MASTER (keepalived priority=101) |
| K3s Server #2 | 192.168.0.121 | 叢集 | Control-Plane BACKUP (keepalived priority=100) |
| **VIP** | **192.168.0.125** | **HA 入口** | **K3s API:6443 + NodePort (32334/32335)** |
| AI/Web | 192.168.0.188 | 大腦 | Nginx, **PostgreSQL:5432 (K3s Datastore)**, Redis:6380, Ollama:11434, OpenClaw:8089, SigNoz:3301 |

### 絕對禁令

```
❌ 禁止在 .188 以外部署決策 AI (OpenClaw, Ollama)
❌ 禁止 K3s 直接訪問公網 (必須透過 .188 Nginx)
❌ 禁止繞過 NetworkPolicy 直連 Pod
❌ 禁止使用 UTC 時區 (必須台北 +8，參考 feedback_timezone_taipei.md)
```

---

## 授權分級機制 (Tier 1~3)

### Tier 1: 自主執行 (無需詢問)

| 動作 | 範例 |
|------|------|
| 查看日誌 | `kubectl logs deployment/...` |
| 檢查狀態 | `kubectl get pods`, `docker ps` |
| 語法檢查 | `tsc --noEmit`, `python -m py_compile` |
| 本地建置 | `docker build`, `pnpm build` |

### Tier 2: 提案確認 (Y/n 詢問)

| 動作 | 範例 |
|------|------|
| 重啟服務 | `kubectl rollout restart` |
| 套用配置 | `kubectl apply -f` |
| Git Push | `git push origin main` |
| Nginx Reload | `nginx -s reload` |

### Tier 3: 統帥親核 (必須等待明確授權)

| 動作 | 範例 |
|------|------|
| 刪除資源 | `kubectl delete`, `docker rm -f` |
| 機密操作 | 修改 K8s Secrets, .env 檔案 |
| 生產資料庫 | DROP TABLE, TRUNCATE |
| 強制推送 | `git push --force` |

---

## K3s 部署規範

### Image Tag 規則

```yaml
# ✅ 正確 (精確版本)
image: 192.168.0.110:5000/library/api:a2f7d12-23406265221

# ❌ 絕對禁止
image: 192.168.0.110:5000/library/api:latest
```

### NodePort 分配

| 服務 | NodePort | 說明 |
|------|----------|------|
| awoooi-api | 32334 | FastAPI Backend |
| awoooi-web | 32335 | Next.js Frontend |

### NetworkPolicy 核心原則

```yaml
# Default Deny All (零信任)
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

# 白名單許可
# - 192.168.0.188/32 (Nginx Gateway)
# - 192.168.0.120/32, 192.168.0.121/32 (K3s Nodes, for NodePort SNAT)
# - 10.42.0.0/16 (K3s Pod CIDR)
```

### ADR-013 YAML/K8s 註解規範

**強制場景**: 危險資源、NetworkPolicy、Secret、PV/PVC

```yaml
# 🔴 危險：此 NetworkPolicy 控制 Worker 出站流量
# 📝 用途：限制 API Pod 只能連接 Redis 和 PostgreSQL
# ⚠️ 修改前請確認 Redis/PostgreSQL 連線不受影響
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: awoooi-api-egress
```

**標記圖例**:
- `🔴` 危險操作
- `📝` 用途說明
- `⚠️` 注意事項

### 🔴🔴 NetworkPolicy DNS 規則 (2026-03-26)

> **血的教訓**: DNS 規則標籤錯誤導致 2 天無告警！

```yaml
# ❌ 錯誤: 使用不存在的標籤
- ports:
    - port: 53
  to:
    - podSelector:
        matchLabels:
          environment: prod    # CoreDNS 沒有這個標籤！
          k8s-app: kube-dns
          system: awoooi       # CoreDNS 沒有這個標籤！

# ✅ 正確: 使用 namespace selector
- ports:
    - port: 53
      protocol: UDP
    - port: 53
      protocol: TCP
  to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
```

### 🔴🔴 CoreDNS 上游 DNS 設定

> **血的教訓**: 容器內無法使用 127.0.0.53 (systemd-resolved)

```yaml
# ❌ 錯誤: 使用 /etc/resolv.conf (指向 127.0.0.53)
forward . /etc/resolv.conf

# ✅ 正確: 使用真實 DNS 伺服器
forward . 8.8.8.8 1.1.1.1
```

---

## 🔴🔴 K3s 生產級優化 (ADR-033)

> **2026-03-28**: K3s 深度優化討論，確立生產級配置標準
> **詳細文件**: `memory/feedback_k3s_optimization_rules.md`

### 必須配置清單

| 配置項 | 狀態 | 驗證指令 |
|--------|------|---------|
| **Swap 關閉** | ✅ 必須 | `free -h \| grep Swap` (全 0) |
| **kube-reserved** | ✅ 必須 | `kubectl describe node \| grep Allocatable` |
| **etcd 備份** | ✅ 必須 | `ls /var/lib/rancher/k3s/server/db/snapshots-backup/` |
| **遠端備份 (rsync)** | ✅ 必須 | `ssh 188 "ls /backup/k3s_etcd/"` |
| **PDB** | ✅ 必須 | `kubectl get pdb -n awoooi-prod` |
| **Startup Probe** | ✅ 必須 | `kubectl describe deploy \| grep startupProbe` |

### K3s 重啟前置作業

```bash
# 1. Alertmanager 靜音 (防止誤報)
amtool silence add alertname=NodeDown node=mon duration=1h

# 2. 確認沒有正在部署的任務
gh run list --workflow=cd.yaml --limit 1

# 3. 重啟 + 驗證
sudo systemctl restart k3s && sleep 10 && kubectl get nodes
```

### HA 架構 (方案 B: 外接 PostgreSQL)

```
VIP 192.168.0.125 (keepalived)
        │
   ┌────┴────┐
   │         │
mon(120)  mon1(121)    ─── K3s Server
   │         │
   └────┬────┘
        │
   188 PostgreSQL      ─── 外接 Datastore
```

### 工具選型 (100% 業界主流)

| 用途 | 工具 | 業界採用 |
|------|------|---------|
| VIP 高可用 | keepalived | ⭐⭐⭐⭐⭐ |
| Pod 保護 | PDB | K8s 原生 |
| 啟動檢查 | Startup Probe | K8s 原生 |
| 檔案同步 | rsync | ⭐⭐⭐⭐⭐ |

### keepalived 快速操作 (VIP 192.168.0.125)

```bash
# 檢查 VIP 狀態
ip addr show ens192 | grep 192.168.0.125  # 120
ip addr show ens160 | grep 192.168.0.125  # 121

# 檢查 keepalived 狀態
sudo systemctl status keepalived

# 強制切換 VIP (在 MASTER 上執行)
sudo systemctl stop keepalived  # VIP 漂移到 BACKUP
sudo systemctl start keepalived # 恢復

# 透過 VIP 存取 K3s API
kubectl --server=https://192.168.0.125:6443 get nodes
```

**配置位置**: `/etc/keepalived/keepalived.conf`
**日誌**: `journalctl -u keepalived -f`

---

## 🔴 可觀測性端點配置 (2026-03-28)

> **血的教訓**: CI/CD workflows 指向錯誤的 OTEL 端點 (121:4318)，導致遙測數據無法送達
> **詳細文件**: `memory/feedback_signoz_otel_config.md`

### 正確端點配置

| 服務 | 端點 | 用途 |
|------|------|------|
| **SignOz Web UI** | `http://192.168.0.188:3301` | APM Dashboard |
| **OTEL gRPC** | `192.168.0.188:24317` | K8s/應用程式 Traces |
| **OTEL HTTP** | `http://192.168.0.188:24318` | CI/CD workflows |
| **Sentry** | `http://192.168.0.110:9000` | Error Tracking |
| **Langfuse** | `http://192.168.0.110:3100` | LLM Tracing |

### CI/CD 配置 (ci.yaml / cd.yaml) - 使用 HTTP

```yaml
# ✅ 正確 (CI/CD shell scripts 用 HTTP)
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.0.188:24318

# ❌ 錯誤 (2026-03-28 已修正)
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.0.121:4318
```

### K8s API 配置 (ConfigMap) - 使用 gRPC

```yaml
# ✅ 正確 (Python SDK 用 gRPC exporter)
# 2026-03-31 修正: 24318 → 24317
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.0.188:24317

# ❌ 錯誤 (會導致 StatusCode.UNAVAILABLE)
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.0.188:24318
```

### 驗證指令

```bash
# 檢查 SignOz 容器運行狀態
ssh ollama@192.168.0.188 "docker ps | grep signoz"

# 測試 OTEL 端點
curl -s http://192.168.0.188:24318/v1/traces -X POST | head -c 100
```

---

## 🔴 ArgoCD Metrics 監控 (2026-03-29) 🆕

> **用途**: Prometheus 抓取 ArgoCD 指標以啟用告警

### NodePort 配置

| Service | NodePort | 用途 |
|---------|----------|------|
| `argocd-metrics-nodeport` | 30882 | Application Controller |
| `argocd-server-metrics-nodeport` | 30883 | Server Metrics |

### Prometheus 抓取

```yaml
- job_name: argocd
  static_configs:
    - targets: ["192.168.0.121:30883"]  # Pod 在 mon1
      labels:
        component: server
```

### 驗證指令

```bash
# 測試 metrics 端點
curl -s http://192.168.0.121:30883/metrics | grep argocd_info

# 檢查 Prometheus target
curl -s "http://192.168.0.188:9090/api/v1/targets" | jq '.data.activeTargets[] | select(.labels.job=="argocd")'
```

---

## 🔴 TLS 證書監控 (2026-03-29) 🆕

> **用途**: 預防證書過期導致服務中斷

### 告警規則

| 告警 | 嚴重度 | 條件 |
|------|--------|------|
| `TLSCertExpiringIn30Days` | warning | < 30 天 |
| `TLSCertExpiringIn7Days` | critical | < 7 天 |
| `TLSCertExpired` | critical | 已過期 |
| `TLSProbeFailure` | warning | 探測失敗 |

### Blackbox 配置

```yaml
- job_name: blackbox-https
  params:
    module: [http_2xx_ssl]
  static_configs:
    - targets:
        - https://awoooi.wooo.work
        - https://192.168.0.120:30443  # ArgoCD UI
```

### 驗證指令

```bash
# 檢查證書過期時間
curl -s "http://192.168.0.188:9090/api/v1/query?query=probe_ssl_earliest_cert_expiry" | jq
```

---

## 🔴🔴🔴 告警鏈路 E2E 驗證 (ADR-025)

> **2026-03-26**: URL 路徑錯誤導致 2 天無告警 (`webhook` vs `webhooks`)

### 部署後 Smoke Test (強制)

```bash
# 每次部署後必須執行
curl -s -X POST "$API_URL/api/v1/webhooks/alertmanager" \
  -H 'Content-Type: application/json' \
  -d '{"receiver":"smoke-test","status":"firing","alerts":[...]}' \
  | jq -e '.success == true' || exit 1
```

### URL 路徑規範

| 正確 | 錯誤 |
|-----|------|
| `/api/v1/webhooks/alertmanager` | `/api/v1/webhook/alertmanager` |
| 複數形式 `webhooks` | 單數形式 `webhook` |

### Alertmanager ConfigMap 修改流程

1. 提取 webhook URL
2. curl 測試 URL 可達性
3. 必須收到 200 或 422 (格式錯但端點存在)
4. 驗證失敗 → **阻止 apply**

---

## Turborepo 快取強化協議

### globalDependencies 必須包含

```json
"globalDependencies": [
  ".env", ".env.*", ".env.*local",
  "tsconfig.json", "tsconfig.*.json",
  "pnpm-lock.yaml"  // 🔴 必須！
]
```

### 快取清洗 SOP

```bash
# 1. 清洗本地快取
rm -rf node_modules/.cache/turbo
find . -name ".turbo" -type d -prune -exec rm -rf '{}' +
find . -name ".next" -type d -prune -exec rm -rf '{}' +

# 2. 無快取重建
pnpm turbo run build --force

# 3. 驗證快取生效
pnpm turbo run build  # 應顯示 cache hit

# 4. 驗證構建正確性
cat apps/web/.next/BUILD_ID
```

### 驗證清單

| 檢查項目 | 通過條件 |
|----------|----------|
| `--force` 編譯 | Exit 0 |
| 二次編譯 | cache hit |
| BUILD_ID | 已更新 |
| 部署健康檢查 | 版本正確 |

---

## CI/CD 規範

### GitHub Actions 鐵律

```yaml
# 必須使用 self-hosted runner
runs-on: [self-hosted, harbor, k8s]

# 禁止跳過 hooks
# ❌ --no-verify
# ❌ --no-gpg-sign
```

### Concurrency 策略 (2026-03-26 教訓)

```yaml
concurrency:
  group: cd-${{ github.workflow }}-${{ github.ref }}
  # 🔴 改為等待而非取消，避免 Runner _diag/pages 檔案衝突
  cancel-in-progress: false
```

**原因**: `cancel-in-progress: true` 在 Runner 清理不完全時會造成：
- `_diag/pages/*.log` 檔案衝突
- Session Conflict 錯誤
- set_output 檔案遺失

---

## 🚨 Runner 殭屍進程修復 (2026-03-26 教訓)

> **問題**: CI/CD Workflow 反覆失敗 (set_output file missing / file already exists / Session Conflict)
> **Memory**: `feedback_runner_zombie_process.md`

### 問題症狀

| 錯誤訊息 | 原因 |
|---------|------|
| `Missing file at path: _runner_file_commands/set_output_*` | Runner 目錄權限問題 |
| `File already exists: _diag/pages/*.log` | 殭屍進程未清理 |
| `TaskAgentSessionConflictException` | 多個 Runner.Listener 同時運行 |
| `could not read Username for 'https://github.com'` | Git Auth Token 讀取失敗 |

### 修復流程 (Tier 2 需統帥確認)

```bash
# Step 1: 停止服務
sudo systemctl stop actions.runner.owenhytsai-awoooi.awoooi-110.service
sudo systemctl stop actions.runner.owenhytsai-awoooi.awoooi-110-2.service

# Step 2: 權限校正 (解決 sudo 造成的 root 擁有權)
sudo chown -R wooo:wooo /home/wooo/actions-runner-awoooi
sudo chown -R wooo:wooo /home/wooo/actions-runner-awoooi-2

# Step 3: 殺死殭屍進程
pkill -9 -u wooo -f 'Runner'

# Step 4: 安全洗地（不加 sudo）
rm -rf /home/wooo/actions-runner-awoooi/_work/*
rm -rf /home/wooo/actions-runner-awoooi-2/_work/*
rm -rf /home/wooo/actions-runner-awoooi*/_diag/pages/*

# Step 5: 重啟服務
sudo systemctl start actions.runner.owenhytsai-awoooi.awoooi-110.service
sudo systemctl start actions.runner.owenhytsai-awoooi.awoooi-110-2.service
```

### 診斷指令

```bash
# 檢查殭屍進程
ps aux | grep -E 'Runner.Listener|Runner.Worker' | grep -v grep

# 檢查 Session 衝突日誌
tail -50 ~/actions-runner-awoooi-2/_diag/Runner_*.log | grep -i conflict

# 驗證權限
ls -la ~/actions-runner-awoooi*/_work/_temp/
```

### Workflow 預防措施

```yaml
# 每個 Job 開始時清理暫存目錄
- name: "Clean Runner temp"
  run: |
    RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
    rm -rf "$RUNNER_TEMP"/* "$RUNNER_ROOT/_diag/pages"/* .claude/worktrees 2>/dev/null || true
```

### Telegram 通報 (閉環)

```bash
# 部署完成必須通報
curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
  -d chat_id="${CHAT_ID}" \
  -d text="${EMOJI} AWOOOI ${STATUS}"
```

---

## 強制驗收程序

### 修改 K8s YAML 後:

```bash
# Step 1: 語法驗證
kubectl apply -f k8s/awoooi-prod/*.yaml --dry-run=client

# Step 2: 套用配置
kubectl apply -f k8s/awoooi-prod/*.yaml

# Step 3: 驗證 Rollout
kubectl rollout status deployment/awoooi-api -n awoooi-prod
```

---

## 🚨 NetworkPolicy Pod Selector (2026-03-23 教訓)

> **事故**: `allow-required-egress` 只匹配 `app=awoooi-api`，Worker 使用 `app=awoooi-worker` 被封鎖，無法連 Redis

### 鐵律: 使用共用 Label 而非個別 app

```yaml
# ❌ 錯誤: 只匹配一種 Pod
podSelector:
  matchLabels:
    app: awoooi-api  # Worker 被排除!

# ✅ 正確: 匹配所有 AWOOOI Pods
podSelector:
  matchLabels:
    system: awoooi  # API + Worker + Web 都匹配
```

### 驗證指令

```bash
# 檢查 Pod labels
kubectl get pods -n awoooi-prod --show-labels

# 檢查 NetworkPolicy selector
kubectl get networkpolicy allow-required-egress -n awoooi-prod \
  -o jsonpath='{.spec.podSelector}'
```

---

## 🚨 殭屍消費者群組清理 (2026-03-23 教訓)

> **事故**: Worker 92 次 CrashLoopBackOff 產生 50 個殭屍消費者

### 清理指令

```bash
# 查看消費者群組狀態
ssh ollama@192.168.0.188 \
  "docker exec openclaw-redis redis-cli -n 10 XINFO GROUPS stream:awoooi_signals"

# 警告訊號: consumers > 5 且有大量死掉的 Pod

# 銷毀並重建 (Tier 3 需統帥授權)
ssh ollama@192.168.0.188 \
  "docker exec openclaw-redis redis-cli -n 10 XGROUP DESTROY stream:awoooi_signals awoooi_workers"
```

### 重建後驗證

```bash
# 消費者數量應該 = 實際 Worker Pod 數量
kubectl get pods -n awoooi-prod | grep worker
# consumers 應 = 上述數量
```

---

## 🚨 PostgreSQL 初始化清單 (2026-03-23 教訓)

> **事故**: K8s Secret 有 `DATABASE_URL` 但 PostgreSQL 沒有對應用戶，導致 `InvalidPasswordError`

### 部署新環境前必須確認

| 項目 | 檢查指令 |
|------|---------|
| PostgreSQL 運行中 | `pg_isready -h 192.168.0.188 -p 5432` |
| 用戶存在 | `\du awoooi` |
| 資料庫存在 | `\l awoooi_prod` |
| 網路連通 | 從 K3s Pod 測試連線 |

### 建立用戶/資料庫 (Tier 3)

```bash
# 在 .188 以 postgres 身份執行
sudo -u postgres psql -c "
  CREATE USER awoooi WITH PASSWORD 'xxx';
  CREATE DATABASE awoooi_prod OWNER awoooi;
  GRANT ALL PRIVILEGES ON DATABASE awoooi_prod TO awoooi;
"
```

### K8s Secret 格式

```bash
# 編碼連線字串
echo -n "postgresql+asyncpg://awoooi:密碼@192.168.0.188:5432/awoooi_prod" | base64 -w0

# 更新 Secret
kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' \
  -p='[{"op":"replace","path":"/data/DATABASE_URL","value":"<base64編碼>"}]'
```

---

## 🚨 冪等性與垃圾回收 (2026-03-23 首席架構師指令)

> **教訓**: 50 個殭屍消費者告訴我們 - 容器會死，但它在外部系統留下的垃圾不會自己消失

### 鐵律 1: 重啟前思考殘留狀態

```bash
# ❌ 危險: 直接重啟，不考慮外部狀態
kubectl rollout restart deployment/awoooi-worker -n awoooi-prod

# ✅ 正確: 重啟前先清理外部殘留
# Step 1: 檢查是否有殭屍消費者
ssh ollama@192.168.0.188 "docker exec openclaw-redis redis-cli -n 10 \
  XINFO GROUPS stream:awoooi_signals"

# Step 2: 如果 consumers 數量異常高，先清理
ssh ollama@192.168.0.188 "docker exec openclaw-redis redis-cli -n 10 \
  XGROUP DESTROY stream:awoooi_signals awoooi_workers"

# Step 3: 重啟後，Consumer Group 會自動重建
kubectl rollout restart deployment/awoooi-worker -n awoooi-prod
```

### 鐵律 2: Stateful 服務必須實作清理機制

| 服務類型 | 殘留風險 | 清理機制 |
|---------|---------|---------|
| Redis Consumer | 殭屍消費者 | graceful shutdown + XGROUP DELCONSUMER |
| PostgreSQL Connection | 連線池殘留 | pool.dispose() on shutdown |
| K8s ConfigMap/Secret | 孤立配置 | ownerReferences 綁定 |
| Harbor Image | 舊版本堆積 | retention policy 自動清理 |

### 鐵律 3: 失敗重試的冪等性檢查

```python
# ✅ 正確: 先檢查再創建 (冪等)
async def ensure_consumer_group():
    try:
        await redis.xgroup_create(stream, group, mkstream=True)
    except ResponseError as e:
        if "BUSYGROUP" not in str(e):  # 已存在不是錯誤
            raise

# ❌ 危險: 不檢查直接創建 (非冪等)
await redis.xgroup_create(stream, group)  # 重試會失敗
```

### 檢查清單 (每次重啟/重建前)

| 檢查項目 | 動作 |
|---------|------|
| Redis Consumer Group | `XINFO GROUPS` 確認 consumers 數量合理 |
| DB Connection Pool | 確認無 idle connection 洩漏 |
| K8s ReplicaSet | 確認舊 RS replicas=0 |
| Pending Messages | `XPENDING` 確認無積壓 |

---

## 🚨 部署驗證鐵律 (2026-03-24 重大事故)

> **事故**: 代碼已提交但 CD workflow 連續失敗，正式環境仍運行舊版本，用戶誤以為功能已修復

### 鐵律: 代碼提交 ≠ 部署完成

```bash
# ❌ 禁止: 假設 git push 就是部署完成
git push && echo "已部署"  # 錯！CD 可能失敗

# ✅ 正確: 必須完整驗證
```

### 部署驗證 SOP (每次 Push 後必執行)

```bash
# Step 1: 確認 CD workflow 成功
gh run list --workflow=cd.yaml --limit 1
# 必須顯示 ✅ completed success

# Step 2: 驗證 Pod 運行版本
kubectl get pods -n awoooi-prod -o jsonpath="{.items[*].spec.containers[*].image}"
# 鏡像 tag 必須與最新 commit SHA 匹配

# Step 3: Health check
curl -f https://api.awoooi.wooo.work/api/v1/health
```

### 檢查清單

| 項目 | 驗證方式 |
|------|---------|
| CD workflow | `gh run list` 狀態為 `success` |
| Pod 鏡像版本 | 與 commit SHA 匹配 |
| Health check | `curl -f` 返回 200 |
| 功能驗證 | 在正式環境實際測試 |

### 如果 CD 失敗

1. **立即查看日誌找出原因**
2. **修復並重新觸發**
3. **不要宣稱「已修復」直到 Pod 版本確認更新**

### 違規後果

- 用戶看到的是舊功能
- 問題被誤報為「已解決」
- **信任度嚴重受損**

---

## 🚨 Alertmanager 路由鐵律 (2026-03-25 災難)

> **事故**: Claude 錯誤將 Alertmanager 指向 OpenClaw，導致 Telegram 發送**舊 AIOPS 格式**

### 正確架構

```
Alertmanager → AWOOOI API (K3s) → Telegram (AWOOOI 格式)
```

### 鐵律: Alertmanager 必須指向 AWOOOI API

```yaml
# ✅ 正確配置 (/home/ollama/momo-pro/monitoring/alertmanager.yml)
receivers:
  - name: awoooi
    webhook_configs:
      - url: 'http://192.168.0.120:32334/api/v1/webhooks/alertmanager'
        send_resolved: true
```

### 禁止的配置

```yaml
# ❌ 禁止指向 OpenClaw (使用舊格式)
url: 'http://192.168.0.188:8088/api/v1/webhook/alertmanager'
```

### 職責分工

| 系統 | 職責 | Telegram 權限 |
|------|------|--------------|
| **AWOOOI API** (K3s 32334) | 告警處理 + 格式化 + 發送 | ✅ |
| **OpenClaw** (188:8088) | AI 大腦、LLM 分析 | ❌ 不處理告警發送 |

### 驗證方式

```bash
# 檢查 Alertmanager 配置
ssh ollama@192.168.0.188 "cat /home/ollama/momo-pro/monitoring/alertmanager.yml"
# url 必須包含 192.168.0.120:32334

# Telegram 格式驗證
# AWOOOI 格式: ═══════════════ 🚨 CRITICAL | xxx ═══════════════
# OpenClaw 舊格式: 🤝 [協同警報] xxx  ← 禁止！
```

---

## 🔴🔴🔴 Telegram Secrets 自動注入鐵律 (ADR-035, 2026-03-29)

> **事故**: K8s Secrets 未注入，Telegram 告警長時間失效
> **根因**: kustomization.yaml 說「secrets 由 CI/CD 單獨處理」，但 CD workflow 從未執行注入
> **ADR**: `docs/adr/ADR-035-telegram-alert-chain-enforcement.md`

### 三層防護機制

```
Layer 1: Pre-flight (CD 開始前)
├── 檢查 OPENCLAW_TG_BOT_TOKEN 存在
├── 檢查 OPENCLAW_TG_CHAT_ID 存在
└── 缺少則 exit 1

Layer 2: Deploy (部署時)
├── kubectl patch secret awoooi-secrets
└── 每次部署都同步 Token

Layer 3: Post-Deploy (部署後)
├── 發送測試告警
├── 驗證 API 回應 success: true
└── 失敗則繞過 API 直接告警
```

### CD Workflow 必須包含

```yaml
# 1. Pre-flight 檢查 (在 pre-flight-check job)
- name: "Check Telegram Secrets"
  run: |
    if [ -z "${{ secrets.OPENCLAW_TG_BOT_TOKEN }}" ]; then
      echo "❌ 缺少 OPENCLAW_TG_BOT_TOKEN"
      exit 1
    fi

# 2. Secrets 自動注入 (在 deploy job)
- name: "Sync K8s Secrets (ADR-035)"
  run: |
    kubectl patch secret awoooi-secrets -n awoooi-prod --type='merge' -p="{
      \"stringData\": {
        \"OPENCLAW_TG_BOT_TOKEN\": \"${{ secrets.OPENCLAW_TG_BOT_TOKEN }}\",
        \"OPENCLAW_TG_CHAT_ID\": \"${{ secrets.OPENCLAW_TG_CHAT_ID }}\"
      }
    }"

# 3. E2E 驗證 (在 deploy job)
- name: "Verify Telegram Alert Chain (ADR-035)"
  run: |
    # 發送測試告警並驗證
```

### 禁止的做法

```yaml
# ❌ 禁止: kustomization.yaml 排除 secrets 但 CD 不處理
resources:
  # 03-secrets.yaml 不納入，由 CI/CD 單獨處理  ← 這句話是騙人的！

# ❌ 禁止: secrets.yaml 使用 CHANGE_ME
stringData:
  OPENCLAW_TG_BOT_TOKEN: "CHANGE_ME"  ← 永遠不會有人改它！

# ❌ 禁止: CD 沒有 kubectl patch secret 步驟
```

### 修改 CD Workflow 時必須確認

1. `Sync K8s Secrets (ADR-035)` 步驟存在
2. `Verify Telegram Alert Chain (ADR-035)` 步驟存在
3. 所有 Telegram Secrets 在 Pre-flight 中檢查

---

## 🔗 CI/CD 整合 (Phase 13.1)

> **目標**: Git 事件觸發 AI 審查 + CI 失敗自動診斷

### GitHub Webhook 整合

```
GitHub Events
    │
    ├─ push → AI 代碼審查 (PR Review Agent)
    │
    ├─ workflow_run.conclusion=failure → AI 診斷根因
    │
    └─ pull_request → AI 安全掃描
        ↓
┌─────────────────────────────────┐
│ AWOOOI API                      │
│ POST /api/v1/webhooks/github    │
└─────────────────────────────────┘
        ↓
┌─────────────────────────────────┐
│ OpenClaw AI 分析                │
│ - 代碼品質評估                  │
│ - 資安掃描                      │
│ - 根因診斷                      │
└─────────────────────────────────┘
        ↓
    Telegram 通知
```

### Webhook 端點

```yaml
# 新增端點
POST /api/v1/webhooks/github
Headers:
  - X-GitHub-Event: push | workflow_run | pull_request
  - X-Hub-Signature-256: sha256=xxx
Body:
  - repository, sender, commits, workflow_run...
```

### 驗證 HMAC

```python
import hmac
import hashlib

def verify_github_signature(payload: bytes, signature: str, secret: str) -> bool:
    """驗證 GitHub Webhook 簽名

    Warning:
        WEBHOOK_HMAC_SECRET 必須與 GitHub 設定一致
    """
    expected = "sha256=" + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

---

## 📊 五主機架構更新 (2026-03-25)

| 主機 | IP | 新增服務 |
|------|-----|----------|
| DevOps | 192.168.0.110 | **Sentry Self-Hosted :9000** |
| K3s | 192.168.0.120 | **awoooi-worker** (獨立 Pod) |

### 部署拓撲更新

```
┌─────────────────────────┐
│   192.168.0.110         │
│   DevOps 金庫           │
├─────────────────────────┤
│ [Docker]                │
│  ├─ Harbor :5000        │
│  ├─ GH Runner           │
│  └─ Sentry :9000  ← NEW │
└─────────────────────────┘
```

### K3s awoooi-prod 服務清單

| 服務 | Deployment | NodePort |
|------|------------|----------|
| Frontend | awoooi-web | 32335 |
| Backend | awoooi-api | 32334 |
| **Worker** | awoooi-worker | - |

---

## 👁️ Phase 15 三層觀測架構 (2026-03-26)

> **Phase 15 全部完成**: 15.1 Langfuse + 15.2 Trace Context + 15.3 Deep Linking

### 三系統互連

```
┌─────────────┐     trace_id      ┌─────────────┐     trace_id      ┌─────────────┐
│   Sentry    │ ◄────────────────► │   SignOz    │ ◄────────────────► │  Langfuse   │
│   Errors    │                    │   Traces    │                    │   LLMOps    │
│ :9000 (.110)│                    │ :3301 (.188)│                    │ :3100 (.110)│
└─────────────┘                    └─────────────┘                    └─────────────┘
```

### Deep Linking URL 格式

| 系統 | URL 格式 |
|------|----------|
| SignOz Trace | `http://192.168.0.188:3301/trace/{trace_id}` |
| Langfuse Trace | `http://192.168.0.110:3100/project/awoooi-openclaw/traces/{id}` |
| Sentry Issue | `http://192.168.0.110:9000/organizations/sentry/issues/{id}/` |

### Trace Context 傳遞 (Phase 15.2)

```
webhooks.py (Producer)
    └── get_trace_context() → {trace_id, span_id}
    └── XADD stream:signals {..., _trace_id, _span_id}
         │
         ▼
signal_worker.py (Consumer)
    └── restore_trace_context() → W3C traceparent 還原
    └── 建立子 Span 繼承原始 Trace
```

### 使用方式

```python
from src.core.deep_linking import DeepLinking

# 取得 SignOz URL
url = DeepLinking.signoz_trace_url(trace_id)

# 取得所有連結
links = DeepLinking.get_all_links(
    otel_trace_id="xxx",
    langfuse_trace_id="yyy",
    sentry_issue_id="123"
)
```

---

## 參考文檔

- `k8s/awoooi-prod/`: K8s Manifests
- `.github/workflows/cd.yaml`: CD Pipeline
- `docs/HARD_RULES.md`: 絕對禁止規則
- `reference_four_hosts.md`: 主機架構參考
- `feedback_alertmanager_awoooi_flow.md`: **🔴 Alertmanager 正確流程**
- `memory/project_phase13_enterprise_aiops.md`: **Phase 13 規劃**
- `memory/project_phase15_langfuse.md`: **📊 Phase 15 全部完成**
- `memory/project_phase17_tech_debt.md`: **🔧 Phase 17 技術債**
- `src/core/deep_linking.py`: **👁️ Deep Linking URL 生成器**
