GCP の予算超過時に課金を自動停止する仕組みをつくる

11 min
gcpcloud-functionspub-sub

GCP の予算アラートはデフォルトではメール通知のみで、課金自体は止まりません。Pub/Sub と Cloud Functions を組み合わせて、予算を超過したらプロジェクトの請求アカウントを自動で切り離す仕組みをつくりました。

予算超過 → Pub/Sub にメッセージ → Cloud Function 起動 → 請求アカウントを切り離し

全コンポーネントが GCP の無料枠内で動くので、運用コストはゼロです。

アーキテクチャ

┌──────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ Cloud Billing│───→│  Pub/Sub Topic   │───→│ Cloud Function  │
│  Budget      │    │ budget-alert-    │    │  stop_billing   │
│              │    │ topic            │    │                 │
└──────────────┘    └──────────────────┘    └────────┬────────┘
                                                     │
                                                     ▼
                                            ┌─────────────────┐
                                            │ Cloud Billing   │
                                            │ API             │
                                            │ (detach)        │
                                            └─────────────────┘
技術用途
Cloud Billing Budgets予算と閾値の設定、Pub/Sub への通知
Pub/Sub予算アラートメッセージの仲介
Cloud Functions (2nd gen)受信メッセージを処理して請求アカウントを切り離す
Cloud Billing APIプロジェクトと請求アカウントのリンクを操作

なぜ Pub/Sub を挟むのか

Cloud Billing Budgets は Cloud Function を直接トリガーする機能を持っておらず、Pub/Sub 経由でしか通知できません。仕様上の制約ではありますが、設計としてもメリットがあります。

  • 疎結合 — Publisher 側は Subscriber の存在を知らなくていい
  • 拡張性 — Slack 通知や LINE 通知を追加したくなったら Subscriber を増やすだけ
  • 信頼性 — Subscriber が落ちていてもメッセージは保持される(デフォルト最大 7 日

構築手順

1. 予算を作成する

GCP コンソールで Billing → Budgets & alerts → Create budget を開きます。

Scope

項目
Namemy-budget(任意)
Time rangeMonthly
Projects監視したいプロジェクト
ServicesAll services

Amount

項目
Budget typeSpecified amount
Target amount200(円・任意の金額)

Actions ではデフォルトの 50% / 90% / 100% に加えて、Add threshold で 100% Forecasted を 1 つ追加します。Forecasted は「このペースだと月末に予算超えそう」という早期警告です。

PercentTrigger
50%Actual
90%Actual
100%Actual
100%Forecasted

Email alerts to billing admins and users にチェックを入れて完了です。Pub/Sub の接続は後で行います。

2. 必要な API を有効化する

gcloud services enable \
  pubsub.googleapis.com \
  cloudfunctions.googleapis.com \
  cloudbuild.googleapis.com \
  cloudbilling.googleapis.com \
  run.googleapis.com

cloudbilling.googleapis.com は請求アカウントの操作に必要です。run.googleapis.com は Cloud Functions 2nd gen が内部的に Cloud Run 上で動くために必要です。

3. Pub/Sub トピックを作成する

gcloud pubsub topics create budget-alert-topic

トピックはメッセージの送信先となる名前付きリソースです。

4. Cloud Function を実装する

作業ディレクトリを作って main.py を書きます。

mkdir -p budget-stopper && cd budget-stopper
# main.py
import base64
import json
from googleapiclient import discovery

PROJECT_ID = 'your-project-id'
PROJECT_NAME = f'projects/{PROJECT_ID}'

def stop_billing(event, context):
    pubsub_data = base64.b64decode(event['data']).decode('utf-8')
    pubsub_json = json.loads(pubsub_data)

    cost_amount = pubsub_json['costAmount']
    budget_amount = pubsub_json['budgetAmount']
    print(f'Cost: {cost_amount}, Budget: {budget_amount}')

    if cost_amount <= budget_amount:
        print(f'No action needed. (Current cost: {cost_amount})')
        return

    billing = discovery.build('cloudbilling', 'v1', cache_discovery=False)
    projects = billing.projects()

    billing_enabled = projects.getBillingInfo(name=PROJECT_NAME).execute().get('billingEnabled')
    if billing_enabled:
        body = {'billingAccountName': ''}
        res = projects.updateBillingInfo(name=PROJECT_NAME, body=body).execute()
        print(f'Billing disabled: {res}')
    else:
        print('Billing already disabled')

ポイントは body = {'billingAccountName': ''} の部分です。空文字を渡すとプロジェクトから請求アカウントが切り離され、課金されるリソース(Cloud Run、Cloud Build など)がすべて停止します。

# requirements.txt
google-api-python-client==2.149.0

5. デプロイする

gcloud functions deploy stop_billing \
  --runtime python311 \
  --trigger-topic budget-alert-topic \
  --entry-point stop_billing \
  --region asia-northeast1 \
  --source .
オプション意味
--runtime python311Python 3.11 で動かす
--trigger-topicこのトピックにメッセージが来たら起動
--entry-pointmain.pystop_billing 関数を呼ぶ
--region asia-northeast1東京リージョン

途中で API [eventarc.googleapis.com] not enabled と聞かれたら y で有効化します。Eventarc は GCP のイベントルーティングサービスで、Cloud Functions 2nd gen はイベントトリガーの管理に Eventarc を使うため必要です。

デプロイには 3〜5 分かかります。最後に state: ACTIVE が表示されれば成功です。

6. サービスアカウントに権限を付与する

Cloud Function が請求アカウントを操作するには roles/billing.admin が必要です。デプロイしたファンクションのサービスアカウントを取得して権限を付与します。

SA=$(gcloud functions describe stop_billing \
  --region asia-northeast1 \
  --format="value(serviceConfig.serviceAccountEmail)")

gcloud billing accounts add-iam-policy-binding XXXXXX-XXXXXX-XXXXXX \
  --member="serviceAccount:$SA" \
  --role="roles/billing.admin"

XXXXXX-XXXXXX-XXXXXX の部分は自分の Billing Account ID に置き換えます。gcloud billing accounts list で確認できます。

7. 予算と Pub/Sub を紐付ける

最後にコンソールで Step 1 で作った予算を編集して、Actions セクションの Connect a Pub/Sub topic to this budget にチェックを入れます。Project と Topic(budget-alert-topic)を選択して保存すると、全パーツが繋がります。

動作確認

実際に予算を超過させなくても、Pub/Sub にテストメッセージを直接送ることで動作確認できます。

安全なテスト

costAmount を予算未満にすれば、ファンクションは起動しますが請求アカウントは切り離されません。

gcloud pubsub topics publish budget-alert-topic \
  --message='{"costAmount": 1, "budgetAmount": 200, "currencyCode": "JPY"}'

ログを確認します。

sleep 30 && gcloud functions logs read stop_billing --region asia-northeast1 --limit 20

期待される出力:

stop-billing  Cost: 1, Budget: 200
stop-billing  No action needed. (Current cost: 1)

実際の停止テスト

⚠️ これを実行するとプロジェクトの課金が本当に止まります。

gcloud pubsub topics publish budget-alert-topic \
  --message='{"costAmount": 999, "budgetAmount": 200, "currencyCode": "JPY"}'

ログに Billing disabled が出れば成功です。

注意点

アラートには遅延がある

Cloud Billing の予算アラートはリアルタイムではありません。コストデータの反映には通常 1 日以内、場合によっては 24 時間以上かかることがあり、また最初の Pub/Sub 通知が届くまでにも数時間かかる場合があります。¥200 ぴったりで止まることはなく、数百円オーバーする可能性があります。

コスト

この仕組み全体は GCP の無料枠内で運用できます。

サービス無料枠今回の使用量
Pub/Sub月 10 GB数 KB
Cloud Functions月 200 万呼び出し月数回
Cloud Build月 120 分デプロイ時のみ

まとめ

  • GCP の予算アラートはデフォルトではメール通知のみで課金を止められないため、Pub/Sub + Cloud Functions で自前の停止機構を構築した
  • 請求アカウントを billingAccountName: '' でデタッチすることで全課金リソースを停止できる
  • 個人開発や検証環境のお守りとして、無料で運用できる

参考