Day27 把 ProjectManagementWeb 部署到 Google Cloud,Day28 則處理上線後的需求變更。程式已經在正式環境運作,接下來最常發生的事不是「專案結束」,而是又來了一個小修改。
改一個欄位、修一段驗證、補一個按鈕,看起來都不大。問題出在修改完成後,還要重新跑格式檢查、建置、測試、製作容器映像、更新資料庫,再部署前後端。第一次會仔細看著清單做,第十次就可能漏掉其中一步。
CI/CD 要處理的,正是這些會一直重複的工作。
這篇會先說明 CI/CD 是什麼、能解決哪些問題,再用 ProjectManagementWeb 目前的 GitHub Actions 與 Google Cloud 架構,整理一次完整的設置順序。最後也會示範:設好之後,開發者平常改程式到底要怎麼使用。
CI 是 Continuous Integration,中文常翻成「持續整合」。
開發者把程式 Push 到 GitHub,或建立 Pull Request 時,CI 會在一台乾淨的 Runner 上重新安裝相依套件、建置並執行測試。它要回答的問題很單純:這次修改能不能安全地放回專案?
CD 則接在 CI 後面,常見的意思有兩種:

ProjectManagementWeb 只有一套正式環境,Backend 發版時還可能更新 SQL Server Schema。這種專案不適合測試一過就直接上線,因此採用持續交付比較穩妥:正式 v* Tag 觸發部署流程,但進入 production Environment 時仍要人工核准。
重複工作交給 Workflow,正式上線由人按下確認。
沒有 CI/CD 時,部署品質很依賴執行者當天的精神和記憶。CI/CD 把做法寫成 Workflow,之後每個版本都走同一套流程。
| 原本的問題 | 接上 CI/CD 後 |
|---|---|
| 有人忘了跑測試或建置 | Push 或 PR 時自動執行 |
| 本機可以跑,換一台電腦卻失敗 | 每次都在乾淨 Runner 重建環境 |
| CI 已經失敗,程式仍被合併 | Branch Ruleset 阻擋合併 |
| 每次部署使用的指令不同 | Workflow 固定建置與部署順序 |
| Google Cloud 長效金鑰散落在 GitHub | WIF(工作負載身分聯盟)交換短效憑證,不保存 JSON Key |
| 上線後才發現版本對不起來 | Tag、Commit、image digest 與 Cloud Run Revision 可以互相追查 |
CI/CD 不能保證程式永遠沒有 Bug。它只能穩定執行已經寫進流程的檢查。沒有加入 Integration Test,就不會憑空多出資料庫測試;沒有部署後驗收,也不會知道登入畫面是否真的能用。
所以設置 CI/CD 的第一步不是追求「全自動」,而是先決定每次改版一定不能漏掉哪些動作。
這個專案分成三個 Repository:
三個 Repository 的工作不同,CI 也不該硬塞成同一份。Backend 檢查 .NET,Frontend 檢查 Vue,Spec 則檢查 Markdown、連結與 Mermaid。

目前三個 Repository 的 CI 都已建立並能正常執行,main 與 develop 也受到 Branch Ruleset 保護。正式部署會用到的 Production Environment、Workload Identity Federation 和 Service Account 已設置完成;真正更新 Cloud Run 的 deploy-production.yml 還沒有接上。
下面的步驟會把「已經正常運作的 CI」與「接下來要補上的 CD」放在同一條路線裡,但不會把規劃中的流程寫成已部署完成。
開始設置 Workflow 前,先在 Backend、Frontend、Spec 分別確認分支、Commit 和遠端 Repository。
git status --short --branch
git branch --show-current
git rev-parse HEAD
git remote -v

ProjectManagementWeb 是三個獨立 Repository,因此要分別記錄三個 Commit SHA。正式發版時還要留下容器 image digest 與 Cloud Run Revision。日後需要回復版本,才知道該回到哪一組程式碼與映像。
GitHub Actions 的 Workflow 放在 Repository 的 .github/workflows/。當 Push 或 Pull Request 符合 on 設定的條件時,GitHub 就會建立 Runner 執行工作。
CI 只負責讀取程式與執行檢查,因此權限保持簡單即可:
permissions:
contents: read
這裡不需要 id-token: write,也不應讀取正式環境。一般 PR 要接受檢查,不需要取得 Google Cloud 的部署身分。
Backend 的 .github/workflows/ci.yml 會執行:
dotnet restore ProjectManagementWeb.slnx
dotnet format ProjectManagementWeb.slnx --verify-no-changes --no-restore
dotnet build ProjectManagementWeb.slnx --configuration Release --no-restore
dotnet test tests/ProjectManagementWeb.UnitTests/ProjectManagementWeb.UnitTests.csproj \
--configuration Release --no-build
dotnet list ProjectManagementWeb.slnx package --vulnerable --include-transitive
這些命令會檢查格式、Release Build、Unit Test 與 NuGet 套件弱點。Workflow 成功後,GitHub Actions 會留下綠色勾勾和完整執行紀錄。

目前 Backend CI 還要再補隔離的 SQL Server Integration Test。設置時需要在 Runner 啟動測試資料庫、提供 PMW_TEST_SQL_CONNECTION,並確認測試沒有因缺少環境而被略過。
Frontend 使用 Node.js 22 與 npm,檢查順序是:
npm ci
npm audit
npm run format:check
npm run lint
npm run type-check
npm run test
npm run build-only
npm ci 會按照 package-lock.json 安裝固定版本,適合用在 CI。後面的命令依序檢查套件、程式格式、型別、Vitest 與 Production Build。
Frontend 的 Playwright E2E 可以放在正式 Tag 前,或安排在 main 的完整檢查裡。PR 先跑速度較快的測試,開發時比較不會每次等太久。
Spec 不會產生網站或 API,仍然需要 CI。它會檢查 Markdown 結構、本機相對連結、Mermaid 語法,以及常見的 Token 或 Private Key 格式。
正式 Push 前也可以先在本機跑同一套驗證,畫面中 25 張 Mermaid 圖都成功完成解析。

CI 的目的不是讓三個 Repository 執行一模一樣的命令,而是替各自的交付內容設好最低檢查標準。
只有 Workflow 還不夠。如果 CI 失敗後仍可直接 Push 到 main,綠燈和紅燈都只是參考。
先讓 Workflow 至少成功執行一次,再到 GitHub Repository 的:
Settings → Rules → Rulesets
替 main 與 develop 設定:

Status Check 的名稱要從實際執行過的 Workflow 選取。如果手動輸入一個不存在的名稱,Pull Request 會一直等待一項永遠不會出現的檢查。
目前是單人維護,所以 Required approvals 不必硬設為 1。否則自己的 PR 會卡在找不到第二位審查者。即使只有一人,仍可要求必須開 PR 並通過 CI。
Backend 與 Frontend Repository 分別到:
Settings → Environments → New environment
建立名為 production 的 Environment,限制只有 v* Tag 可以使用,並加入 Required reviewer。

單人維護時,Prevent self-review 要保持關閉,不然觸發 Workflow 的人無法核准自己的部署。日後有其他協作者,再把核准規則收緊。
Project ID、Region、Cloud Run Service 名稱可以放在 Environment Variables。資料庫連線字串、JWT 私鑰和 SMTP 密碼仍由 Google Secret Manager 管理,不要複製到 Workflow 裡。
GitHub Actions 要推送 Artifact Registry 或更新 Cloud Run,必須先向 Google Cloud 證明自己是誰。
這裡使用 Workload Identity Federation,中文是「工作負載身分聯盟」,簡稱 WIF。
可以把 WIF 想成一個臨時換證櫃檯。GitHub Actions 不需要隨身帶著永久有效的 Google Cloud 金鑰,而是先拿 OIDC Token 證明自己的來源。Google Cloud 會核對 Repository ID 與 Git Ref,條件相符才發出短效憑證;Workflow 結束後,這張憑證也會很快失效。
這種做法省去了下載 Service Account JSON Key,再把長效金鑰存進 GitHub Secret 的步驟。即使有人取得舊的 Workflow 紀錄,也拿不到一把可以長期使用的雲端鑰匙。
目前已建立:
github-actions
github-backend
github-frontend
Provider 只接受指定 Repository ID 與 refs/tags/v...。Feature branch 可以執行 CI,但不能換到正式部署憑證。
接著替 Backend 與 Frontend 分別建立 Builder、Deployer Service Account。

Builder 負責推送映像,Deployer 負責更新 Cloud Run。兩種工作使用不同身分,某一邊的權限需要調整時,不會連另一邊一起放大。
最後,把 WIF Principal 加入對應 Service Account,角色選擇 Workload Identity User。

完成這些設定後,GitHub 已經有取得短效身分的管道。真正的部署動作仍要由 CD Workflow 定義。
Backend Repository 接下來要新增 .github/workflows/deploy-production.yml,只接受正式 Tag:
on:
push:
tags:
- 'v*'
permissions:
contents: read
id-token: write
environment: production
id-token: write 讓 Workflow 可以取得 OIDC Token;environment: production 則讓工作停在人工核准處。
Backend 的部署順序如下:

Mermaid 原始檔:pic/Day29/10-Backend正式部署流程.mmd
流程先建置 API 與 Migrator 映像,再記錄 image digest。接著更新並執行 database-migrator,Migration 成功後才建立新的 API Revision。候選 API 先不接正式流量,完成健康檢查與資料庫 Readiness 後再切換。
目前 /health 只能確認 ASP.NET Core Process 有回應。正式啟用 CD 前,還要補上會使用 pmw_app 連接資料庫的 /health/ready,避免 API 看起來活著,實際上卻讀不到 Cloud SQL。
Frontend 的 CD 同樣由 v* Tag 觸發,使用自己的 WIF Provider、Builder 與 Deployer。Backend 穩定後,再部署前端候選 Revision。

Mermaid 原始檔:pic/Day29/11-Frontend正式部署流程.mmd
候選前端至少要確認首頁、/sign-in 與靜態檔案都能載入,接著實際登入,確認 Nginx 可以把 /api/** 送到 Backend。最後再做一筆會讀取 Cloud SQL 的查詢。這些檢查通過後,才把正式流量切到新 Revision。
CI/CD 完成後,開發者不需要每天進 GitHub 設定或 Google Cloud Console。平常改版會走下面這條路:
develop 建立 feature/* 分支。develop。如果 CI 沒通過,就點進失敗的 Step 看 Log,在原分支修正後再次 Push。Workflow 會自動重跑,不需要重開 Pull Request。
功能在 develop 整合完成並驗收後,再依 Git Flow 合併到 main。確定這個 Commit 就是要上線的版本,再建立正式 Tag:
git switch main
git pull --ff-only
git tag v1.0.0
git push origin v1.0.0
接下來由 GitHub Actions 接手:
production Environment,等待人工核准。這時每天真正需要記住的,只有分支、Pull Request、CI 結果和正式 Tag。那些容易漏掉的建置與部署命令,已經寫在 Workflow 裡。
第一次設置的工作很多,因為要把檢查規則、雲端身分和部署順序寫清楚。設好之後,大部分設定不需要反覆操作。
| 第一次設置 | 之後每次改版 |
|---|---|
| 建立 CI Workflow | Push 分支、開 Pull Request |
| 設定 Branch Ruleset | 查看 CI 是否通過 |
| 建立 Production Environment | 合併經過驗證的修改 |
| 建立 WIF、Service Account 與 IAM | 建立正式 Tag |
| 建立 Backend/Frontend CD Workflow | 核准部署並做瀏覽器驗收 |
Workflow 本身也是程式碼。測試命令、Node.js 或 .NET 版本、Google Cloud 資源名稱有變動時,應透過 Pull Request 修改 Workflow,讓變更留下審查與歷史紀錄。
Cloud Run 可以把流量切回上一個正常 Revision,所以每次部署都要記錄 Stable Revision 與 image digest。候選版本還沒接流量就檢查失敗,直接停止部署;切換後才發現問題,則把流量切回舊 Revision。
資料庫需要另外處理。Cloud Run 回復流量,不會自動回復 EF Core Migration。Schema 變更應盡量保持向前相容,正式發版前也要準備資料備份與獨立的資料庫回復方案。
CI/CD 解決的不是「怎麼少寫幾行指令」,而是每次修改都用同一套方式檢查與交付。開發者仍要決定測什麼、何時發版,也要看懂失敗的 Log;自動化負責把決定好的流程老實重跑。
ProjectManagementWeb 目前已完成三個 Repository 的 CI、Branch Ruleset、Production Environment、WIF 與部署用 Service Account。下一步是補上 Backend Integration Test、DB-backed Readiness,以及真正更新 Cloud Run 的 Backend/Frontend CD Workflow。完成第一次正式 Tag 驗收後,這條 CI/CD 路線才算完整接通。
下一篇會回頭整理這 30 天,看看一個模糊需求如何一路變成規格、程式、測試與能夠持續交付的專案。