非同期タスクの実行

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

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

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

始める前に

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

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

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

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

タスクキューの作成

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

gcloud tasks queues create QUEUE-ID

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

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

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

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

Console

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

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

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

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

    Cloud Console は、この名前に基づいてサービス アカウント ID を生成します。必要に応じて 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 に置き換えます。

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

タスクキューに送信するタスクを作成するときは、プロジェクト、ロケーション、キュー名、タスクに関連付ける以前に作成したサービス アカウントのメールアドレス、タスクを実行するプライベート Cloud Run サービスの URL、送信する必要があるその他のデータを指定します。

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

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

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

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

requirements.txt ファイルをメモします。

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

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

次のステップ