Cloud Run 第一次切 Custom Domain 到 Prod: Domain Mapping、Search Console 與憑證等待期踩坑記錄

這篇記錄第一次把 Cloud Run 的 custom domain 從既有環境切到正式 prod service 時, 我實際踩到的幾個坑。

表面上看起來只是把 api.example.com 指到新的 Cloud Run service, 但真正會卡住的通常不是 Terraform syntax, 而是:

  • domain ownership
  • IAM 身分
  • certificate provisioning
  • first deploy sequencing

如果這幾件事沒有事先想清楚, 第一次 prod 啟用很容易卡在最後一步。

這個問題的典型場景

通常情境會像這樣:

  • custom domain already exists
  • old mapping points to a non-prod or legacy service
  • new prod service is ready
  • Terraform now manages the prod service and wants to own the domain mapping too

這時候最直覺的想法是:

  • import the existing mapping
  • let Terraform replace it during the first prod deploy

這個方向本身沒有錯, 但會牽動幾個 provider 外的前置條件。

第一個坑: Terraform 能管 domain mapping, 不代表現在這個身分有權重新建立它

我第一次遇到的錯誤不是 DNS, 也不是 Cloud Run service 本身, 而是類似這種訊息:

1
Caller is not authorized to administer the domain api.example.com.

這個錯誤的重點不是 Cloud Run 壞掉, 而是:

  • the current deploy identity can talk to GCP
  • but it is not recognized as a domain owner for that domain

對 Cloud Run custom domain 來說, 能不能 create or recreate domain mapping, 跟 Search Console 的 ownership 有關。

如果現在是新的 deploy-prod service account 要接手第一次正式 prod 切換, 那它也必須被視為這個 domain 的 owner。

第二個坑: Full user 不夠, 要用 Owner

這個差異很容易看漏。

在 Search Console 裡:

  • Full user is not the same as Owner
  • Full user cannot satisfy domain ownership requirements for this flow

如果你只是把 deploy service account 加成 Full user, Terraform 仍然可能在 create domain mapping 的時候失敗。

比較穩的做法是:

  1. add the deploy-prod service account as an owner
  2. add it on the parent domain if possible
  3. wait a few minutes for the permission change to propagate
  4. rerun the failed deploy

第三個坑: import 成功不代表 domain 已經完成切換

第一次切 prod 的時候, 常見做法會像這樣:

1
2
3
4
import {
  to = module.service.google_cloud_run_domain_mapping.api[0]
  id = "locations/us-east1/namespaces/my-project/domainmappings/api.example.com"
}

這樣做的好處是, Terraform 可以先接手既有 mapping, 然後在 plan 裡看見 route mismatch, 再做 replace。

但要注意:

  • import success only means state ownership is established
  • it does not mean the new domain target is ready yet

真正的切換是否完成, 還要看 mapping status 和 certificate status。

第四個坑: DNS 正確, HTTPS 仍然可能暫時不能用

這是第一次正式啟用最容易讓人誤判的地方。

你可能會看到:

  • DNS already points to ghs.googlehosted.com
  • DomainRoutable = True
  • but curl https://api.example.com still fails

這時候不一定是 DNS 設錯, 也不一定是 Terraform 沒做完。

很常見的情況是 managed certificate 還在 provisioning。

我會先看 domain mapping status:

1
2
3
4
gcloud beta run domain-mappings describe \
  --domain api.example.com \
  --region us-east1 \
  --format="yaml(status)"

如果你看到類似這些狀態:

1
2
3
Ready: True
CertificateProvisioned: True
DomainRoutable: True

那 GCP 控制層基本上已經完成。這時候本機還短暫打不到 HTTPS, 很可能只是 edge propagation 還沒完全擴散到位。

第五個坑: 第一次 prod deploy 的順序不能亂

如果 domain mapping 也要在第一次 prod deploy 一起切, 順序要很嚴格。

我現在會建議用這個順序:

1
2
3
4
5
6
7
1. prepare prod platform resources
2. populate prod secrets
3. remove legacy domain mapping from the old Terraform state
4. import the mapping into the new prod service root
5. run the first prod deploy
6. verify mapping status and HTTPS readiness
7. remove the one-time import block

這裡最容易做錯的是 3 和 4。

如果舊 root 還在管 domain mapping, 新 root 又要 import 同一個 mapping, state ownership 會混亂。

第六個坑: push trigger 是否要在第一次 prod deploy 前就打開

這不是 Cloud Run 問題, 但跟第一次 prod 啟用很有關。

如果 deploy-prod.yml 已經有 push: main, 那某次 merge 本身就可能直接變成第一次 prod deploy。

這代表你不能用平常 merge feature 的心態看這個 PR。你要把它當成正式部署事件。

比較穩的做法通常是二選一:

  • keep the first prod deploy manual
  • or finish every prerequisite before the merge that enables auto prod deploy

只要前置還沒做完, 就不該讓 main 自動踩進第一次 prod 切換。

我現在會怎麼驗證第一次切換真的完成

我會分成三層看:

Terraform layer

  • prod service root plans cleanly
  • the import block is no longer needed
  • the old root no longer owns the mapping

GCP 控制層

  • mappedRouteName points to the prod service
  • Ready = True
  • CertificateProvisioned = True
  • DomainRoutable = True

User-visible layer

  • curl -I https://api.example.com/health succeeds
  • opening the domain in a browser works
  • the service behavior matches prod, not legacy/dev

如果只有前兩層成立, 第三層還沒過, 我通常不會說切換完成。

一個實用的心理模型

我現在會把這件事拆成兩個不同問題:

  1. who owns the domain mapping state
  2. who is authorized to administer the domain

Terraform import 只是在處理第一題。

Search Console ownership 和 Cloud Run domain authorization, 才是在處理第二題。

這兩題如果混在一起看, 很容易以為 import 成功就代表一切都 ready。

常用 gcloud 指令

第一次切 custom domain 到 prod 時, 最容易回頭查的通常是 mapping 狀態和 service URL。這幾條我會直接留在筆記裡。

查 domain mapping 的完整狀態

1
2
3
4
gcloud beta run domain-mappings describe \
  --domain api.example.com \
  --region us-east1 \
  --format="yaml(status)"

只看 mapping 目前對到哪條 route

1
2
3
4
gcloud beta run domain-mappings describe \
  --domain api.example.com \
  --region us-east1 \
  --format='value(status.mappedRouteName)'

列出目前 region 內的 domain mappings

1
2
gcloud beta run domain-mappings list \
  --region us-east1

查 prod service 的 Cloud Run URL

1
2
3
gcloud run services describe app-prod \
  --region us-east1 \
  --format='value(status.url)'

查 prod service 現在用的 image

1
2
3
gcloud run services describe app-prod \
  --region us-east1 \
  --format='value(spec.template.spec.containers[0].image)'

查目前 custom domain 對應的 service 詳細資訊

1
2
3
gcloud run services describe app-prod \
  --region us-east1 \
  --format='yaml(status.url,traffic)'

結論

第一次把 Cloud Run custom domain 切到正式 prod service, 真正難的通常不是 HCL, 而是 cloud 控制層以外的邊界條件。

最重要的幾件事是:

  • make sure the deploy identity is a real domain owner
  • treat the first prod deploy as a rollout event
  • verify mapping status and certificate status separately
  • remove one-time import scaffolding after cutover

只要把這幾件事分開看, 第一次 prod 啟用就會清楚很多。

References

0%