Sentry Integration (補強 SignOz): - Add @sentry/nextjs for frontend error tracking + session replay - Add sentry-sdk[fastapi] for backend error tracking - Create sentry.client/server/edge.config.ts - Integrate with next.config.js + instrumentation.ts - Add Sentry exception capture in FastAPI error handler - Create deployment scripts for Self-Hosted @ 192.168.0.110 CI/CD Fixes: - Fix F821 Undefined name 'Field' in incidents.py - Add NEXT_PUBLIC_API_URL env var to CI build step - Add build-arg to Docker build verification E2E Test Improvements: - Fix strict mode violations in dashboard-acceptance tests - Add timeout increase for Phase 4 demo tests - Make tests more resilient to UI variations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
10 KiB
10 KiB
Skill 04: AWOOOI DevOps Commander
基礎設施指揮官
管轄範圍: Docker, K3s, Nginx, NetworkPolicy, CI/CD 觸發條件: 修改
k8s/,Dockerfile,.github/workflows/,nginx
四主機架構 (絕對邊界)
| 主機 | IP | 角色 | 部署內容 |
|---|---|---|---|
| DevOps | 192.168.0.110 | 金庫 | Harbor Registry, GitHub Runner |
| Security | 192.168.0.112 | 安全 | Kali Scanner |
| K3s Master | 192.168.0.120 | 叢集 | K3s Control Plane |
| K3s Worker | 192.168.0.121 | 叢集 | K3s Worker Node |
| AI/Web | 192.168.0.188 | 大腦 | Nginx, PostgreSQL, Redis, Ollama, SigNoz |
絕對禁令
❌ 禁止在 .188 以外部署決策 AI (OpenClaw, Ollama)
❌ 禁止 K3s 直接訪問公網 (必須透過 .188 Nginx)
❌ 禁止繞過 NetworkPolicy 直連 Pod
授權分級機制 (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 規則
# ✅ 正確 (精確版本)
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 核心原則
# 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
# 🔴 危險:此 NetworkPolicy 控制 Worker 出站流量
# 📝 用途:限制 API Pod 只能連接 Redis 和 PostgreSQL
# ⚠️ 修改前請確認 Redis/PostgreSQL 連線不受影響
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: awoooi-api-egress
標記圖例:
🔴危險操作📝用途說明⚠️注意事項
Turborepo 快取強化協議
globalDependencies 必須包含
"globalDependencies": [
".env", ".env.*", ".env.*local",
"tsconfig.json", "tsconfig.*.json",
"pnpm-lock.yaml" // 🔴 必須!
]
快取清洗 SOP
# 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 鐵律
# 必須使用 self-hosted runner
runs-on: [self-hosted, harbor, k8s]
# 禁止跳過 hooks
# ❌ --no-verify
# ❌ --no-gpg-sign
Telegram 通報 (閉環)
# 部署完成必須通報
curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
-d chat_id="${CHAT_ID}" \
-d text="${EMOJI} AWOOOI ${STATUS}"
強制驗收程序
修改 K8s YAML 後:
# 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
# ❌ 錯誤: 只匹配一種 Pod
podSelector:
matchLabels:
app: awoooi-api # Worker 被排除!
# ✅ 正確: 匹配所有 AWOOOI Pods
podSelector:
matchLabels:
system: awoooi # API + Worker + Web 都匹配
驗證指令
# 檢查 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 個殭屍消費者
清理指令
# 查看消費者群組狀態
ssh ollama@192.168.0.188 \
"docker exec clawbot-redis redis-cli -n 10 XINFO GROUPS stream:awoooi_signals"
# 警告訊號: consumers > 5 且有大量死掉的 Pod
# 銷毀並重建 (Tier 3 需統帥授權)
ssh ollama@192.168.0.188 \
"docker exec clawbot-redis redis-cli -n 10 XGROUP DESTROY stream:awoooi_signals awoooi_workers"
重建後驗證
# 消費者數量應該 = 實際 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)
# 在 .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 格式
# 編碼連線字串
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: 重啟前思考殘留狀態
# ❌ 危險: 直接重啟,不考慮外部狀態
kubectl rollout restart deployment/awoooi-worker -n awoooi-prod
# ✅ 正確: 重啟前先清理外部殘留
# Step 1: 檢查是否有殭屍消費者
ssh ollama@192.168.0.188 "docker exec clawbot-redis redis-cli -n 10 \
XINFO GROUPS stream:awoooi_signals"
# Step 2: 如果 consumers 數量異常高,先清理
ssh ollama@192.168.0.188 "docker exec clawbot-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: 失敗重試的冪等性檢查
# ✅ 正確: 先檢查再創建 (冪等)
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 連續失敗,正式環境仍運行舊版本,用戶誤以為功能已修復
鐵律: 代碼提交 ≠ 部署完成
# ❌ 禁止: 假設 git push 就是部署完成
git push && echo "已部署" # 錯!CD 可能失敗
# ✅ 正確: 必須完整驗證
部署驗證 SOP (每次 Push 後必執行)
# 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 失敗
- 立即查看日誌找出原因
- 修復並重新觸發
- 不要宣稱「已修復」直到 Pod 版本確認更新
違規後果
- 用戶看到的是舊功能
- 問題被誤報為「已解決」
- 信任度嚴重受損
參考文檔
k8s/awoooi-prod/: K8s Manifests.github/workflows/cd.yaml: CD Pipelinedocs/HARD_RULES.md: 絕對禁止規則reference_four_hosts.md: 主機架構參考