Claude CodeでGCP請求が急増した原因を特定 — BigQueryエクスポート活用
GCPの請求をBigQueryにエクスポートし、dry runで見積もってから日次、SKU、プロジェクト、ラベルの順に増分を分解して、Claude Codeで急増の原因候補を絞る手順です。
GCPの請求が先月の倍になったとき、コンソールのレポートでは「Compute Engineが増えた」までは分かっても、どのSKUがどのプロジェクトで増えたのかを掘るのに手間がかかります。Cloud Billingの請求エクスポートをBigQueryに出しておけば、行単位のデータにSQLを当てられます。そのSQLの組み立てと読み取りをClaude Codeに任せるのが、本記事の手順です。
進め方は4段階です。日次で急増の日を見つけ、サービスとSKUとプロジェクトで増分を並べ、ラベルで担当を割り出し、最後に原因の候補を絞ります。各クエリは--dry_runで見積もってから実行します。
請求エクスポートには何が入っているか
Cloud Billingのエクスポートには3種類があります。ここで使うStandard usage costは、請求アカウントのコストと使用量の標準的なデータで、サービス、SKU、プロジェクト、ラベル、ロケーション、コスト、使用量、クレジット、調整、通貨が含まれます。リソース単位まで見たい場合はDetailed usage costを使いますが、SKUとプロジェクトの分解にはStandardで足ります。
データはgcp_billing_export_v1_<BILLING_ACCOUNT_ID>という名前のテーブルに入ります。分解で使う主な列は次のとおりです。
| 列 | 内容 | 分解での使い道 |
|---|---|---|
service.description | 内容コストを報告したサービス名 | 分解での使い道最初の切り口 |
sku.description | 内容サービス内のリソース種別 | 分解での使い道増えた単価項目の特定 |
project.id | 内容プロジェクトID | 分解での使い道増えた場所の特定 |
labels / project.labels | 内容キーと値の繰り返し項目 | 分解での使い道チームや環境での集計 |
credits | 内容適用されたクレジットの繰り返し項目 | 分解での使い道正味コストの計算 |
usage_start_time | 内容1時間単位の使用枠の開始時刻 | 分解での使い道日次の推移 |
invoice.month | 内容請求書の年月(YYYYMM) | 分解での使い道請求書との突き合わせ |
labelsとcreditsは繰り返し項目なので、集計ではUNNESTで展開します。この点を知らないと、コストにクレジットを足し忘れたり、ラベルのない行が結果から消えたりします。
エクスポートを有効にした時期でデータの範囲が変わる
過去に遡って調べたいときは、データセットのロケーションを確認します。
- マルチリージョン(EUまたはUS)のデータセットは、エクスポートを初めて有効にした月の前月初めからのデータが追加されます。初回の遡及には最大5日かかることがあります
- リージョンのデータセットは、有効にした日以降の分しか入りません。遡及されません
- データセットのロケーションは作成後に変更できません
先月の請求が倍になった件を調べたいのに、エクスポートを有効にしたのが先週だった。リージョンのデータセットではこの組み合わせで、過去分は空になります。この場合、エクスポートでは過去分を調べられず、コンソールのコストレポートなど別の手段が必要です。
Claude Codeに渡す前の準備
請求テーブルへのアクセスは読み取りで足ります。Claudeに書き込み系のコマンドを実行させない設定と、毎回同じ前提を渡すCLAUDE.mdを先に用意します。
.claude/settings.jsonには次のように書きます。
{
"permissions": {
"ask": ["Bash(bq query *)"],
"deny": ["Bash(bq rm *)", "Bash(bq mk *)", "Bash(bq load *)"]
}
}ルールの評価順は、denyが最優先で、askは対応するallowがあっても確認を求めます。bq queryをaskにしておけば、実行のたびに--dry_runが付いているかを自分の目で確かめられます。bq queryの実行前にdry runを自動で走らせて、上限超過を止める仕組みを組みたい場合は、Claude CodeでBigQueryのdry runを使い上限超のクエリをhookで止めるのhookが使えます。
CLAUDE.mdには、テーブル名と作法を書いておきます。
## GCP請求の調査
- 対象テーブル: `my-proj.billing_export.gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX`
- クエリは必ず `bq query --use_legacy_sql=false --dry_run` で見積もり、
処理バイト数を報告してから実行する
- 期間は `usage_start_time` で絞り、全期間のスキャンはしない
- 金額は `cost` と `credits` の合計(正味)で出し、クレジットは別列でも示す
- 結果は原因の断定ではなく「候補」として、根拠の行と一緒に報告する最後の1行が効きます。請求データから分かるのは「どこで増えたか」で、「なぜ増えたか」ではないためです。ここを最初に決めておくと、Claudeが数字の並びから原因を言い切る事態を避けられます。
手順1: 日次の推移で急増の日を特定する
まず、急増がいつ始まったかを日次で見ます。Claudeへの依頼は短く済みます。
過去30日の日次の正味コスト(cost + credits)を、サービス別に出して。
まずdry runで見積もって。タイムゾーンはAsia/Tokyo。Claudeが組み立てるクエリは、おおよそ次の形になります(公式のクエリ例に沿った形です)。
SELECT
DATE(usage_start_time, 'Asia/Tokyo') AS day,
service.description AS service,
SUM(cost) + SUM(IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0)) AS net_cost
FROM `my-proj.billing_export.gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX`
WHERE usage_start_time >= TIMESTAMP('2026-09-28')
GROUP BY day, service
ORDER BY day DESC, net_cost DESCdry runの出力は次の1行で、処理されるバイト数が分かります。
bq query --use_legacy_sql=false --dry_run '<上のクエリ>'有効なクエリなら「Query successfully validated. Assuming the tables are not modified, running this query will process N bytes of data.」と返ります。dry runでスロットは使われず、課金もされません。ただし、行レベルセキュリティでマスクされたテーブルではdry runが常に0バイトを返すため、見積もりは当てになりません。0バイトが返ったときは、見積もりが当てにならないテーブルかどうかを疑ってください。
日次の結果を見るときは、時間帯の扱いに注意します。usage_start_timeは1時間単位の使用枠の開始時刻です。タイムゾーンを揃えずに集計すると、日の境目で数字がずれます。公式のクエリ例ではUS/Pacificが使われているので、コンソールの数字と見比べるときは、どのタイムゾーンで日を切ったかを先に確認してください。
手順2: SKUとプロジェクトで増分を出す
急増の日が分かったら、その前後の期間を2つ並べて、増えた順に並べます。直近7日と、その前の7日の差を取る例です。
WITH daily AS (
SELECT
DATE(usage_start_time, 'Asia/Tokyo') AS day,
project.id AS project_id,
service.description AS service,
sku.description AS sku,
cost + IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0) AS net_cost
FROM `my-proj.billing_export.gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX`
WHERE usage_start_time >= TIMESTAMP('2026-10-15')
)
SELECT
project_id, service, sku,
SUM(IF(day >= DATE '2026-10-22', net_cost, 0)) AS recent_week,
SUM(IF(day < DATE '2026-10-22', net_cost, 0)) AS prior_week,
SUM(IF(day >= DATE '2026-10-22', net_cost, 0))
- SUM(IF(day < DATE '2026-10-22', net_cost, 0)) AS delta
FROM daily
GROUP BY project_id, service, sku
ORDER BY delta DESC
LIMIT 20結果の上位20行を、Claudeに読ませます。読み取りの依頼では、次の3点を指定すると精度が上がります。
- 増分の合計に対して、上位何行で何割を説明できるか
- 増えたSKUが、使用量の増加か単価項目の切り替わりか(
usage.amount_in_pricing_unitsで確認) - 新規に現れた行(
prior_weekが0)と、既存の行の増加の区別
最後の区別が重要です。新規の行が増分の大半なら、新しいリソースやサービスが立ち上がった可能性が高く、既存の行の増加なら、既存の負荷が増えた可能性が高くなります。調べる先が変わるため、最初に切り分けます。
増分が小さい行ばかりで、合計が合わないこともあります。その場合はcost_typeで分けてください。この列はregular、tax、adjustment、rounding errorのいずれかで、税や調整が合計を動かしていることがあります。
手順3: ラベルで担当チームに結びつける
増えたSKUとプロジェクトが分かっても、誰が立ち上げたかは分かりません。ラベルを付けて運用している環境なら、labelsから担当や環境を引けます。公式の例にあるとおり、ラベルのキーの絞り込みはWHEREではなくLEFT JOIN UNNESTのON句に書きます。
SELECT
l.value AS environment,
SUM(cost) AS cost
FROM `my-proj.billing_export.gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX`
LEFT JOIN UNNEST(labels) AS l
ON l.key = "environment"
WHERE usage_start_time >= TIMESTAMP('2026-10-22')
GROUP BY environmentWHEREに書くと、そのキーを持たない行が落ちて、ラベル未設定のコストが結果から消えます。ラベル付けが漏れているコストが急増の原因という場合もあるため、environmentがNULLの行は残して確認します。プロジェクトに付けたラベルはproject.labelsにあり、リソースのラベルとは別の列です。どちらで付けているかをClaudeに伝えておかないと、空の結果が返ります。
クレジットが増分を隠していないか
正味コストで見ていると、クレジットの適用状況が変わっただけで増分が出ることがあります。たとえば無料枠(FREE_TIER)や契約割引(COMMITTED_USAGE_DISCOUNT)、プロモーションのクレジットが終わると、使用量が同じでも正味コストは上がります。credits.typeはクレジットの由来を示す列で、公式のクエリ例も割引とプロモーションを別の集計列に分けています。
増分の行を見つけたら、costだけの増減と、クレジットを引いた後の増減を並べてください。costが横ばいで正味だけ動いているなら、使い方が変わったのではなく、クレジットの側で何かが切れた可能性があります。依頼文に「credits.type別の内訳も出して」と添えれば、Claudeが同じクエリを書き換えます。
手順4: 候補を絞り、原因は別の証拠で確かめる
ここまでで、「どのプロジェクトのどのSKUが、いつから、どれだけ増えたか」が出ます。Claudeに報告させるときは、次の形にそろえると読みやすくなります。
| 項目 | 例 |
|---|---|
| 増分の大きい行 | 例プロジェクトA、Cloud Storageのデータ転送SKU、前週比で増加 |
| 開始日 | 例10月23日から |
| 新規か既存か | 例既存の行の増加 |
| 候補 | 例外向き転送の増加、または大きなオブジェクトの配信開始 |
| 確かめる先 | 例Cloud Storageのアクセスログ、当日のデプロイ履歴 |
請求データだけでは、候補の裏付けにはなりません。SKUの名前は原因の方向を示すだけで、実際に何が起きたかは、ログやデプロイ履歴、設定変更の記録と照らして確かめる必要があります。Claude Codeに他のデータも読ませる場合は、同じ要領でログの集計クエリを書かせられます。AWSでの同じ作業は、ClaudeでAWSのコスト急増を調べるで、使用タイプまで分解する手順を扱っています。
数字が合わないときの切り分け
請求エクスポートの集計は、請求書の金額とそのまま一致するとは限りません。つまずきやすい点をまとめます。
落とし穴
タイムスタンプで集計した合計が請求書と一致しない
月末近くの使用は、翌月の請求書に回ることがあります。usage_start_timeやexport_timeで集計すると、後から報告された使用が混ざります。請求書と突き合わせるときは、タイムスタンプではなくinvoice.monthで絞ります。
- 直近のデータが足りない。エクスポートの反映は通常1日以内ですが、24時間を超えることもあります。昨日の分が少ないのは異常ではなく、遅延のことがあります
- 新しく作ったプロジェクトの
project情報が空になる。作成から24時間以内の使用では、プロジェクト情報が付かないことがあります。集計でプロジェクト別に分けると、この分が「未分類」に見えます - 結果が空になる。リージョンのデータセットで、エクスポートを有効にした日より前を指定している可能性があります
- 税の行が混ざる。2020年9月1日以降、税はプロジェクトごとの行として出ます。コストの増減を見るときは
cost_typeがtaxの行を分けます
どの経路を使うか
BigQueryにつなぐ手段は2つあります。1回きりの調査なら、bqコマンドをClaudeに実行させるのが手軽で、権限の絞り込みも本記事のようにsettings.jsonで足ります。同じ分析を毎月続けるなら、Claude CodeにBigQueryプラグインを入れて接続・分析するの構成のほうが、接続の設定を残せます。
請求の調査は、頻度が低く、扱う金額が大きいという性質があります。依頼は短く、クエリは見積もってから実行し、結果は候補として受け取る。この3点を決めておけば、Claudeに分解を任せつつ、原因の判断は自分の手に残せます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。