經過前 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) 存取權限無縫掛載進去!
在本地端打包好的 Docker Image,總不可能用隨身碟傳到雲端伺服器吧?我們需要一個中繼站。
將 Image 推送到 AR 後,Cloud Run 就能以 Google Cloud 內網極速將其拉取並進行自動化部署與擴展。
在進行任何 Google Cloud 操作與 API 啟用前,必須先確保系統的 gcloud CLI 已完成 Google 帳號授權登入:
# 1. 執行 Google Cloud 帳號登入(會自動開啟瀏覽器進行 OAuth 授權)
gcloud auth login
# 2. 驗證當前已登入與生效的帳號列表
gcloud auth list
我們可以在 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)機房,以取得最低的網路延遲與最快傳輸速度。為了讓本地端的 Docker 有權限推送到 Google Cloud,我們需要執行一次憑證驗證指令:
# 設定 Docker 使用 gcloud 憑證助手存取 asia-east1-docker.pkg.dev
gcloud auth configure-docker asia-east1-docker.pkg.dev
如果使用的是 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。看著終端機的進度條跑完,你的 Image 就安穩躺在 Google Cloud 的私有保險箱裡了:
docker push asia-east1-docker.pkg.dev/kakeru-ai/kakeru-ai-repo/kakeru-ai-api:1.0.0
還記得 Day 7 我們說過,透過 ADC (Application Default Credentials),程式碼裡完全不用寫死任何密碼嗎?現在我們要在 Cloud Run 實現它!
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。
執行 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 埠號流量轉發給容器。部署完成後,畫面上會出現成功的綠色勾勾與專屬公網網址。
Cloud Run 預設監聽的容器 Port 是 8080。這也是為什麼我們在 Day 11 的 Dockerfile 中特別寫了 EXPOSE 8080,以及 Spring Boot application.yml 設為 server.port=8080。若兩者不齊,會引發 Container failed to start 超時錯誤。
exec format error如前述,在 Mac M 晶片電腦上開發時,必須加上 --platform linux/amd64 進行 Cross-Platform Build,否則編譯出的 ARM64 映像檔無法在 Cloud Run (AMD64) 上執行。
@Recover 簽名不匹配陷阱 (Cannot locate recovery method)/chat 等涉及資料庫/外部 API 的端點時,若遇到非預期例外,API 回傳 {"status": "error", "message": "Cannot locate recovery method"}。@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 服務的穩定性,並設定最基本的呼叫限制與併發防護!