Exécuter des tâches asynchrones

Vous pouvez utiliser Cloud Tasks pour mettre en file d'attente de façon sécurisée une tâche afin qu'elle soit traitée de manière asynchrone par un service Cloud Run. Vous trouverez ci-dessous des cas d'utilisation types :

  • Préservation des requêtes en cas d'incidents de production inattendus
  • Lissage des pics de trafic en retardant le travail qui n'est pas destiné aux utilisateurs
  • Accélération du temps de réponse des utilisateurs en déléguant à un autre service le traitement des opérations en arrière-plan lentes, telles que les mises à jour de bases de données ou le traitement par lot
  • Limitation du taux d'appel aux services de sauvegarde, tels que les bases de données et les API tierces

Cette page explique comment mettre en file d'attente des tâches transférées de manière sécurisée via le protocole HTTPS vers un service Cloud Run privé. Elle décrit le comportement requis pour le service Cloud Run privé, les autorisations requises pour le compte de service, la création de la file d'attente de tâches et la création de tâches.

Avant de commencer

Activez l'API Cloud Tasks sur le projet que vous utilisez.

Déployer un service Cloud Run pour gérer les tâches

Pour déployer un service qui accepte les tâches envoyées à la file d'attente de tâches, déployez le service de la même manière que n'importe quel autre service Cloud Run. Le service Cloud Run doit renvoyer un code HTTP 200 pour confirmer la réussite une fois le traitement de la tâche terminé.

Les tâches seront transmises à ce service Cloud Run en tant que requêtes HTTPS par Cloud Tasks.

La réponse à Cloud Tasks doit avoir lieu dans le délai avant expiration configuré. Pour les charges de travail qui doivent s'exécuter plus longtemps que le délai maximal avant expiration de Cloud Tasks, envisagez d'utiliser les tâches Cloud Run.

Déployer avec Terraform

Pour savoir comment appliquer ou supprimer une configuration Terraform, consultez la page Commandes Terraform de base.

Pour créer un service, ajoutez le code ci-dessous à votre fichier .tf :

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

  deletion_protection = false # set to "true" in production

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

Créer une file d'attente de tâches

Ligne de commande

Pour créer une file d'attente de tâches, utilisez la commande

gcloud tasks queues create QUEUE-ID

en remplaçant QUEUE-ID par le nom que vous souhaitez attribuer à la file d'attente de tâches. Celui-ci doit être unique dans votre projet. Si vous êtes invité à créer une application App Engine dans votre projet, répondez y pour la créer. Cloud Tasks l'utilise pour la file d'attente : veillez à choisir le même emplacement que pour votre service Cloud Run.

La configuration de file d'attente de tâches par défaut devrait fonctionner dans la plupart des cas. Cependant, vous pouvez définir des limites de débit et des paramètres de nouvelle tentative différents si vous le souhaitez.

Terraform

Pour savoir comment appliquer ou supprimer une configuration Terraform, consultez la page Commandes Terraform de base.

Pour créer une file d'attente de tâches, ajoutez le code ci-dessous à votre fichier .tf :

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

Appliquez les modifications en saisissant terraform apply.

Créer un compte de service à associer aux tâches

Vous devez créer un compte de service qui sera associé aux tâches mises en file d'attente. Ce compte de service doit disposer du rôle IAM "Demandeur Cloud Run" pour que la file d'attente de tâches puisse envoyer des tâches au service Cloud Run. .

Console

  1. Dans la console Google Cloud, accédez à la page Comptes de service.

    Accéder à la page "Comptes de service"

  2. Sélectionnez un projet.

  3. Saisissez le nom du compte de service à afficher dans la console Google Cloud.

    La console Google Cloud génère un ID de compte de service basé sur ce nom. Modifiez l'ID si nécessaire. Vous ne pourrez pas le modifier par la suite.

  4. Facultatif : saisissez la description du compte de service.

  5. Cliquez sur Créer et continuer.

  6. Facultatif : cliquez sur le champ Sélectionner un rôle.

  7. Sélectionnez Cloud Run > Demandeur Cloud Run.

  8. Cliquez sur OK.

Ligne de commande

  1. Créez le compte de service :

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

    Remplacer

    • SERVICE_ACCOUNT_NAME par un nom écrit en minuscules unique dans votre projet Google Cloud, par exemple my-invoker-service-account-name ;
    • DISPLAYED_SERVICE_ACCOUNT_NAME par le nom que vous souhaitez afficher pour ce compte de service, par exemple dans la console : My Invoker Service Account.
  2. Pour Cloud Run, autorisez votre compte de service à appeler votre service :

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

    Remplacer

    • SERVICE par le nom du service que vous souhaitez que Cloud Tasks appelle ;
    • SERVICE_ACCOUNT_NAME par le nom du compte de service ;
    • PROJECT_ID par l'ID de votre projet Google Cloud
  3. Accordez à votre compte de service l'accès au projet afin qu'il soit autorisé à effectuer des actions spécifiques sur les ressources de votre projet :

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

    Remplacer

    • RESOURCE_ID : ID de votre projet Google Cloud.

    • PRINCIPAL : l'identifiant du compte principal, qui se présente généralement sous la forme suivante : PRINCIPAL_TYPE:ID. Exemple : user:my-user@example.com. Pour obtenir la liste complète des valeurs possibles pour PRINCIPAL, consultez la documentation de référence sur les liaisons de stratégie.

Terraform

Pour savoir comment appliquer ou supprimer une configuration Terraform, consultez la page Commandes Terraform de base.

Ajoutez le code ci-dessous à votre fichier .tf :

Créez le compte de service :

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

Pour Cloud Run, autorisez votre compte de service à appeler votre service :

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}"]
}

Appliquez les modifications en saisissant terraform apply.

Créer des tâches HTTP avec des jetons d'authentification

Lorsque vous créez une tâche à envoyer à la file d'attente de tâches, vous spécifiez le projet, l'emplacement, le nom de la file d'attente, l'adresse e-mail du compte de service précédemment créé à associer aux tâches, l'URL du service Cloud Run privé qui exécutera la tâche, ainsi que toutes les autres données à envoyer. Vous pouvez choisir de coder ces valeurs en dur, même si des valeurs telles que l'ID de projet, l'emplacement et l'adresse e-mail du compte de service peuvent être récupérées de manière dynamique à partir du serveur de métadonnées Cloud Run.

Reportez-vous à la documentation de l'API Cloud Tasks pour en savoir plus sur le corps de la requête de tâche. Notez que les requêtes contenant des charges utiles de données doivent utiliser la méthode HTTP PUT ou POST.

Le code qui met les tâches en file d'attente doit disposer des autorisations IAM nécessaires pour le faire, par exemple le rôle "Empileur Cloud Tasks". Votre code disposera des autorisations IAM nécessaires si vous utilisez le compte de service par défaut sur Cloud Run.

Les exemples suivants créent des requêtes de tâches qui incluent également la création d'un jeton d'en-tête. Des jetons OIDC sont utilisés dans les exemples. Pour utiliser un jeton OAuth, remplacez le paramètre OIDC par le paramètre OAuth approprié au langage dans la construction de la requête.

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

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

Notez le fichier 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();

Notez le fichier 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"
  }
}

Étapes suivantes