Cloud Functionsの裏側の動きを深掘りしてみた

記事タイトルとURLをコピーする

G-gen の藤岡です。当記事では、Google Cloud(旧称 GCP)の Cloud Functions(第 2 世代)で関数をデプロイする時の裏側の動きを Cloud Logging から深掘りしていきます。Cloud Functions は裏側を意識せずに使えるサービスではありますが、各サービスとの関係を理解しておくとエラー時に役立ちます。

前提知識

Cloud Functions

Cloud Functions は、FaaS(Function as a Service)と呼ばれるサーバーレスのコンピューティングサービスです。 詳細は以下の記事をご参照ください。 blog.g-gen.co.jp

Cloud Functions には従来の第 1 世代と、Cloud RunEventarc がベースの機能強化された第 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 サービス エージェント

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 にジョイン。ハイキューの映画を4回は見に行きたい。

Google Cloud All Certifications Engineer / Google Cloud Partner Top Engineer 2024