本章目標
把一次交付拆成可回溯的建置、設定、資料庫、服務健康、API/瀏覽器與外部聯調證據;知道什麼能在本機證明,什麼必須到指定測試站重新驗證。命令已對照目前來源,本輪未部署、未修改業務環境,也未執行完整業務交付門。
前置條件與安全邊界
先讀測試策略、快速開始與當前 backend/frontend AGENTS.md。確認 checkout、branch、image tag、資料庫、租戶和 listener 所屬;不停止別人的服務、不刪除 volume、不把秘密寫入 command line、log、Apifox 或文件。
交付證據矩陣
| 證據 | 命令/入口 | 可回答 | 不能回答 |
|---|---|---|---|
| backend static/unit | make ci-core | Go code、契約、生成與 lint(不含 tests/unit/...) | 真實依賴可用 |
| backend integration/E2E | make ci-local | testcontainers 真 DB 整合;隔離 compose 的 API、worker 流程 | 測試站版本 |
| frontend quality | pnpm lint、pnpm typecheck、pnpm test:run、pnpm deadcode、pnpm build | bundle、元件與 transport contract | 真實 Keycloak/租戶 |
| Apifox | make apifox-isolated | 隔離 stack 上的場景和斷言 | 沒跑的場景、錯版本 |
| deployment | 重建指定 image + health/API/browser | 某版本某環境的實際行為 | 其他租戶與未測路徑 |
只有符合該層門檻才能用相應語句描述;「build 綠」不是「部署完成」。後端沒有平台 CI(契約測試禁止加入 CI 設定檔),交付門是本機的 make ci-local 加 Apifox;前端 GitLab CI 只跑 lint 與 typecheck。目前已知紅燈見測試策略。
Backend 本機驗證
在 nexus-pro-be-plus 根目錄,先依專案要求跑靜態、unit、contract、drift 與 migration structural:
make ci-core需要資料庫與隔離服務時才執行:
make ci-localci-local 的順序是 ci-core → make integration(testcontainers,不經 compose)→ 以隔離 project/image 執行 up -d --build --wait --wait-timeout 120 api worker → make e2e → compose-clean(down --remove-orphans 並刪除該次 image)。compose healthcheck 打的是 /healthz,--wait 只證明程序存活;/readyz 由 E2E 測試斷言。若命令中途失敗,要保留失敗階段、project/image 名稱與 cleanup 結果,不用「後續重跑」覆蓋第一次證據。
Compose、migration 與 health
根目錄 docker-compose.yml 是完整的隔離 stack(postgres、keycloak、redis、db-provision、migrate、api、worker,另有 tooling profile 的 ops),供 make e2e/make ci-local 疊加 E2E override 使用。日常開發改用 ./deploy/dev-local/stack.sh up -d 只起外部依賴,再在本機以 set -a; . ./.env; set +a; go run ./cmd/api 執行(見 deploy/dev-local/README.md)。
cmd/compose-env 先確保根 .env 有六個 COMPOSE_* 秘密(缺少時以亂數產生,檔案權限 0600),再以 --exec 執行後面的命令;PostgreSQL volume 已存在卻缺原 admin 密碼時會拒絕執行,因此它不是純 read-only 檢查。這組 COMPOSE_* 只屬於這個 stack,不要和 deploy/dev-local/.env 混用。根 compose 預設綁 127.0.0.1:38080(API)與 127.0.0.1:38081(Keycloak),後者與 dev-local 的 Keycloak 同埠,兩者不要同時起;make ci-local 的 E2E override 改用隨機埠,不受影響。可先驗證 config,再在擁有資料庫授權時啟動:
go run ./cmd/compose-env --exec docker compose config -q
go run ./cmd/compose-env --exec docker compose up -d --build --wait --wait-timeout 120 api workerup api worker 會依 depends_on 先跑一次性的 db-provision 與 migrate up,把所有待執行 migration 套到持久 volume new-nexus-pro-data,之後才啟動 api、worker。
API 的 /healthz 只代表程序存活;/readyz 執行啟動時組裝的 readiness 檢查:postgres、objectstore 為關鍵,失敗回 503 not_ready;redis 可降級,失敗時該項標 degraded,整體仍回 200。worker 的 /healthz、/readyz、/metrics 由自己的 ops listener(METRICS_ADDR)提供,根 compose 映射到本機 127.0.0.1:39092;API 的 /readyz 不代表 worker ready。應記錄實際 port、image digest、migration version 與 response body/status。不要以刪除 PostgreSQL volume 解決密碼或 migration 問題,先核對原設定、備份與資料用途。
cmd/migrate 只提供 up、down、status、validate:沒有 dry-run,也沒有 up-to(up 一次套用全部待執行版本);make migration-structural 只做結構收集、不執行 SQL。影響評估要靠閱讀 SQL、migrate status 與隔離庫實跑(make integration、make ci-local),再在有授權的環境套用。已發布 migration 由 db/migrations/FROZEN.sha256 契約鎖住,不可改寫。每個 migration 都有 goose Down,但部署流程不會自動 downgrade;rollback 不是只退 image,還要知道 schema 是否可 down、資料是否需還原,以及 outbox/worker 是否有半完成副作用。
部署時的 migration
- 根 compose:
db-provision→migrate up→ api/worker(見上)。 - 測試站:
deploy/test-server/deploy.sh在 PostgreSQL 運行時先pg_dump備份,再跑db-provision、migrate,最後起 api/worker/web;migration 失敗需要人工處理,不自動 downgrade。 - 首次部署:
deploy/BOOTSTRAP.md要求先備份並套用目前版本 migration,goose 最新版本必須與執行檔內嵌的 migration 一致;bootstrap 本身不執行 migration。
Frontend 建置與設定
在 nexus-pro-web-plus(前端 AGENTS.md 優先 nub run,pnpm 亦可;不要順手重建 lockfile):
pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test:run
pnpm deadcode
pnpm build
pnpm start # 只是本地近似,見下方 standalone 說明next.config.ts 設定 output: 'standalone';容器實際執行的是 .next/standalone 裡的 node server.js,pnpm start(next start)在此設定下會出現警告,只能當本地近似。
執行期環境變數:NEXUS_API_BASE_URL 供 proxy.ts 把同源 /v1/** 轉發到後端,並從 httpOnly cookie 注入 Authorization(未設定時回 503);NEXT_PUBLIC_API_BASE_URL 留空時瀏覽器打同源 /v1/**,不要填後端網域。Keycloak 由 KEYCLOAK_BASE_URL、KEYCLOAK_REALM、KEYCLOAK_CLIENT_ID 提供(public client,Authorization Code + PKCE)。這些都在執行期讀取,同一個 image 可佈到多個環境;APP_ENV(只認 dev/prod)與 APP_BUILD_TIME 則在建構期內聯進 bundle。cookie 屬性寫在程式裡:httpOnly、sameSite=lax,secure 只在 NODE_ENV=production 開啟,所以 production 必須走 HTTPS,沒有對應的環境變數。NEXT_PUBLIC_MSW、NEXT_PUBLIC_MOCK_AUTH 只在 development 使用:production build 遇到 true 會直接失敗,runtime 的 libs/mockMode.ts 也會再擋一次。不能把秘密放進 NEXT_PUBLIC_*。
部署前核對前端 build time、version、proxy.ts 轉發、cookie flags、origin 與後端 CORS(CORS_ALLOWED_ORIGINS 空值即停用 CORS middleware);前端程式沒有設定 CSP header,需要時到部署層確認。重新整理每個代表性 route,確認未登入 redirect、登入後 /v1/me 成功、租戶與權限正確,並沒有把 mock badge 或 fixture data 帶到部署站。
前端 CI 與容器
.gitlab-ci.yml引用nexus-pro/nexus-pro-iac的general.gitlab-ci.yml(tag-release 流程)。依檔案註解:dev 分支 build 後回寫 IaC 的 image tag 交給 ArgoCD;打 tag 建置 stg,prd 走人工 gate 並 promote 同一個 image。模板本身在 IaC 倉庫,本站未核對。- build 以 kaniko build-arg 傳入
APP_ENV與APP_BUILD_TIME:ENVIRONMENT為 dev 時是dev,為 stg(tag 建置)時直接烙prod,因為 stg image 會原封 promote 到 prd。 static-check(.prestage)只跑pnpm lint與pnpm typecheck;pnpm test:run因基準樹既有失敗刻意未納入,所以 pipeline 綠不等於測試綠。Dockerfile多階段建置 standalone 產物,以非 root 使用者在 3000 埠執行node server.js。- 探針:
/api/healthz不碰外部依賴、恆回 200(liveness/startup);/api/readyz以 2 秒逾時查後端/readyz,後端不健康或連不到時回 503,NEXUS_API_BASE_URL未設定時回 200 並標not_configured。
首次部署資料與定稿設定
deploy/BOOTSTRAP.md 是首次部署的資料初始化手冊,不是設定的事實來源。前置:先備份並套用目前版本 migration,從同一版程式碼建置 API、worker、ops,並準備好 Keycloak realm、登入 client、provisioning service client 與 Google OAuth。必要時以 NEXUSPLUS_ENV_FILE=/secure/bootstrap.env ./deploy/keycloak/apply-realm.sh 套用 realm(會修改目標 realm)。私密設定檔由 deploy/bootstrap.env.example 複製,owner 為執行帳號、權限 0600。
./deploy/bootstrap.sh --config /secure/bootstrap.env --dry-run
./deploy/bootstrap.sh --config /secure/bootstrap.env --apply
./deploy/bootstrap.sh --config /secure/bootstrap.env --apply --resume
./deploy/bootstrap.sh --config /secure/bootstrap.env --verify-onlyexit code:0 表示自動檢查與必要人工驗收全部通過,1 是設定、進度檔或執行錯誤,2 是部分完成或必要驗收尚未完成。中斷後以 --resume 繼續,不要刪除進度檔或換目錄繞過;人工驗收寫在 STATE_DIR/acceptance.json,只能標記確實通過的項目。
bootstrap 成功不代表假別/表單已與定稿一致,定稿基準另依 deploy/APPROVED-CONFIG.md:先以 python3 tools/approved_config.py --tenant … --api … --issuer … 輸出只讀差異與 plan_sha256;人工核對後加 --apply --approve-plan 帶入該次 plan_sha256,並以 --backup 指定尚不存在的私密檔;恢復 worker 後再跑 --verify-only,必須 exit 0。憑證只從環境變數提供,不放命令參數;工具本身的測試是 make approved-config-test(已納入 make unit)。
Apifox 與 test-site 驗收
後端可呼叫端點的場景必須維護在套件 29010。交付驗證用 make apifox-isolated:需要 Docker、已登入的 Apifox CLI、既有 .env 與雲端寫入授權;它建隔離 stack、種 fixture、建臨時雲端環境並在結束時清理,也會 unset 繼承的 NEXUS_DEV_PASSWORD。手動 make apifox-test 必須提供 APIFOX_RUNTIME_VARS 與匹配的 APIFOX_ENV;只設 NEXUS_DEV_PASSWORD 會在送出請求前 exit 2。細節見測試策略。
make apifox-isolated驗收記錄至少包含:commit/image、base URL、tenant/account(不記密碼)、場景名稱、HTTP status、斷言總數、失敗 response 的 trace ID 和時間。Apifox CLI 回 success 但斷言數為 0,不算有效證據;schema auto-import 成功也不代表頁面真正可用。
測試站自動部署
deploy/test-server/ 只服務測試站;GitLab 快照以 filter-repo 移除了 deploy/test-server、docs、memory,從 GitLab clone 看不到這些檔案。流程:
- systemd
nexus-test-deploy.timer每 2 分鐘執行poll.sh,抓兩個倉庫 GitHubdev的 HEAD;不包含主機記錄的最低基準 commit 就拒絕,SHA 組合沒變則不部署。 deploy.sh以render.py從後端docker-compose.yml渲染測試站 compose;密碼保存在主機shared/stack.env(0600),既有資料庫缺原密碼時拒絕執行。- 建置後端 image(後端
Dockerfile)與 web image(deploy/test-server/Dockerfile.web,執行next start,不是前端根目錄的Dockerfile)。 - PostgreSQL 已在運行時先
pg_dump備份;起 postgres、redis、keycloak 後跑db-provision與migrate,再起 api、worker、web,並檢查 API/readyz與前端/login。 - 成功後切換
current,由cleanup.py清除舊 release image(保留目前、上一版與容器仍引用的 image)。
測試站沿用 development 模式與 Keycloak 開發啟動方式,不是完整生產部署規格;日誌看 journalctl -u nexus-test-deploy.service。驗收前先以主機上 release 的 revisions 確認實際部署的後端/前端 SHA,再以當次版本重新操作登入、表單、IAM、HR、考勤或附件流程。若瀏覽器被 login redirect、後端 listener 不是目標 checkout、或資料不在正確租戶,狀態應寫成 blocked/未驗證,不可推測通過。
常見錯誤
- 只重啟 process,沒有
--build,實際仍跑舊 image。 - 用 development mock 或 fixture 報告為真實 API/Keycloak 成功。
- 把
/healthz200 或 compose--wait成功當成 PostgreSQL、worker 或外部整合 ready;把 API/readyz200 當成 redis 正常或 worker ready。 - 只設
NEXUS_DEV_PASSWORD就跑make apifox-test。 - 手改後端
.env.example或api/config-reference.md,而不是改internal/config註冊表後執行make generate。 - 把前端 GitLab pipeline 綠當成
pnpm test:run綠。 - 先刪 volume、刪 migration metadata 或重建資料,再把結果當修復。
- 將密碼、token、service-account JSON、private URL 或 tenant 個資貼入 log。
- 共用 test site 有別人資料,卻沒有記錄 tenant、版本和 data fixture 來源。
預期結果與相關文件
預期結果: 報告能分開列出 code baseline、聚焦測試、ci-local、Apifox、部署與 browser evidence;每個未跑的 gate 都寫出原因。
設定事實來源:後端是 internal/config 註冊表,make generate 由它產生 .env.example 與 api/config-reference.md(含預設值、必填、機密與規則),make config-drift 逐字比對,手改生成物會讓 gate 失敗;前端是 .env.example 與實際讀取變數的 proxy.ts、app/api/auth、next.config.ts。deploy/BOOTSTRAP.md 是首次部署手冊,deploy/test-server/ 只服務測試站。文件內容與實際 checkout 不一致時以程式碼為準。