GCPの全プロジェクトに権限を一括付与してみた

GCPの全プロジェクトに権限を一括付与してみた

GCPの1000件超のプロジェクトに一括でOwner権限を付与する作業を自動化してみました。処理速度の改善、Cloud Shell接続の問題対策、予期しなかった自動生成プロジェクトの発見など、簡単に見えた作業が実はいろいろと大変だった経験をお話しします。
2026.09.25

こんにちは、非エンジニアのHaradaです。
社内で検証用として使われるGoogle Cloud(旧称: GCP)の管理をしています。

はじめに

GCPには「重要な通知(請求関連やサービス終了など)は、プロジェクトに直接Ownerとして登録されているユーザーにしか届かない」という仕様があります。
組織(Organization)やフォルダ単位でOwnerロールを継承させていても、この手の重要通知は届きません。

管理側として「重要なメールを見逃しては困る」と考え、既存の全GCPプロジェクトに、利用者に加えて担当者2名を直接Ownerとして追加することにしました。

「CloudShellでコマンド実行すれば即可能なのでは」と軽く考えてAIに相談しながら進めてたのですが、蓋を開けてみたら想定の何倍も大変でした。

※以降、実際の担当者名やプロジェクト名はすべて仮名・ダミー値に置き換えています。

やりたかったこと

  • 対象: 組織配下の既存Google Cloudプロジェクト全部
  • やること: 担当者A・担当者Bの2名を roles/owner として直接IAM付与
  • 制約: 手作業でポチポチやるには数が多すぎるので、CloudShellから gcloud コマンドでスクリプト実行したい

第一段階: 牛歩すぎる処理速度

AIに相談し、まずはシンプルに以下のコマンドを実行しました。

USER_A_EMAIL="user-a@example.com"
USER_B_EMAIL="user-b@example.com"

for PROJECT_ID in $(gcloud projects list --format="value(projectId)"); do
  gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
    --member="user:${USER_A_EMAIL}" \
    --role="roles/owner" \
    --condition=None

  gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
    --member="user:${USER_B_EMAIL}" \
    --role="roles/owner" \
    --condition=None
done

処理自体は進んでいるものの、いつまでたっても終わりません。(多分30分くらいは待っていた気がします)

add-iam-policy-binding (=権限を追加するコマンド)を1回打つたびに、
Googleのサーバーに『今の権限リストをください』『この人を追加して、また送り返して』というやりとりが発生します。
このやりとり1回に1〜3秒くらいかかるため、これを1つのプロジェクトごとに、担当者2人分で2回、しかも順番待ちで1つずつやっていたので、プロジェクトの数だけこの待ち時間がそのまま積み重なって、とんでもない時間になっていました。

AIに確認をすると「対象プロジェクトはお互い独立しているので、並列化しても安全」ということが分かりました。
xargs -P で並列実行に切り替えました。

add_owners() {
  local project_id="$1"
  gcloud projects add-iam-policy-binding "${project_id}" \
    --member="user:${USER_A_EMAIL}" --role="roles/owner" --condition=None --quiet \
    > "logs/${project_id}.log" 2>&1

  gcloud projects add-iam-policy-binding "${project_id}" \
    --member="user:${USER_B_EMAIL}" --role="roles/owner" --condition=None --quiet \
    >> "logs/${project_id}.log" 2>&1

  echo "done: ${project_id}"
}
export -f add_owners
export USER_A_EMAIL USER_B_EMAIL

mkdir -p logs
gcloud projects list --format="value(projectId)" | \
  xargs -P 20 -I{} bash -c 'add_owners "$@"' _ {}

プロジェクトごとにログファイルを分けて出力するようにしました。
grep -l ERROR logs/*.log のようにファイル単位で検索できるようにしておくと、後の調査がだいぶ楽になります。

第二段階: Cloud Shellが切断される

対象プロジェクトが数百件規模だったので、並列化してもそれなりに時間がかかります。
案の定、実行中にブラウザ側のCloud Shell接続が切れる、という事態に何度か遭遇しました。

対策として、nohup と disown を組み合わせて完全にバックグラウンド化しました。
簡単に言うと、nohup と disown は「電話を切っても作業を続けておいて」という指示です。

nohup bash -c '
  gcloud projects list --format="value(projectId)" | \
    xargs -P 20 -I{} bash -c "add_owners \"\$@\"" _ {}
' > run_summary.log 2>&1 &
disown

これで、ブラウザのタブを閉じたり、接続が一時的に切れたりしても、
Cloud Shellの仮想マシン側で処理が継続するようになります(VM自体が完全にアイドル状態でシャットダウンされない限りは大丈夫)。
実行結果はすべてログファイルに残るので、再接続後に tail -f run_summary.log を実行すれば続きを追うことができました。

つまずきポイント2: 同じコマンドを2回実行してしまった

バックグラウンド化したら、これまでのようにコマンドが走らないため「反応していない?」と思い、
同じ起動コマンドを2回叩いてしまい、同じ対象リストに対して2つのジョブが同時に走らせてしまいました。
特に実害はなかったのですが、APIへの負荷が倍になり、レート制限に引っかかりやすくなるリスクがあります。

バックグラウンドジョブを片方だけ止めたかったので、PIDの前にマイナスを付けてプロセスグループごと止めました。

kill -- -<PID>

単に kill <PID> だとパイプライン内の子プロセス(xargs や gcloud)が生き残ってしまいます。

第三段階: 「何千件」の正体

対象プロジェクト数を確認したところ、当初把握していた数百件が、実際には1000件を超えていることが判明しました。
中身を見てみると、その大半が sys- から始まる長い数字のプロジェクトIDです。

これは、**Googleが裏側で自動発行する「デフォルトのGoogle Cloudプロジェクト」**でした。
Apps Scriptの公式ドキュメントによると、Apps Scriptのプロジェクトは認可管理のために必ず何らかのGoogle Cloudプロジェクトを利用する仕組みになっており、ユーザーが明示的にGCPプロジェクトを指定しない場合は、Apps Script側が自動でバックグラウンド用のデフォルトプロジェクトを作成して割り当てます
(参考: Apps Script - Cloud Platform projects)。

社内の環境で実際に確認できた範囲では、このデフォルトプロジェクトは sys- から始まる長いプロジェクトIDで作成されていました。

社員が業務でちょっとGASのスクリプトを書いたりAI Studioを触ったりするたびに、裏で勝手にプロジェクトが増えていた、というわけです。今回の目的(重要通知を確実に届ける)に照らすと、これらの自動生成プロジェクトにまでOwnerを追加する意味は薄いと判断し、対象から除外することにしました。

gcloud projects list --format="value(projectId)" | \
  grep -Ev '^(sys-|gen-lang-client-)' > projects_target.txt

wc -l projects_target.txt
# => 1138

これで対象件数が現実的な規模まで絞り込めました。

番外編: フォルダ単位での絞り込みに挑戦して撃沈

管理しているGCPプロジェクトは、部門ごとにフォルダ分けをして管理しています。
なので、「フォルダ単位で対象を指定できないか」も検討しました。
シャドウプロジェクトは大抵フォルダに入らず組織直下に転がっているだろう、という仮説です。

これを実現するには、Cloud Asset Inventory(gcloud asset search-all-resources)を使うと、フォルダ配下のプロジェクトを取得できます。

gcloud services enable cloudasset.googleapis.com

gcloud asset search-all-resources \
  --scope="folders/${FOLDER_ID}" \
  --asset-types="cloudresourcemanager.googleapis.com/Project" \
  --format="value(displayName)"

ところが、対象の25フォルダのうち大半で PERMISSION_DENIED が返ってきました。

ERROR: (gcloud.asset.search-all-resources) [user@example.com] does not have permission to access folders instance [xxxxxxxxxx:searchAllResources] (or it may not exist)

ここで学んだのは、「プロジェクト単位でOwner権限を持っていること」と「フォルダ単位で中身を覗ける権限を持っていること」はまったく別物だということです。
個々のプロジェクトのOwnerであっても、フォルダやOrganizationレベルの閲覧権限(roles/cloudasset.viewer など)がなければ、フォルダ配下を検索することはできません。

組織管理者に権限付与を依頼するとその分の待ち時間が発生してしまうため、今回は一旦諦め

最終結果

最終的に、対象1138プロジェクトに対して実行した結果は次の通りです。

  • 成功: 1129プロジェクト(担当者A・B双方へのOwner付与が完了)
  • エラー: 9プロジェクト

エラーになった9件は、各プロジェクトを確認し、対応を保留にしたり、コンソールから手動付与したりしました。

参考

おわりに

「CloudShellでコマンドをちゃちゃっと実行するだけ」の作業のはずが、蓋を開けてみるとGoogle CloudのIAM周りの仕様やCloud Shellの特性など、色々な学びがある作業になりました。
最終的には1138件中1129件を自動で処理でき、残り9件も原因が明確になったので、個別対応の方針まで立てることができました。

何かの参考になれば幸いです。

この記事をシェアする

関連記事