iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Build on Google AI

單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?系列 第 12

Day 12 | 把 Docker Image 戴上手銬!部署 Cloud Run 與安全金鑰管理

  • 分享至 

  • xImage
  •  

前言:把本地的模擬,轉化為雲端上的真實場景

經過前 11 天的淬鍊,Kakeru AI 教練 API 已經在本地端化身為一個極度輕量、安全的 Docker Image。

今天要讓他戴上手銬了。什麼他做錯什麼事!?

其實是上 Cloud(上銬),上雲端的部分。(🙄🙄🙄)

回顧 Day 3 我們討論過的架構選型,我們捨棄了需要自行管理伺服器的 Compute Engine (GCE),選擇了能自動擴展、按次計費的無伺服器容器服務 Cloud Run。今天我們將與 AI 協作,實作完整的部署:先將 Image 推送到專屬的雲端倉庫 Artifact Registry (AR),接著部署到 Cloud Run,並呼應 Day 7 的資安精神,優雅地將 Secret Manager 的金鑰與 IAM ADC (Application Default Credentials) 存取權限無縫掛載進去!

觀念解說:什麼是 Artifact Registry (AR)?

在本地端打包好的 Docker Image,總不可能用隨身碟傳到雲端伺服器吧?我們需要一個中繼站。

  • GitHub 是存放專案原始碼 (Source Code) 的倉庫。
  • Docker Hub 是存放公開 Docker Image 的全球倉庫。
  • Google Cloud Artifact Registry (AR) 就是你專屬的私有企業級 Docker 保險箱。

將 Image 推送到 AR 後,Cloud Run 就能以 Google Cloud 內網極速將其拉取並進行自動化部署與擴展。

動手實作 1:將 Image 推送至 Artifact Registry

步驟 0:Google Cloud 帳號登入與 CLI 身份驗證 (Prerequisite)

在進行任何 Google Cloud 操作與 API 啟用前,必須先確保系統的 gcloud CLI 已完成 Google 帳號授權登入:

# 1. 執行 Google Cloud 帳號登入(會自動開啟瀏覽器進行 OAuth 授權)
gcloud auth login

# 2. 驗證當前已登入與生效的帳號列表
gcloud auth list

步驟 1:在 Google Cloud 啟用 API 並建立存放區 (Repository)

我們可以在 Google Cloud Console 介面操作,或是直接使用 gcloud CLI 進行設定:

# 1. 切換專案為 kakeru-ai
gcloud config set project kakeru-ai

# 2. 啟用 Cloud Run, Artifact Registry 與 Secret Manager API
gcloud services enable artifactregistry.googleapis.com run.googleapis.com secretmanager.googleapis.com

# 3. 在 asia-east1 (台灣) 建立 Docker 格式的存放區 kakeru-ai-repo
gcloud artifacts repositories create kakeru-ai-repo \
    --repository-format=docker \
    --location=asia-east1 \
    --description="Kakeru AI Docker Repository"
  • 指令意義
    • -repository-format=docker:指定倉庫用於存放 Docker 容器映像檔。
    • -location=asia-east1:將倉庫設在台灣(asia-east1)機房,以取得最低的網路延遲與最快傳輸速度。

步驟 2:設定本地端 Docker 驗證與跨平台打包

為了讓本地端的 Docker 有權限推送到 Google Cloud,我們需要執行一次憑證驗證指令:

# 設定 Docker 使用 gcloud 憑證助手存取 asia-east1-docker.pkg.dev
gcloud auth configure-docker asia-east1-docker.pkg.dev

【重大踩坑避雷:Apple Silicon / Mac M 系列跨平台打包】

如果使用的是 Apple Silicon (M1/M2/M3/M4) Mac 晶片,預設 docker build 會產出 linux/arm64 架構的 Image。然而 Cloud Run 的 Serverless 執行環境是 x86_64 (linux/amd64)。若直接推送 ARM64 Image,Cloud Run 啟動時會報錯: Application failed to start: failed to load /opt/java/openjdk/bin/java: exec format error

因此在編譯打標籤時,必須明確指定 --platform linux/amd64:

# 針對 Cloud Run x86_64 進行跨平台打包並設定 Google Cloud 標籤 (Tag)
# 格式:[區域]-docker.pkg.dev/[專案ID]/[存放區名稱]/[Image名稱]:[標籤]
docker build --platform linux/amd64 \
  -t asia-east1-docker.pkg.dev/kakeru-ai/kakeru-ai-repo/kakeru-ai-api:1.0.0 .
  • 指令意義解析
    • -platform linux/amd64:模擬 x86_64 環境進行編譯,確保容器能在 Cloud Run 上正常執行。
    • -t:依 Google Cloud 規範將 Image 打上完整包含 Google Cloud 網域與專案路徑的 Tag。

步驟 3:推送 Image 至 Artifact Registry

看著終端機的進度條跑完,你的 Image 就安穩躺在 Google Cloud 的私有保險箱裡了:

docker push asia-east1-docker.pkg.dev/kakeru-ai/kakeru-ai-repo/kakeru-ai-api:1.0.0

動手實作 2:Cloud Run 部署與金鑰無縫注入

還記得 Day 7 我們說過,透過 ADC (Application Default Credentials),程式碼裡完全不用寫死任何密碼嗎?現在我們要在 Cloud Run 實現它!

步驟 1:賦予 IAM 服務帳戶 Secret Accessor 權限

Cloud Run 預設會使用 Compute Engine Default Service Account ({PROJECT_NUMBER}-compute@developer.gserviceaccount.com) 運行。我們需要賦予該帳戶讀取 Secret Manager 的權限:

# 取得專案編號並拼湊預設服務帳戶 Email
PROJECT_NUMBER=$(gcloud projects describe kakeru-ai --format="value(projectNumber)")
SERVICE_ACCOUNT="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"

# 賦予 Secret Accessor 角色
gcloud projects add-iam-policy-binding kakeru-ai \
    --member="serviceAccount:${SERVICE_ACCOUNT}" \
    --role="roles/secretmanager.secretAccessor"

意義解析:Spring Cloud Google Cloud 在 Cloud Run 啟動時會透過 ADC 自動取得服務帳戶身份,向 Secret Manager 安全讀sm://projects/kakeruai/secrets/GEMINI_API_KEY/versions/latest,以達到非Hardcode。

步驟 2:部署服務至 Cloud Run

執行 Cloud Run 部署指令:

gcloud run deploy kakeru-ai-api \
    --image=asia-east1-docker.pkg.dev/kakeru-ai/kakeru-ai-repo/kakeru-ai-api:1.0.0 \
    --region=asia-east1 \
    --platform=managed \
    --allow-unauthenticated \
    --port=8080
  • 指令關鍵參數解析
    • -allow-unauthenticated:允許未驗證的叫用,使我們的 REST API 能夠公開對外提供服務。
    • -port=8080:指定 Cloud Run 將公網 8080 埠號流量轉發給容器。

部署完成後,畫面上會出現成功的綠色勾勾與專屬公網網址。

踩坑與避雷指南

1. Port 號對齊 (Port Mismatch)

Cloud Run 預設監聽的容器 Port 是 8080。這也是為什麼我們在 Day 11 的 Dockerfile 中特別寫了 EXPOSE 8080,以及 Spring Boot application.yml 設為 server.port=8080。若兩者不齊,會引發 Container failed to start 超時錯誤。

2. Apple Silicon Mac 的 exec format error

如前述,在 Mac M 晶片電腦上開發時,必須加上 --platform linux/amd64 進行 Cross-Platform Build,否則編譯出的 ARM64 映像檔無法在 Cloud Run (AMD64) 上執行。

3. Spring Retry @Recover 簽名不匹配陷阱 (Cannot locate recovery method)

  • 現象:當呼叫 /chat 等涉及資料庫/外部 API 的端點時,若遇到非預期例外,API 回傳 {"status": "error", "message": "Cannot locate recovery method"}
  • 原因:Spring Retry 的 @Retryable 方法觸發降級時,會尋找第一個參數型別與拋出 Exception 匹配的 @Recover 方法。若原本修飾符寫死 recoverChat(RestClientException e, ...),當系統拋出 FirestoreException 或一般 RuntimeException 時,Spring Retry 找不到匹配的簽名,因而拋出 ExhaustedRetryException
  • 最佳實踐修復: 將 @Recover 降級方法的簽名統一修訂為 Throwable e,並在資料庫操作處加上安全容錯處理:
@Recover
public String recoverChat(Throwable e, String sessionId, String prompt) {
    log.error("Gemini API 完全失去連線,執行對話降級處理", e);
    return "教練現在有點累,請稍後再試!";
}

今日總結與明日預告

恭喜上銬!當 Cloud Run 部署完成,畫面上出現那串綠色勾勾與公開 URL 時,API 正式在網際網路上誕生了。在與 AI 協作下,實現了雲端沒有伺服器需要維護、沒有寫死金鑰、相容跨平台架構、且擁有自動擴展的彈性。

但是,API 公開上網後,如果被惡意腳本狂刷流量,我們的 Google Cloud 帳單可能會直接爆炸。明天(Day 13),我們將進入本階段的最後一哩路:API 測試與流量防護。我們將驗證 Cloud Run 服務的穩定性,並設定最基本的呼叫限制與併發防護!


上一篇
Day 11 | 容器化實戰:撰寫 Dockerfile,將 Spring Boot 應用程式輕量打包
下一篇
Day 13 | 上線驗證與流量防護:Cloud Run 擴展限制與 API Rate Limiting 實戰
系列文
單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言