Acione funções a partir do Pub/Sub com o Eventarc

Este tutorial demonstra como escrever e acionar funções do Cloud Run baseadas em eventos com um acionador do Pub/Sub.

Pode configurar o encaminhamento de eventos, incluindo a origem do evento e o destino do evento, especificando filtros para um acionador do Eventarc. Para o exemplo neste tutorial, a publicação de uma mensagem num tópico do Pub/Sub aciona o evento e é enviado um pedido à sua função sob a forma de um pedido HTTP.

Se está a começar a usar o Pub/Sub e quer saber mais, consulte a documentação do Pub/Sub para inícios rápidos e referências importantes.

Funções necessárias

O utilizador ou o administrador tem de conceder à conta de implementação, à identidade do acionador e, opcionalmente, ao agente de serviço do Pub/Sub e ao agente de serviço do Cloud Storage as seguintes funções de IAM.

Funções necessárias para a conta do implementador

  1. Se criou o projeto, tem a função básica de proprietário (roles/owner). Por predefinição, esta função do Identity and Access Management (IAM) inclui as autorizações necessárias para acesso total à maioria dos Google Cloud recursos, e pode ignorar este passo.

    Se não for o criador do projeto, as autorizações necessárias têm de ser concedidas no projeto ao principal adequado. Por exemplo, um principal pode ser uma Conta Google (para utilizadores finais) ou uma conta de serviço (para aplicações e cargas de trabalho de computação). Para mais informações, consulte a página Funções e autorizações do destino de eventos.

    Para receber as autorizações necessárias para concluir este tutorial, peça ao seu administrador que lhe conceda as seguintes funções de IAM no seu projeto:

    Para mais informações sobre a atribuição de funções, consulte o artigo Faça a gestão do acesso a projetos, pastas e organizações.

    Também pode conseguir as autorizações necessárias através de funções personalizadas ou outras funções predefinidas.

    Tenha em atenção que, por predefinição, as autorizações do Cloud Build incluem autorizações para carregar e transferir artefactos do Artifact Registry.

Funções necessárias para a identidade do acionador

  1. Tome nota da conta de serviço predefinida do Compute Engine, uma vez que a vai anexar a um acionador do Eventarc para representar a identidade do acionador para fins de teste. Esta conta de serviço é criada automaticamente depois de ativar ou usar um Google Cloud serviço que usa o Compute Engine e com o seguinte formato de email:

    PROJECT_NUMBER-compute@developer.gserviceaccount.com

    Substitua PROJECT_NUMBER pelo seu Google Cloud número do projeto. Pode encontrar o número do projeto na página Boas-vindas da Google Cloud consola ou executando o seguinte comando:

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

    Para ambientes de produção, recomendamos vivamente que crie uma nova conta de serviço e lhe conceda uma ou mais funções do IAM que contenham as autorizações mínimas necessárias e siga o princípio do privilégio mínimo.

  2. Por predefinição, os serviços do Cloud Run só podem ser chamados por proprietários do projeto, editores do projeto e administradores e invocadores do Cloud Run. Pode controlar o acesso por serviço. No entanto, para fins de teste, conceda a função de invocador do Cloud Run (run.invoker) no Google Cloud projeto à conta de serviço do Compute Engine. Isto concede a função a todos os serviços e trabalhos do Cloud Run num projeto.
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/run.invoker

    Tenha em atenção que, se criar um acionador para um serviço do Cloud Run autenticado sem conceder a função de invocador do Cloud Run, o acionador é criado com êxito e está ativo. No entanto, o acionador não funciona conforme esperado e é apresentada uma mensagem semelhante à seguinte nos registos:

    The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.
  3. Conceda a função de recetor de eventos do Eventarc (roles/eventarc.eventReceiver) no projeto à conta de serviço predefinida do Compute Engine para que o acionador do Eventarc possa receber eventos de fornecedores de eventos.
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role=roles/eventarc.eventReceiver

Função opcional para o agente do serviço do Cloud Storage

  • Antes de criar um acionador para eventos diretos do Cloud Storage, conceda a função de publicador do Pub/Sub (roles/pubsub.publisher) ao agente do serviço 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'

Função opcional para o agente do serviço Pub/Sub

  • Se ativou o agente do serviço Cloud Pub/Sub a 8 de abril de 2021 ou antes, para suportar pedidos de envio autenticados do Pub/Sub, conceda a função de criador de tokens de conta de serviço (roles/iam.serviceAccountTokenCreator) ao agente do serviço. Caso contrário, esta função é concedida por predefinição:
    gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
        --role=roles/iam.serviceAccountTokenCreator

Crie um tópico do Pub/Sub

No Cloud Run, os tópicos do Pub/Sub não são criados automaticamente quando implementa uma função. Antes de implementar a sua função, publique uma mensagem neste tópico do Pub/Sub para acionar a função:

gcloud pubsub topics create YOUR_TOPIC_NAME

Prepare a candidatura

  1. Clone o repositório da app de exemplo para a sua máquina local:

    Node.js

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

    Python

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

    Ir

    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. Altere para o diretório que contém o código de exemplo para aceder ao Pub/Sub:

    Node.js

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

    Python

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

    Ir

    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. Veja o exemplo de código:

    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() + "!"
        )
    
    

    Ir

    
    // 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);
    }

Implemente uma função orientada por eventos

Para implementar a função, execute o seguinte comando no diretório que contém o código de exemplo:

Node.js

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, nodejs22. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

Python

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, python313. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

Ir

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, go125. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

Java

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, java21. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

.NET

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, dotnet8. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

Ruby

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, ruby34. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

PHP

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

Substituição:

  • FUNCTION com o nome da função que está a implementar. Se omitir este parâmetro, é-lhe pedido que introduza um nome quando executar o comando.
  • BASE_IMAGE com o ambiente de imagem base para a sua função, por exemplo, php84. Para mais detalhes sobre as imagens base e os pacotes incluídos em cada imagem, consulte o artigo Tempos de execução de idiomas e imagens base suportados.

Se lhe for pedido que crie um repositório na região especificada, responda premindo y. Quando a implementação estiver concluída, a CLI Google Cloud apresenta um URL onde o serviço está em execução.

Crie um acionador do Eventarc

Para implementar a função com um acionador do Pub/Sub, execute o seguinte comando no diretório que contém o código de exemplo:

  1. Crie um acionador do 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

    Substituição:

    • TRIGGER_NAME com o nome do acionador.
    • FUNCTION com o nome da sua função.
    • PROJECT_NUMBER com o seu Google Cloud número do projeto.

    Tenha em atenção que, quando cria um acionador do Eventarc pela primeira vez num projeto, pode haver um atraso no aprovisionamento do agente do serviço Eventarc. Google Cloud Normalmente, pode resolver este problema tentando criar o acionador novamente. Para mais informações, consulte o artigo Erros de acesso negado.

  2. Confirme que o acionador foi criado com êxito. Tenha em atenção que, embora o acionador seja criado imediatamente, pode demorar até dois minutos para que um acionador fique totalmente funcional.

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

    O resultado deve ser semelhante ao seguinte:

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

Acionar a função

Para testar a função Pub/Sub:

  1. Atribua o tópico a uma variável:

    TOPIC_ID=$(gcloud eventarc triggers describe TRIGGER_NAME --location $REGION --format='value(transport.pubsub.topic)')
    
  2. Publicar uma mensagem no tópico:

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

O serviço do Cloud Run regista o corpo da mensagem recebida. Pode ver esta informação na secção Registos da sua instância do Cloud Run:

  1. Navegue para a Google Cloud consola.
  2. Clique na função.
  3. Selecione o separador Registos.

    Os registos podem demorar alguns momentos a aparecer. Se não os vir imediatamente, verifique novamente após alguns momentos.

  4. Procure a mensagem "Hello World!".