Ejecutar tareas asíncronas

Puedes usar Cloud Tasks para poner en cola de forma segura una tarea que se procesará de forma asíncrona mediante un servicio de Cloud Run. Estos son algunos de los usos más habituales:

  • Conservar las solicitudes durante incidentes de producción inesperados
  • Suavizar los picos de tráfico retrasando el trabajo que no está orientado a los usuarios
  • Reducir el tiempo de respuesta de los usuarios delegando operaciones en segundo plano lentas para que las gestione otro servicio, como las actualizaciones de bases de datos o el procesamiento por lotes
  • Limitar la frecuencia de llamadas a servicios de backend, como bases de datos y APIs de terceros

En esta página se muestra cómo poner en cola tareas que se envían de forma segura a través del protocolo HTTPS a un servicio privado de Cloud Run. Describe el comportamiento necesario para el servicio privado de Cloud Run, los permisos de cuenta de servicio necesarios, la creación de colas de tareas y la creación de tareas.

Antes de empezar

Habilita la API Cloud Tasks en el proyecto que estés usando.

Desplegar un servicio de Cloud Run para gestionar tareas

Para desplegar un servicio que acepte tareas enviadas a la cola de tareas, despliega el servicio de la misma forma que cualquier otro servicio de Cloud Run. El servicio de Cloud Run debe devolver un código HTTP 200 para confirmar que la tarea se ha completado correctamente.

Cloud Tasks enviará las tareas a este servicio de Cloud Run como solicitudes HTTPS.

La respuesta a Cloud Tasks debe producirse en el tiempo de espera configurado. Para las cargas de trabajo que deban ejecutarse durante más tiempo que el tiempo de espera máximo de Cloud Tasks, considera la posibilidad de usar trabajos de Cloud Run.

Crear una cola de tareas

Línea de comandos

Para crear una cola de tareas, usa el comando

gcloud tasks queues create QUEUE-ID

Sustituye QUEUE-ID por el nombre que quieras dar a tu cola de tareas. Debe ser único en tu proyecto. Si se te pide que crees una aplicación de App Engine en tu proyecto, responde y para crearla. Cloud Tasks usa esta información para la cola, así que asegúrate de elegir la misma ubicación que uses para tu servicio de Cloud Run.

La configuración predeterminada de la cola de tareas debería funcionar en la mayoría de los casos. Sin embargo, si quieres, puedes definir otros límites de frecuencia y parámetros de reintento.

Terraform

Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de Terraform.

Para crear una cola de tareas, añade lo siguiente a tu archivo .tf:

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

Para aplicar los cambios, introduce terraform apply.

Crear una cuenta de servicio para asociarla a las tareas

Debes crear una cuenta de servicio que se asociará a las tareas en cola. Esta cuenta de servicio debe tener el rol de IAM Invocador de Cloud Run para permitir que la cola de tareas envíe tareas al servicio de Cloud Run. .

Consola

  1. En la Google Cloud consola, ve a la página Cuentas de servicio.

    Ir a Cuentas de servicio

  2. Selecciona un proyecto.

  3. Escribe el nombre de la cuenta de servicio, que será el que se muestre en la Google Cloud consola.

    La consola genera un ID de cuenta de servicio basado en este nombre. Google Cloud Edita el ID si es necesario. No podrás cambiarlo más adelante.

  4. Opcional: Escribe una descripción de la cuenta de servicio.

  5. Haz clic en Crear y continuar.

  6. Opcional: Haz clic en el campo Selecciona un rol.

  7. Selecciona Cloud Run > Invocador de Cloud Run.

  8. Haz clic en Listo.

Línea de comandos

  1. Crea la cuenta de servicio:

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

    Sustituir

    • SERVICE_ACCOUNT_NAME con un nombre en minúsculas único en tu proyecto Google Cloud , por ejemplo, my-invoker-service-account-name.
    • DISPLAYED_SERVICE_ACCOUNT_NAME con el nombre que quieras que se muestre en esta cuenta de servicio, por ejemplo, en la consola, My Invoker Service Account.
  2. En Cloud Run, concede permiso a tu cuenta de servicio para invocar tu servicio:

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

    Sustituir

    • SERVICE con el nombre del servicio que quieras que invoque Cloud Tasks.
    • SERVICE_ACCOUNT_NAME con el nombre de la cuenta de servicio.
    • PROJECT_ID por el ID de tu proyecto. Google Cloud
  3. Concede acceso al proyecto a tu cuenta de servicio para que pueda realizar acciones concretas con los recursos del proyecto:

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

    Sustituir

    • RESOURCE_ID: tu ID de proyecto Google Cloud .

    • PRINCIPAL: identificador de la entidad de seguridad o del miembro, que suele tener el siguiente formato: PRINCIPAL_TYPE:ID. Por ejemplo, user:my-user@example.com. Para ver una lista completa de los valores que puede tener PRINCIPAL, consulta la referencia de Policy Binding.

Terraform

Para saber cómo aplicar o quitar una configuración de Terraform, consulta Comandos básicos de Terraform.

Añade lo siguiente a tu archivo .tf:

Crea la cuenta de servicio:

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

En Cloud Run, concede permiso a tu cuenta de servicio para invocar tu servicio:

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 los cambios, introduce terraform apply.

Crear tareas HTTP con tokens de autenticación

Cuando creas una tarea para enviarla a la cola de tareas, debes especificar el proyecto, la ubicación, el nombre de la cola, el correo electrónico de la cuenta de servicio creada anteriormente que se asociará a las tareas, la URL del servicio privado de Cloud Run que ejecutará la tarea y cualquier otro dato que necesites enviar. Puedes codificar estos valores, aunque los valores como el ID de proyecto, la ubicación y el correo de la cuenta de servicio se pueden obtener de forma dinámica del servidor de metadatos de Cloud Run.

Consulta la documentación de la API Cloud Tasks para obtener información sobre el cuerpo de la solicitud de tarea. Ten en cuenta que las solicitudes que contienen cargas útiles de datos deben usar el método HTTP PUT o POST.

El código que pone en cola las tareas debe tener los permisos de gestión de identidades y accesos necesarios para hacerlo, como el rol Poner en cola de Cloud Tasks. Tu código tendrá los permisos de gestión de identidades y accesos necesarios si usas la cuenta de servicio predeterminada en Cloud Run.

En los siguientes ejemplos se crean solicitudes de tareas que también incluyen la creación de un token de encabezado. En los ejemplos se usan tokens de OIDC. Para usar un token de OAuth, sustituye el parámetro OIDC por el parámetro de OAuth adecuado en el idioma correspondiente al crear la solicitud.

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

Ten en cuenta el archivo 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());
    }
  }
}

Ten en cuenta el archivo 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();

Ten en cuenta el archivo 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"
  }
}

Siguientes pasos