非同期タスクの実行

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

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 を使用してデプロイする

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

resource "google_cloud_run_service" "default" {
    name     = "cloud-run-service-name"
    location = "us-central1"
    provider = google-beta
    template {
      spec {
            containers {
                image = "gcr.io/cloudrun/hello"
            }
      }
    }
    traffic {
      percent         = 100
      latest_revision = true
    }
}

Google Cloud プロジェクトで Terraform 構成を適用するには、次の手順を行います。

  1. Cloud Shell を起動します。
  2. Terraform 構成を適用する Google Cloud プロジェクトを設定します。
    export GOOGLE_CLOUD_PROJECT=PROJECT_ID
    
  3. ディレクトリを作成し、そのディレクトリで新規ファイルを開きます。ファイル名には .tf 拡張子が必要です(例: main.tf)。
    mkdir DIRECTORY && cd DIRECTORY && nano main.tf
    
  4. サンプルを main.tf にコピーします。
  5. 環境に適用するサンプル パラメータを確認し、変更します。
  6. Ctrl-x を押してから y を押して、変更を保存します。
  7. Terraform を初期化します。
    terraform init
  8. 構成を確認して、Terraform が作成または更新するリソースが想定どおりであることを確認します。
    terraform plan

    必要に応じて構成を修正します。

  9. 次のコマンドを実行します。プロンプトで「yes」と入力して、Terraform 構成を適用します。
    terraform apply

    Terraform に「Apply complete!」のメッセージが表示されるまで待ちます。

  10. Google Cloud プロジェクトを開いて結果を表示します。Google Cloud コンソールの UI でリソースに移動して、Terraform によって作成または更新されたことを確認します。

タスクキューの作成

コマンドライン

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

gcloud tasks queues create QUEUE-ID

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

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

Terraform

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

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

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 に置き換えます。

Terraform

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

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

resource "google_service_account" "sa" {
  account_id   = "cloud-run-task-invoker"
  display_name = "Cloud Run Task Invoker"
  provider = google-beta
}

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

resource "google_cloud_run_service_iam_binding" "binding" {
  location = google_cloud_run_service.default.location
  service = google_cloud_run_service.default.name
  role = "roles/run.invoker"
  members = ["serviceAccount:${google_service_account.sa.email}"]
  provider = google-beta
  project = google_cloud_run_service.default.project
}

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

"""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.10.2

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.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.1.1</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: {
      headers: {
        'Content-Type': 'text/plain',
      },
      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": ">=12.0.0"
  },
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "mocha",
    "start": "node server.js"
  },
  "dependencies": {
    "@google-cloud/tasks": "^3.0.3",
    "body-parser": "^1.18.3",
    "express": "^4.16.3"
  },
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^8.0.0",
    "uuid": "^9.0.0"
  }
}

次のステップ