概要 #
Cloud Billingの予算は、コスト管理の入口としてよく紹介されます。ところが設定しようとすると、いくつも引っかかります。
APIが無効で一覧すら取れません。terraform applyは403で通りません。Pub/Sub通知のために権限を付けろと書かれているサービスアカウントは、そもそも名前が違っていました。
そして最も大事な点があります。この記事で作る通知だけの予算(alerts-only budget)は、課金を止めません。
2026年7月27日に Spend Cap Budget が Preview で提供開始されました。対象サービスなら、新規の利用を自動停止できます。
対象サービスは4つです。
- Gemini API
- Gemini Enterprise Agent Platform
- Cloud Run
- Cloud Run functions
ただし、確かめた範囲では Terraform から作れませんでした。理由はあとで実際に確かめます。
上限に達しても通知が飛ぶだけで、リソースは動き続けます。「予算を設定したから安心」にはなりません。
元にしたPoCは通知を請求アカウントの既定メールに任せていて、届くかどうかを確かめていませんでした。この記事ではPub/Subに送り、メッセージを実際に読みます。
対象読者は、TerraformでGoogle Cloudのリソースを作ったことがある人です。請求周りの入門は扱いません。
通知を受けてリソースを停止する実装、組織レベルの予算、BigQueryへの課金データエクスポートは扱いません。
検証すること #
- User ADC から Budget API を使うのに何が要るのか
- プロジェクトの指定は ID か番号か
- Pub/Sub 通知は本当に届くのか
- 通知のメッセージには何が入るのか
- Publisher の権限は誰が付けるのか
- 通常の予算を超えたら何が起きるのか
- Spend Cap Budget は Terraform で作れるのか
- 予算の操作は監査ログで追えるのか
前提環境 #
- Google Cloud CLI、Terraform
- 請求アカウントに対する
roles/billing.adminまたはroles/billing.costsManager - 通知先トピックの
pubsub.topics.setIamPolicy(Budget API が Publisher ロールを付けるのに要る)。ドメイン制限共有を有効にしている組織では、あわせて除外設定も要る - Pub/Sub のトピックとサブスクリプションを作成・削除・受信する権限
- User ADC を使う場合、
billing_projectに指定するプロジェクトのserviceusage.services.use billing_projectに指定するプロジェクトで Budget API(billingbudgets.googleapis.com)を有効化しておくこと- 検証時のバージョン:Terraform 1.14.5、google 7.46.0
今回は請求アカウントの権限で作成します。プロジェクト1つを対象にする予算なら、プロジェクト側の権限でも作成できます(resourcemanager.projects.get / billing.resourcebudgets.read / billing.resourcebudgets.write)。
使用するTerraformコード #
以降の gcloud はリソース名を短く書いています。CLI の参照先を、Terraform の project_id と揃えてから実行してください。
gcloud config set project YOUR_PROJECT_ID
構成 #
flowchart LR
BA["請求アカウント"] --> B["予算
上限 1 JPY"]
P["プロジェクトの課金"] -.->|"budget_filter"| B
B -->|"all_updates_rule"| T["Pub/Sub トピック"]
T --> S["サブスクリプション
pull"]
VMもクラスタも作りません。Pub/Subのトピックとサブスクリプションだけなので、ほぼ無料です。
予算は請求先アカウント配下のリソース #
まずここでつまずきます。予算はプロジェクトに属していません。
gcloud billing budgets list --billing-account=BILLING_ACCOUNT_ID \
--filter="displayName:tf-adv" --format="value(name,displayName)"
billingAccounts/BILLING_ACCOUNT_ID/budgets/3198661d-... tf-adv-budget-pubsub
リソース名がbillingAccounts/で始まります。プロジェクトを削除しても予算は残ります。 gcloud projects系のコマンドでは一切見えません。
destroyの確認も、terraform state listが0件では足りず、請求アカウント側で見る必要があります。
User ADC では provider に user_project_override が要る #
terraform applyが通らないところから始まりました。
Error creating Budget: googleapi: Error 403: Your application is authenticating
by using local Application Default Credentials. The billingbudgets.googleapis.com
API requires a quota project, which is not set by default.
gcloud auth application-default set-quota-projectを実行しても同じエラーです。providerに書く必要があります。
provider "google" {
project = var.project_id
region = var.region
user_project_override = true
billing_project = var.project_id
}
User ADC で Budget API を呼ぶなら、API のクォータプロジェクトを明示する必要があります。 Terraform の公式資料も、User ADC では user_project_override = true と billing_project の両方を求めています。
billing_project はクォータの請求先であって、予算が属する請求アカウントや、予算が集計する費用の対象とは別です。
budget_filter.projects は「番号」 #
project IDではなくprojects/<番号>の形式です。
data "google_project" "scope" {
project_id = var.project_id
}
resource "google_billing_budget" "main" {
budget_filter {
projects = ["projects/${data.google_project.scope.number}"]
}
}
Publisher権限は自分で付けなくても通知が届いた #
Pub/Sub通知の手順として、請求のサービスエージェントにroles/pubsub.publisherを付けるという説明をよく見ます。
gcloud pubsub topics add-iam-policy-binding tf-adv-budget-notifications \
--member="serviceAccount:billing-budgets@system.gserviceaccount.com" \
--role=roles/pubsub.publisher
ERROR: INVALID_ARGUMENT: Service account billing-budgets@system.gserviceaccount.com does not exist.
候補として挙がる次の3つは、いずれも存在しませんでした。
billing-budgets@system.gserviceaccount.com
billing-budgets-pubsub@system.gserviceaccount.com
cloud-billing-budgets@system.gserviceaccount.com
正しいプリンシパルは billing-budget-alert@system.gserviceaccount.com です。ドメイン制限共有の除外設定のページに、Pub/Sub で予算アラートを受け取る際のプリンシパルとして載っています。
手で付けなくても通知は届きました。Pub/Sub 連携を設定する側に pubsub.topics.setIamPolicy があれば、Publisher ロールは自動で付きます。
公式ドキュメントにも具体的なアドレスの記載はありません。「Pub/Sub Publisherロールを付与する権限が必要」とだけ書かれています。
そこでバインディングを一切付けずに作成しました。結果は次のとおりです。
通知は届いた #
Terraform が作るのはリソースまでで、権限の付与と配信は Budget API 側が動かします。
sequenceDiagram
participant TF as Terraform
participant BG as 予算 / Budget API
participant T as Pub/Sub トピック
participant SUB as サブスクリプション
TF->>T: トピックとサブスクリプションを作る
TF->>BG: 予算を作る(all_updates_rule)
BG->>T: roles/pubsub.publisher を自分に付与
Note over BG,T: IAM バインディングは1つも書いていない
BG->>T: 最初の通知(今回は作成から約7分後)
T->>SUB: pull で1件
Note over BG,SUB: 以降も1日に複数回。at-least-once で重複も順序入れ替わりもある
作成してから、サブスクリプションを1分おきに見ました。
02:30:32 予算作成
02:31:13 受信: 0件
02:32:34 受信: 0件
02:33:54 受信: 0件
02:35:15 受信: 0件
02:36:36 受信: 0件
02:37:39 受信: 1件
約7分で届きました。 Terraform では IAM バインディングを1つも書いていません。誰に権限が付いたのかを見ます。
gcloud pubsub topics get-iam-policy tf-adv-budget-notifications --format=json
{
"bindings": [
{
"members": [
"serviceAccount:billing-budget-alert@system.gserviceaccount.com"
],
"role": "roles/pubsub.publisher"
}
]
}
Budget API が自分で付けています。設定する側に pubsub.topics.setIamPolicy があれば足りました。
ただし組織ポリシー次第です。ドメイン制限共有を有効にしていると、billing-budget-alert@system.gserviceaccount.com の付与自体が拒否されます。その場合は除外設定が要ります。
Pub/Sub 通知が送られるのは、閾値を超えた瞬間ではありません。現在の予算の状態が1日に複数回送られます。
公式には初回まで数時間かかることがあるとされています。今回7分で届いたのは早いほうです。
配信は at-least-once です。同じ内容が複数回届くことも、順序が入れ替わることもあります。 通知を受けて処理を書くなら、何度実行しても同じ結果になるようにします。
通知の中身 #
gcloud pubsub subscriptions pull tf-adv-budget-notifications-sub \
--auto-ack --limit=3 --format=json
data は Base64 でエンコードされた UTF-8 の JSON です。以下は jq で属性と本文を取り出して整形したもので、pull の生の出力そのままではありません。
属性です。
billingAccountId: BILLING_ACCOUNT_ID
budgetId: 3198661d-c1c0-4446-862b-a89a93f6754f
schemaVersion: 1.0
本文です。
{
"budgetDisplayName": "tf-adv-budget-pubsub",
"alertThresholdExceeded": 1.0,
"costAmount": 110.03,
"costIntervalStart": "2026-09-01T07:00:00Z",
"budgetAmount": 1.0,
"budgetAmountType": "SPECIFIED_AMOUNT",
"currencyCode": "JPY",
"forecastThresholdExceeded": 1.0
}
attributesにbudgetIdがあるので、複数の予算を1つのトピックに集約しても振り分けられます。
dataには累積コスト(costAmount)と予算額(budgetAmount)の両方が入ります。超過分を計算するのに追加のAPI呼び出しは要りません。
costAmount の定義は「amount of costs accrued」で、確定した請求額ではありません。使用量からコストへの反映には遅れがあります。
上限を超えてもリソースは止まらなかった #
予算額1円に対して costAmount は110.03円。alertThresholdExceeded: 1.0が立っています。
この110.03円は、この検証で使った額ではありません。 costIntervalStart が 2026-09-01 なので、同じプロジェクトの当月の累計です。予算の対象をプロジェクト全体にしているため、他の検証で動かしていた費用が入っています。
gcloud pubsub topics list --format="value(name.basename())"
tf-adv-budget-notifications
トピックは一覧に残っていました。ただしこれは一覧に出るかどうかを見ただけで、送受信が続くかは確かめていません。
この検証で作ったのは Pub/Sub のトピックとサブスクリプションだけです。言えるのは「超過しても、このトピックが自動で消えることはなかった」ところまでです。 停止されたかどうかも、その後の課金も測っていません。
通知だけの予算が spending cap ではないことは公式の仕様です。実測はそれと矛盾しませんでした。
止めたいなら、この通知を受けて停止する処理を自分で書きます。Cloud Functions や Cloud Run へ push 配信し、そこでインスタンスを止める形です。
請求を無効化する方法も公式に案内されていますが、サービスが止まりデータに影響が出ることがあります。 副作用の大きい選択肢です。
「予算を設定したから使いすぎない」と考えていると、請求書を見て驚くことになります。
CURRENT_SPEND と FORECASTED_SPEND(仕様の整理) #
ここは実測ではなく、設定した閾値ルールの整理です。どちらがいつ発火したかまでは比較していません。
閾値ルールは2種類の基準を持てます。
thresholdRules:
- spendBasis: CURRENT_SPEND
thresholdPercent: 0.5
- spendBasis: CURRENT_SPEND
thresholdPercent: 1.0
- spendBasis: FORECASTED_SPEND
thresholdPercent: 1.0
CURRENT_SPENDは既に使った金額、FORECASTED_SPENDは期間終了時点の予測です。
前者は「もう使った」、後者は「このままだと超える」を知らせます。答える問いが違うので、必要に応じて併用します。
Spend Cap Budget は、確かめた範囲では Terraform から作れなかった #
冒頭で触れた Spend Cap Budget は、この記事の方法では作れません。 3つの層すべてに無いためです。
gcloud にオプションがありません。
gcloud billing budgets create --help | grep -icE "spend.?cap|enforce"
0
alpha と beta でも 0 でした。create の引数一覧には --billing-account、--budget-amount、--filter-projects、--notifications-rule-pubsub-topic などが並びますが、上限を強制する引数はありません。
Budget API v1 にもフィールドがありません。リソースの項目は name / displayName / budgetFilter / amount / thresholdRules / notificationsRule / etag / ownershipScope で、spendCap にあたるものは含まれません。
google_billing_budget の引数にもありません。
amount / billing_account / display_name / budget_filter
threshold_rules / all_updates_rule / ownership_scope / deletion_policy
公式の手順も Console だけを案内しています。確かめたのは gcloud 562.0.0、Budget API v1、google provider 7.46.0 です。どれにも手段がありませんでした。
公開 API に出れば provider が追える性質のものなので、将来もできないという話ではありません。
制約も強めです。
| 項目 | 制限 |
|---|---|
| スコープ | 単一プロジェクト × 単一サービスのみ |
| 対象サービス | Gemini API / Gemini Enterprise Agent Platform / Cloud Run / Cloud Run functions |
| 期間 | Monthly 固定(毎月1日開始) |
| 対象外 | Folder、Organization、ラベル、複数プロジェクト、複数サービス |
停止も即時ではありません。
the enforcement of spend caps aren’t instant and any cost overages are billed as normal
止まるのは新規の利用だけです。公式は3つを分けて書いています。
- 処理中のリクエストは完了まで進み、そのぶん課金される
- Compute や Storage のような永続リソースの固定費は止まらない
- コスト反映の遅れによる超過分は通常どおり課金される
解除は Console で「Lift spend cap」を選ぶ必要があり、サービスの復帰に最大1時間かかります。
IaC で完結させたいなら、当面は通知を受けて自分で止める側に寄せることになります。
予算の操作は監査ログで追えなかった #
gcloud logging read 'protoPayload.serviceName="billingbudgets.googleapis.com"' \
--limit=5 --freshness=8h
(0件)
同じ時間範囲では、他サービスの監査ログを確認できました。ログ機能そのものは動いています。
billingbudgets 0件
pubsub 10件
serviceusage 8件
ここで「予算の操作は監査ログに残らない」と書きたくなりますが、それは言い過ぎです。
予算は請求アカウント配下のリソースなので、監査ログも請求アカウントのスコープに出ている可能性があります。今回はそのスコープで検索して0件でした。
ただし「権限はあったが0件だった」とまでは確かめていません。 実行者へのロール付与状況を見ていないためです。
gcloud billing accounts get-iam-policy BILLING_ACCOUNT_ID --format="value(bindings.role)"
roles/billing.admin
roles/billing.costsManager
roles/billing.user
gcloud logging read --billing-account=...は何も返しませんでした。ただし、これを権限不足とは判断できません。
roles/billing.adminには logging.logEntries.list と logging.privateLogEntries.list が含まれています。
$ gcloud iam roles describe roles/billing.admin --format='value(includedPermissions)' \
| tr ';' '\n' | grep logging
logging.logEntries.list
logging.logServiceIndexes.list
logging.logServices.list
logging.logs.list
logging.privateLogEntries.list
また、get-iam-policy が出すのはロール名だけで、実行者への付与状況までは分かりません。ロールの権限は確かめましたが、0件だった原因はそこまでで追えていません。
そもそも Budget API は監査ログを出さない可能性が高いです。監査ログに対応するサービスの一覧に cloudbilling.googleapis.com は載っていますが、Budget API の billingbudgets.googleapis.com は載っていません。Cloud Billing の監査ログにも、予算の作成・更新・削除は対象メソッドとして挙がっていません。
2026年9月の時点では、予算の操作を Cloud Audit Logs で追えるとは確認できませんでした。 権限を足しても、そもそも出ていないなら見つかりません。
後片付け #
terraform destroy
gcloud billing budgets list --billing-account=BILLING_ACCOUNT_ID --filter="displayName:tf-adv"
(0件)
terraform state listが0件でも、請求先アカウント側で予算が消えたことを確かめます。 予算は請求アカウント配下にあるためです。
まとめ #
- 予算はプロジェクトに属さない。 請求先アカウント配下にあり、プロジェクトを消しても残る
- User ADC ではproviderに
user_project_overrideとbilling_projectが要る。API リクエストのクォータプロジェクトを指定する budget_filter.projectsはproject 番号を要求する- Publisher ロールは Budget API が自分で付ける。 トピックの IAM Policy を見ると
billing-budget-alert@system.gserviceaccount.comが入っている。よく挙がる3つのアドレスは、2026年9月時点では受け付けられなかった - 通知は届く。 今回は作成から約7分。ただし公式には初回まで数時間かかることがある。閾値超過の瞬間ではなく現在の状態が1日に複数回送られ、配信は at-least-once
- 通知には累積コストと予算額の両方が入る。
attributesのbudgetIdで複数予算を振り分けられる - 通知だけの予算は課金を止めない(公式仕様)。上限1円に対し110.03円でもトピックは一覧に残った。実測で言えるのは「消えなかった」ところまで
- Spend Cap Budget は、確かめた範囲では Terraform から作れなかった。 gcloud 562.0.0・Budget API v1・provider 7.46.0 のどれにも該当する引数が無く、公式の手順も Console だけ。公開 API に出れば provider が追える
- 予算の操作は監査ログで追えなかった。
billingbudgets.googleapis.comは監査ログ対応サービスの一覧に無く、2026年9月の時点では追跡できると確認できない
参考資料 #
- Google Cloud: Set budgets and budget alerts
- Google Cloud: Manage programmatic budget alert notifications
- Google Cloud: 予算通知の形式とデータ例
- Terraform Registry: google_billing_budget
- Terraform Registry: user_project_override
- Google Cloud: Manage spend cap budgets
- Cloud Logging: Google Cloud services with audit logs