先日、既存のCloud Functionsを第1世代から第2世代へ移行する機会がありました。 その際、IAM設定が想像以上に変わっていたことに驚き、また、カスタムサービスアカウントの設定方法も第1世代とは異なっていました。
この記事では、実際の移行作業で得た技術的な気づきと、正常に動作させるまでに必要だった設定を共有します。
この記事の対象読者
- Google CloudでCloud Functionsを使用している方
- 第1世代から第2世代への移行を検討・実施している方
- TerraformでCloud Functionsを管理している方
移行時に最初に直面した問題:IAMリソースタイプの変更
第2世代への移行で最初につまずいたのは、TerraformのIAMリソースが全く異なることでした。
第1世代での設定(従来の方法)
# 第1世代ではこう書いていた
resource "google_cloudfunctions_function_iam_member" "invoker" {
project = var.project_id
region = "asia-northeast1"
cloud_function = "my-function"
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:${var.service_account_email}"
}
第2世代で必要な設定(Cloud Runリソースを使用)
# 第2世代ではCloud Runのリソースを使う必要がある
resource "google_cloud_run_service_iam_member" "invoker" {
project = var.project_id
location = "asia-northeast1"
service = "my-function" # 関数名がそのままサービス名になる
role = "roles/run.invoker" # ロールも変わっている!
member = "serviceAccount:${var.service_account_email}"
}
この変更の理由は、第2世代のCloud FunctionsがCloud Runベースで動作するようになったためです1。 つまり、内部的にはCloud Runサービスとして扱われているということです。
第1世代と第2世代のロールの違い(ここが重要!)
移行作業で最も苦戦したのが、必要なロールが大きく変わっていたことでした。 Google Cloudの公式ドキュメントを調査した結果、以下の違いが明確になりました:
デプロイ時に必要なロール
第1世代:
resource "google_project_iam_member" "cloud_build_permissions_gen1" {
for_each = toset([
"roles/cloudfunctions.developer", # Cloud Functions開発者
"roles/iam.serviceAccountUser", # サービスアカウント使用権限
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.cloud_build.email}"
}
第2世代2:
resource "google_project_iam_member" "cloud_build_permissions_gen2" {
for_each = toset([
"roles/cloudfunctions.developer", # Cloud Functions開発者(まだ必要)
"roles/run.admin", # Cloud Run管理者権限が追加で必要!
"roles/iam.serviceAccountUser", # サービスアカウント使用権限
"roles/eventarc.admin", # Pub/Subトリガーを使う場合は必須
"roles/artifactregistry.reader", # コンテナイメージへのアクセス
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.cloud_build.email}"
}
実行時に必要なロール
第1世代:
roles/cloudfunctions.invoker
- 関数を呼び出すため
第2世代3:
roles/run.invoker
- Cloud Runサービスとして呼び出すため4roles/eventarc.eventReceiver
- Pub/Sub以外のEventarcイベントを受信する場合5
この違いを理解せずに移行すると、「権限があるはずなのに動かない」という状況に陥ります。
特にroles/run.admin
が必要なことは、公式ドキュメントの「Cloud Functions第2世代はCloud Runインフラストラクチャ上で動作する」という記述6を理解していないと気づきにくいポイントでした。
サービスアカウントの変更点
さらに重要な変更点として、デフォルトのランタイムサービスアカウントが変更されています7:
第1世代:
- ランタイム:
PROJECT_ID@appspot.gserviceaccount.com
(App Engineデフォルト)
第2世代:
- ランタイム:
PROJECT_NUMBER-compute@developer.gserviceaccount.com
(Compute Engineデフォルト)
カスタムサービスアカウントの実装で学んだこと
デフォルトサービスアカウントの課題
デフォルトではPROJECT_NUMBER-compute@developer.gserviceaccount.com
が使用されますが、このアカウントはEditor権限を持っています8。セキュリティの観点から、専用のサービスアカウントを作成することにしました。
実際に動作した構成
以下は、実際のプロジェクトで正常に動作した構成を汎用化したものです:
# Cloud Build用サービスアカウント(デプロイを実行する)
resource "google_service_account" "cloud_build" {
account_id = "cloud-build-deployer"
project = var.project_id
display_name = "Cloud Build Deployer"
description = "Cloud Functionsのデプロイを実行するサービスアカウント"
}
# Cloud Functions実行用サービスアカウント
resource "google_service_account" "cloud_functions" {
account_id = "cf-my-function"
project = var.project_id
display_name = "Cloud Functions Runtime"
description = "Cloud Functions実行時に使用するサービスアカウント"
}
必要な権限設定
ここが重要なポイントです。Cloud BuildがCloud Functionsのサービスアカウントを使用してデプロイを実行するため、roles/iam.serviceAccountUser
権限の付与が必要でした9:
# Cloud BuildがFunctionsのサービスアカウントを使用するための権限
resource "google_service_account_iam_member" "cloud_build_act_as" {
service_account_id = google_service_account.cloud_functions.name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_service_account.cloud_build.email}"
}
この権限により、Cloud Buildサービスアカウントが、Cloud Functions用のサービスアカウントをCloud Functionsの実行アイデンティティとして指定できるようになります。この設定がないと、デプロイ時に権限エラーが発生します。
Pub/Subトリガーで遭遇した特殊なケース
Pub/Subサービスアカウントの形式
Pub/Subトリガーを使用する場合、以下の設定が必要でした:
resource "google_cloud_run_service_iam_member" "pubsub_invoker" {
project = var.project_id
location = "asia-northeast1"
service = "my-function"
role = "roles/run.invoker"
member = "serviceAccount:service-${var.project_number}@gcp-sa-pubsub.iam.gserviceaccount.com"
}
ここで注意すべきは、PROJECT_ID
ではなくPROJECT_NUMBER
を使用する点です。最初はこれに気づかず、なぜ権限エラーが出るのか悩みました。
なぜPub/SubのIAM設定が複雑になったのか
調査したところ、第2世代ではPub/SubトリガーがEventarcを介して動作するようになったためでした10。公式ドキュメントによると、Eventarcトリガーを作成する際にはroles/eventarc.admin
権限が必要で11、トリガーサービスアカウントにはroles/run.invoker
権限が必要です12。
Cloud Build経由でデプロイする場合の追加要件
公式ドキュメントを確認したところ、Cloud Build経由でGen2関数をデプロイする場合、以下の追加権限も必要でした13:
# Cloud Buildサービスアカウントに追加で必要な権限
resource "google_project_iam_member" "cloud_build_additional" {
for_each = toset([
"roles/storage.objectViewer", # ソースコードへのアクセス
"roles/logging.logWriter", # ビルドログの書き込み
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.cloud_build.email}"
}
Cloud Buildでのデプロイ設定
実際に使用したCloud Build設定(汎用化済み):
steps:
- id: deploy cloud function
name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: 'bash'
args:
- '-c'
- |
gcloud functions deploy my-function \
--gen2 \
--region=asia-northeast1 \
--trigger-topic=my-topic \
--runtime=nodejs20 \
--entry-point=main \
--service-account=${_CF_SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com
--gen2
フラグと--service-account
オプションが重要です。
Secret Managerとの統合(追加の発見)
環境変数ではなくSecret Managerを使う利点
移行作業中に、Secret Managerとの統合が第2世代でより使いやすくなっていることに気づきました2。
# Secret Managerへのアクセス権限
resource "google_secret_manager_secret_iam_member" "function_secrets" {
for_each = toset([
"API_KEY",
"DATABASE_PASSWORD",
])
project = var.project_id
secret_id = each.value
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cloud_functions.email}"
}
デプロイ時の設定:
--set-secrets="API_KEY=API_KEY:latest" \
--set-secrets="DB_PASSWORD=DATABASE_PASSWORD:latest"
これにより、環境変数に直接機密情報を設定する必要がなくなりました。
移行作業で得た教訓
1. 関数名とCloud Runサービス名の関係
第2世代では、デプロイした関数名がそのままCloud Runサービス名になります。これを理解していないと、IAM設定時に混乱します。
2. PROJECT_NUMBERとPROJECT_IDの使い分け
特にPub/Subのサービスアカウントでは、必ずPROJECT_NUMBERを使用する必要があります:
正しい: service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com
間違い: service-my-project-id@gcp-sa-pubsub.iam.gserviceaccount.com
3. 権限エラーのデバッグ方法
権限エラーが発生した場合、Cloud Loggingで以下を確認すると原因が特定しやすいです:
- どのサービスアカウントが使用されているか
- どのロールが不足しているか
- リソース名(特にCloud Runサービス名)が正しいか
まとめ
Cloud Functions第2世代への移行では、以下の点で大きな変更がありました:
- IAMリソースタイプの変更:
google_cloudfunctions_function_iam_member
からgoogle_cloud_run_service_iam_member
へ - ロールの変更:
roles/cloudfunctions.invoker
からroles/run.invoker
へ - 追加で必要なロール:
roles/run.admin
、roles/eventarc.admin
、roles/artifactregistry.reader
- Pub/Subトリガーの複雑化:Eventarc経由になったことによる追加設定
- デフォルトサービスアカウントの変更:App EngineデフォルトからCompute Engineデフォルトへ
- カスタムサービスアカウントの権限:
roles/iam.serviceAccountUser
の必要性
これらの変更は、第2世代がCloud Runベースになったことに起因しています。 公式ドキュメントにも「Cloud Functions第2世代はCloud Runインフラストラクチャ上で動作する」と明記されており14、この根本的なアーキテクチャの変更が、IAM設定の違いにつながっています。
移行作業を行う際は、これらの違いを理解しておくと、スムーズに進められるはずです。 特に、Cloud Run関連の権限が必要になることは、事前に知っておくべき重要なポイントです。
この記事が、同じような移行作業を行う方の参考になれば幸いです。
参考資料
- Compare Cloud Run functions | Cloud Run Documentation
- Access control with IAM | Cloud Run functions Documentation
- Function Identity | Cloud Run functions Documentation
- Terraform Tutorial | Cloud Run functions Documentation
Google Cloud Functions is now Cloud Run functions | Google Cloud Blog ↩︎
Cloud Functions IAM Roles | Cloud Run functions Documentation ↩︎ ↩︎
Access control with IAM | Cloud Run functions Documentation ↩︎
第2世代のCloud FunctionsはCloud Run上で動作するため、
roles/cloudfunctions.invoker
の代わりにroles/run.invoker
が必要です ↩︎Roles and permissions for Cloud Run targets | Eventarc Standard ↩︎
Best practices for using service accounts | IAM Documentation ↩︎
Create triggers from Pub/Sub events | Cloud Run Documentation ↩︎
Roles and permissions for Cloud Run targets | Eventarc Standard ↩︎
Pub/SubサービスエージェントがCloud Run関数を呼び出すために必要な権限です ↩︎
Build process overview | Cloud Run functions Documentation ↩︎
Google Cloud Functions is now Cloud Run functions | Google Cloud Blog ↩︎