Como executar tarefas assíncronas

É possível usar o Cloud Tasks para enfileirar com segurança uma tarefa e processá-la de maneira assíncrona por um serviço do Cloud Run. Os casos de uso típicos incluem:

  • Como preservar solicitações por meio de incidentes de produção inesperados
  • Como suavizar picos de tráfego atrasando o trabalho que não é voltado para o usuário
  • Como acelerar o tempo de resposta do usuário delegando operações lentas em segundo plano para serem processadas por outro serviço, como atualizações de banco de dados ou processamento em lote
  • Como limitar a taxa de chamadas a serviços de apoio, como bancos de dados e APIs de terceiros

Nesta página, mostramos como colocar em fila tarefas que são enviadas com segurança por meio do protocolo HTTPS para um serviço particular do Cloud Run. Descrevemos o comportamento necessário para o serviço particular do Cloud Run, as permissões necessárias da conta de serviço, a criação da fila de tarefas e a criação de tarefas.

Antes de começar

Ative a API Cloud Tasks no projeto que você está usando.

Como implantar um serviço do Cloud Run para processar tarefas

Para implantar um serviço que aceite tarefas enviadas para a fila de tarefas, implante o serviço da mesma forma que qualquer outro serviço do Cloud Run. O serviço do Cloud Run precisa retornar um código HTTP 200 para confirmar o sucesso após a conclusão do processamento da tarefa.

As tarefas serão enviadas para esse serviço do Cloud Run como solicitações HTTPS pelo Cloud Tasks.

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

Faça a implantação com o Terraform

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

Para criar um serviço, adicione o seguinte ao seu arquivo .tf:

resource "google_cloud_run_v2_service" "default" {
  name     = "cloud-run-task-service"
  location = "us-central1"

  template {
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello"
    }
  }
}

Como criar uma fila de tarefas

Linha de comando

Para criar uma fila de tarefas, use o comando

gcloud tasks queues create QUEUE-ID

substituindo QUEUE-ID pelo nome que você quer dar à fila de tarefas: ele precisa ser exclusivo no projeto. Se você for solicitado a criar um aplicativo do App Engine no seu projeto, responda y para criá-lo. O Cloud Tasks usa isso para a fila: escolha o mesmo local que você está usando para o serviço do Cloud Run.

A configuração padrão da fila de tarefas funcionará na maioria dos casos. Mas é possível definir diferentes limites de taxa e repetir parâmetros, se quiser.

Terraform

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

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

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

Para aplicar as mudanças, insira terraform apply.

Como criar uma conta de serviço para associar às tarefas

Você precisa criar uma conta de serviço que será associada às tarefas enfileiradas. Essa conta de serviço precisa ter o papel do IAM de Chamador do Cloud Run para permitir que a fila de tarefas envie tarefas para o serviço Cloud Run. .

Console

  1. No console do Google Cloud, acesse a página Contas de serviço.

    Acessar a página "Contas de serviço"

  2. Selecione um projeto.

  3. Insira um nome de conta de serviço a ser exibido no console do Google Cloud.

    O console do Google Cloud gerará um ID de conta de serviço com base nesse nome. Edite o ID se for necessário. Não será possível alterar o ID depois.

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

  5. Clique em Criar e continuar.

  6. Opcional: clique no campo Selecionar um papel.

  7. Selecione Cloud Run > Chamador do Cloud Run.

  8. Clique em Concluído.

Linha de comando

  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 por um nome em letras minúsculas exclusivo no projeto do Google Cloud, por exemplo, my-invoker-service-account-name.
    • DISPLAYED_SERVICE_ACCOUNT_NAME pelo nome que você quer exibir para essa conta de serviço, por exemplo, no console, My Invoker Service Account.
  2. Para o Cloud Run, conceda à conta de serviço permissão para invocar o serviço:

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

    Substitua:

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

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

    Substituir

    • RESOURCE_ID pelo ID do projeto no Google Cloud.

    • PRINCIPAL: um identificador do principal ou do membro, que geralmente 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 vinculação de políticas.

Terraform

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

Adicione a instrução a seguir ao seu arquivo .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 à conta de serviço permissão para invocar o 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}"]
}

Para aplicar as mudanças, insira terraform apply.

Como criar tarefas HTTP com tokens de autenticação

Ao criar uma tarefa para enviar à fila de tarefas, você especifica o projeto, o local, o nome da fila, o e-mail da conta de serviço criada anteriormente para associar a tarefas, o URL do serviço particular do Cloud Run que executará a tarefa e quaisquer outros dados que seja necessário enviar. É possível fixar esses valores no código, embora valores como o ID do projeto, o local e o e-mail da conta de serviço possam ser recuperados dinamicamente no servidor de metadados do Cloud Run.

Consulte a documentação da API Cloud Tasks para ver detalhes sobre o corpo da solicitação de tarefa. Observe que as solicitações que contêm payloads de dados precisam usar o método HTTP PUT ou POST.

O código que enfileira as tarefas precisa ter as permissões de IAM necessárias para fazer isso, como o papel de Enfileirador do Cloud Tasks. Seu código terá as permissões de IAM necessárias se você usar a conta de serviço padrão no Cloud Run.

Os exemplos a seguir mostram como criar solicitações de tarefa que também incluem a criação de um token de cabeçalho. Os tokens OIDC são usados nos exemplos abaixo. Se for um token OAuth, substitua o parâmetro OIDC pelo parâmetro OAuth apropriado da linguagem ao criar uma solicitação.

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,
        )
    )

Observe o arquivo requirements.txt:

google-cloud-tasks==2.13.1

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

Observe o arquivo 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>

Go

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();

Observe o arquivo 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": "^4.0.0",
    "express": "^4.16.3"
  },
  "devDependencies": {
    "c8": "^8.0.0",
    "chai": "^4.2.0",
    "mocha": "^10.0.0",
    "uuid": "^9.0.0"
  }
}

A seguir