# AWOOOI 主機角色與負載平衡評估

> Version: v1.1
> Last updated: 2026-06-13 Asia/Taipei
> Scope: 110 / 120 / 121 / 188；112 是 Kali，不納入本輪服務搬遷。

---

## 1. 結論

120 / 121 目前是 K3s control-plane AA，不是傳統一主一從 AS。

但這只代表 Kubernetes 控制面雙節點可用，不代表業務 workload 已經 AA。2026-06-13 00:13 live check 顯示：

| 項目 | 120 `mon` | 121 `mon1` | 判讀 |
|------|-----------|------------|------|
| K3s unit | `k3s active`、`k3s-agent inactive` | `k3s active`、`k3s-agent inactive` | 兩台都是 server / control-plane |
| Kubernetes role | `control-plane` | `control-plane` | 控制面 AA |
| Taint / schedulable | 無 taint、`Unschedulable=false` | 無 taint、`Unschedulable=false` | 120 可以排程 |
| CPU / memory | CPU 約 `2%`、Memory 約 `8%` | CPU 約 `3%`、Memory 約 `20%` | 兩台都有餘裕，121 較高 |
| Pod 實際落點 | `awoooi-api` 2 顆、`awoooi-web` 1 顆、`awoooi-worker` 1 顆 | `awoooi-web` 1 顆、auto-repair canary、部分 CronJob 歷史 Pod | API 仍集中在 120，Web 已分散 |

專業判定：

- `控制面`：AA。
- `業務 workload`：Web 已分散，但 API 兩個 replica 都在 120，不算完整 workload AA。
- `資料層`：不可直接視為 AA；若仍依賴 188 PostgreSQL 或 local-path PVC，120/121 只是承載層，不是資料高可用層。
- `下一步`：AWOOOI API / Web / Worker topology spread 已進 Gitea main 並由 ArgoCD 套用；後續才能規劃把 110 / 188 無狀態服務搬進 K3s，不先搬核心資料庫。

---

## 2. 現況負載與角色

### 2.1 110

110 是目前最混合、最需要拆角色的主機。

| 類別 | Live evidence | 負載/風險 |
|------|---------------|-----------|
| Public gateway / edge | `80/443`、多個 public route upstream | 改 Nginx / firewall 影響面最大 |
| Registry / image supply | Harbor `5000`、registry / portal / jobservice / db / redis | K3s image pull 依賴它，不適合先搬 |
| Source / CI | Gitea `3001`、runner / build 相關目錄 | build / dump 期間會推高 CPU |
| Observability / incident tools | Prometheus、Alertmanager、Sentry / Snuba / Kafka / ClickHouse | 容器多、啟動順序複雜 |
| Product services | StockPlatform v2、agent-bounty、Tsenyang、VTuber、Bitan 等 | 多數可逐步轉 K3s，但資料層需先分離 |
| Backup control | `/backup/scripts/*`、每日備份與 offsite sync | 本輪恢復完成前不可搬 |

2026-06-12 15:19 live check：110 RAM `62Gi`、root disk `89%`、load 約 `2.79 / 3.01 / 3.25`；正在跑 Gitea dump 時 top CPU 包含 `gitea dump`、ClickHouse、Docker、Kafka。

2026-06-13 00:14 live check：110 `systemctl is-system-running=running`、failed units `0`、load 約 `4.45 / 3.28 / 2.89`、Swap `166Mi/7.8GiB`；top CPU 主要是 Gitea、ClickHouse、Kafka、Docker 與一個 `prisma db push` 工作。判讀：110 不在服務故障狀態，但仍是高混合負載主機，第一批應先移走無狀態 app 層與 build/preview 類工作。

### 2.2 188

188 是資料、AI、觀測與自動化中心。

| 類別 | Live evidence | 負載/風險 |
|------|---------------|-----------|
| Data layer | PostgreSQL `5432`、Redis `6379`、momo DB、product DBs | 多服務依賴，不可直接熱搬 |
| momo | `momo-scheduler`、`momo-pro-system`、Telegram bot | 有資料一致性與匯入流程風險 |
| AI / workflow | OpenClaw、LiteLLM、Open WebUI、n8n | 可拆，但需 provider route / secrets / queue 邊界 |
| Observability | SignOz、ClickHouse、Zookeeper、Grafana、Prometheus、Alertmanager、Loki | stateful 且啟動重，不適合第一批 |
| Product services | VibeWork、Tsenyang、agent-bounty 等 | 可按 stateless / stateful 拆分 |

2026-06-12 15:19 live check：188 RAM `62Gi`、root disk `52%`、Swap 約 `2Gi/8Gi`；top CPU 包含 `momo-scheduler`、Next.js、ClickHouse。

2026-06-13 00:14 live check：188 `systemctl is-system-running=degraded`，failed units 是 `certbot.service` 與 `snap.certbot.renew.service`；cold-start 仍確認 PostgreSQL、Redis、momo、SignOz、Docker services 綠。判讀：這是 TLS 自動續期治理債，不是本輪 K3s workload balancing 的業務服務阻斷。

### 2.3 120 / 121

120 / 121 是 K3s control-plane pair，目前最適合承接標準化容器 workload。

| 類別 | 目前狀態 | 判讀 |
|------|----------|------|
| K3s control-plane | 120 / 121 都 `Ready control-plane` | 控制面 AA |
| Workload placement | API / Web 已分散在 120 / 121；Worker 單副本在 121 | AWOOOI core workload 已完成第一階段均衡 |
| 記憶體餘裕 | 120 約 `29Gi` available；121 約 `27Gi` available | 有承接空間 |
| Storage | root 各約 490G，本輪 120 剛做 root fsck 後恢復 | 不應立刻承接核心 stateful |
| Network | VIP `192.168.0.125` / NodePort 已用於 AWOOOI | 適合承接 ingress 後方 app |

---

## 3. 可以搬、暫緩搬、禁止直接搬

### 3.1 第一批：可優先搬到 K3s 的服務

條件：無狀態、可重新 build、已有容器 image、資料庫可先留在原 host、health endpoint 清楚。

| 優先序 | 候選 | 來源主機 | 建議策略 | 原因 |
|--------|------|----------|----------|------|
| P1-A | Tsenyang website | 110 / 188 都有部署痕跡 | 先做單一 canonical deployment，再進 K3s | 靜態/Next 類服務，資料風險低 |
| P1-A | VTuber web/admin/api | 110 | app 先搬；DB 留 110，備份通過後再評估 DB | 可降低 110 Docker container 數 |
| P1-A | Bitan app / PostgREST edge | 110 | app edge 先搬；PostgreSQL 暫留 110 | public route 可用 K3s NodePort / ingress |
| P1-B | StockPlatform web/admin/api/edge | 110 | app 層先搬；Postgres/Redis 暫留 110 | 目前 110 服務多，拆 app 有效 |
| P1-B | agent-bounty web / worker | 110 / 188 | 先統一來源與 port，再搬 app；DB/Redis 暫留 | 兩台都有部署痕跡，需先消除重複角色 |
| P1-B | 輕量內部工具 / preview | 188 | 容器標準化後搬 | 對核心資料面影響小 |

第一批完成標準：

- 每個 deployment 有 `requests` / `limits`。
- replicas >= 2 的服務必須有 pod anti-affinity 或 topology spread。
- 120 / 121 上 Pod 落點 max skew <= 1。
- public route、internal VIP、health endpoint、Alertmanager visibility、備份矩陣都通過。

### 3.2 第二批：可以規劃，但不能在恢復期直接搬

| 候選 | 來源主機 | 需要先完成 |
|------|----------|------------|
| Product-specific PostgreSQL / Redis | 110 / 188 | backup / restore drill、RPO/RTO、PVC 策略、回滾路徑 |
| Langfuse / analytics 類服務 | 110 | DB / object storage / secret route 梳理 |
| n8n / workflow | 188 | workflow export、credential escrow evidence、callback URL 切換 |
| Open WebUI / AI 工具 | 188 | model provider route、volume backup、外部 endpoint fallback |
| Docker registry mirror / cache | 110 / 188 | image retention、auth、K3s pull secret、Harbor 分工 |

### 3.3 不建議直接搬的核心

| 對象 | 原因 | 正確路徑 |
|------|------|----------|
| Harbor | K3s image supply 依賴它；搬壞會反過來讓 K3s deploy 失敗 | 先做 replica / mirror / restore 演練 |
| Gitea | source-of-truth / CI 影響大，dump / runner / SSH / webhook 邊界多 | 先獨立備份與 restore drill |
| Sentry / Snuba / Kafka / ClickHouse | stateful、容器多、啟動順序敏感 | 先拆 consumer load，再評估專用 observability host |
| SignOz ClickHouse / Zookeeper | stateful 且重 IO | 需要獨立 storage 與 retention plan |
| AWOOOI / momo 主 PostgreSQL | 多系統依賴且資料一致性要求高 | 先建立 replica / tested restore，不做熱搬 |
| `/backup` 控制面 | 本輪恢復與 DR 證據中心 | 等 DR scorecard complete 後另開遷移案 |

---

## 4. 新資料庫能不能放 120 / 121

可以，但要分等級。

| 等級 | 是否可放 | 條件 |
|------|----------|------|
| Dev / staging / demo DB | 可以 | 明確標示非 production；每日備份；可接受重建 |
| 新產品的低風險 production DB | 可以試點 | 必須先完成 backup、restore drill、PVC 保留策略、監控與 offsite sync |
| 核心 production DB | 暫不建議 | 目前 120/121 只有兩節點 control-plane，若使用 local-path PVC，Pod 可重建不代表資料可 failover |
| 高可用 production DB | 需要另案 | 需要 PostgreSQL operator、replica、storage replication 或外部 DB HA，不是單純 `kubectl apply postgres` |

判定原則：

```text
Kubernetes 可以重排 Pod，不等於資料會跟著安全重排。
沒有 restore drill 的 DB，不算可遷移完成。
沒有 storage replication 的 stateful workload，不算 AA。
```

---

## 5. 120 / 121 Workload 均衡修正

目前不是 taint 或 cordon 問題，因為 120 / 121 都 schedulable。主要原因是 120 長時間不可用後，既有 Running Pod 不會自動搬回來；live deployment 也尚未帶 `topologySpreadConstraints`，所以 scheduler 只有 soft anti-affinity，仍可能把同一服務副本放在同一台。

### 5.1 立即可做的只讀檢查

```bash
ssh wooo@192.168.0.120 '
sudo kubectl get nodes -o wide
sudo kubectl get pods -A -o wide
sudo kubectl top nodes
sudo kubectl top pods -A --sort-by=cpu
'
```

### 5.2 受控修正順序

1. 等 P0 備份鏈、offsite verifier、cold-start scorecard 通過。
2. 在 AWOOOI `awoooi-prod` API / Web / Worker 補 `topologySpreadConstraints`。
3. replicas >= 2 的服務使用 `topologyKey: kubernetes.io/hostname`，目標 120 / 121 分散。
4. 單副本服務先不強制搬，除非有明確重啟窗口。
5. 以 rolling restart 或下一次 deploy 觸發重排，不用暴力 delete 全部 Pod。
6. 觀察 `kubectl get pods -A -o wide`、public route、Alertmanager、cold-start scorecard。

### 5.3 2026-06-13 Gitea / ArgoCD Live 狀態

已在 Gitea main commit `acaae999 fix(k8s): add prod workload topology spread` 補上：

- `k8s/awoooi-prod/05-deployment-web.yaml`
- `k8s/awoooi-prod/06-deployment-api.yaml`
- `k8s/awoooi-prod/08-deployment-worker.yaml`

2026-06-13 12:43 live refresh 發現 `ScheduleAnyway` 仍可能讓 `awoooi-api` 兩顆副本合法同落 120；cold-start 仍綠，但不能宣稱 API workload balanced。

修正策略改為 `maxSkew: 1`、`minDomains: 2`、`topologyKey: kubernetes.io/hostname`、`whenUnsatisfiable: DoNotSchedule`。120 / 121 都 Ready 時，API / Web replicas=2 必須跨節點；Worker 單副本仍可跑，未來擴副本時也必須分散。

2026-06-13 13:00 追加判讀：只把 spread 改硬仍不夠。API rollout 時舊 Pod 仍在 120 terminating，新 ReplicaSet 兩顆都排到 121，scheduler 當下因舊 Pod 尚在而視為不違反 skew；舊 Pod 消失後 live API 反而變成 121 集中。因此 API / Web 也必須使用 `maxSurge: 0`、`maxUnavailable: 1`，讓 rollout 先釋出一格再排新 Pod，避免新舊 ReplicaSet 交錯造成最終偏斜。

2026-06-13 12:59 live verification：ArgoCD revision `60f653a0` synced；API / Web 均 `ready=2/2`，strategy `maxSurge=0 / maxUnavailable=1`，spread `minDomains=2 / DoNotSchedule`。`awoooi-api` pod 分別在 `mon` / `mon1`，`awoooi-web` pod 分別在 `mon` / `mon1`。完整 cold-start rerun `PASS=83 WARN=0 BLOCKED=0`。

驗證：

- `kubectl kustomize k8s/awoooi-prod` 可渲染三個 `topologySpreadConstraints`。
- `git diff --check` 通過。
- ArgoCD `awoooi-prod` 已同步到 revision `acaae99986aee2e1f5630984981ccb0f2b676bb8`，operation `Succeeded`。
- Live deployment 的 API / Web / Worker 均需有 `topologySpreadConstraints`。
- `awoooi-api` replicas=2：120 / 121 各一顆，否則是 `SERVICE_OK_PLACEMENT_DRIFT`。
- `awoooi-web` replicas=2：120 / 121 各一顆。
- `awoooi-worker` replicas=1：可在任一節點；若 replicas > 1，必須跨 120 / 121 分散。
- Rollout 後冷啟動：2026-06-13 00:34 final scorecard `PASS=83 WARN=0 BLOCKED=0`。00:33 曾因 110 `known_hosts` 對 120 / 188 過期而短暫 blocked，已在備份後刷新 host key trust。

補充：rolling update 當下 API 新 Pod 先集中到 121，因為舊 Pod 正在 120 Terminating；後續以單顆 stateless API Pod 受控重排，最終達到 120 / 121 各一顆。沒有執行全 namespace delete、Docker restart、Nginx reload 或 firewall 變更。

建議的分散目標：

| Workload | 目標 |
|----------|------|
| `awoooi-api` replicas=2 | 120 / 121 各一個，`whenUnsatisfiable=DoNotSchedule` |
| `awoooi-web` replicas=2 | 120 / 121 各一個，`whenUnsatisfiable=DoNotSchedule` |
| `awoooi-worker` replicas=1 | 可留任一節點；多副本時 `whenUnsatisfiable=DoNotSchedule` |
| ArgoCD / Velero / kube-system 單副本 | 不急搬；先保持穩定 |
| DaemonSet | 120 / 121 各一個，已符合 |

---

## 6. 遷移階段與完成度

| 階段 | 狀態 | 完成度 | 說明 |
|------|------|--------|------|
| A. 角色判定 | DONE | 100% | 120/121 已確認是 K3s control-plane AA；workload 尚未均衡 |
| B. 服務盤點 | DONE | 100% | 110 / 188 主要服務、port、container 類型已盤點 |
| C. 遷移分層 | DONE | 100% | 分為可優先搬、可規劃、禁止直接搬 |
| D. 實際 workload 均衡 | LIVE_VERIFIED | 100% | API / Web hard topology spread 與 no-surge rollout 已進 Gitea main / ArgoCD live；API/Web 皆 120 / 121 各一顆 |
| E. 第一批無狀態服務遷移 | NOT_STARTED | 0% | 需另開變更窗口與 rollback plan |
| F. Stateful 遷移 | BLOCKED_BY_DESIGN | 0% | 需要 storage / backup / restore / HA 設計 |

目前總結：評估完成 `100%`；AWOOOI core workload 均衡實作 `100%`；第一批服務遷移實作 `0%`。下一階段仍只應先搬無狀態 app 層，不搬核心資料服務。

---

## 7. 建議優先順序

| 優先序 | 工作 | 目的 |
|--------|------|------|
| P0 | 完成 120 回復後備份鏈與 cold-start scorecard | 先證明平台回到可恢復基線 |
| P1 | 保持 AWOOOI prod topology spread，觀察後續 deploy 是否維持 120 / 121 分散 | 防止 API/Web 再度單邊集中 |
| P1 | 建立「第一批無狀態服務」K3s 遷移清單與 rollback plan | 安全釋放 110 Docker 壓力 |
| P2 | 先搬 app 層，不搬 DB；DB 仍走既有備份與連線 | 降低變更風險 |
| P2 | 對新 DB 建立 backup / restore drill 模板 | 讓未來新資料庫可安全落 K3s |
| P3 | 評估 Harbor / Gitea / Sentry / SignOz 的專用節點或 HA 架構 | 不把重 stateful 混進恢復期 |

---

## 8. 禁止事項

- 不在 P0 恢復鏈未完成時搬資料庫。
- 不用 `kubectl delete pod --all` 當成重平衡手段。
- 不把 local-path PVC 的 Pod 重建誤認為資料高可用。
- 不把 Harbor / Gitea / Sentry / SignOz 直接丟進 120 / 121 來「平均負載」。
- 不在未補 backup / restore drill 前宣稱新 DB production ready。
- 不因為 120 / 121 是 control-plane AA，就宣稱整體服務 AA。
