Blog / Firebase

課金を止める関数が、動いているのに効かなかった

2nd gen の実行サービスアカウントは第1世代と違う

2026年08月16日 公開 / Firebase ・ Cloud Functions ・ 課金対策

Firebase を従量課金プランに切り替えるとき、いちばんの懸念は支払いの上限が存在しないことです。予算超過を検知して課金そのものを切る関数を用意しましたが、「デプロイもログも正常なのに、実際には止まらない」という壊れ方をしました。

01 — 仕組み

予算アラートを受けて課金を切る

流れはこうです。Cloud Billing で予算アラートを作り、通知先を Pub/Sub のトピックにします。 そのトピックを購読する関数を置いておき、超過を知らせるメッセージが来たら プロジェクトの課金アカウントを切り離します。切れればそれ以上は課金されません。

設定手順そのものは短いものでした。

1. 関数をデプロイ初回は成果物置き場のクリーンアップポリシー未設定でエラーになったので、firebase functions:artifacts:setpolicy で解決しました。
2. 予算アラートテスト用にごく小さい金額で作り、Pub/Sub トピックに接続します。
3. 権限付与サービスアカウントに、課金アカウント側で課金管理者の権限を与えます。
4. 発火テストgcloud pubsub topics publish で偽の超過メッセージを流し、実際に止まるか確認します。

02 — 罠

ログは出るのに、最後の一行だけ失敗する

手順書どおりに権限を付けたのに、テストするとこうなりました。 関数は起動し、ログに受け取った金額まで出力され、そして最後の課金停止の呼び出しだけが PERMISSION_DENIED で失敗する。一見すると成功しているように見える壊れ方です。

原因は、権限を与えた相手が違ったことでした。 参考にした手順は Cloud Functions 第1世代の実行アカウント(App Engine の既定アカウント)を前提にしていましたが、 実際にデプロイされていたのは 第2世代で、こちらは Cloud Run が基盤です。 実行アカウントは Compute Engine の既定アカウントでした。 つまり、動いていないアカウントに権限を付けて満足していたわけです。

確認は次のコマンドが早いです。推測せずに、実際にどのアカウントで動いているかを見ます。

gcloud functions describe <関数名> \
  --format="value(serviceConfig.serviceAccountEmail)"

なお、途中でプロジェクト側に別の課金関連の権限を足してみましたが、これは無関係で直りませんでした。 公式ドキュメントにも、必要なのは課金アカウント側の課金管理者の権限だけだと書かれています。 効かない権限を足していくと、原因がさらに見えにくくなります。

03 — 教訓

安全装置は、発火させて確かめる

この関数は「普段は絶対に動かないもの」です。だからこそ、置いただけで安心してしまいます。 実際に偽のメッセージを流して発火させるまで、壊れていることに気づけませんでした。 バックアップと同じで、復旧を試していないバックアップは無いのと同じという話だと思っています。

Follow

こういう記録を置いています

個人開発でつまずいた話や、作りかけのものは Instagram(@kenpiro1012)と note にも置いています。 記事の一覧は記事ページにまとまっています。