Files
awoooi/.agents/skills/04-awoooi-devops-commander.md
OG T 9bff46a1b0 feat: integrate Sentry + fix CI/CD issues
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>
2026-03-24 15:19:52 +08:00

10 KiB
Raw Blame History

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-apiWorker 使用 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 失敗

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

違規後果

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

參考文檔

  • k8s/awoooi-prod/: K8s Manifests
  • .github/workflows/cd.yaml: CD Pipeline
  • docs/HARD_RULES.md: 絕對禁止規則
  • reference_four_hosts.md: 主機架構參考