Eventarc を使用して Pub/Sub から関数をトリガーする

このチュートリアルでは、Pub/Sub トリガーを使用してイベント ドリブンの Cloud Run functions を作成してトリガーする方法について説明します。

Eventarc トリガーのフィルタを指定すると、イベントソースやイベント ターゲットなど、イベントの転送を構成できます。このチュートリアルの例では、Pub/Sub トピックにメッセージをパブリッシュするとイベントがトリガーされ、リクエストが HTTP リクエストの形式で関数に送信されます。

Pub/Sub を初めて使用される場合、詳細については、Pub/Sub のドキュメントでクイックスタートと主要なリファレンスをご覧ください。

必要なロール

ご自身または管理者が、デプロイ担当者アカウント、トリガー ID、さらに必要に応じて Pub/Sub サービス エージェントと Cloud Storage サービス エージェントに次の IAM ロールを付与する必要があります。

デプロイ担当者のアカウントに必要なロール

  1. プロジェクト作成者には、基本オーナーロールroles/owner)が付与されます。デフォルトでは、この Identity and Access Management(IAM)ロールには、ほとんどの Google Cloudリソースへのフルアクセスに必要な権限が含まれており、この手順は省略できます。

    プロジェクト作成者でない場合は、プロジェクトで適切なプリンシパルに必要な権限を付与する必要があります。プリンシパルは Google アカウント(エンドユーザーの場合)やサービス アカウント(アプリケーションとコンピューティング ワークロードの場合)になることもあります。詳細については、イベントの宛先のロールと権限のページをご覧ください。

    このチュートリアルを完了するために必要な権限を取得するには、プロジェクトに対する次の IAM ロールを付与するよう管理者に依頼してください。

    ロールの付与については、プロジェクト、フォルダ、組織へのアクセス権の管理をご覧ください。

    必要な権限は、カスタムロールや他の事前定義ロールから取得することもできます。

    デフォルトでは、Cloud Build の権限には、Artifact Registry アーティファクトをアップロードおよびダウンロードするための権限が含まれています

トリガー ID に必要なロール

  1. Compute Engine のデフォルトのサービス アカウントをメモしておいてください。テスト目的で、Eventarc トリガーにこのサービス アカウントを関連付け、トリガーの ID として使用します。このサービス アカウントは、Compute Engine を使用する Google Cloud サービスを有効にするか使用すると自動的に作成されます。メールアドレスの形式は次のとおりです。

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    PROJECT_NUMBER は、使用する Google Cloudプロジェクト番号に置き換えます。プロジェクト番号は、 Google Cloud コンソールの [ようこそ] ページで確認できます。また、次のコマンドでも確認できます。

    gcloud projects describe PROJECT_ID --format='value(projectNumber)'

    本番環境では、新しいサービス アカウントを作成して、必要最小限の権限を含む、最小権限の原則に従った 1 つ以上の IAM ロールを付与することを強くおすすめします。

  2. デフォルトでは、Cloud Run サービスを呼び出すことができるのは、プロジェクト オーナー、プロジェクト編集者、Cloud Run 管理者、起動元のみです。サービスごとにアクセスを制御できます。ただし、テスト目的の場合は、 Google Cloud プロジェクトの Cloud Run 起動元ロールrun.invoker)を Compute Engine サービス アカウントに付与してください。これにより、プロジェクト内のすべての Cloud Run サービスとジョブにロールが付与されます。
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/run.invoker

    Cloud Run 起動元ロールを付与せずに認証済みの Cloud Run サービスのトリガーを作成すると、トリガーは正常に作成され、アクティブになります。ただし、トリガーが期待どおりに機能せず、次のようなメッセージがログに記録されます。

    The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.
  3. プロジェクトの Eventarc イベント レシーバーのロールroles/eventarc.eventReceiver)を Compute Engine のデフォルト サービス アカウントに付与して、Eventarc トリガーがイベント プロバイダからイベントを受信できるようにします。
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/eventarc.eventReceiver

Cloud Storage サービス エージェントのオプションのロール

  • Cloud Storage からの直接イベントのトリガーを作成する前に、Cloud Storage サービス エージェントに Pub/Sub パブリッシャーのロールroles/pubsub.publisher)を付与します。

    SERVICE_ACCOUNT="$(gcloud storage service-agent --project=PROJECT_ID)"
    
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member="serviceAccount:${SERVICE_ACCOUNT}" \
        --role='roles/pubsub.publisher'

Pub/Sub サービス エージェントのオプションのロール

  • 2021 年 4 月 8 日以前に、認証済みの Pub/Sub push リクエストをサポートするために Cloud Pub/Sub サービス エージェントを有効にした場合は、サービス アカウント トークン作成者のロールroles/iam.serviceAccountTokenCreator)をサービス エージェントに付与します。それ以外の場合、このロールはデフォルトで付与されます。
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
        --role=roles/iam.serviceAccountTokenCreator

Pub/Sub トピックを作成する

Cloud Run では、関数をデプロイするときに Pub/Sub トピックが自動的に作成されません。関数をデプロイする前に、この Pub/Sub トピックにメッセージを公開して関数をトリガーします。

gcloud pubsub topics create YOUR_TOPIC_NAME

アプリケーションを準備する

  1. ローカルマシンにサンプルアプリのリポジトリのクローンを作成します。

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
    

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
    

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git
    

    .NET

    git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git
    

    Ruby

    git clone https://github.com/GoogleCloudPlatform/ruby-docs-samples.git
    

    PHP

    git clone https://github.com/GoogleCloudPlatform/php-docs-samples.git
    
  2. Pub/Sub にアクセスするためのサンプルコードがあるディレクトリに移動します。

    Node.js

    cd nodejs-docs-samples/functions/v2/helloPubSub/
    

    Python

    cd python-docs-samples/functions/v2/pubsub/
    

    Go

    cd golang-samples/functions/functionsv2/hellopubsub/
    

    Java

    cd java-docs-samples/functions/v2/pubsub/
    

    .NET

    cd dotnet-docs-samples/functions/helloworld/HelloPubSub/
    

    Ruby

    cd ruby-docs-samples/functions/helloworld/pubsub/
    

    PHP

    cd php-docs-samples/functions/helloworld_pubsub/
    
  3. サンプルコードを見てみましょう。

    Node.js

    const functions = require('@google-cloud/functions-framework');
    
    // Register a CloudEvent callback with the Functions Framework that will
    // be executed when the Pub/Sub trigger topic receives a message.
    functions.cloudEvent('helloPubSub', cloudEvent => {
      // The Pub/Sub message is passed as the CloudEvent's data payload.
      const base64name = cloudEvent.data.message.data;
    
      const name = base64name
        ? Buffer.from(base64name, 'base64').toString()
        : 'World';
    
      console.log(`Hello, ${name}!`);
    });

    Python

    import base64
    
    from cloudevents.http import CloudEvent
    import functions_framework
    
    
    # Triggered from a message on a Cloud Pub/Sub topic.
    @functions_framework.cloud_event
    def subscribe(cloud_event: CloudEvent) -> None:
        # Print out the data from Pub/Sub, to prove that it worked
        print(
            "Hello, " + base64.b64decode(cloud_event.data["message"]["data"]).decode() + "!"
        )
    
    

    Go

    
    // Package helloworld provides a set of Cloud Functions samples.
    package helloworld
    
    import (
    	"context"
    	"fmt"
    	"log"
    
    	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
    	"github.com/cloudevents/sdk-go/v2/event"
    )
    
    func init() {
    	functions.CloudEvent("HelloPubSub", helloPubSub)
    }
    
    // MessagePublishedData contains the full Pub/Sub message
    // See the documentation for more details:
    // https://cloud.google.com/eventarc/docs/cloudevents#pubsub
    type MessagePublishedData struct {
    	Message PubSubMessage
    }
    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See the documentation for more details:
    // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
    type PubSubMessage struct {
    	Data []byte `json:"data"`
    }
    
    // helloPubSub consumes a CloudEvent message and extracts the Pub/Sub message.
    func helloPubSub(ctx context.Context, e event.Event) error {
    	var msg MessagePublishedData
    	if err := e.DataAs(&msg); err != nil {
    		return fmt.Errorf("event.DataAs: %w", err)
    	}
    
    	name := string(msg.Message.Data) // Automatically decoded from base64.
    	if name == "" {
    		name = "World"
    	}
    	log.Printf("Hello, %s!", name)
    	return nil
    }
    

    Java

    import com.google.cloud.functions.CloudEventsFunction;
    import com.google.gson.Gson;
    import functions.eventpojos.PubSubBody;
    import io.cloudevents.CloudEvent;
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    import java.util.logging.Logger;
    
    public class SubscribeToTopic implements CloudEventsFunction {
      private static final Logger logger = Logger.getLogger(SubscribeToTopic.class.getName());
    
      @Override
      public void accept(CloudEvent event) {
        // The Pub/Sub message is passed as the CloudEvent's data payload.
        if (event.getData() != null) {
          // Extract Cloud Event data and convert to PubSubBody
          String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
          Gson gson = new Gson();
          PubSubBody body = gson.fromJson(cloudEventData, PubSubBody.class);
          // Retrieve and decode PubSub message data
          String encodedData = body.getMessage().getData();
          String decodedData =
              new String(Base64.getDecoder().decode(encodedData), StandardCharsets.UTF_8);
          logger.info("Hello, " + decodedData + "!");
        }
      }
    }

    .NET

    using CloudNative.CloudEvents;
    using Google.Cloud.Functions.Framework;
    using Google.Events.Protobuf.Cloud.PubSub.V1;
    using Microsoft.Extensions.Logging;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace HelloPubSub;
    
    public class Function : ICloudEventFunction<MessagePublishedData>
    {
        private readonly ILogger _logger;
    
        public Function(ILogger<Function> logger) =>
            _logger = logger;
    
        public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
        {
            string nameFromMessage = data.Message?.TextData;
            string name = string.IsNullOrEmpty(nameFromMessage) ? "world" : nameFromMessage;
            _logger.LogInformation("Hello {name}", name);
            return Task.CompletedTask;
        }
    }

    Ruby

    require "functions_framework"
    require "base64"
    
    FunctionsFramework.cloud_event "hello_pubsub" do |event|
      # The event parameter is a CloudEvents::Event::V1 object.
      # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
      name = Base64.decode64 event.data["message"]["data"] rescue "World"
    
      # A cloud_event function does not return a response, but you can log messages
      # or cause side effects such as sending additional events.
      logger.info "Hello, #{name}!"
    end

    PHP

    
    use CloudEvents\V1\CloudEventInterface;
    use Google\CloudFunctions\FunctionsFramework;
    
    // Register the function with Functions Framework.
    // This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=cloudevent` environment
    // variable when deploying. The `FUNCTION_TARGET` environment variable should
    // match the first parameter.
    FunctionsFramework::cloudEvent('helloworldPubsub', 'helloworldPubsub');
    
    function helloworldPubsub(CloudEventInterface $event): void
    {
        $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
    
        $cloudEventData = $event->getData();
        $pubSubData = base64_decode($cloudEventData['message']['data']);
    
        $name = $pubSubData ? htmlspecialchars($pubSubData) : 'World';
        fwrite($log, "Hello, $name!" . PHP_EOL);
    }

イベント ドリブン関数をデプロイする

関数をデプロイするには、サンプルコードを含むディレクトリで次のコマンドを実行します。

Node.js

  gcloud run deploy FUNCTION \
        --source . \
        --function helloPubSub \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: nodejs22)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

Python

  gcloud run deploy FUNCTION \
        --source . \
        --function subscribe \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: python313)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

Go

  gcloud run deploy FUNCTION \
        --source . \
        --function HelloPubSub \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: go125)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

Java

  gcloud run deploy FUNCTION \
        --source . \
        --function functions.SubscribeToTopic \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: java21)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

.NET

  gcloud run deploy FUNCTION \
        --source . \
        --function HelloPubSub.Function \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: dotnet8)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

Ruby

  gcloud run deploy FUNCTION \
        --source . \
        --function hello_pubsub \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: ruby34)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

PHP

  gcloud run deploy FUNCTION \
        --source . \
        --function helloworldPubsub \
        --base-image BASE_IMAGE \

次のように置き換えます。

  • FUNCTION: デプロイする関数の名前。このパラメータを省略すると、コマンドを実行するときに名前の入力を求められます。
  • BASE_IMAGE: 関数のベースイメージ環境(例: php84)。ベースイメージと各イメージに含まれるパッケージの詳細については、サポートされている言語ランタイムとベースイメージをご覧ください。

指定したリージョンにリポジトリを作成するように求められたら、y を押して応答します。デプロイが完了すると、サービスが実行されている URL が Google Cloud CLI に表示されます。

Eventarc トリガーを作成する

Pub/Sub トリガーを使用して関数をデプロイするには、サンプルコードを含むディレクトリで次のコマンドを実行します。

  1. Eventarc Pub/Sub トリガーを作成します。

    gcloud eventarc triggers create TRIGGER_NAME  \
        --location=${REGION} \
        --destination-run-service=FUNCTION \
        --destination-run-region=${REGION} \
        --event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
        --service-account=PROJECT_NUMBER-compute@developer.gserviceaccount.com

    次のように置き換えます。

    • TRIGGER_NAME: トリガーの名前。
    • FUNCTION: 関数の名前。
    • PROJECT_NUMBER: Google Cloud プロジェクト番号。

    Google Cloud プロジェクトで初めて Eventarc トリガーを作成する場合は、Eventarc サービス エージェントのプロビジョニングに遅延が発生する可能性があります。この問題は通常、トリガーを再度作成することで解決できます。詳細については、権限拒否エラーをご覧ください。

  2. トリガーが正常に作成されたことを確認します。トリガーはすぐに作成されますが、トリガーが完全に機能するまでに 2 分ほどかかることがあります。

    gcloud eventarc triggers list --location=${REGION}

    出力例を以下に示します。

    NAME: helloworld-events
    TYPE: google.cloud.pubsub.topic.v1.messagePublished
    DESTINATION: Cloud Run service: helloworld-events
    ACTIVE: Yes
    LOCATION: us-central1
    

関数をトリガーする

Pub/Sub 関数をテストするには:

  1. トピックを変数に割り当てます。

    TOPIC_ID=$(gcloud eventarc triggers describe TRIGGER_NAME --location $REGION --format='value(transport.pubsub.topic)')
    
  2. トピックにメッセージをパブリッシュします。

    gcloud pubsub topics publish $TOPIC_ID --message="Hello World"
    

Cloud Run サービスは、受信したメッセージの本文をログに記録します。これは、Cloud Run インスタンスの [ログ] セクションで確認できます。

  1. Google Cloud コンソールに移動します。
  2. 関数をクリックします。
  3. [ログ] タブを選択します。

    ログが表示されるまで少し時間がかかることがあります。すぐに表示されない場合は、しばらくしてからもう一度確認してください。

  4. 「Hello World!」というメッセージを見つけます。