Ejecuta tareas asíncronas

Puedes usar Cloud Tasks para poner en cola de forma segura una tarea que un servicio de Cloud Run procesará de forma asíncrona. Los casos prácticos más comunes incluyen los siguientes:

  • Preservar las solicitudes mediante incidentes de producción imprevistos
  • Suavizar los incrementos de tráfico mediante el retraso del trabajo que no está orientado al usuario
  • Acelerar el tiempo de respuesta del usuario mediante la delegación de operaciones lentas en segundo plano para que las controle otro servicio, como actualizaciones de bases de datos o procesamiento por lotes
  • Limitar la tarifa de llamadas a servicios de creación de copias de seguridad, como bases de datos y API de terceros

En esta página, se muestra cómo poner en cola tareas que se envían de forma segura mediante el protocolo HTTPS a un servicio privado de Cloud Run. Se describe el comportamiento requerido para el servicio privado de Cloud Run, los permisos necesarios de la cuenta de servicio, la creación de listas de tareas en cola y la creación de tareas.

Antes de comenzar

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

Implementa un servicio de Cloud Run para controlar tareas

Para implementar un servicio que acepte tareas enviadas a la lista de tareas en cola, implementa el servicio de la misma manera que cualquier otro servicio de Cloud Run. En el servicio de Cloud Run, se debe mostrar un código HTTP 200 para confirmar el procesamiento correcto de la tarea.

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

Crea una lista de tareas en cola

Para crear una lista de tareas en cola, usa el siguiente comando:

gcloud tasks queues create QUEUE-ID

Reemplaza QUEUE-ID por el nombre que deseas asignar a la lista de tareas en cola: debe ser único en el proyecto. Si se te solicita que crees una app de App Engine en el proyecto, responde y para crearla. Cloud Tasks usa esto para la cola: asegúrate de elegir la misma ubicación que usas para el servicio de Cloud Run.

La configuración predeterminada de la lista de tareas en cola debería funcionar en la mayoría de los casos. Sin embargo, si lo deseas, puedes establecer diferentes límites de frecuencia y parámetros de reintento.

Crea una cuenta de servicio para asociar con las tareas

Debes crear una cuenta de servicio que se asociará con las tareas en cola. Esta cuenta de servicio debe tener la función de IAM de Invocador de Cloud Run para permitir que la lista de tareas en cola envíe las tareas al servicio de Cloud Run. .

Console

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

    Ir a Cuentas de servicio

  2. Selecciona un proyecto

  3. Ingresa un nombre de cuenta de servicio para mostrar en Cloud Console.

    Cloud Console genera un ID de cuenta de servicio a partir de este nombre. Si es necesario, edita el ID. No podrás cambiar el ID más adelante.

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

  5. Haz clic en Crear.

  6. Haz clic en el campo Seleccionar una función.

  7. En Todas las funciones, selecciona Cloud Run > Invocador de Cloud Run.

  8. Haga 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"

    Reemplaza los siguientes elementos:

    • SERVICE_ACCOUNT_NAME por un nombre único en minúscula dentro del proyecto de Google Cloud, por ejemplo, my-invoker-service-account-name
    • DISPLAYED_SERVICE_ACCOUNT_NAME por el nombre que deseas mostrar para esta cuenta de servicio, por ejemplo, en la consola, My Invoker Service Account
  2. En Cloud Run, debes otorgar permiso a la cuenta de servicio para invocar el servicio:

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

    Reemplaza los siguientes elementos:

    • SERVICE por el nombre del servicio que deseas que Cloud Tasks invoque
    • SERVICE_ACCOUNT_NAME por el nombre de la cuenta de servicio
    • PROJECT_ID por el ID del proyecto de Google Cloud

Crea tareas HTTP con tokens de autenticación

Cuando creas una tarea para enviar a la lista de tareas en cola, debes especificar el proyecto, la ubicación, el nombre de la cola, el correo electrónico de la cuenta de servicio que creaste antes a fin de asociarla con las tareas, la URL del servicio privado de Cloud Run que ejecutará la tarea y cualquier otro dato que debas enviar.

Consulta la documentación de la API de Cloud Tasks para obtener detalles sobre el cuerpo de la solicitud de la 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 las tareas en cola debe tener los permisos de IAM necesarios para hacerlo, como la función Agregador de elementos en cola de Cloud Tasks. El código tendrá los permisos de IAM necesarios si usas la cuenta de servicio predeterminada en Cloud Run.

Los siguientes ejemplos crean solicitudes de tareas que también incluyen la creación de un token de encabezado. Se usan los tokens OIDC en los ejemplos. A fin de usar un token de OAuth, reemplaza el parámetro OIDC por el parámetro de OAuth apropiado para el lenguaje en la construcción de la solicitud.

Python

"""Create a task for a given queue with an arbitrary payload."""

from google.cloud import tasks_v2

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

# TODO(developer): Uncomment these lines and replace with your values.
# project = 'my-project-id'
# queue = 'my-queue'
# location = 'us-central1'
# url = 'https://example.com/task_handler?param=value'
# audience = 'https://example.com/task_handler'
# service_account_email = 'service-account@my-project-id.iam.gserviceaccount.com';
# payload = 'hello'

# Construct the fully qualified queue name.
parent = client.queue_path(project, location, queue)

# Construct the request body.
task = {
    "http_request": {  # Specify the type of request.
        "http_method": tasks_v2.HttpMethod.POST,
        "url": url,  # The full url path that the task will be sent to.
        "oidc_token": {
            "service_account_email": service_account_email,
            "audience": audience,
        },
    }
}

if payload is not None:
    # The API expects a payload of type bytes.
    converted_payload = payload.encode()

    # Add the payload to the request.
    task["http_request"]["body"] = converted_payload

# Use the client to build and send the task.
response = client.create_task(request={"parent": parent, "task": task})

print("Created task {}".format(response.name))
return response

Ten en cuenta el archivo requirements.txt:

google-cloud-tasks==2.7.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());
    }
  }
}

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.google.cloud</groupId>
  <artifactId>cloudtasks-snippets</artifactId>
  <packaging>jar</packaging>
  <name>Google Cloud Tasks Snippets</name>
  <url>https://github.com/googleapis/java-tasks</url>

  <!--
    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.0.23</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>24.0.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.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Go

import (
	"context"
	"fmt"

	cloudtasks "cloud.google.com/go/cloudtasks/apiv2"
	taskspb "google.golang.org/genproto/googleapis/cloud/tasks/v2"
)

// 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: %v", 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: %v", 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: {
      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": ">=10"
  },
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "mocha",
    "start": "node server.js"
  },
  "dependencies": {
    "@google-cloud/tasks": "^2.4.2",
    "body-parser": "^1.18.3",
    "express": "^4.16.3"
  },
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^8.0.0",
    "uuid": "^8.0.0"
  }
}

¿Qué sigue?