FR-184 · 需求討論稿 v1 · 2026-11-07(D1~D8 已裁,定案在 design.md)
急件:POC 階段先用一台 GCE VM 跑既有安裝版 stack,程式零改動;基礎設施用 Terraform 描述,換雲只換一個 module;K8s 延後到有 SLA 需求
決策者 11-07 定調:急著上雲端、自己營運、POC 階段、一套就好、GCP 台灣機房;規格本稿建議 4 核 16 GB,決策者裁先 8 GB(D3)。本稿把三件事講清楚:為什麼 VM 不是 K8s、每月要花多少(全部實查,不是記憶值)、十個服務在 VM 上能不能全起(全部能,已對照 189 POC 實測)。探脈發現一個會改設定方向的事:DEPLOYMENT_MODE=saas 現在不能直接用(三個設定頁會消失、更新中心不認照),雲端這台第一期要跑 host 模式,見 D2。
整案一句話:在 GCP 台灣機房開一台 VM,用 Terraform 建好網路、磁碟、固定 IP、備份、祕密容器,然後在上面跑主產品既有的 install.sh,等於多了一台住在雲端的 POC;出版管線的「直接部署」多一台目標主機,之後升級跟 189 一樣按一個鈕。
| 階段 | 做什麼 | 產出 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| 批 1 環境 | guidant-ai-infra repo 登記進工作坊;Terraform 平台 module 寫完,plan 看得到要建的全部資源 |
modules/platform-gcp/*.tf、envs/gcp-prod/main.tf |
首腦跑 terraform plan,列出 VM、磁碟、IP、防火牆、bucket、secret 容器,沒有紅字 |
| 批 2 建機 | 首腦 terraform apply;Cloudflare 子網域指到固定 IP |
GCP 上一台 VM、一個對外 IP、DNS 一筆 | 瀏覽器開 https://<網域> 看到 nginx 的 502(服務還沒裝,但 TLS 與 DNS 通) |
| 批 3 裝機 | 在 VM 上跑既有安裝包、填 .env、License 線上開通、SMTP 填 Brevo |
可登入的系統 | 決策者登入、建專案、上傳證據、按 AI 分類、匯出 SSP pdf、收到一封系統信 |
| 批 3 管線 | FR-173 直接部署加目標 gcp-prod |
.gitlab/ci/deploy.yml 多一個 job |
GitLab Run pipeline 選 gcp-prod,約 10 分鐘後雲端版本號變新 |
| 批 4 營運 | 每日快照、pg_dump 推 GCS、記憶體與磁碟告警 | 排程與告警規則 | 隔天 GCS 裡看到一個 dump 檔;手動把記憶體灌滿收到一封告警信 |
| 批 4 驗收 | 全功能跑一輪 | 驗收清單勾完 | 清單見 §7 |
依賴:批 1 前決策者要做完的事已全部完成(GCP 專案 guidant-ai-cloud-514012、帳單、API、state bucket,11-07 驗過)。批 2 要決策者給 Cloudflare 的 API 權杖與網域名稱。批 3 要 Brevo 帳號與 AI 金鑰。
主系統到今天只有落地版:客戶自己的主機裝安裝包。決策者 11-07 要自己營運一套雲端版給客戶試用,POC 階段,而且急。
現況對這件事有利的地方比想像多:
install.sh --upgrade,雲端這台只是第四台。不利的只有一件:程式裡有「跑在一台機器上」的假設(機器指紋、主機撿升級單、排程跟著 api),這些在 VM 上完全不是問題,只在 K8s 才會撞到。所以 VM 是急件的正解,K8s 留到有 SLA 需求。
兩層切開,換雲的縫在中間:
| 層 | 內容 | 換雲時 |
|---|---|---|
平台層(Terraform,modules/platform-gcp/) |
VPC、防火牆、VM、資料碟、固定 IP、快照排程、GCS 備份 bucket、Secret Manager 容器、開機腳本(swap、docker、掛碟) | 重寫這個 module |
| 介面(module outputs,固定欄位) | public_ip、instance_name、ssh_user、backup_bucket、data_mount_path |
不變 |
| 應用層 | 主產品安裝包+install.sh,一行不改 |
不變 |
Terraform repo guidant-ai-infra 骨架已建(commit 7ec4f3a、78e8255):.gitignore、CLAUDE.md(runner 只做到 plan,apply 由決策者或首腦)、envs/gcp-prod/ 的 provider 與 GCS 後端、terraform init 與 validate 已在新專案上通過。
抓法:Google 定價頁是前端渲染,用無視窗瀏覽器切到台灣區抓;AWS 抓定價頁背後的官方 JSON;DigitalOcean 與 Hetzner 官方頁純文字。抓取日 2026-11-07,美元。
| 項目 | GCP 台灣 | AWS 台北 | AWS 東京 |
|---|---|---|---|
| 4 核 16 GB 每小時 | e2-standard-4 0.1552 | t3.xlarge 0.1958 | t3.xlarge 0.2176 |
| 4 核 32 GB 每小時 | e2-highmem-4 0.2093 | — | — |
| 磁碟每 GB 月 | pd-balanced 0.10 | gp3 0.0864 | gp3 0.096 |
| 快照每 GB 月 | 0.05 | 0.045 | 0.05 |
| 固定 IP 每小時 | 0.005 | 0.005 | 0.005 |
| 出站流量每 GB | 亞洲 0.12 | 每月 100 GB 免費 | 同左 |
| 一年承諾 | 最多 63 折 | t3.xlarge 0.137 | 同左 |
DigitalOcean 與 Hetzner 實查後退出候選:前者沒有 4 核 16 GB 的 Basic 方案(要 General Purpose 月費 126,不便宜);後者新加坡節點沒有 4 核 16 GB、多個方案標缺貨。
144GCP 台灣,隨需
100GCP 台灣,一年承諾後
166AWS 台北,隨需
184GCP 台灣 32 GB,隨需
16 GB 對 32 GB 差 40 美元(承諾後差 25)。決策者已選 16 GB。
| VM 16 GB | GKE Autopilot 正統(受管 DB/Redis) | GKE 高可用 | |
|---|---|---|---|
| 每月隨需 | 144 | 約 290(pod 申請量縮到實測峰值後) | 約 770 |
| 程式改動 | 零 | 八處拿掉單機假設 | 同左 |
| 上線 | 3 到 5 天 | 3 到 4 週 | 3 到 4 週 |
K8s 貴在兩件結構性的事:Autopilot 每個 pod 按申請量預留、不能像單機那樣共用記憶體;DB、Redis、負載平衡器在單機裡是免費搭便車,K8s 各自一張帳單。決策者 11-07 裁「POC 走 VM」。
結論:全部能,不需要 GPU、不需要巢狀 docker、不需要特權容器。
| 服務 | 怎麼跑 | 189 現值 | 190 現值 | compose 上限 |
|---|---|---|---|---|
| api | BE image,RUN_MODE=api |
4.0 GB | 1.9 GB | 6 GB |
| worker | 同 image | 2.2 GB | 1.6 GB | 3 GB |
| socketio | 同 image | 1.8 GB | 1.6 GB | 3 GB |
| sandbox AI 分類器 | evidence-classifier image,Prompt Guard ONNX 模型 1.1 GB 跑 CPU;真正的 LLM 分類打 api.anthropic.com | 0.1 GB 閒置 | 同左 | 4 GB |
| 弱點掃描、SBOM、CINC | 在 api 容器內 | 含在 api | ||
| LibreOffice 轉檔、PDF 匯出 | api 容器內建 | 含在 api | ||
| db、redis、seaweedfs、bugsink | 各自容器 | 0.9+0.01+0.7+0.4 GB | bugsink 1 GB | |
| fe nginx | 對外 80/443 | 0.01 GB | ||
| 合計 | 約 10 GB | 約 6 GB | 硬上限加總 17 GB |
189 是跑六天、有舊資料的狀態;4 核 Xeon 平均負載 0.68,CPU 很閒。磁碟方面 189 用 135 GB,其中 30 GB 是歷史 image、6.6 GB 升級包暫存、9 GB 證據檔,資料庫只有 631 MB;雲端從乾淨安裝起算,100 GB 夠 POC。
16 GB 的風險講清楚:compose 硬上限加總 17 GB,只有「AI 分類批次、弱點掃描、LibreOffice 匯出同時發生」才碰到。對策是 8 GB swap 當保險絲(189 也有 4 GB swap)加「記憶體超過 85% 或 swap 動用就告警」,告警響了 Terraform 改一個變數升 32 GB,關機重開兩分鐘。
D1 — 雲端這台的部署形態:第一期跑 host,不跑 saas
探脈發現 DEPLOYMENT_MODE=saas 現在有兩個洞:①全公司共用設定(SMTP/LDAP/登入政策)讀寫都卡「總部」(common/authz/tenant_hq.py),saas 下沒有任何租戶算總部,三頁整個消失(DEV 2026-10-04 就是因此改回 host,見 memory project_dev_deployment_mode_host);②更新中心只認落地版的照(product_update_flow.py:193),saas 照會讓「立即檢查」回尚未開通。
建議:第一期 DEPLOYMENT_MODE=host、TURNSTILE_ENABLED=false,跟 189 一模一樣,等於多一台 POC 住在雲端。機器指紋綁這台 VM(Terraform 建的 VM 有穩定的 /etc/machine-id),License 走線上開通或離線開通都行。saas 模式的兩個洞另開 FR(總部判定、更新中心認 saas 照),做完再切。理由:急件不該被 saas 的既有缺口擋住;host 模式在 VM 上沒有任何技術問題。
D2 — Turnstile 人機驗證:第一期關,不開
FE image 是落地版 build(.env.onprem 烙 VITE_TURNSTILE_ENABLED=false),要開 Turnstile 得重 build 一顆 FE image 並拿正式的 site key。
建議:第一期關,與 D1 一致。雲端站有 Cloudflare WAF 擋在前面,POC 階段使用者是邀請制,沒有公開註冊入口,暴力登入的風險可接受。要開時另開一張卡:FE 多一個 build mode、Cloudflare 申請 site key。
D3 — 規格與 swap:e2-standard-4、8 GB swap、告警 85%
建議:照決策者已裁的 16 GB。swap 與告警寫進 Terraform 與開機腳本。升級路徑是改 machine_type 變數重 apply。
D4 — 祕密放哪:Secret Manager 只放容器,值由決策者手動放
Terraform 建 secret 的「殼」(名稱與權限),不寫值;值由決策者用 gcloud secrets versions add 放進去。VM 開機腳本或裝機步驟從 Secret Manager 讀出來填 .env。
建議:這樣 Terraform state 與 git 裡永遠沒有密碼。要放的有:DB 兩組密碼、Redis 四組、SMTP 帳密、AI 金鑰、License Center token、classifier token、bugsink secret。裝機時 install.sh 本來會自動產生 DB/Redis 密碼,只有 SMTP、AI、LC token 三組是外部給的,其餘讓 installer 產生後回寫 Secret Manager 備查。
D5 — 防火牆:80/443 只放行 Cloudflare 的 IP 範圍,不開給全世界
建議:Terraform 從 Cloudflare 公開的 IP 清單(https://www.cloudflare.com/ips-v4)產防火牆規則,繞過 Cloudflare 直打 VM IP 會被擋,WAF 才有意義。SSH 只放行 GitLab CI runner(188 的對外 IP)與決策者的 IP,寫在 ssh_allowed_cidrs 變數。
D6 — 寄信:Brevo SMTP 587
GCE 封鎖 25 port 出站,這是 GCP 鐵則。公司信箱不是 Google Workspace。
建議:Brevo 免費方案每天 300 封,用自家網域當寄件人,Cloudflare 加三筆 DNS 驗證。SMTP 設定在主系統後台「郵件設定」頁填(host 模式下總部看得到),不進 .env。
D7 — License Center 放行:Cloudflare Access 鎖 VM 固定 IP
建議:決策者已選。Terraform output 印出 public_ip,決策者貼到 Cloudflare Access 的 IP 規則。BE 程式不動。將來多台或換 K8s 再改 service token。
D8 — 出版管線:gcp-prod 比照 poc,部署前要按 ▶ 確認
FR-173 的 deploy.yml 一個 job 對應一台,poc 是 when: manual。
建議:deploy-gcp-prod 比照 poc:GUARDED=1、when: manual。deploy_e2e.sh 的 case 多一行 gcp-prod) E2E_HOST=jedi@<public_ip>。IP 從 Terraform output 來,寫進 GitLab CI 變數 GCP_PROD_HOST,不寫死在腳本。
tenant_hq.py 與 product_update_flow.py。envs/ 目錄結構保留 gcp-stg/ 的位置,要加是複製變數檔。批 4 驗收卡的清單,決策者親手做:
https://<網域>,憑證是 Cloudflare 簽的、沒有警告。直打 VM IP 被擋。gcp-prod,按 ▶,約 10 分鐘後系統版本號變新、資料都在。gcloud billing projects describe 確認 billingEnabled: true。/etc/machine-id:host 模式綁機器指紋。GCE 從映像開機會產生新的 machine-id,但從快照還原的 VM 會沿用舊的,災難復原後指紋不變,License 不用重開。從頭重建 VM 指紋會變,要重新開通,寫進營運手冊。CF-Connecting-IP。現在 nginx.onprem.conf 沒處理,列為批 3 的小項:加 real_ip_header 設定。project_dev_deployment_mode_host(saas 模式三頁消失的根因)~/Projects/Billows/Audit-Manager/guidant-ai-infra(GitLab auditmanager/guidant-ai-infra)scratchpad/pricing/*.txt(不入版控)