G-gen の藤岡です。当記事では、Google Cloud(旧称 GCP)の Cloud Functions(第 2 世代)で関数をデプロイする時の裏側の動きを Cloud Logging から深掘りしていきます。Cloud Functions は裏側を意識せずに使えるサービスではありますが、各サービスとの関係を理解しておくとエラー時に役立ちます。 前提知識 Cloud Functions サービスアカウントとサービスエージェント ユーザー管理サービスアカウント(サービスアカウント) ユーザー管理サービスアカウント デフォルトのサービスアカウント Google 管理サービスアカウント(サービスエージェント) サービス固有のサービスエージェント Google API サービスエージェント Google 管理のサービスアカウントのロールマネージャー Cloud Functions(第 2 世代) サービスアカウントとサービスエージェント Cloud Logging から確認した結果 前提 リソースの相関図 Cloud Logging のログ内容 0. API の有効化 1. Cloud Storage へソースコードのアップロード 2. Artifact Registry へリポジトリの作成 3. Cloud Build でビルド 4. Artifact Registry へコンテナイメージのプッシュ Tips Artifact Registry のリポジトリ 前提知識 Cloud Functions Cloud Functions は、FaaS(Function as a Service)と呼ばれるサーバーレスのコンピューティングサービスです。 詳細は以下の記事をご参照ください。 blog.g-gen.co.jp Cloud Functions には従来の第 1 世代と、 Cloud Run と Eventarc がベースの機能強化された第 2 世代の 2 種類が提供されています。 両者の詳細な違いは ドキュメント をご参照ください。当記事ではこれ以降 Cloud Functions の第 2 世代を前提に記載します。 サービスアカウントとサービスエージェント サービスアカウント は「ユーザー管理サービスアカウント」と「Google 管理サービスアカウント」の 2 つのカテゴリに分けられます。 サイトによって表記が異なる場合がありますが、当記事では こちらのドキュメント の表記に従って記載します。 一般的に、「ユーザー管理サービスアカウント」が サービスアカウント と呼ばれ、「Google 管理サービスアカウント」が サービスエージェント と呼ばれます。 blog.g-gen.co.jp 更に詳細に分類すると以下のようになります。 カテゴリ タイプ プリンシパル ユーザー管理サービスアカウント ユーザー管理サービスアカウント SERVICE_ACCOUNT_NAME @PROJECT_ID.iam.gserviceaccount.com ユーザー管理サービスアカウント デフォルトのサービスアカウント サービスによる (例:Compute Engine の場合 PROJECT_NUMBER-compute @developer.gserviceaccount.com Google 管理サービスアカウント サービス固有のサービス エージェント サービスによる (例:Compute Engine の場合 service-PROJECT_NUMBER @compute-system.iam.gserviceaccount.com Google 管理サービスアカウント Google API サービス エージェント PROJECT_NUMBER @cloudservices.gserviceaccount.com Google 管理サービスアカウント Google 管理のサービス アカウントのロール マネージャー service-agent-manager @system.gserviceaccount.com ユーザー管理サービスアカウント(サービスアカウント) ユーザー管理サービスアカウント ユーザー管理サービスアカウント は、プロジェクトリソースで、ユーザー自身が作り Compute Engine や Cloud Functions 等のリソースにアタッチして使います。 プリンシパルは SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com です。 デフォルトのサービスアカウント デフォルトのサービス アカウント は、API の有効化やサービスの利用時に自動で作成されるアカウントです。 デフォルトのサービスアカウントには 編集者(roles/editor) が付与されます。 Google 管理サービスアカウント(サービスエージェント) サービス固有のサービスエージェント サービス固有のサービス エージェント は、Google Cloud サービスが別のサービスを呼び出すときに用いる特殊なサービスアカウントです。 例として、Cloud Logging で Cloud Storage バケットにログシンクする時は、サービスエージェントが裏側では使われています。 付与されているロールを削除すると、普段は意識しない裏側で動いている Google Cloud サービスが機能しなくなる場合があるので注意が必要です。 Google API サービスエージェント Google API サービスエージェント は、サービス固有のサービスエージェントと同様に裏側で使われます。 PROJECT_NUMBER@cloudservices.gserviceaccount.com というプリンシパルでデフォルトで 編集者(roles/editor) が付与されます。 Google 管理のサービスアカウントのロールマネージャー Google 管理のサービスアカウントのロールマネージャー は、上記の Google 管理サービスアカウントを管理します。 サービスエージェントが自動で作成された際、サービスエージェントへのロールの付与をこのロールマネージャーが裏で行っています。 プリンシパルは service-agent-manager@system.gserviceaccount.com で、これは IAM のページには表示されず、 監査ログ にのみ表示されます。 Cloud Functions(第 2 世代) サービスアカウントとサービスエージェント Cloud Functions では、デフォルトで以下のサービスアカウントとサービスエージェントを使います。 第 2 世代では、デフォルトでは Cloud Functions のサービスアカウントに Compute Engine のデフォルトのサービスアカウント( PROJECT_NUMBER-compute@developer.gserviceaccount.com )を使いますが、Compute Engine API が有効化されていない場合はこのサービスアカウントが存在しないため、App Engine のデフォルトのサービスアカウント( PROJECT_ID@appspot.gserviceaccount.com )が使われます。 サービス カテゴリ プリンシパル デフォルトロール Cloud Functions サービスアカウント PROJECT_NUMBER-compute @developer.gserviceaccount.com / PROJECT_ID @appspot.gserviceaccount.com 編集者 Cloud Functions サービスエージェント service-PROJECT_NUMBER @gcf-admin-robot.iam.gserviceaccount.com Cloud Functions サービス エージェント Cloud Build サービスアカウント PROJECT_NUMBER @cloudbuild.gserviceaccount.com Cloud Build サービス アカウント Cloud Build サービスエージェント service-PROJECT_NUMBER @gcp-sa-cloudbuild.iam.gserviceaccount.com Cloud Build サービス エージェント Artifact Registry サービスエージェント service-PROJECT_NUMBER @gcp-sa-artifactregistry.iam.gserviceaccount.com Artifact Registry サービス エージェント 参考: IAM によるアクセス制御 Cloud Logging から確認した結果 前提 コンソールから Cloud Functions(第 2 世代)をデプロイした時の一連の流れを示しています。 Cloud Logging のログから挙動を確認しています。 ログは一部を抜粋しています。 リソースの相関図 Cloud Functions をデプロイする時の各リソースの相関は以下の通りです。 リソースの相関図 Cloud Logging のログ内容 0. API の有効化 Cloud Functions(第 2 世代)で関数をデプロイするには以下の API を有効化します。 Cloud Build API Cloud Functions API Cloud Logging API Cloud Pub/Sub API Artifact Registry API Cloud Run Admin API API を有効化すると、サービスアカウントやサービスエージェントが自動で作られ、前述の Google 管理のサービスアカウントのロールマネージャー( service-agent-manager@system.gserviceaccount.com )がそれぞれにロールを付与していきます。 # 一部抜粋 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Google 管理のサービス アカウントのロールマネージャーが実行 " principalEmail ": " service-agent-manager@system.gserviceaccount.com " } , ... " serviceName ": " cloudresourcemanager.googleapis.com ", " methodName ": " SetIamPolicy ", ... " resourceName ": " projects/PROJECT_ID ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": { " bindingDeltas ": [ { # Cloud Build サービスアカウントへ権限付与 " action ": " ADD ", " role ": " roles/cloudbuild.builds.builder ", " member ": " serviceAccount:PROJECT_NUMBER@cloudbuild.gserviceaccount.com " } ] } } , ... 1. Cloud Storage へソースコードのアップロード Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )が Cloud Storage に 2 つのバケットをの作成と、zip 化されたソースコードをアップロードします。 gcf-v2-uploads-PROJECT_NUMBER-REGION gcf-v2-sources-PROJECT_NUMBER-REGION # gcf - v2 - uploads -PROJECT_NUMBER- asia - northeast1 バケットの作成 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.buckets.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-uploads-PROJECT_NUMBER-asia-northeast1 ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": { " bindingDeltas ": [ { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketReader ", " member ": " projectViewer:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectReader ", " member ": " projectViewer:fast-chess-399414 " } ] } } , ... # gcf - v2 - uploads -PROJECT_NUMBER- asia - northeast1 バケットへ zip 化したソースコードをアップロード { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.objects.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-uploads-PROJECT_NUMBER-asia-northeast1/objects/5065d76a-5591-4d40-980f-5b9c425f92e4.zip ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": {} } , ... # gcf - v2 - sources -PROJECT_NUMBER- asia - northeast1 バケットの作成 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.buckets.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-sources-PROJECT_NUMBER-asia-northeast1 ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": { " bindingDeltas ": [ { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketReader ", " member ": " projectViewer:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectReader ", " member ": " projectViewer:fast-chess-399414 " } ] } } , ... # gcf - v2 - sources -PROJECT_NUMBER- asia - northeast1 バケットへ zip 化したソースコードをアップロード { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.objects.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-sources-PROJECT_NUMBER-asia-northeast1/objects/function-1/function-source.zip ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": {} } , ... 2. Artifact Registry へリポジトリの作成 Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )が Artifact Registry に REGION-docker.pkg.dev/PROJECT_ID/gcf-artifacts リポジトリを作成します。 # gcf - artifacts リポジトリの作成 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " artifactregistry.googleapis.com ", " methodName ": " google.devtools.artifactregistry.v1.ArtifactRegistry.CreateRepository ", ... " resourceName ": " projects/PROJECT_ID/locations/asia-northeast1 ", " request ": { " repository ": { " labels ": { " goog-managed-by ": " cloudfunctions " } , " name ": " projects/PROJECT_ID/locations/asia-northeast1/repositories/gcf-artifacts ", " format ": " DOCKER " } , " @type ": " type.googleapis.com/google.devtools.artifactregistry.v1.CreateRepositoryRequest ", " repositoryId ": " gcf-artifacts ", " parent ": " projects/PROJECT_ID/locations/asia-northeast1 " } , ... 3. Cloud Build でビルド Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )が Cloud Build でビルドを開始します。 # Cloud Build が開始 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " cloudbuild.googleapis.com ", " methodName ": " google.devtools.cloudbuild.v1.CloudBuild.CreateBuild ", ... " resourceName ": " projects/PROJECT_ID/builds ", ... Cloud Storage の gcf-v2-sources-PROJECT_NUMBER-asia-northeast1 バケット内の zip 化されたソースコードをビルドで使います。 # gcf - v2 - sources -PROJECT_NUMBER- asia - northeast1 バケット内のソースコードを取得 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.objects.get ", ... " resourceName ": " projects/_/buckets/gcf-v2-sources-PROJECT_NUMBER-asia-northeast1/objects/function-1/function-source.zip ", ... 4. Artifact Registry へコンテナイメージのプッシュ Cloud Build のサービスアカウント( PROJECT_NUMBER@cloudbuild.gserviceaccount.com )がビルドしたコンテナイメージを Artifact Registry へプッシュします。 # ビルド済みコンテナイメージのプッシュ { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Build のサービスアカウントが実行 " principalEmail ": " PROJECT_NUMBER@cloudbuild.gserviceaccount.com ", " serviceAccountDelegationInfo ": [ { " firstPartyPrincipal ": { " principalEmail ": " cloud-build-argo-foreman@prod.google.com " } } ] , " principalSubject ": " serviceAccount:PROJECT_NUMBER@cloudbuild.gserviceaccount.com " } , ... " serviceName ": " artifactregistry.googleapis.com ", " methodName ": " Docker-StartUpload ", ... " resourceName ": " projects/PROJECT_ID/locations/asia-northeast1/repositories/gcf-artifacts ", ... Tips Artifact Registry のリポジトリ Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )は Artifact Registry のリポジトリを作る前に REGION-docker.pkg.dev/PROJECT_ID/gcf-artifacts リポジトリを探しに行きます。 初回デプロイでは、対象のリポジトリは存在しないため、以下のエラーとなります。 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": { " code ": 5 , " message ": " repository not found: namespaces/PROJECT_ID/repositories/gcf-artifacts " } , " authenticationInfo ": { " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " artifactregistry.googleapis.com ", " methodName ": " google.devtools.artifactregistry.v1.ArtifactRegistry.GetRepository ", ... " severity ": " ERROR ", ... 上記のエラーの後に、CreateRepository メソッドでリポジトリが作成されます。このことから、以下の記事の「(b)Cloud Functions(第二世代)が自動生成するCloud Storage バケット、Artifact Registry リポジトリはCMEKでの暗号化に非対応」の項目にあるように先にリポジトリを作成し CMEK の設定をすることで、Cloud Functions で使うリポジトリの CMEK の利用が可能となります。 blog.g-gen.co.jp 藤岡 里美 (記事一覧) クラウドソリューション部 接客業からエンジニアへ。2022年9月 G-gen にジョイン。Google Cloud 認定資格は全冠。2023 夏アニメのオススメは、ダークギャザリング。箏を習っています :) Follow @fujioka57621469