Executar tarefas assíncronas

Pode usar o Cloud Tasks para colocar em fila de forma segura uma tarefa a ser processada de forma assíncrona por um serviço do Cloud Run. Exemplos de utilização típicos:

  • Preservar pedidos através de incidentes de produção inesperados
  • Suavizar os picos de tráfego atrasando o trabalho que não é orientado para o utilizador
  • Aumentar a velocidade do tempo de resposta do utilizador delegando operações em segundo plano lentas para serem processadas por outro serviço, como atualizações de bases de dados ou processamento em lote
  • Limitar a taxa de chamadas a serviços de apoio, como bases de dados e APIs de terceiros

Esta página mostra como colocar em fila tarefas que são enviadas de forma segura através do protocolo HTTPS para um serviço privado do Cloud Run. Descreve o comportamento necessário para o serviço privado do Cloud Run, as autorizações da conta de serviço necessárias, a criação de filas de tarefas e a criação de tarefas.

Antes de começar

Ative a API Cloud Tasks no projeto que está a usar.

Implementar um serviço do Cloud Run para processar tarefas

Para implementar um serviço que aceite tarefas enviadas para a fila de tarefas, implemente o serviço da mesma forma que qualquer outro serviço do Cloud Run. O serviço Cloud Run tem de devolver um código HTTP 200 para confirmar o êxito após a conclusão do processamento da tarefa.

As tarefas são enviadas para este serviço do Cloud Run como pedidos HTTPS pelo Cloud Tasks.

A resposta ao Cloud Tasks tem de ocorrer dentro do tempo limite configurado. Para cargas de trabalho que precisam de ser executadas durante mais tempo do que o limite de tempo máximo do Cloud Tasks, considere usar tarefas do Cloud Run.

Criar uma fila de tarefas

Linha de comandos

Para criar uma fila de tarefas, use o comando

gcloud tasks queues create QUEUE-ID

substituindo QUEUE-ID pelo nome que quer dar à fila de tarefas: tem de ser exclusivo no seu projeto. Se lhe for pedido que crie uma app do App Engine no seu projeto, responda y para a criar. O Cloud Tasks usa esta informação para a fila. Certifique-se de que escolhe a mesma localização que está a usar para o seu serviço do Cloud Run.

A configuração da fila de tarefas predefinida deve funcionar na maioria dos casos. No entanto, se quiser, pode definir opcionalmente limites de taxa diferentes e parâmetros de repetição.

Terraform

Para saber como aplicar ou remover uma configuração do Terraform, consulte os comandos básicos do Terraform.

Para criar uma fila de tarefas, adicione o seguinte ao ficheiro .tf:

resource "google_cloud_tasks_queue" "default" {
  name     = "cloud-tasks-queue-name"
  location = "us-central1"
}

Aplique as alterações introduzindo terraform apply.

Criar uma conta de serviço para associar às tarefas

Tem de criar uma conta de serviço que vai ser associada às tarefas colocadas em fila. Esta conta de serviço tem de ter a função IAM do invocador do Cloud Run para permitir que a fila de tarefas envie tarefas para o serviço do Cloud Run. .

Consola

  1. Na Google Cloud consola, aceda à página Contas de serviço.

    Aceda a Contas de serviço

  2. Selecione um projeto.

  3. Introduza um nome da conta de serviço a apresentar na Google Cloud consola.

    A Google Cloud consola gera um ID da conta de serviço com base neste nome. Edite o ID, se necessário. Não pode alterar o ID posteriormente.

  4. Opcional: introduza uma descrição da conta de serviço.

  5. Clique em Criar e continuar.

  6. Opcional: clique no campo Selecionar uma função.

  7. Selecione Cloud Run > Cloud Run Invoker.

  8. Clique em Concluído.

Linha de comandos

  1. Crie a conta de serviço:

    gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \
       --display-name "DISPLAYED_SERVICE_ACCOUNT_NAME"

    Substituir

    • SERVICE_ACCOUNT_NAME com um nome em minúsculas exclusivo no seu Google Cloud projeto, por exemplo, my-invoker-service-account-name.
    • DISPLAYED_SERVICE_ACCOUNT_NAME com o nome que quer apresentar para esta conta de serviço, por exemplo, na consola, por exemplo, My Invoker Service Account.
  2. Para o Cloud Run, conceda à sua conta de serviço autorização para invocar o seu serviço:

    gcloud run services add-iam-policy-binding SERVICE \
       --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
       --role=roles/run.invoker

    Substituir

    • SERVICE com o nome do serviço que quer que seja invocado pelo Cloud Tasks.
    • SERVICE_ACCOUNT_NAME com o nome da conta de serviço.
    • PROJECT_ID com o seu Google Cloud ID do projeto.
  3. Conceda à sua conta de serviço acesso ao projeto para que tenha autorização para concluir ações específicas nos recursos do seu projeto:

    gcloud projects add-iam-policy-binding RESOURCE_ID \
       --member=PRINCIPAL --role=roles/run.invoker

    Substituir

    • RESOURCE_ID: o ID do seu Google Cloud projeto.

    • PRINCIPAL: um identificador para o principal ou o membro, que normalmente tem o seguinte formato: PRINCIPAL_TYPE:ID. Por exemplo, user:my-user@example.com. Para ver uma lista completa dos valores que PRINCIPAL pode ter, consulte a referência de associação de políticas.

Terraform

Para saber como aplicar ou remover uma configuração do Terraform, consulte os comandos básicos do Terraform.

Adicione o seguinte ao ficheiro .tf:

Crie a conta de serviço:

resource "google_service_account" "default" {
  account_id   = "cloud-run-task-invoker"
  display_name = "Cloud Run Task Invoker"
}

Para o Cloud Run, conceda à sua conta de serviço autorização para invocar o seu serviço:

resource "google_cloud_run_service_iam_binding" "default" {
  location = google_cloud_run_v2_service.default.location
  service  = google_cloud_run_v2_service.default.name
  role     = "roles/run.invoker"
  members  = ["serviceAccount:${google_service_account.default.email}"]
}

Aplique as alterações introduzindo terraform apply.

Criar tarefas HTTP com tokens de autenticação

Quando cria uma tarefa para enviar para a fila de tarefas, especifica o projeto, a localização, o nome da fila, o email da conta de serviço criada anteriormente para associar às tarefas, o URL do serviço privado do Cloud Run que vai executar a tarefa e quaisquer outros dados que precise de enviar. Pode optar por codificar estes valores, embora os valores como o ID do projeto, a localização e o email da conta de serviço possam ser obtidos dinamicamente a partir do servidor de metadados do Cloud Run.

Consulte a documentação da API Cloud Tasks para ver detalhes sobre o corpo do pedido de tarefas. Tenha em atenção que os pedidos que contêm payloads de dados têm de usar o método HTTP PUT ou POST.

O código que coloca as tarefas na fila tem de ter as autorizações de IAM necessárias para o fazer, como a função de colocador na fila do Cloud Tasks. O seu código vai ter as autorizações do IAM necessárias se usar a conta de serviço predefinida no Cloud Run.

Os exemplos seguintes criam pedidos de tarefas que também incluem a criação de um token de cabeçalho. Os tokens OIDC são usados nos exemplos. Para usar um token OAuth, substitua o parâmetro OIDC pelo parâmetro OAuth adequado ao idioma na criação do pedido.

Python

from typing import Optional

from google.cloud import tasks_v2


def create_http_task_with_token(
    project: str,
    location: str,
    queue: str,
    url: str,
    payload: bytes,
    service_account_email: str,
    audience: Optional[str] = None,
) -> tasks_v2.Task:
    """Create an HTTP POST task with an OIDC token and an arbitrary payload.
    Args:
        project: The project ID where the queue is located.
        location: The location where the queue is located.
        queue: The ID of the queue to add the task to.
        url: The target URL of the task.
        payload: The payload to send.
        service_account_email: The service account to use for generating the OIDC token.
        audience: Audience to use when generating the OIDC token.
    Returns:
        The newly created task.
    """

    # Create a client.
    client = tasks_v2.CloudTasksClient()

    # Construct the request body.
    task = tasks_v2.Task(
        http_request=tasks_v2.HttpRequest(
            http_method=tasks_v2.HttpMethod.POST,
            url=url,
            oidc_token=tasks_v2.OidcToken(
                service_account_email=service_account_email,
                audience=audience,
            ),
            body=payload,
        ),
    )

    # Use the client to build and send the task.
    return client.create_task(
        tasks_v2.CreateTaskRequest(
            parent=client.queue_path(project, location, queue),
            task=task,
        )
    )

Tenha em atenção o ficheiro requirements.txt:

google-cloud-tasks==2.18.0

Java

import com.google.cloud.tasks.v2.CloudTasksClient;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.cloud.tasks.v2.HttpRequest;
import com.google.cloud.tasks.v2.OidcToken;
import com.google.cloud.tasks.v2.QueueName;
import com.google.cloud.tasks.v2.Task;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.nio.charset.Charset;

public class CreateHttpTaskWithToken {

  public static void main(String[] args) throws IOException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project-id";
    String locationId = "us-central1";
    String queueId = "my-queue";
    String serviceAccountEmail =
        "java-docs-samples-testing@java-docs-samples-testing.iam.gserviceaccount.com";
    createTask(projectId, locationId, queueId, serviceAccountEmail);
  }

  // Create a task with a HTTP target and authorization token using the Cloud Tasks client.
  public static void createTask(
      String projectId, String locationId, String queueId, String serviceAccountEmail)
      throws IOException {

    // Instantiates a client.
    try (CloudTasksClient client = CloudTasksClient.create()) {
      String url =
          "https://example.com/taskhandler"; // The full url path that the request will be sent to
      String payload = "Hello, World!"; // The task HTTP request body

      // Construct the fully qualified queue name.
      String queuePath = QueueName.of(projectId, locationId, queueId).toString();

      // Add your service account email to construct the OIDC token.
      // in order to add an authentication header to the request.
      OidcToken.Builder oidcTokenBuilder =
          OidcToken.newBuilder().setServiceAccountEmail(serviceAccountEmail);

      // Construct the task body.
      Task.Builder taskBuilder =
          Task.newBuilder()
              .setHttpRequest(
                  HttpRequest.newBuilder()
                      .setBody(ByteString.copyFrom(payload, Charset.defaultCharset()))
                      .setHttpMethod(HttpMethod.POST)
                      .setUrl(url)
                      .setOidcToken(oidcTokenBuilder)
                      .build());

      // Send create task request.
      Task task = client.createTask(queuePath, taskBuilder.build());
      System.out.println("Task created: " + task.getName());
    }
  }
}

Tenha em atenção o ficheiro pom.xml:

<?xml version='1.0' encoding='UTF-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.tasks</groupId>
  <artifactId>cloudtasks-snippets</artifactId>
  <packaging>jar</packaging>
  <name>Google Cloud Tasks Snippets</name>

  <!--
    The parent pom defines common style checks and testing strategies for our samples.
    Removing or replacing it should not affect the execution of the samples in anyway.
  -->
  <parent>
    <groupId>com.google.cloud.samples</groupId>
    <artifactId>shared-configuration</artifactId>
    <version>1.2.0</version>
  </parent>

  <properties>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>


  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.32.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-tasks</artifactId>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.google.truth</groupId>
      <artifactId>truth</artifactId>
      <version>1.4.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Ir

import (
	"context"
	"fmt"

	cloudtasks "cloud.google.com/go/cloudtasks/apiv2"
	taskspb "cloud.google.com/go/cloudtasks/apiv2/cloudtaskspb"
)

// createHTTPTaskWithToken constructs a task with a authorization token
// and HTTP target then adds it to a Queue.
func createHTTPTaskWithToken(projectID, locationID, queueID, url, email, message string) (*taskspb.Task, error) {
	// Create a new Cloud Tasks client instance.
	// See https://godoc.org/cloud.google.com/go/cloudtasks/apiv2
	ctx := context.Background()
	client, err := cloudtasks.NewClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewClient: %w", err)
	}
	defer client.Close()

	// Build the Task queue path.
	queuePath := fmt.Sprintf("projects/%s/locations/%s/queues/%s", projectID, locationID, queueID)

	// Build the Task payload.
	// https://godoc.org/google.golang.org/genproto/googleapis/cloud/tasks/v2#CreateTaskRequest
	req := &taskspb.CreateTaskRequest{
		Parent: queuePath,
		Task: &taskspb.Task{
			// https://godoc.org/google.golang.org/genproto/googleapis/cloud/tasks/v2#HttpRequest
			MessageType: &taskspb.Task_HttpRequest{
				HttpRequest: &taskspb.HttpRequest{
					HttpMethod: taskspb.HttpMethod_POST,
					Url:        url,
					AuthorizationHeader: &taskspb.HttpRequest_OidcToken{
						OidcToken: &taskspb.OidcToken{
							ServiceAccountEmail: email,
						},
					},
				},
			},
		},
	}

	// Add a payload message if one is present.
	req.Task.GetHttpRequest().Body = []byte(message)

	createdTask, err := client.CreateTask(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("cloudtasks.CreateTask: %w", err)
	}

	return createdTask, nil
}

Node.js

// Imports the Google Cloud Tasks library.
const {CloudTasksClient} = require('@google-cloud/tasks');

// Instantiates a client.
const client = new CloudTasksClient();

async function createHttpTaskWithToken() {
  // TODO(developer): Uncomment these lines and replace with your values.
  // const project = 'my-project-id';
  // const queue = 'my-queue';
  // const location = 'us-central1';
  // const url = 'https://example.com/taskhandler';
  // const serviceAccountEmail = 'client@<project-id>.iam.gserviceaccount.com';
  // const payload = 'Hello, World!';

  // Construct the fully qualified queue name.
  const parent = client.queuePath(project, location, queue);

  const task = {
    httpRequest: {
      headers: {
        'Content-Type': 'text/plain', // Set content type to ensure compatibility your application's request parsing
      },
      httpMethod: 'POST',
      url,
      oidcToken: {
        serviceAccountEmail,
      },
    },
  };

  if (payload) {
    task.httpRequest.body = Buffer.from(payload).toString('base64');
  }

  console.log('Sending task:');
  console.log(task);
  // Send create task request.
  const request = {parent: parent, task: task};
  const [response] = await client.createTask(request);
  const name = response.name;
  console.log(`Created task ${name}`);
}
createHttpTaskWithToken();

Tenha em atenção o ficheiro package.json:

{
  "name": "appengine-cloudtasks",
  "description": "Google App Engine Cloud Tasks example.",
  "license": "Apache-2.0",
  "author": "Google Inc.",
  "private": true,
  "engines": {
    "node": ">=16.0.0"
  },
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "c8 mocha -p -j 2 --timeout 30000",
    "start": "node server.js"
  },
  "dependencies": {
    "@google-cloud/tasks": "^5.0.0",
    "express": "^4.16.3"
  },
  "devDependencies": {
    "c8": "^10.0.0",
    "chai": "^4.5.0",
    "mocha": "^10.0.0",
    "uuid": "^10.0.0"
  }
}

O que se segue?