使用 Eventarc 從 Pub/Sub 觸發函式

本教學課程將示範如何使用 Pub/Sub 觸發條件,編寫及觸發以事件為準的 Cloud Run 函式。

您可以為 Eventarc 觸發條件指定篩選器,藉此設定事件的轉送方式,包括事件來源和事件目標。以本教學課程中的範例來說,將訊息發布至 Pub/Sub 主題會觸發事件,並以 HTTP 要求的形式將要求傳送至函式。

如果您是 Pub/Sub 新手,想進一步瞭解這項服務,請參閱 Pub/Sub 說明文件,取得快速入門導覽課程和重要參考資料。

必要的角色

您或管理員必須授予部署者帳戶、觸發程序身分,以及視需要授予 Pub/Sub 服務代理程式和 Cloud Storage 服務代理程式下列 IAM 角色。

部署者帳戶的必要角色

  1. 如果您是專案建立者,系統會授予基本「擁有者」角色 (roles/owner)。根據預設,這個身分與存取權管理 (IAM) 角色包含完全存取大多數 Google Cloud資源所需的權限,因此您可以略過這個步驟。

    如果您不是專案建立者,必須在專案中將必要權限授予適當的主體。舉例來說,主體可以是 Google 帳戶 (適用於使用者) 或服務帳戶 (適用於應用程式和運算工作負載)。詳情請參閱活動目的地的「角色和權限」頁面。

    如要取得完成本教學課程所需的權限,請要求管理員為您授予專案的下列 IAM 角色:

    如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和機構的存取權」。

    您或許還可透過自訂角色或其他預先定義的角色取得必要權限。

    請注意,根據預設,Cloud Build 權限包含上傳及下載 Artifact Registry 構件的權限

觸發身分所需的角色

  1. 記下 Compute Engine 預設服務帳戶,因為您會將其附加至 Eventarc 觸發程序,代表觸發程序的身分以進行測試。啟用或使用採用 Compute Engine 的服務後,系統會自動建立這個服務帳戶,電子郵件地址格式如下: Google Cloud

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    PROJECT_NUMBER 替換為專案編號。 Google Cloud您可以在 Google Cloud 控制台的「歡迎」頁面中找到專案編號,也可以執行下列指令:

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

    在正式環境中,我們強烈建議建立新的服務帳戶,並授予一或多個包含最低必要權限的 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 Invoker 角色,系統仍會成功建立並啟用觸發條件。不過,觸發條件不會正常運作,記錄中會顯示類似下列內容的訊息:

    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 直接事件的觸發條件前,請將 Pub/Sub 發布者角色 (roles/pubsub.publisher) 授予 Cloud Storage 服務代理:

    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 日當天或之前啟用 Cloud Pub/Sub 服務代理,請將服務帳戶權杖建立者角色 (roles/iam.serviceAccountTokenCreator) 授予服務代理,以支援已驗證的 Pub/Sub 推送要求。否則,系統會預設授予這個角色:
    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 回應。部署完成後,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. 確認觸發條件是否已成功建立。請注意,雖然觸發條件會立即建立,但最多可能需要兩分鐘才能正常運作。

    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. 選取 [Logs] (記錄) 分頁標籤。

    記錄需要一些時間才會出現。如果沒有立即看到記錄,請稍候片刻再查看一次。

  4. 尋找「Hello World!」訊息。