把測試環境搬到新 GCP 機器:Load Balancer、Certificate Manager、nginx 全紀錄
把測試環境從舊 GCP VM 搬到獨立機器,換上 Certificate Manager 自動化憑證跟新 Load Balancer,記錄踩到的幾個坑。
最近把公司幾個測試環境從舊的 GCP VM 搬到新機器,順便換了一套 Load Balancer 跟憑證管理方式。整個過程踩了幾個坑,值得記錄一下。
背景:為什麼要搬機器
原本好幾個測試環境全部擠在同一台舊的 GCP VM 上,而且這台機器跟正式環境是同一台——測試環境和正式環境共用機器資源、共用 Load Balancer。這在流量隔離、資源競爭上都不理想。
新的方向是另外開一台專門給測試環境用的機器,搭配獨立的 Load Balancer、獨立的 TLS 憑證管理,把測試環境逐一搬過去,跟正式環境徹底切開。
這次刻意選擇「一個環境一個環境搬」,不是一次全部切過去——先搬全新的環境(沒有既有流量包袱),驗證整套流程沒問題後,再搬已經有真實使用者在用的環境。
新舊架構長怎樣
舊架構是這樣:Cloudflare(DNS-only 直連)→ 舊 Load Balancer(TCP 轉發)→ 單一 backend service → instance group → 舊 VM → nginx 自己讀 Host header 決定轉給哪個後端 port。
這裡有個容易忽略的重點:GCP Load Balancer 本身完全沒做任何路由判斷,它只是把所有流量無腦轉給同一個 backend,「這個請求該給哪個環境」這件事完全交給 VM 上的 nginx 自己看 server_name 決定。
新架構邏輯跟舊的一樣(LB 不做路由判斷,交給 nginx),差別在於換了一台全新的 VM 跟正式環境完全分開,而且 TLS 憑證改用 GCP Certificate Manager(Google Managed Certificate),可以用 Certificate Map 一次掛多個網域,並透過 DNS Authorization 自動化簽發、續約,不用再手動上傳憑證檔案。
核心機制:Certificate Manager 的四段式設定
要讓一個新網域在新 LB 上有 HTTPS,需要做滿四件事,缺一不可:
1. DNS Authorization —— 跟 Google 證明「你真的擁有這個網域」,做法是要求在 DNS 上加一筆特定的 CNAME 記錄:
gcloud certificate-manager dns-authorizations create dns-authz-example \
--domain="example.yourdomain.com" \
--location=global
2. Managed Certificate —— 憑證本身:
gcloud certificate-manager certificates create example-tls \
--domains="*.example.yourdomain.com" \
--dns-authorizations="dns-authz-example" \
--location=global
憑證建立後狀態會是 PROVISIONING,Google 要驗證 DNS Authorization、實際簽發憑證,通常要等 15–60 分鐘才會變成 ACTIVE,這段純粹是等待,中間可以先做下一步。
3. Certificate Map Entry —— 把憑證掛進地圖:
gcloud certificate-manager maps entries create example-tls \
--map="my-tls-map" \
--certificates="example-tls" \
--hostname="*.example.yourdomain.com"
Certificate Map 可以讓所有網域共用同一張地圖——每個新環境只是在同一張地圖裡加一筆新的 entry,不需要每個環境各自建一個新的 Load Balancer。這張地圖只要掛在 LB 的 target-https-proxy 上一次,之後加新 entry 進去,新網域立刻就能用同一個 LB 服務,不用改 LB 本身的任何設定。
4. DNS 記錄指到新 LB —— 前面三步都是「讓新 LB 有能力回應這個網域的 HTTPS 請求」,最後這步才是真的把流量導過去:把 A 記錄的目標換成新 LB 的 IP,Proxy 狀態維持 DNS only(灰雲)。
為什麼一定要 DNS only,不能開 CDN Proxy
這是這次踩到、也是最值得記下來的一個坑:Certificate Manager 的 DNS Authorization 驗證,以及後續使用者真正連線時的 TLS handshake,都需要直接打到 GCP 的 IP。如果 DNS 記錄開了 CDN Proxy(例如 Cloudflare 的橘雲),流量會先進 CDN 自己的邊緣節點,CDN 用它自己的憑證跟使用者交握,再決定要不要轉發到後面的 origin——這樣一來 DNS Authorization 驗證會失敗(Google 驗證時連到的是 CDN 的 IP),就算僥倖簽出來了,終端使用者實際上拿到的也是 CDN 的憑證,不是 Google Managed Certificate,等於白簽。
每一筆新增/修改的 DNS 記錄都要反覆確認是 DNS only(灰雲),這是一個很容易在管理介面上手滑點成 proxy 模式的地方。
nginx 設定:一個檔案管所有測試環境
新機器上所有測試環境的 nginx server block 都寫在同一個檔案裡,用 upstream 區分各環境對應的後端 port:
upstream backend-envA {
server 127.0.0.1:5002;
keepalive 8;
}
server {
listen 80;
server_name *.envA.yourdomain.com;
set $backend_upstream backend-envA;
...
}
這次的經驗是:nginx 這層的 server block 其實可以一次把所有預計要搬的環境都先寫好,就算還沒掛上對應的憑證、DNS 也還沒切過去也沒關係——nginx 設定檔本身不會因為缺 DNS/憑證而出錯。等真正要搬某個環境時,只需要做「把 GCP 憑證跟 DNS 接上去」這最後一里路,不用再回頭改 nginx。這讓分批搬遷變得輕鬆很多。
兩種搬遷難度:全新環境 vs 既有環境
全新環境要六個步驟都走一次:DNS Authorization → 等生效 → Managed Certificate → 等 ACTIVE → Certificate Map Entry → DNS 記錄指到新 LB,中間有兩次要等 Google 那邊處理的空檔。
已經在用的環境如果檢查發現 Certificate Manager 相關資源早就已經設定好、狀態是 ACTIVE,代表可能之前已經做過準備,只是 DNS 還沒切過去而已。這種狀況可以先用 curl --resolve 或帶自訂 Host header 直接打新 LB 測試(不改真實 DNS,先驗證新 LB 端能不能正常服務這個網域),確認正常後只需要改一筆 DNS 記錄,流量就完全切換過去,完全不用碰憑證或 nginx。
這也提醒一件事:動手之前先盤點清楚「這個環境的憑證/DNS 資源到底存不存在」,不要預設每個環境都要走完整流程,不然會做很多其實不需要做的重複工作。
一個容易忽略的 DNS 細節:萬用字元的比對優先權
搬完一個環境做最終驗證時,發現某個「沒有接子網域、只有環境名稱本身」的網址還是指向舊 LB,一開始以為漏改了什麼設定,後來查證才發現:「子網域.環境名稱.主網域」這種兩層萬用字元的記錄已經改到新 LB 了,這是真正要改的那筆;但「環境名稱.主網域」本身(沒有子網域)其實是被更上層、範圍更廣的單層萬用字元接住的,這筆一直指向舊 LB,而且是所有環境共用的。
因為實際上不會有人直接打「沒有子網域」的這種網址,所以這個現象雖然一開始看起來像遺漏,但對任何真實使用情境都沒有影響。這說明了 DNS 比對規則是精確比對優先於萬用字元——驗證時多測幾個隨機子網域,比只測單一個網址更能看出真實狀況,不然容易被單一測試案例的巧合結果誤導。
小結
這次遷移歸納下來的操作原則:
- 一次搬一個環境,不要求一步到位,降低單次變更的風險與驗證難度
- 先確認資源現況,再決定要走「從零建立」還是「切換既有設定」,避免重工
- nginx 設定可以提前備好,就算對應的憑證/DNS 還沒準備好也不影響
- DNS 只做 DNS only,不要開 CDN proxy,尤其是牽涉到憑證自動化驗證的網域
- 每個新網域至少測兩個以上的隨機子網域,避免被單一測試案例的巧合結果誤導