iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
AI Engineering

Agent 開發, 從本地到雲端: AI 工程師的 K8s 筆記系列 第 23 篇

Day 23|把一個 agent API 部署到 Cloud Run:secret、min instances、並發設定

  • 分享至 

  • xImage
  •  

cover

POV:你把 LLM 的 API key 貼進 Dockerfile 的 ENV,image 推上去、服務起來了,然後才想到這個 image 誰都拉得到。

前兩天講的是「哪種 workload 適合 Cloud Run」,今天直接動手。第五、六週我會用同一個 workload 依序走完 Cloud Run(今天)、GKE Autopilot(明天)、AKS(Day 27),所以先把它定義清楚。

共用的 workload:agent-api

一個很小的 FastAPI 服務,聽 PORT(預設 8080),兩個端點:

  • GET /healthz:回 {"ok": true}。
  • POST /ask:收 {"q": "..."},呼叫 LLM API 後回 {"answer": "..."};API key 從環境變數 LLM_API_KEY 讀。

無狀態、request-driven,就是 Day 21 說的那種 Cloud Run 最喜歡的形狀。假設 image 已經 build 好推到 Artifact Registry:asia-east1-docker.pkg.dev/PROJECT_ID/agents/agent-api:v1(PROJECT_ID 換成你的)。

先老實說:下面的指令我是對著 gcloud run deploy --help 與官方文件逐個旗標核過的,但寫這篇的當下沒有為了這篇重新部署一次,所以 front matter 標 verified: false。

部署:把 key 從 image 裡拿出來,把三個數字留給自己想

開頭那個 POV 是最省事、也最危險的做法。比較對的做法是 key 進 Secret Manager,部署時用 --set-secrets 注入成環境變數,程式碼一行都不用改。注意 --set-secrets 只是「指向」那個 secret,Cloud Run 的執行 service account 還要拿到 Secret Manager Secret Accessor 角色才讀得到,這步官方文件有寫,很容易漏:

# 1. key 進 Secret Manager(從 stdin 讀,不留在 shell history)
printf '%s' "$LLM_API_KEY" | gcloud secrets create llm-api-key \
  --data-file=- --replication-policy=automatic

# 2. 讓執行 service account 讀得到它(沒指定 --service-account 時,
#    Cloud Run 用的是 Compute Engine 預設 SA)
PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format='value(projectNumber)')
gcloud secrets add-iam-policy-binding llm-api-key \
  --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --role=roles/secretmanager.secretAccessor

# 3. 部署
gcloud run deploy agent-api \
  --image=asia-east1-docker.pkg.dev/PROJECT_ID/agents/agent-api:v1 \
  --region=asia-east1 \
  --port=8080 \
  --cpu=1 --memory=512Mi \
  --min-instances=0 --max-instances=3 \
  --concurrency=8 \
  --set-secrets=LLM_API_KEY=llm-api-key:latest \
  --no-allow-unauthenticated

部署完驗證。--no-allow-unauthenticated 代表要帶 identity token 才打得到,而且光有 token 不夠,呼叫者本身還要有 roles/run.invoker:自己部署的帳號通常已經有,要讓別的帳號或 service account 打,得先用 gcloud run services add-iam-policy-binding 綁給它,不然 token 是對的也會拿到 403:

URL=$(gcloud run services describe agent-api --region=asia-east1 \
  --format='value(status.url)')
TOKEN=$(gcloud auth print-identity-token)
curl -s -H "Authorization: Bearer $TOKEN" "$URL/healthz"
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"q":"ping"}' "$URL/ask"

第一個 curl 如果明顯等了一下才回,很可能是冷啟動,也就是 --min-instances=0 的代價;不過網路或 handler 本身慢也會有同樣現象,要確認得看 log 裡有沒有新 instance 起來。

部署完,其他旗標大多是「照抄就好」,真正會影響行為與帳單的是三個擴縮/並發旋鈕:

--min-instances:0 就是 scale-to-zero,沒人用就不付錢,代價是冷啟動;設 1 以上就是花錢買掉冷啟動。這就是 Day 21 那個矛盾的旋鈕本體。

--max-instances:instance 數的上限,不是請求數的上限。同時能處理的請求大約是 max-instances × concurrency(這份設定是 3 × 8 = 24),而一個請求可能對 LLM 發不只一次呼叫,所以它只是「一波流量最多能放大到多少」的粗略護欄;真正的天花板是 LLM 供應商的 rate limit,要一起算。

--concurrency:一個 instance 同時處理幾個請求。agent-api 大部分時間在等 LLM 回來(I/O bound),可以往上調;如果你的 agent 是在本機跑模型推論(CPU / GPU bound),就要往下調。這個值沒有標準答案,取決於 workload 的性格,這也是 Day 06 為什麼要先分類。

--cpu / --memory 是示範值,它們是每個 instance 拿到的資源配置與上限,不是 Day 09 那種給排程器參考的 requests,Cloud Run 沒有排程這一層。它們跟 --concurrency 是一組的:一個 instance 撐得住多少並發,取決於你給它多少 CPU 與記憶體,要用實際壓測回頭調,不是憑感覺填。價格不寫死,以官方定價頁為準。

我真的踩過的:不是每個服務都長這個形狀

Day 17 講過我評估 liaostudio 搬 Cloud Run 後決定暫不搬。回頭看,卡住的正好就是今天這幾個旗標:

  • 它的轉寫 job 狀態放在 in-memory 的 dict,沒有外部儲存。要是開兩個 instance,POST 進來的 job 跟後續 GET 輪詢可能落在不同 instance 上互相看不到,只能 --min-instances=1 --max-instances=1 鎖死,scale-to-zero 與水平擴展都沒了。
  • 它用背景 thread 繼續轉寫、先回 job_id。在 Cloud Run 上,request 回完之後 CPU 預設會被節流,背景工作可能被拖慢甚至停住,不能指望它一定跑完;要開 --no-cpu-throttling,計費模型就從「按請求」變得比較像「固定跑一台小機器」。
  • 官方配額頁寫的 HTTP/1 request body 上限是 32 MiB(HTTP/2 沒有這條),它的上傳走 HTTP/1 就會撞到;這點後來因為前端先用 ffmpeg.wasm 抽音軌再上傳而變得沒那麼致命,但一開始確實是評估表上的一條。

agent-api 之所以能用今天這組旗標直接上,是因為它無狀態、每個請求自己收尾。今天的指令不難,難的是你的服務有沒有長成能吃這組旗標的形狀。

三個常見的錯

  1. 讀不到 secret:Cloud Run 的執行 service account 沒有 Secret Manager Secret Accessor 角色。錯誤訊息通常只說權限不足,不會告訴你少哪個角色。
  2. 容器起來就死:程式聽的 port 跟 --port 不一致,或一啟動就去連 LLM 但 key 還沒注入。先用 /healthz 確認容器活著,再測 /ask。
  3. 閒著也在計費:--min-instances 不小心設成 1 以上。回想 Day 21 的問題:大部分時間沒人用的服務,該不該一直開著?

明天用同一個 image、同一組環境變數,搬到 GKE Autopilot,看這幾個旗標在 YAML 裡各自變成什麼。

今日一句話

Cloud Run 上部署 agent API 真正要想的是三個擴縮/並發旋鈕:min-instances 決定你要不要吞冷啟動,max-instances 配上 concurrency 是流量放大的粗略護欄,concurrency 本身看你的 agent 是在等 LLM 還是在算;cpu / memory 則是資源與帳單,要跟並發一起量。

延伸閱讀


上一篇
Day 22|GKE Autopilot vs Standard:AI 工程師版本的比較,你會遇到的三個決定
系列文
Agent 開發, 從本地到雲端: AI 工程師的 K8s 筆記 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言