執行非同步工作

您可以使用 Cloud Tasks 安全地將工作加入佇列,由 Cloud Run 服務非同步處理。常見用途包括:

  • 在發生非預期的正式版事件時保留要求
  • 延遲非使用者端工作,以應付流量遽增的情況
  • 將緩慢的背景作業委派給其他服務處理,例如資料庫更新或批次處理,藉此縮短使用者回應時間
  • 限制對資料庫和第三方 API 等後端服務的呼叫頻率

本頁說明如何將工作透過 HTTPS 通訊協定安全地推送至私有 Cloud Run 服務,並將工作排入佇列。本文說明私有 Cloud Run 服務的必要行為、必要的服務帳戶權限、工作佇列建立作業,以及工作建立作業。

事前準備

在您使用的專案中啟用 Cloud Tasks API

部署 Cloud Run 服務來處理工作

如要部署可接收傳送至工作佇列的工作的服務,請部署服務,方式與其他 Cloud Run 服務相同。Cloud Run 服務必須在處理完工作後,傳回 HTTP 200 代碼來確認成功。

Cloud Tasks 會以 HTTPS 要求的形式,將工作推送至這項 Cloud Run 服務。

Cloud Tasks 的回應必須在設定的逾時時間內完成。如要執行的工作負載超過 Cloud Tasks 最長逾時時間,請考慮使用 Cloud Run 工作

建立工作佇列

指令列

如要建立工作佇列,請使用下列指令:

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 叫用者 IAM 角色,才能允許工作佇列將工作推送至 Cloud Run 服務。。

控制台

  1. 前往 Google Cloud 控制台的「Service Accounts」(服務帳戶) 頁面。

    前往「Service Accounts」(服務帳戶)

  2. 選取專案。

  3. 輸入要顯示在 Google Cloud 控制台的服務帳戶名稱。

    Google Cloud 控制台會根據這個名稱產生服務帳戶 ID。請視需要編輯 ID,ID 設定後即無法變更。

  4. 選用:輸入服務帳戶的說明。

  5. 按一下「建立並繼續」

  6. 選用:按一下「請選擇角色」欄位。

  7. 依序選取「Cloud Run」 >「Cloud Run Invoker」

  8. 按一下 [完成]

指令列

  1. 建立服務帳戶:

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

    取代

    • SERVICE_ACCOUNT_NAME,並使用小寫名稱,且該名稱在專案中不得重複,例如 my-invoker-service-account-name。 Google Cloud
    • 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 改成您的專案 ID。 Google Cloud
  3. 將專案存取權授予服務帳戶,讓服務帳戶有權限對專案中的資源完成特定動作:

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

    取代

    • RESOURCE_ID:您的 Google Cloud 專案 ID。

    • PRINCIPAL:主體或成員的 ID,通常採用以下格式: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 服務網址,以及您需要傳送的任何其他資料。您可以選擇將這些值硬式編碼,但專案 ID、位置和服務帳戶電子郵件等值可以從 Cloud Run 中繼資料伺服器動態擷取

如要瞭解工作要求主體的詳細資料,請參閱 Cloud Tasks API 說明文件。 請注意,含有資料酬載的要求必須使用 HTTP PUTPOST 方法。

排定工作佇列的程式碼必須具備必要的 IAM 權限,例如 Cloud Tasks Enqueuer 角色。如果您在 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.18.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.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"
  }
}

後續步驟