跳至主要內容
ACE 服務實戰 — 第 3/11 篇

用 Cloud Build 串起測試、建置與 Cloud Run 部署

內文字級

用 Cloud Build 串起測試、建置與 Cloud Run 部署
更新:2026-10-07

Terraform 可以管資源設定;應用程式更新時,還是得測試、建映像、推送並部署。如果每次都由人手動執行這串指令,步驟很容易漏掉,也難查是哪個版本上線。

Cloud Build 可以把這些步驟寫進 cloudbuild.yaml,再由觸發器按條件執行。是否自動部署到正式環境,仍由團隊決定與設定。

這篇會先看 build、step 和 trigger 怎麼配合,再把 Artifact Registry、Secret Manager 與服務帳號權限接進部署流程。最後比較什麼情況適合用 Cloud Build,什麼情況沿用 GitHub Actions。

安全 CI/CD 供應鏈:Commit 經 Trigger 進入 Cloud Build 的 Test、Build、Scan,Artifact Registry 保存帶 Digest 與 Provenance 的不可變產物;Cloud Deploy 將同一 digest 依序推進 Dev、Staging、Approval、Canary、Production 並部署至 Cloud Run 或 GKE;目標傳回 Monitoring/SLO telemetry 以決定 Continue、Pause 或 Rollback,回滾選取 Registry 中已知 digest;Secret Manager 與最小權限 service accounts 提供短效存取,secrets 不嵌入 artifact

安全交付的核心是build once、promote the same digest:測試、掃描與 provenance 都綁在同一個不可變產物,環境推進不重新建置。部署後 telemetry 回到 SLO gate,決定繼續、暫停或讓 Cloud Deploy 取回 Registry 中已知良好的 digest;密碼與 API key 則由 Secret Manager 在執行時提供,不烤進 image。


一、為什麼需要 CI/CD?

手動部署的問題

開發者 → 手動建 Docker Image → 手動推送到 Registry
       → 手動 SSH 到 VM 或執行 gcloud run deploy
       → 手動驗證部署是否成功
       → 如果失敗,手動回滾

這個流程的問題:

  • 容易出錯:手動步驟容易漏執行或順序錯誤
  • 沒有一致性:每個開發者的部署步驟可能不同
  • 無法稽核:誰在什麼時間部署了什麼版本?
  • 速度慢:一個部署可能需要 30 分鐘人工操作

CI/CD 管道的目標

git push → 自動觸發
         → 執行測試(CI)
         → 建立 Docker Image
         → 推送到 Artifact Registry
         → 部署到 Cloud Run(CD)
         → 通知成功或失敗

每一步都自動跑,開發者就能專心寫程式。


二、Cloud Build 核心概念

2.1 什麼是 Cloud Build?

Cloud Build 是 GCP 的全託管建置服務。每個 Build 都跑在自己獨立的 worker 上(每個步驟各是一個容器),彼此不干擾,也能多個 Build 同時跑。

費用部分這裡列清楚:

  • 免費額度:每月 2,500 Build 分鐘免費,預設機型為 e2-standard-2(自 2023-09-01 起,由舊制的每日 120 分鐘 / e2-medium 預設改成現在這樣)
  • 超出計費:預設的 e2-standard-2 是 $0.006/Build 分鐘;若改用較小的 e2-medium 則是 $0.003/Build 分鐘

還有一件事先記在心裡:Cloud Build 會用一個預設服務帳號(service account)替你跑 build,後面授權 Artifact Registry、Cloud Run、Secret Manager 全都圍著這個帳號轉。先有這個概念,等下到 Secret 跟權限章節就不會被它突然冒出來嚇到。(補充:2024 年之後新建的專案,預設可能改用 Compute Engine 的預設服務帳號,第八章會講清楚兩者差別。)

2.2 Build 的三個核心元素

Build(建置任務)
  ├── Steps(建置步驟)— 有序或平行執行的動作
  ├── Triggers(觸發器)— 什麼事件觸發 Build
  └── Artifacts(產物)— Build 的輸出(Docker Image、檔案等)

2.3 Build Step 的結構

每個 Build Step 都會使用指定的容器映像來執行指令:

steps:
  - name: 'CONTAINER_IMAGE' # 要執行的 Docker 映像
    id: 'STEP_ID' # 步驟識別碼(選填)
    args: ['ARG1', 'ARG2'] # 傳給容器的指令和參數
    env: ['KEY=VALUE'] # 環境變數
    dir: 'path/in/workspace' # 工作目錄
    waitFor: ['OTHER_STEP_ID'] # 等待哪些步驟完成後再執行

內建 Builder 映像(Cloud Build 維護的工具容器):

Builder說明
gcr.io/cloud-builders/dockerDocker 指令
gcr.io/google.com/cloudsdktool/cloud-sdkgcloud / gsutil 指令
gcr.io/cloud-builders/gitGit 指令
gcr.io/cloud-builders/npmnpm 指令
gcr.io/cloud-builders/goGo 建置工具

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。


三、Artifact Registry:Docker 映像的正確存放位置

⚠️ 重要公告:Container Registry(gcr.io)已於 2025 年 3 月 18 日關閉,無法再寫入映像;GKE 自 2025 年 6 月 3 日起也無法再讀取存放在 Container Registry 的映像。所有新專案必須使用 Artifact Registry。

3.1 建立 Artifact Registry Docker 倉庫

# 啟用 Artifact Registry API
gcloud services enable artifactregistry.googleapis.com

# 建立 Docker 倉庫(以 asia-east1 為例)
gcloud artifacts repositories create my-docker-repo \
    --repository-format=docker \
    --location=asia-east1 \
    --description="Application Docker images"

# 查看倉庫列表
gcloud artifacts repositories list

3.2 Artifact Registry 映像路徑格式

LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG

# 範例:
asia-east1-docker.pkg.dev/my-project-123/my-docker-repo/my-app:v1.0.0
asia-east1-docker.pkg.dev/my-project-123/my-docker-repo/my-app:latest

對比 Container Registry(舊,已棄用):

# 既有 gcr.io 網址(若已遷到 Artifact Registry,仍可繼續使用)
gcr.io/my-project/my-app:v1.0
asia.gcr.io/my-project/my-app:latest

# 新建一般 Artifact Registry repository 常用的網址
asia-east1-docker.pkg.dev/my-project/my-repo/my-app:v1.0

📝 考場提點

Container Registry 已停止服務,新專案應使用 Artifact Registry。不過 Artifact Registry 也支援 gcr.io 網址;看到映像路徑時,要確認它實際由哪個服務託管,不能只憑網域名稱判斷。

若舊專案仍依賴 Container Registry 的儲存與權限設定,需要檢查映像和自動化流程是否都已遷移;單純改網址或 Region,不能代替這些驗證。

3.3 設定本地 Docker 認證(測試用)

# 設定 Docker 認證到 Artifact Registry
gcloud auth configure-docker asia-east1-docker.pkg.dev

# 測試推送
docker tag my-app:latest asia-east1-docker.pkg.dev/my-project/my-repo/my-app:latest
docker push asia-east1-docker.pkg.dev/my-project/my-repo/my-app:latest

四、撰寫 cloudbuild.yaml

4.1 基本範例:測試 → 建置 → 推送 → 部署

以下是一個完整的 Cloud Build 設定,把 Node.js 應用程式部署到 Cloud Run:

# cloudbuild.yaml

steps:
  # Step 1: 安裝依賴套件
  - name: 'node:24'
    id: 'install'
    entrypoint: 'npm'
    args: ['ci']

  # Step 2: 執行單元測試
  - name: 'node:24'
    id: 'test'
    entrypoint: 'npm'
    args: ['test']
    waitFor: ['install']

  # Step 3: 建立 Docker Image
  - name: 'gcr.io/cloud-builders/docker'
    id: 'build'
    args:
      - 'build'
      - '-t'
      - 'asia-east1-docker.pkg.dev/$PROJECT_ID/my-docker-repo/my-app:$COMMIT_SHA'
      - '-t'
      - 'asia-east1-docker.pkg.dev/$PROJECT_ID/my-docker-repo/my-app:latest'
      - '.'
    waitFor: ['test']

  # Step 4: 推送到 Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    id: 'push'
    args:
      - 'push'
      - '--all-tags'
      - 'asia-east1-docker.pkg.dev/$PROJECT_ID/my-docker-repo/my-app'
    waitFor: ['build']

  # Step 5: 部署到 Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    id: 'deploy'
    args:
      - 'gcloud'
      - 'run'
      - 'deploy'
      - 'my-service'
      - '--image=asia-east1-docker.pkg.dev/$PROJECT_ID/my-docker-repo/my-app:$COMMIT_SHA'
      - '--region=asia-east1'
      - '--allow-unauthenticated'
    waitFor: ['push']

# 聲明 Build 產出的映像(Cloud Build 會記錄到 Build 歷史)
images:
  - 'asia-east1-docker.pkg.dev/$PROJECT_ID/my-docker-repo/my-app:$COMMIT_SHA'
  - 'asia-east1-docker.pkg.dev/$PROJECT_ID/my-docker-repo/my-app:latest'

# 設定 Build 逾時時間(預設 60 分鐘)
timeout: '1200s'

# 選項設定
options:
  logging: CLOUD_LOGGING_ONLY # 日誌只送到 Cloud Logging

4.2 內建替換變數(Substitution Variables)

Cloud Build 自動提供以下變數供 cloudbuild.yaml 使用:

變數說明範例值
$PROJECT_IDGCP 專案 IDmy-project-123456
$BUILD_ID此次 Build 的唯一 IDabc123-def456-...
$COMMIT_SHA完整的 Git commit hasha1b2c3d4e5f6...
$SHORT_SHA前 7 碼 commit hasha1b2c3d
$BRANCH_NAME觸發 Build 的分支名稱main、feature/new-api
$TAG_NAME觸發 Build 的 Git tagv1.0.0
$REPO_NAME倉庫名稱my-app-repo

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

# 使用變數範例
- name: 'gcr.io/cloud-builders/docker'
  args:
    - 'build'
    - '-t'
    - 'asia-east1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'
    - '--label=build-id=$BUILD_ID'
    - '--label=branch=$BRANCH_NAME'
    - '.'

4.3 自訂替換變數

你可以在 cloudbuild.yaml 中定義自己的變數(名稱必須以 _ 開頭):

substitutions:
  _SERVICE_NAME: 'my-service'
  _REGION: 'asia-east1'
  _MIN_INSTANCES: '1'

steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    args:
      - 'gcloud'
      - 'run'
      - 'deploy'
      - '$_SERVICE_NAME'
      - '--region=$_REGION'
      - '--min-instances=$_MIN_INSTANCES'

五、平行執行 Build Steps

5.1 為什麼要平行執行?

預設情況下,Steps 是一個接一個跑。但如果前後端可以各自獨立建置,讓它們平行跑就能省下不少 Build 時間。

5.2 平行執行語法

steps:
  # Step 1 和 Step 2 同時開始(waitFor: ['-'] = 立刻開始)
  - name: 'gcr.io/cloud-builders/docker'
    id: 'build-frontend'
    args:
      [
        'build',
        '-t',
        'asia-east1-docker.pkg.dev/$PROJECT_ID/repo/frontend:$COMMIT_SHA',
        './frontend',
      ]
    waitFor: ['-'] # '-' 表示不等待任何步驟,立刻開始

  - name: 'gcr.io/cloud-builders/docker'
    id: 'build-backend'
    args:
      ['build', '-t', 'asia-east1-docker.pkg.dev/$PROJECT_ID/repo/backend:$COMMIT_SHA', './backend']
    waitFor: ['-'] # 同樣立刻開始,與 build-frontend 平行執行

  # Step 3 等 Step 1 和 Step 2 都完成後才執行
  - name: 'gcr.io/cloud-builders/docker'
    id: 'push-frontend'
    args: ['push', 'asia-east1-docker.pkg.dev/$PROJECT_ID/repo/frontend:$COMMIT_SHA']
    waitFor: ['build-frontend']

  - name: 'gcr.io/cloud-builders/docker'
    id: 'push-backend'
    args: ['push', 'asia-east1-docker.pkg.dev/$PROJECT_ID/repo/backend:$COMMIT_SHA']
    waitFor: ['build-backend']

  # 最後的部署等兩個推送都完成
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    id: 'deploy'
    args:
      [
        'gcloud',
        'run',
        'deploy',
        'my-service',
        '--region=asia-east1',
        '--image=asia-east1-docker.pkg.dev/$PROJECT_ID/repo/backend:$COMMIT_SHA',
      ]
    waitFor: ['push-frontend', 'push-backend']

執行流程示意:

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  S["Build 開始"] --> F["build-frontend<br/>waitFor: ['-']"]
  S --> B["build-backend<br/>waitFor: ['-']"]
  F --> PF["push-frontend<br/>waitFor: build-frontend"]
  B --> PB["push-backend<br/>waitFor: build-backend"]
  PF --> J{"兩個 push<br/>都完成?"}
  PB --> J
  J -->|"是"| D["deploy<br/>waitFor: 兩個 push"]
  J -->|"任一步驟失敗"| X["Build 失敗,不部署"]
waitFor: ['-'] 讓兩條建置分支同時開始;deploy 是 join point,只有前後端推送都成功才會執行。

六、Secret Manager 整合:安全管理建置密碼

6.1 為什麼不能把密碼放在 cloudbuild.yaml?

cloudbuild.yaml 是放在 git 倉庫裡的,任何有倉庫存取權的人都看得到。所以密碼絕對不能硬寫在這裡。

6.2 使用 Secret Manager 注入密碼

前置作業:把密碼存到 Secret Manager

# 建立 Secret
gcloud secrets create db-password \
    --replication-policy="automatic"

# 設定 Secret 的值
echo -n "my-super-secret-db-password" | \
    gcloud secrets versions add db-password --data-file=-

# 授予 Cloud Build SA 讀取 Secret 的權限(這裡用 legacy SA;新專案請換成實際執行 Build 的服務帳號,見 8.1)
gcloud secrets add-iam-policy-binding db-password \
    --member="serviceAccount:$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')@cloudbuild.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

在 cloudbuild.yaml 中使用 Secret:

# 聲明 Secret 來源
availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/db-password/versions/latest
      env: 'DB_PASSWORD' # secretEnv 名稱,不需底線前綴
    - versionName: projects/$PROJECT_ID/secrets/api-key/versions/latest
      env: 'API_KEY'

steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    id: 'run-migration'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        gcloud run jobs execute db-migration \
          --region=asia-east1 \
          --update-env-vars DB_PASSWORD=$$DB_PASSWORD
    secretEnv: ['DB_PASSWORD'] # 宣告此步驟使用的 Secret 變數

  - name: 'node:24'
    id: 'integration-test'
    entrypoint: 'bash'
    args:
      - '-c'
      - 'API_KEY=$$API_KEY npm run test:integration'
    secretEnv: ['API_KEY']
查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

sequenceDiagram
  participant B as Cloud Build
  participant SA as Build Service Account
  participant SM as Secret Manager
  participant S as 指定 Build Step
  B->>SA: 以建置服務帳號執行
  SA->>SM: 請求指定 secret version
  SM->>SM: 驗證 secretAccessor 權限
  alt 權限通過
    SM-->>S: 以 secretEnv 注入到該 Step
    S->>S: bash 以 $$VARIABLE 在執行期展開
    S-->>B: 回傳成功/失敗,不把值寫入設定
  else 權限不足
    SM-->>B: PERMISSION_DENIED,停止 Build
  end
availableSecrets 宣告來源,secretEnv 限定可見的步驟;雙錢字號把展開時機留給容器內的 shell,避免被 Cloud Build substitutions 提前處理。

💡 重要語法:

  • Secret 環境變數名稱不需要以底線開頭,可直接用一般命名如 DB_PASSWORD、API_KEY(官方範例就用 PASSWORD、USERNAME、GH_TOKEN 等)。需要以底線 _ 開頭的是使用者自訂的替換變數(substitutions)(如 _DEPLOY_ENV),兩者是不同概念,別混淆。
  • 在 bash 腳本中引用時用雙錢字號 $$DB_PASSWORD(單個 $ 是 Cloud Build 替換,$$ 才會跳脫成字面 $,讓 bash 在執行期取得 Cloud Build 注入的 Secret 值)

七、設定觸發器(Triggers)

7.1 觸發器類型

觸發器類型使用場景
Push to branch推送到 main 分支自動部署
Push to tag打 v1.0.0 tag 觸發 Production 部署
Pull requestPR 開啟時執行測試(不部署)
Manual手動從 Console 觸發
Pub/Sub接收 Pub/Sub 訊息觸發
Webhook接收 HTTP 請求觸發

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

Cloud Build 建立觸發條件表單,事件類型可選推送至分支版本、推送新標記、提取要求(PR)、手動叫用、Pub/Sub 訊息、Webhook 事件;來源可選 Cloud Build 存放區或 Developer Connect,存放區代別分第 1 代與第 2 代

上面那張表的六種觸發器,在 Console 裡就是這一排單選鈕:推送至分支版本、推送新標記、提取要求(PR)、手動叫用、Pub/Sub、Webhook——你選哪一種,就決定了 cloudbuild.yaml 裡能拿到哪些替換變數(選分支才有 $BRANCH_NAME、選標記才有 $TAG_NAME)。要做到「只在 main 自動部署」,就是選第一顆「推送至分支版本」再把分支填成 ^main$;選「提取要求(PR)」則照慣例只跑測試、不部署。另外別漏了下方的「存放區代別」——第 1 代是舊的、第 2 代才是官方現在推薦的(7.2 連 GitHub 用的就是它),新專案接倉庫記得挑第 2 代。

7.2 連接 GitHub 倉庫(第二代,推薦)

# Step 1: 連接 GitHub 倉庫(第二代)
# 在 GCP Console → Cloud Build → Repositories → 2nd gen 分頁,
# 建立 host connection(會安裝 Cloud Build GitHub App)並連結倉庫

# Step 2: 建立 Push to Main 觸發器
# 第 2 代倉庫用 --repository 與 --region(舊的 --repo-name/--repo-owner 是第 1 代寫法);
# 新專案的預設服務帳號不是 legacy SA,建立觸發器時要用 --service-account 指定(見第八章)
gcloud builds triggers create github \
    --name="deploy-on-push-to-main" \
    --repository="projects/my-project/locations/asia-east1/connections/my-connection/repositories/my-app-repo" \
    --region="asia-east1" \
    --branch-pattern="^main$" \
    --build-config="cloudbuild.yaml" \
    --service-account="projects/my-project/serviceAccounts/custom-build-sa@my-project.iam.gserviceaccount.com" \
    --description="Deploy to Cloud Run on push to main"

# Step 3: 建立 PR 測試觸發器(預設要有協作者在 PR 留言 /gcbrun 才會跑;要每個 PR 自動跑請加 --comment-control=COMMENTS_DISABLED)
gcloud builds triggers create github \
    --name="test-on-pull-request" \
    --repository="projects/my-project/locations/asia-east1/connections/my-connection/repositories/my-app-repo" \
    --region="asia-east1" \
    --pull-request-pattern=".*" \
    --build-config="cloudbuild.pr.yaml" \
    --service-account="projects/my-project/serviceAccounts/custom-build-sa@my-project.iam.gserviceaccount.com" \
    --description="Run tests on pull request"

7.3 多環境部署策略

常見的分支策略:

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  F["feature/*"] --> PR["Pull Request Trigger<br/>測試與 lint,不部署"]
  PR --> D["develop"]
  D --> S["staging"]
  S --> TS["Staging Trigger"]
  TS --> BS["建置並部署<br/>Staging Service"]
  S --> M["main"]
  M --> TP["Production Trigger"]
  TP --> G["建置、測試與審批 Gate"]
  G --> BP["部署 Production Service"]
觸發器用 branch pattern 決定何時啟動,cloudbuild.yaml 再依 BRANCH_NAME 選擇目標;PR 路徑只驗證,不應直接部署正式環境。
steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    id: 'deploy'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        if [ "$BRANCH_NAME" = "main" ]; then
          gcloud run deploy my-app-prod \
            --image=asia-east1-docker.pkg.dev/$PROJECT_ID/repo/my-app:$COMMIT_SHA \
            --region=asia-east1
        elif [ "$BRANCH_NAME" = "staging" ]; then
          gcloud run deploy my-app-staging \
            --image=asia-east1-docker.pkg.dev/$PROJECT_ID/repo/my-app:$COMMIT_SHA \
            --region=asia-east1
        fi

八、服務帳號與最小權限設定

8.1 Cloud Build 預設服務帳號

Cloud Build 會用一個預設服務帳號替你跑 Build,而「預設是哪一個」在 2024 年改過,新手最容易在這裡踩坑,先講清楚:

  • 新專案(現行預設):Google 在 2024 年 5~6 月分批改了行為,新專案的第一個 Build 之後,Cloud Build 預設改用 Compute Engine 預設服務帳號:

    {PROJECT_NUMBER}-compute@developer.gserviceaccount.com
  • 既有專案(舊行為):如果你的專案在這次變更之前就已經跑過第一個 Build,它會沿用舊的 legacy 服務帳號,格式是:

    {PROJECT_NUMBER}@cloudbuild.gserviceaccount.com

換句話說,判斷依據不是「專案哪年建的」,而是「第一個 Build 是在變更前還是變更後跑的」。所以如果你在新專案裡照著 @cloudbuild.gserviceaccount.com 找半天找不到,先別懷疑自己——去 Cloud Build 設定看一下實際是哪一個 SA 在跑 build,下面的授權對象就綁那一個。(官方說明:Cloud Build default service account change)

8.2 常見任務所需的 IAM 角色

# 取得 PROJECT_NUMBER
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
CLOUDBUILD_SA="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com"  # 新專案請換成 ${PROJECT_NUMBER}-compute@developer.gserviceaccount.com 或你的自訂服務帳號(見 8.1)

# 允許推送到 Artifact Registry
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/artifactregistry.writer"

# 允許部署到 Cloud Run
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/run.admin"

# 允許讓 Cloud Run 使用指定的 Service Account(Cloud Run 部署需要)
gcloud iam service-accounts add-iam-policy-binding \
    cloud-run-sa@${PROJECT_ID}.iam.gserviceaccount.com \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/iam.serviceAccountUser"

# 允許讀取 Secret Manager 的 Secret
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/secretmanager.secretAccessor"

📝 考場提點

Cloud Build 部署 Cloud Run 時跳 PERMISSION_DENIED,是 Cloud Build 部署時很典型的錯誤。答案要同時抓兩個角色:

  • roles/run.admin:讓 Build SA 能管理 Cloud Run 服務(建立、更新、部署)
  • roles/iam.serviceAccountUser:讓 Build SA 能把 Cloud Run 的 runtime 服務帳號「附加到服務」(actAs)

只選 run.admin、漏掉 serviceAccountUser 是容易掉進去的陷阱選項——少了後者,部署時就會卡在無法把 runtime SA 附加到服務(actAs)。另外提醒:新專案的預設 Build SA 可能是 Compute Engine 預設 SA(見 8.1),授權對象別綁到那個已經不在跑 build 的舊 SA 上。

GCP IAM 角色選擇選單,上方是「基本」角色 Owner/Editor/Viewer,下方展開大量 predefined 預先定義角色,指派給服務帳號時從這裡挑選

在 Console 裡把角色指派給 Cloud Build 服務帳號,就是在這個選單做取捨。上面「基本」那三顆(Owner/Editor/Viewer)權限大到危險——給一個 Editor 幾乎等於把整個專案交出去;『最小權限』的做法是往下挑 predefined 預先定義角色,像你前面授的 run.admin、artifactregistry.writer,剛好夠用、不多給一分。萬一這個 SA 被入侵,攻擊者能碰的也就只有你勾的那幾格。

8.3 使用自訂服務帳號(最小權限原則)

# cloudbuild.yaml 中指定自訂服務帳號
serviceAccount: 'projects/my-project/serviceAccounts/custom-build-sa@my-project.iam.gserviceaccount.com'

steps:
  - name: '...'
    args: [...]

options:
  logging: CLOUD_LOGGING_ONLY # 指定自訂服務帳號時必須自己指定日誌去處

九、檢視 Build 歷史與除錯

9.1 檢視 Build 歷史

# 列出最近的 Build
gcloud builds list --limit=10

# 查看特定 Build 的詳細資訊
gcloud builds describe BUILD_ID

# 串流顯示 Build 日誌(等待 Build 完成)
gcloud builds log --stream BUILD_ID

# 手動觸發 Build(不需要 push)
gcloud builds submit --config=cloudbuild.yaml .

9.2 推送前先手動送出 Build 測試 cloudbuild.yaml

在推送到 GitHub 前,可以先用 gcloud builds submit 從本機手動送出一次 Build(實際仍在 Cloud Build 上執行):

# 上傳當前目錄的程式碼,執行 cloudbuild.yaml
gcloud builds submit --config=cloudbuild.yaml .

# 手動指定內建替換變數的值(本機送出不會像觸發器那樣自動帶入)
gcloud builds submit --config=cloudbuild.yaml \
    --substitutions=BRANCH_NAME=test,COMMIT_SHA=local-test .

9.3 常見錯誤與解決方式

錯誤原因解決方式
permission denied 推送映像Cloud Build SA 無 Artifact Registry 寫入權限授予 artifactregistry.writer 角色
Cloud Run 部署失敗Cloud Build SA 無 Cloud Run 管理權限授予 run.admin 和 iam.serviceAccountUser 角色
Secret 讀取失敗未授予 secretmanager.secretAccessor對特定 Secret 或整個專案授予此角色
Build 逾時預設 60 分鐘逾時在 cloudbuild.yaml 設定 timeout: '1800s'

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。


十、GitHub Actions vs Cloud Build

兩者都能做 GCP 的 CI/CD,怎麼選就看你的情境:

比較面向Cloud BuildGitHub Actions
最適合GCP 原生整合、需要存取私有 VPC程式碼在 GitHub、跨平台部署
GCP 整合原生支援,直接使用 GCP 服務需自行設定認證(官方建議用 Workload Identity Federation)
私有網路支援(透過 Private Pools)自架 runner 可放進自己的網路;託管 runner 只有 larger runner 可接 Azure VNET
社群資源較小龐大(數以萬計的公開 Actions)
免費額度每月 2,500 分鐘GitHub 提供的 runner 分鐘數
學習曲線中等較低(有大量範例)

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

表格裡的 Workload Identity Federation(WIF) 是讓 GitHub 不用存任何 GCP 金鑰、就能換取 GCP 短期憑證來部署的機制。它避免你把長期 service account 金鑰塞進 GitHub Secrets(金鑰外洩風險高);細節不必背,知道「GitHub Actions 要打 GCP,官方建議用 WIF 而不是貼金鑰」就夠了。

實務上常見的混搭:GitHub Actions 跑 CI,Cloud Build 跑 CD

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  PR["Pull Request"] --> GH["GitHub Actions CI"]
  GH --> T["Unit Test/Lint/PR<br/>Checks"]
  T --> G{"CI 通過並合併?"}
  G -->|"否"| X["阻擋合併"]
  G -->|"是"| W["以 Workload Identity Federation<br/>取得短效 GCP 憑證"]
  W --> CB["觸發 Cloud Build CD"]
  CB --> AR["建置並推送<br/>Artifact Registry"]
  AR --> M["執行 Migration/<br/>部署前檢查"]
  M --> D["部署 Cloud Run/GKE"]
GitHub Actions 處理靠近 PR 的品質檢查,Cloud Build 接手 GCP 內的產物與部署;跨平台認證使用 WIF 短效憑證,不保存 service account key。

十一、ACE 考試重點

現行 ACE 考綱沒有點名 Cloud Build,但 Section 3.1 列了「Deploying new versions of a Cloud Run application」和「Configuring GKE to access Artifact Registry」,這一章的題型可以對著這兩項準備。

類型 1:Container Registry vs Artifact Registry

題目:你在 cloudbuild.yaml 中使用 gcr.io/my-project/my-app:latest 作為映像路徑。這個做法有什麼問題?

  • ✅ Container Registry 已棄用,應改用 Artifact Registry(asia-east1-docker.pkg.dev/my-project/my-repo/my-app:latest)

類型 2:服務帳號權限

題目:Cloud Build 執行 gcloud run deploy 時出現 PERMISSION_DENIED 錯誤。最可能的原因是?

  • ✅ Cloud Build 的服務帳號缺少 roles/run.admin 角色,或缺少 roles/iam.serviceAccountUser(用於指定 Cloud Run 的服務帳號)

類型 3:平行建置

題目:你的 cloudbuild.yaml 有前端和後端兩個獨立的 Docker 建置步驟,預設會依序執行。如何讓它們同時執行?

  • ✅ 在兩個步驟加上 waitFor: ['-'],讓它們立即並行啟動

類型 4:觸發器設定

題目:你希望只在推送到 main 分支時才自動部署到生產環境,其他分支的 push 不觸發部署。如何設定?

  • ✅ 建立 Cloud Build Trigger,Branch 設定為 ^main$(正規表達式精確匹配)

類型 5:Secret 管理

題目:Cloud Build 的 cloudbuild.yaml 需要使用資料庫密碼,如何安全地傳入?

  • ❌ 直接把密碼寫在 cloudbuild.yaml
  • ❌ 存到 git 倉庫的 .env 檔案
  • ✅ 存到 Secret Manager,在 availableSecrets 中引用,用 secretEnv 注入到 Build Step

十二、完整實戰流程總結

# === 一次性設定 ===

# 1. 建立 Artifact Registry
gcloud artifacts repositories create my-docker-repo \
    --repository-format=docker \
    --location=asia-east1

# 2. 授予執行 Build 的服務帳號必要權限
#    新專案預設是 Compute Engine 預設服務帳號;2024 年變更前就跑過 Build 的舊專案才是
#    ${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com(見 8.1)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID \
    --format='value(projectNumber)')
CLOUDBUILD_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/artifactregistry.writer"

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/run.admin"

# 讓 Build 能把日誌寫進 Cloud Logging
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/logging.logWriter"

# 部署 Cloud Run 還要能把 runtime 服務帳號附加到服務(actAs);
# cloudbuild.yaml 沒另外指定,runtime 就是 Compute Engine 預設服務帳號
gcloud iam service-accounts add-iam-policy-binding \
    "${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
    --member="serviceAccount:${CLOUDBUILD_SA}" \
    --role="roles/iam.serviceAccountUser"

# 3. 建立 GitHub 連接(在 GCP Console 操作)
# Cloud Build → Repositories → 2nd gen → Create host connection,再連結倉庫

# 4. 建立觸發器(第 2 代倉庫)
gcloud builds triggers create github \
    --name="deploy-on-push-main" \
    --repository="projects/${PROJECT_ID}/locations/asia-east1/connections/my-connection/repositories/my-app" \
    --region="asia-east1" \
    --branch-pattern="^main$" \
    --build-config="cloudbuild.yaml" \
    --service-account="projects/${PROJECT_ID}/serviceAccounts/${CLOUDBUILD_SA}"

# === 之後每次 push 都自動執行 ===
git add .
git commit -m "feat: add new feature"
git push origin main
# → 自動觸發 Cloud Build
# → 測試 → 建置 → 推送 → 部署

總結

概念要記住的重點
Artifact RegistryContainer Registry 已棄用,必須用 Artifact Registry
映像路徑格式LOCATION-docker.pkg.dev/PROJECT/REPO/IMAGE:TAG
Secret 變數用 secretEnv 宣告,腳本中以雙錢字號 $$SECRET_NAME 引用(名稱不需底線前綴;底線前綴是「自訂替換變數 substitutions」的規則,如 _MY_VAR)
平行執行waitFor: ['-'] 讓步驟立刻開始,不等前一步
服務帳號需要 artifactregistry.writer + run.admin + serviceAccountUser(PERMISSION_DENIED 排查見第八章考場提點)
免費額度每月 2,500 Build 分鐘免費(預設機型 e2-standard-2)
觸發器^main$ 正規表達式精確匹配分支名稱

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

系列文章連結


延伸閱讀


🎓 本文是「GCP 精通之路」系列的 ACE-208 篇(進階篇),掌握 Cloud Build CI/CD 後,建議繼續學習 GCP 企業上雲遷移策略(ACE-301)。

下一課 GCP-108:Cloud SQL 入門,學習托管關聯式資料庫的完全指南。

ACE 服務實戰 — 第 3 篇/共 11 篇 查看系列全覽 →

讀完後,留一個學習紀錄

試著用自己的話說出一個重點,以及你會如何運用。實作文章請先核對結果與清理資源,再標記為讀完。

文章進度以你確認讀完的標記為準,可隨時取消。舊版造訪紀錄保留,但不計入讀完;這份自評不代表已掌握內容。

留言討論

文章目錄與系列

徽章解鎖!