Skip to main content

GitHub Actions 自動部署(CD)到 GCP Cloud Run 完整教學

本文聚焦在 「程式碼推上 GitHub → 自動 build → 自動部署到 Cloud Run」 這條 CD 管線的設定。

⚠️ 安全提醒:文中所有專案 ID、專案編號、Service Account email、WIF 路徑等敏感資訊,都以佔位符(<...>)取代。實際操作時請替換成自己的值,切勿把明碼寫進公開文件。


整體流程

整條 CD 管線只要一個 git push 就會自動跑完:

git push origin main
        │
        ▼
   GitHub Actions 觸發
        │
        ├─ ① 透過 WIF 認證 GCP(免金鑰)
        ├─ ② docker build 映像檔
        ├─ ③ docker push 到 Artifact Registry
        └─ ④ gcloud run deploy 部署到 Cloud Run
        │
        ▼
   瀏覽器開 Cloud Run URL 看到網站 🎉

💡 核心觀念:用 Workload Identity Federation(WIF) 建立 GitHub 與 GCP 的信任關係,GitHub Actions 不需要持有任何金鑰即可取得部署權限。


前置準備

1. 啟用必要的 GCP API

於 GCP Console 啟用以下 API:

  • Cloud Run Admin API
  • Artifact Registry API
  • Cloud Build API
  • Service Networking API
  • Secret Manager API

2. 建立 Artifact Registry(存放 Docker 映像檔)

設定 值
Format Docker
Region asia-east1
Mode Standard

完整路徑格式:

asia-east1-docker.pkg.dev/<PROJECT_ID>/<REPOSITORY>

3. 建立 Service Account 並授予角色

建立一個專供 GitHub 推送/部署使用的 Service Account,逐一加上以下 3 個角色:

角色 用途
Artifact Registry 寫入者(Writer) 推送映像檔(⚠️ 不是「服務代理」)
Cloud Run 管理員 部署服務
服務帳戶使用者 部署時模擬執行身分

設定 Workload Identity Federation(免金鑰認證)

建立 Workload Identity Pool

  • Pool ID:github-pool

新增 OIDC Provider

設定 值
Provider type OpenID Connect (OIDC)
Provider Name github-provider
Issuer URL https://token.actions.githubusercontent.com

屬性對應(Attribute Mapping)

Google OIDC
google.subject assertion.sub
attribute.repository assertion.repository
attribute.repository_owner assertion.repository_owner

屬性條件(關鍵安全設定)

限制只有自己的 GitHub 帳號/組織能使用此 Provider:

assertion.repository_owner == '<GITHUB_OWNER>'

綁定 Service Account 與 WIF

到 Service Account 的 「具備存取權的主體(Principals with access)」 分頁,點 「授予存取權」,新增主體:

principalSet://iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/github-pool/attribute.repository/<GITHUB_OWNER>/<REPO_NAME>

角色:Workload Identity 使用者

📌 容易搞混的地方:「這個 SA 能做什麼」要在 IAM 頁編輯;「誰能用這個 SA」要在 SA 的「具備存取權的主體」頁編輯。


GitHub Secrets 設定

到 GitHub repo 的 Settings → Secrets and variables → Actions,新增 3 個 Secrets(實際值請填自己的,勿外流):

Name 內容說明
GCP_PROJECT_ID 你的 GCP 專案 ID
GCP_WIF_PROVIDER projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/github-pool/providers/github-provider
GCP_SA_EMAIL 部署用 Service Account 的 email

GitHub Actions Workflow

於 .github/workflows/push-image.yml:

name: Build, Push and Deploy to Cloud Run

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  id-token: write   # WIF 取得 OIDC token 必須

env:
  REGION: asia-east1
  REPOSITORY: <REPOSITORY>
  IMAGE_NAME: <IMAGE_NAME>
  SERVICE_NAME: <SERVICE_NAME>

jobs:
  build-push-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
          service_account: ${{ secrets.GCP_SA_EMAIL }}

      - name: Set up gcloud
        uses: google-github-actions/setup-gcloud@v2

      - name: Configure Docker auth
        run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet

      - name: Build Docker image
        run: |
          docker build -t ${{ env.REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .

      - name: Push to Artifact Registry
        run: |
          docker push ${{ env.REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy ${{ env.SERVICE_NAME }} \
            --image=${{ env.REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            --region=${{ env.REGION }} \
            --platform=managed \
            --allow-unauthenticated \
            --port=8080 \
            --memory=256Mi \
            --cpu=1 \
            --min-instances=0 \
            --max-instances=2

      - name: Show service URL
        run: |
          URL=$(gcloud run services describe ${{ env.SERVICE_NAME }} --region=${{ env.REGION }} --format='value(status.url)')
          echo "Service URL: $URL"

觸發部署

git add .
git commit -m "feat: setup CI/CD pipeline"
git push origin main

推送後,到 GitHub repo 的 Actions 分頁看執行狀況;跑完後開 Cloud Run 配發的 URL 即可看到部署結果。


踩過的坑(CD 相關)

🕳️ 坑 1:組織政策禁止建立 SA 金鑰

錯誤訊息

服務帳戶金鑰建立功能已停用
組織政策:iam.disableServiceAccountKeyCreation

解法:改用 Workload Identity Federation,完全不用金鑰 —— 這其實是更安全的業界最佳實踐。

🕳️ 坑 2:Artifact Registry 角色加錯

錯誤訊息

denied: Permission 'artifactregistry.repositories.uploadArtifacts' denied
錯誤的角色 正確的角色
Artifact Registry 服務代理(Service Agent) Artifact Registry 寫入者(Writer)

🕳️ 坑 3:授予存取權的入口位置

要在 SA 詳情頁分頁列最右邊的「具備存取權的主體 / Principals with access」,才會看到正確的「+ 授予存取權」按鈕,而不是「權限」分頁。

🕳️ 坑 4:Cloud Run 要求容器必須監聽 HTTP port

只「跑完就結束」的容器會一直重啟。Cloud Run 是「無狀態 HTTP 服務」,容器必須監聽 port(預設 8080)。


安全提醒

  • 🔒 切勿把專案 ID、專案編號、SA email、WIF 完整路徑等寫進公開文件;統一以 Secrets 管理。
  • 🔒 WIF 的屬性條件(repository_owner)務必設定,避免其他人的 repo 冒用你的 Provider。
  • 🔒 permissions: id-token: write 是 WIF 運作的必要條件,別漏掉。