非同期タスクの実行

Cloud Tasks を使用すると、タスクを安全にキューに追加し、Cloud Run サービスによって非同期に処理されます。主な用途としては、次のようなものがあります。

  • 本番環境で予期しないインシデントが発生した場合にリクエストを保存する
  • ユーザーが操作していない作業を遅らせてトラフィックの急増を緩和する
  • データベースの更新やバッチ処理など、低速のバックグラウンド オペレーションを委任し、別のサービスで処理することで、ユーザーの応答時間を短縮する
  • データベースやサードパーティ API などのバッキング サービスに対するコールレートを制限する

このページでは、タスクをキューに追加して、HTTPS プロトコルを介して限定公開の Cloud Run サービスに安全に push する方法について説明します。プライベート Cloud Run サービスに必要な動作、必要なサービス アカウント権限、タスクキューの作成、タスクの作成について説明します。

始める前に

使用しているプロジェクトで Cloud Tasks API を有効にします

タスクを処理するための Cloud Run サービスのデプロイ

タスクキューに送信されたタスクを受け入れるサービスをデプロイするには、他の Cloud Run サービスと同じ方法でサービスをデプロイします。Cloud Run サービスは、タスクの処理が完了した後に成功を確認するため、HTTP 200 コードを返す必要があります。

Cloud Tasks は、この Cloud Run サービスに HTTPS リクエストとしてタスクを push します。

クラウドタスクへのレスポンスは、構成されたタイムアウト内に行う必要があります。Cloud Tasks のタイムアウトの最大値を超えて実行する必要があるワークロードの場合は、Cloud Run ジョブの使用を検討してください。

Terraform を使用してデプロイする

Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

サービスを作成するには、次のコードを .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"
    }
  }
}

タスクキューの作成

コマンドライン

タスクキューを作成するには、次のコマンドを使用します。

gcloud tasks queues create QUEUE-ID

QUEUE-ID は、タスクキューに付ける名前に置き換えます。これはプロジェクト内で一意である必要があります。プロジェクトで App Engine アプリを作成するように求められたら、y と返信して作成します。Cloud Tasks はこれをキューに使用します。Cloud Run サービスで使用しているのと同じロケーションを選択してください。

ほとんどの場合、デフォルトのタスクキュー構成で機能します。ただし、必要に応じて異なるレート制限再試行パラメータを設定することもできます。

Terraform

Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

タスクキューを作成するには、次のコードを .tf ファイルに追加します。

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

terraform apply」と入力して変更を適用します。

タスクに関連付けるサービス アカウントの作成

キューに入れられたタスクに関連付けられるサービス アカウントを作成する必要があります。このサービス アカウントには、タスクキューが Cloud Run サービスにタスクを push することを許可する Cloud Run Invoker IAM ロールが必要です。

コンソール

  1. Google Cloud コンソールで、[サービス アカウント] ページに移動します。

    [サービス アカウント] に移動

  2. プロジェクトを選択します。

  3. Google Cloud コンソールに表示するサービス アカウント名を入力します。

    この名前に基づいてサービス アカウント ID が生成され、Google Cloud コンソールに表示されます。必要に応じて ID を編集します。後で ID を変更することはできません。

  4. (省略可)サービス アカウントの説明を入力します。

  5. [作成して続行] をクリックします。

  6. (省略可)[ロールを選択] フィールドをクリックします。

  7. [Cloud Run] > [Cloud Run 起動元] を選択します。

  8. [完了] をクリックします。

コマンドライン

  1. サービス アカウントを作成します。

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

    次のように置き換えます。

    • SERVICE_ACCOUNT_NAME は、Google Cloud プロジェクト内で一意の小文字の名前(my-invoker-service-account-name など)に置き換えます。
    • DISPLAYED_SERVICE_ACCOUNT_NAME は、このサービス アカウントに対してコンソール上で表示する名前(My Invoker Service Account など)に置き換えます。
  2. Cloud Run では、サービス アカウントにサービスを呼び出す権限を付与します。

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

    次のように置き換えます。

    • SERVICE は、Cloud Tasks によって呼び出されるようにするサービスの名前に置き換えます。
    • SERVICE_ACCOUNT_NAME は、サービス アカウントの名前に置き換えます。
    • PROJECT_ID は、Google Cloud プロジェクト ID に置き換えます。
  3. サービス アカウントにプロジェクトへのアクセス権を付与すると、プロジェクト内のリソースに対して特定の操作を行う権限が与えられます。

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

    次のように置き換えます。

    • RESOURCE_ID: Google Cloud プロジェクト ID。

    • PRINCIPAL: プリンシパルまたはメンバーの識別子。通常、PRINCIPAL_TYPE:ID の形式です(たとえば、user:my-user@example.com)。PRINCIPAL に使用できる値の一覧については、ポリシー バインディングのリファレンスをご覧ください。

Terraform

Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

次のコードを .tf ファイルに追加します。

サービス アカウントを作成します。

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

Cloud Run では、サービス アカウントにサービスを呼び出す権限を付与します。

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

terraform apply」と入力して変更を適用します。

認証トークンを使用した HTTP タスクの作成

タスクキューに送信するタスクを作成するときは、プロジェクト、ロケーション、キュー名、タスクに関連付ける以前に作成したサービス アカウントのメールアドレス、タスクを実行するプライベート Cloud Run サービスの URL、送信する必要があるその他のデータを指定します。これらの値はハードコードすることもできますが、プロジェクト ID、ロケーション、サービス アカウントのメールアドレスなどの値は、Cloud Run メタデータ サーバーから動的に取得できます。

タスクのリクエスト本文の詳細については、Cloud Tasks API のドキュメントをご覧ください。データ ペイロードを含むリクエストでは、HTTP PUT または POST メソッドを使用する必要があります。

タスクをキューに追加するコードには、Cloud Tasks Enqueuer のロールなど、タスクをキューに追加するのに必要な IAM 権限が付与されていなければなりません。Cloud Run でデフォルトのサービス アカウントを使用する場合は、必要な IAM 権限がコードに付与されます。

次の例では、タスク リクエストが作成され、ヘッダー トークンの作成も含まれます。この例では OIDC トークンが使用されます。OAuth トークンを使用する場合は、リクエストの作成時に OIDC パラメータを、それぞれの言語に応じて適切な OAuth パラメータに置き換えます。

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

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

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

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

次のステップ