花最多錢的,不是你以為的那個
經驗具現化 — EP06:帳單分析自動化
《EP01:懶人需要一個部署方案》講過我每天都看帳單。帳單本身不難看懂,用了雲端那麼多年,項目大致都認得。重點是要有意識地週期性檢查,看花費項目是否與認知有落差。
GCP Console 上的數字看得到,但要逐項比對哪些是預期的、哪些不該出現,不是難,是花時間,加上 billing console 的操作本身就煩瑣。舉例來說,我將 Cloud Run 的 Service 改成 request based billing,理論上沒流量就不花錢。但 Artifact Registry 呢?Cloud Storage 呢?這些是搭建服務會使用的基礎建設,我們主觀地決定讓 AI agent 採用它們,但它們的成長曲線,得在合理的範圍內。
有一次為了解決 Cloud Run deploy 時 container image 建立耗時的問題,做了架構調整:替專案預先建立 base image,把不常變動的 dependency 先打包好,deploy 時只需要疊上應用程式碼,速度快很多。但隔天例行檢查帳單時,出現了預期外的花費。看到 Artifact Registry 的網路傳輸費用(SKU: Network Internet Egress AsiaPacific to AsiaPacific)有明顯成長,是前幾天沒有的,就瞬間明白了。
流程是這樣:
GitHub Actions Artifact Registry
(external) (GCP)
| |
| 1. build base image |
| (pull from Docker Hub) |
| |
| 2. push base image |
| -----------------------> |
| |
- - - - - - - - - - - - - - -
(1-2 only when base changes)
- - - - - - - - - - - - - - -
| |
| 3. pull base image |
| <----------------------- |
| (network egress) |
| |
| 4. docker build |
| (base + app code) |
| |
| 5. push final image |
| -----------------------> |
| |
base image 存在 GCP 的 Artifact Registry,GitHub Actions 要 build 最終 image 時得先把 base image 拉出來,build 完再 push 回去。那個從 Artifact Registry 被拉出來的 base image 就是多花的網路傳輸費用。
對有經驗的雲端使用者來說,看到這個帳單大概就直接反應過來了:GitHub Actions 根本不需要去拉那個已經建好的 base image。解法很簡單,讓建立 image 的動作完全在 GCP 內部完成就行了。因此在這個時候,我們才開始啟用 Cloud Build 服務。
先讓帳單變得可查
GCP 有一個功能叫 Billing Export,可以把帳單資料匯出到 BigQuery。照著文件設定就好,找不到選項的話截圖給 AI agent 讓它協助定位。
開了之後要等,而且等的時間比你想的久。這個東西非常有趣:打開之後不是馬上就有資料,GCP 會從今年一月一日開始慢慢回填。我三月底開的,隔天去看只有一月到二月中的資料,三月的完全沒有。再過兩三天多一點,到三月底才慢慢看到三月的資料出現,真正看到完整的三月帳單已經是四月一、二號的事了。這種「設定完馬上去查結果發現沒資料」的落差,如果沒有日誌記錄,很容易誤以為設定錯了。
資料進來之後,我寫了一個 billing skill。跟 AI 說一聲,它就去 BigQuery 查帳單,不用自己開 Console。
Billing Skill 的設計
這個 skill 分三層,漸進式分析:
第一層是 pre-check。確認 bq CLI 裝了沒、project 對不對、billing export table 存不存在。如果 table 不存在,直接輸出設定步驟,不讓你跑空查詢。
第二層是 quick summary,也是預設行為。跑本週每日費用,用兩種方式抓異常:統計法(mean + 2σ,資料夠多的時候)和比較法(跟上週同一天比,漲幅超過 50% 且差額超過 NT$1 才標記)。再列出近七天花費最高的 SKU 排序,讓你一眼看到錢花在哪。
第三層是 full analysis,要的時候才跑。三十天趨勢、各 service 費用分佈、SKU 層級排序。給你完整的成本結構。
整個 skill 就是純 SQL 查詢,不需要額外的 Python 套件,bq CLI 查完直接在 terminal 看結果。
不過,如果你還記得我們最初設計 GCP Cloud Run 部署方案時的目標:本地機器不需要有任何 gcloud 帳號登入。很明顯,目前這個 skill 直接在本地跑 bq CLI,不符合這個設計。要符合的話也很簡單,把查詢動作委託給 GitHub Actions,用 Workload Identity Federation 搭配設好權限的 service account 就可以了。
有了 Skill 後就可以彈性分析帳單了
以下是三月初到四月的每週主要花費,從 BigQuery billing export 實際查詢而來:
| 週次 | 主要花費 | 金額(NTD) | 備註 |
|---|---|---|---|
| 3/2 - 3/8 | Compute Engine PD Snapshot | NT$0.69 | 固定開銷 |
| Cloud Storage | NT$0.05 | 正常 | |
| 3/9 - 3/15 | Compute Engine PD Snapshot | NT$0.69 | 固定開銷 |
| Cloud Storage | NT$0.05 | 正常 | |
| 3/16 - 3/22 | Cloud Storage Class A Ops | NT$30.58 | ⚠️ 意外(詳見下方) |
| Cloud Storage Standard | NT$1.71 | ||
| Cloud Run Network Egress | NT$0.84 | ||
| Compute Engine PD Snapshot | NT$0.69 | 固定開銷 | |
| Artifact Registry Storage | NT$0.69 | image 開始累積 | |
| 3/23 - 3/29 | AR Network Egress (Asia-Asia) | NT$13.22 | ⚠️ 呼應開頭的 egress 故事 |
| Artifact Registry Storage | NT$11.13 | 132 個 image、23.9 GB | |
| Cloud Storage Standard | NT$7.99 | ||
| Cloud Storage Class A Ops | NT$2.09 | ||
| Compute Engine PD Snapshot | NT$0.69 | 固定開銷 | |
| 3/30 - 4/5 | Gemini API (Image output) | NT$21.05 | ⚠️ 興趣使然的花費 |
| Artifact Registry Storage | NT$9.97 | ✅ cleanup policy 生效,開始下降 | |
| Cloud Storage Standard | NT$7.64 | ||
| Gemini API (Text) | NT$1.36 | ||
| Cloud Storage Class A Ops | NT$1.28 | ||
| Compute Engine PD Snapshot | NT$0.61 | 固定開銷 |
幾個值得注意的點:
Cloud Run 本身的費用始終趨近零。設定 request based billing,沒流量就不花錢,整個觀察期間 Cloud Run 最高的一週也只有 NT$0.84,全部是網路傳輸。
表格裡標了三個意外花費,各自有不同的故事:
3/16 那週 Cloud Storage Class A Operations 突然飆到 NT$30.58。這是整個觀察期間單一 SKU 最高的一筆。Class A Operations 是寫入和 metadata 操作,平常每週不到一塊錢,這週暴增三十倍。原因是 media archive 專案初期不打算放資料庫,直接讓 GCS 充當資料庫暴力操作。每次使用者瀏覽網頁,就會去做各種列出素材清單、透過圖檔取得素材 metadata 的操作。Class A Operations 按次計費,瀏覽幾次頁面就累積幾次 API 呼叫,量一上來費用就跟著上來了。
3/23 那週的 Artifact Registry Network Egress NT$13.22,就是文章開頭說的那個故事。為了加速 deploy 引入 base image,結果 image 在 Artifact Registry 和 GitHub Actions 之間來回傳遞。發現後改用 Cloud Build,egress 就歸零了。
3/30 那週的 Gemini API NT$21.05 是興趣使然的花費。測試 Gemini 的圖片生成功能,image output token 的單價不低,玩了幾次就花掉二十幾塊。這類花費不是部署平台的成本,是自己好奇心的代價,知道就好。
處理帳單花費的心態
上面列了好幾種帳單花費,每一筆成因都不一樣。有些是刻意的選擇,像是用 GCS 當資料庫、測試 Gemini 圖片生成。有些是刻意選擇了某種實作方式,但沒有意識到這樣會造成花費的提升,像是 base image 的 egress。不管我們有沒有主觀認為錢會這樣被使用,看到帳單之後都還有修正的機會,只要它不爆量就行了。
以 Artifact Registry Storage 為例,解法就很直接:設 cleanup policy。
{
"keep-recent-1": { "action": "Keep", "keepCount": 3 },
"delete-old": { "action": "Delete", "olderThan": "7d" }
}
保留最新 3 個版本,超過 7 天的自動刪除。
可以這樣做的原因很簡單:我們每次部署總是用最新的版本,也沒有退回舊版的需求。就算真的要退版,也是建立一個更新的版號但使用舊版的內容,不會去拉舊的 image。既然如此,多餘的東西直接刪掉就可以了。保留 3 個是給自己一點安全邊界,但老實說,大概也用不到。
設完 policy 之後,image 數量從 132 降到 46,之後持續自動清理。
再來看 GCS Operations 的改善。前面提到 media archive 專案不打算處理資料庫,直接拿 GCS 當資料庫用,結果 Class A Operations 飆到 NT$30.58。那要改善花費的時候怎麼做呢?
一種做法是給它一個正式的資料庫。但問題來了,如果要特意開一台機器來存,那就是另一種花費。我曾經考慮過幫它設定一個 Tailscale sidecar,連回家裡的機器直接用地端的 PostgreSQL。但想了想,我沒有任何一台固定的主機保證一定存在且不被關機。
換個方向想,其實只要它的操作行為不要這麼多就好了。既然是 side project,乾脆直接用最簡單的 SQLite。讓 AI agent 把原本直接操作 GCS 的邏輯改成存在 SQLite,只要有任何 update 或 delete 行為,就上傳一份 SQLite 到 GCS 上面。所以 SQLite 只是這些 GCS operations 的 buffer。
雖然聽起來有一點亂來,但這就是我自己的個人專案,別人也管不到我呀。
帳單 Skill 帶來的改變
因為 billing skill 被建立出來,我們可以有彈性又不厭其煩地去檢查帳單問題,用不同的角度查看目前的成本合不合理。想看每週趨勢就看每週,想看 SKU 明細就看 SKU,不用每次都自己開 Console 慢慢點。
更重要的是,這也幫助我把「檢查帳單」這個習慣具現化成一個可以執行的功能。原本只是每天看一眼 Console 的模糊習慣,現在變成了一個有結構的分析流程,可以重複執行,也可以讓 AI agent 代勞。
下一篇,來做個階段性的回顧。