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

Cloud Build CI/CD 實戰:從程式碼到生產環境全自動化

ACE-208

「好的 CI/CD 管道讓你能自信地每天部署十次,而不是每月提心吊膽地部署一次。」

你已經學會用 Terraform 管理基礎設施(ACE-207),但應用程式本身怎麼部署?每次更新都要手動 docker builddocker pushgcloud run deploy

這正是 Cloud Build 要解決的問題。它是 GCP 的全託管 CI/CD 服務,你只要推一個 git commit,程式碼就會自動跑完測試、打包,然後部署到生產環境。

你將學到:

  • ✅ Cloud Build 核心概念:builds、steps、triggers
  • ✅ 撰寫 cloudbuild.yaml:建立完整的 CI/CD 管道
  • ✅ Artifact Registry:Docker 映像的正確管理方式(注意:Container Registry 已棄用)
  • ✅ 連接 GitHub 設定自動觸發
  • ✅ 平行執行 Build Steps 加速建置
  • ✅ Secret Manager 整合:安全管理建置密碼
  • ✅ 服務帳號與最小權限設定
  • ✅ GitHub Actions vs Cloud Build 選型

一、為什麼需要 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 在獨立的 Docker 容器中執行
  • 並行處理:支援多個 Build 同時執行
  • 免費額度:每天 120 Build 分鐘免費(預設 e2-medium 機型)
  • 超出計費:預設 e2-medium $0.003/Build 分鐘;e2-standard-2 為 $0.006/Build 分鐘

2.2 Build 的三個核心元素

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

2.3 Build Step 的結構

每個 Build Step 本質上是「執行一個 Docker 容器」:

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 日停止寫入存取,並自 2025 年 6 月 3 日起停止讀取存取(無法再拉取映像);自 2025 年 10 月 14 日起,gcr.io 端點將改由 Artifact 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/my-project/my-app:v1.0
asia.gcr.io/my-project/my-app:latest

# ✅ 新格式(Artifact Registry)
asia-east1-docker.pkg.dev/my-project/my-repo/my-app:v1.0

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:20'
    id: 'install'
    entrypoint: 'npm'
    args: ['ci']

  # Step 2: 執行單元測試
  - name: 'node:20'
    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'
      - '--platform=managed'
      - '--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 的分支名稱mainfeature/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']

執行流程示意

時間軸:
t=0    build-frontend ──────────────────┐
t=0    build-backend  ─────────────────┐│
                                       ││
t=3m   push-frontend ◄─── 完成後立刻 ──┘│
t=3m   push-backend  ◄─── 完成後立刻 ───┘

t=4m   deploy        ◄────── 兩者完成後 ─┘

六、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 的權限
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:20'
    id: 'integration-test'
    entrypoint: 'bash'
    args:
      - '-c'
      - 'API_KEY=$$API_KEY npm run test:integration'
    secretEnv: ['API_KEY']

💡 重要語法

  • Secret 環境變數名稱不需要以底線開頭,可直接用一般命名如 DB_PASSWORDAPI_KEY(官方範例就用 PASSWORDUSERNAMEGH_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 請求觸發

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

# Step 1: 連接 GitHub 倉庫(推薦使用 Cloud Build GitHub App)
# 前往 GitHub Marketplace 安裝 Google Cloud Build App,
# 或在 GCP Console → Cloud Build → Repositories 中操作

# Step 2: 建立 Push to Main 觸發器
gcloud builds triggers create github \
    --name="deploy-on-push-to-main" \
    --repo-name="my-app-repo" \
    --repo-owner="my-github-username" \
    --branch-pattern="^main$" \
    --build-config="cloudbuild.yaml" \
    --description="Deploy to Cloud Run on push to main"

# Step 3: 建立 PR 測試觸發器
gcloud builds triggers create github \
    --name="test-on-pull-request" \
    --repo-name="my-app-repo" \
    --repo-owner="my-github-username" \
    --pull-request-pattern=".*" \
    --build-config="cloudbuild.pr.yaml" \
    --description="Run tests on pull request"

7.3 多環境部署策略

常見的分支策略:

分支策略(Gitflow 簡化版):

feature/* ──→ develop ──→ staging ──→ main
                              │            │
                              ↓            ↓
                    部署到 Staging    部署到 Production

cloudbuild.yaml 中根據 $BRANCH_NAME 決定部署目標:
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:

{PROJECT_NUMBER}@cloudbuild.gserviceaccount.com

8.2 常見任務所需的 IAM 角色

# 取得 PROJECT_NUMBER
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
CLOUDBUILD_SA="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com"

# 允許推送到 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"

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

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

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

九、檢視 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 本地測試 cloudbuild.yaml

在推送到 GitHub 前,可以用 gcloud builds submit 在本地測試:

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

# 只測試特定步驟(使用 substitutions 覆蓋預設值)
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.adminiam.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)不直接支援
Binary Authorization原生支援不支援
社群資源較小龐大(2 萬+ 公開 Actions)
免費額度120 分鐘/天GitHub 提供的 runner 分鐘數
學習曲線中等較低(有大量範例)

混合使用(2025 最佳實踐)

GitHub Actions(CI)→ Cloud Build(CD)

GitHub Actions 負責:
  - 執行單元測試、lint
  - PR 審查檢查

Cloud Build 負責:
  - 建立並推送 Docker Image 到 Artifact Registry
  - 部署到 Cloud Run / GKE
  - 執行資料庫 Migration

十一、ACE 考試重點

Cloud Build 和 CI/CD 是 ACE 考試中「自動化與 DevOps」域的重點。

類型 1:Container Registry vs Artifact Registry

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

  • 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. 授予 Cloud Build SA 必要權限
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID \
    --format='value(projectNumber)')
CLOUDBUILD_SA="${PROJECT_NUMBER}@cloudbuild.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"

# 3. 建立 GitHub 連接(在 GCP Console 操作)
# Cloud Build → Repositories → Connect Repository

# 4. 建立觸發器
gcloud builds triggers create github \
    --name="deploy-on-push-main" \
    --repo-name="my-app" \
    --repo-owner="my-github-user" \
    --branch-pattern="^main$" \
    --build-config="cloudbuild.yaml"

# === 之後每次 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
免費額度每天 120 Build 分鐘免費
觸發器^main$ 正規表達式精確匹配分支名稱

系列文章連結


延伸閱讀


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

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

ACE 服務實戰 — 3/11 完成 查看系列全覽 →

留言討論

徽章解鎖!