はじめに
Cloud Run で動かす MCP サーバーを追加しようとしたところ、Terraform の apply がサービスアカウントの作成で失敗しました。原因は、GCP プロジェクトのサービスアカウント数が上限に達していたことです。
サービスアカウント(以下 SA)は、人ではなくアプリケーションやジョブが GCP の API を呼ぶときに使うアカウントです。本記事では、SA の上限に達したときの対処法として、権限が同じ SA をまとめて枠を空ける方法と、quota の引き上げ方を、実際に対応した記録をもとに解説します。
本記事のまとめ
- SA 作成時の
429エラーはrateLimitExceededと表示されるが、SA 数の上限に達したことによるエラーで、待っても直らない。 - 対処は「SA をまとめて枠を空ける」と「quota の引き上げを申請する」の 2 つ。前者は反映直後に効き、後者は審査があり、反映の目安は 2〜3 週間とされる。
- 権限の種類が同じだった Cloud Build 専用 SA 25 個を 1 個にまとめ、24 枠を空けた。
- 削除した SA は quota の枠を占有しない。まとめた直後に、SA は 100 個から 76 個に減った。
- SA の差し替えと旧 SA の削除を同じ apply に入れると、Cloud Build のトリガー(ビルドを自動で実行する設定)の更新がすべて
403になる。apply は 2 回に分ける。

サービスアカウント上限エラー(429)は待っても直らない
エラーには rateLimitExceeded と出ますが、実体はプロジェクトあたりの SA 数の上限です。時間をおいても SA の数は減らないため、待っても直りません。
CI で実行した terraform apply は、次のエラーで SA の作成に失敗しました。
Error 429: A quota has been reached for project number PROJECT_NUMBER:
Service accounts per project.
...
"retryDelay": "86401s" ... rateLimitExceeded
retryDelay に 86,401 秒(約 24 時間)が付いているため一時的な制限に見えますが、実際に数えると SA はちょうど上限に張り付いていました。
quota(割り当て)とは、GCP がプロジェクトごとに設けているリソース数や API 呼び出し回数の上限です。SA の一覧と、SA 数の quota は次のコマンドで確認できます。
gcloud iam service-accounts list --project=PROJECT_ID
gcloud beta quotas info describe ServiceAccountsPerProject \
--service=iam.googleapis.com --project=PROJECT_ID2 つ目のコマンドの出力(抜粋)は次のとおりです。value が上限値を表します。
quotaId: ServiceAccountsPerProject
metric: iam.googleapis.com/quota/service-account-count
details: { value: '100' }
私のプロジェクトでは上限が 100 で、SA もちょうど 100 個ありました。公式ドキュメントによると上限値はプロジェクトによって異なるため、まず自分のプロジェクトの値を確かめてください。

対処は次の 2 つです。効くまでの時間と必要な作業が違うため、状況に合わせて選ぶか、両方を並行して進めてください。
| 対処 | 効くまで | 必要な作業 |
| SA をまとめて枠を空ける | 反映した直後 | 権限の見直しと、SA を使う設定の差し替え |
| quota の引き上げを申請する | 審査があり、目安は 2〜3 週間 | 申請だけ。コードの変更は不要 |
私は両方を並行して進めました。先に効いたのは SA をまとめる対処だったため、そちらから説明します。
SA をまとめて枠を空ける(反映直後に効く)
不要な SA が見つからなかったため、権限の種類が同じだった Cloud Build 専用の SA をまとめました。25 個を 1 個にまとめ、反映した直後に 24 枠を空けています。
まず不要な SA が無いか確認する
最初に SA を種類ごとに数えました。無効化されている SA は 0 個で、どれも使われていました。
| 分類 | 数 | 扱い |
| Terraform で管理している SA | 96 | すべて稼働中 |
| GCP が自動で作るデフォルトの SA(Compute Engine / App Engine) | 2 | 削除対象外 |
Google AI Studio が自動で作った SA(ais-gemini-key-*) | 2 | 消してはいけない |
ais-gemini-key-* は、Google AI Studio で Gemini API キーを発行したときに自動で作られる SA です。削除すると、その SA にひもづく Gemini API キーが無効になります。
この SA のメールアドレスは @<プロジェクト番号>.iam.gserviceaccount.com の形で、自分で決めた命名規則から外れています。不要な SA だと思って消さないよう注意してください。
25 個の SA は権限の種類が同じだった
Cloud Build は GCP のビルドサービスです。GitHub への push などをきっかけにビルドを自動で実行する設定を「トリガー」と呼び、トリガーごとにビルドで使う SA を指定できます。私の環境では、Cloud Run へデプロイするパイプラインごとにビルド用の SA を作っており、その数が 25 個になっていました。
付与していたロールを並べると、25 個すべてが次の 5 つでした。違いは、どの Artifact Registry リポジトリと、どの実行用 SA を対象にするかだけです。
roles/logging.logWriter project 単位
roles/run.developer project 単位
roles/storage.objectViewer Cloud Build 用バケット単位
roles/artifactregistry.writer Artifact Registry リポジトリ単位
roles/iam.serviceAccountUser 実行用 SA 単位
Artifact Registry はコンテナイメージを保管するサービスです。roles/iam.serviceAccountUser は、別の SA として処理を実行する(actAs)ための権限を指します。
統合で広がる権限を確認する
SA を共有すると、個別に絞っていた権限の範囲が広がります。広がるのは次の 2 点です。
| 権限 | 統合前 | 統合後 |
roles/artifactregistry.writer | 自分用のリポジトリ 1 つ | すべてのリポジトリ |
roles/iam.serviceAccountUser | 対応する実行用 SA 1 つ | すべての実行用 SA |
ただし、Cloud Run を更新する roles/run.developer は、統合前からプロジェクト単位で付与していました。つまり 25 個のどの SA も、もともとプロジェクト内の任意の Cloud Run を更新できる状態でした。
SA を分けていても、守れていた境界は見た目ほど強くなかったことになります。広がる差は小さいと判断し、統合を選びました。
統合の手順
共有の SA を 1 個作り、各トリガーが使う SA をそれに差し替えてから、不要になった SA を削除しました。リポジトリ単位と実行用 SA 単位の権限は、プロジェクト単位にまとめると必要以上に広がるため、個別の付与を残して付与先だけを共有 SA に変えています。
私の環境は Terraform で管理しているため、terraform plan で SA の削除 25 件と作成 1 件が出ることを確かめてから反映しました。

削除した SA は quota の枠を占有しない
SA をまとめると、多くの SA を削除することになります。削除した SA はしばらく復元できるため、その間は枠を占有すると私は考えていましたが、実際には占有しません。
公式ドキュメントには、削除した SA は quota に数えないと明記されています。削除から 30 日以内なら gcloud beta iam service-accounts undelete で復元でき、30 日を過ぎると完全に削除されます。
移行時の注意:SA の差し替えと削除は apply を 2 回に分ける
統合を 1 回の apply で反映したところ、SA の削除は成功したものの、Cloud Build トリガー 28 本の更新がすべて Error 403: The caller does not have permission で失敗しました。CI の SA は roles/editor を持っており、Owner 相当の自分のアカウントで更新しても同じ 403 だったため、権限不足ではありません。
28 本のトリガーの設定を調べると、すべてが削除済みの SA を参照していました。Terraform の設定から旧 SA への参照が消えたことで、「トリガーを更新してから SA を削除する」という順序の依存がなくなり、旧 SA が先に削除されたためです。参照先の SA が存在しないトリガーは、更新を 403 で拒否されるようです。
このときは、トリガーの設定をバックアップしてから削除し、apply で作り直して復旧しました。depends_on(リソースを処理する順序を明示する設定)を足しても防げないため、これから統合する場合は次の 2 回に分けて apply してください。
- 新しい SA の作成と、トリガーの参照先の差し替えだけを apply する
- 参照が移ったことを確認してから、旧 SA の削除を apply する
結果:SA は 100 個から 76 個に減った
まとめた直後に、SA の数は 100 個から 76 個に減りました。quota の承認を待たずに、止まっていた MCP サーバー用の SA を作成できています。
その後、共有 SA を使うトリガーが 3 回発火し、すべて成功しました。ビルドだけでなく Cloud Run の更新まで共有 SA で通っており、新しいリビジョンが作られたことも確認しています。

ただし、発火を確かめたトリガーはまだ 1 本だけです。権限の付け替え漏れは各トリガーの初回ビルドで表に出るため、残りは初回ビルドのときに確認します。
quota の引き上げを申請する(審査あり・目安 2〜3 週間)
SA 数の quota は申請で引き上げられますが、100 を超える値には Google の審査が必要です。コードを変えずに枠そのものを広げられる一方、反映まで時間がかかります。
SA 数の quota(ServiceAccountsPerProject)は、GCP コンソールの「IAM と管理」にある「Quotas & System Limits」ページから引き上げを申請できます。私は 100 から 250 への引き上げを申請しました。
申請フォームには「値が 100 を超える場合はサービス プロバイダ(Google)の承認が必要」と書かれていました。公式ドキュメントでも、quota の調整リクエストは審査の対象とされています。申請時に示された反映の目安は 2〜3 週間でしたが、執筆時点で quota の画面を確認すると、値は 250 と表示されていました。

SA をまとめる対処で当面の枠を空け、quota の引き上げで今後の余裕を確保する形で、両方を並行して進めています。
まとめ
SA の上限に達したときの対処は、SA をまとめて枠を空けるか、quota を引き上げるかの 2 つです。今回の対応から分かったことは次のとおりです。
- SA 作成時の 429 は SA 数の上限によるもので、待っても直らない。まず
gcloudで SA の数と quota を確認する。 - 権限の種類が同じ SA をまとめると、反映した直後に枠が空く。削除した SA は枠を占有しない。
- SA をまとめるときは、参照の差し替えと旧 SA の削除を別の apply に分ける。
- quota の引き上げはコードを変えずに枠を広げられるが、審査があり、反映の目安は 2〜3 週間とされる。私は SA をまとめる対処と並行して申請した。
パイプラインごとに SA を作っている場合は、付与しているロールを並べて比べてみてください。種類が同じなら、上限へ達する前にまとめることを検討する価値があります。

