비동기 태스크 실행

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 작업을 사용하세요.

Terraform을 사용하여 배포

Terraform 구성을 적용하거나 삭제하는 방법은 기본 Terraform 명령어를 참조하세요.

서비스를 만들려면 다음을 .tf 파일에 추가합니다.

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

  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 서비스로 푸시하도록 허용하려면 이 서비스 계정에 Cloud Run 호출자 IAM 역할이 있어야 합니다. .

Console

  1. Google Cloud 콘솔에서 서비스 계정 페이지로 이동합니다.

    서비스 계정으로 이동

  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을 Google Cloud 프로젝트 내에서 고유한 소문자 이름으로 바꿉니다(예: my-invoker-service-account-name).
    • DISPLAYED_SERVICE_ACCOUNT_NAME을 Console에서 이 서비스 계정에 표시하려는 이름으로 바꿉니다(예: 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가 가질 수 있는 값의 전체 목록은 정책 binding 참조를 확인하세요.

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 큐 추가자 역할과 같이 필요한 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": "^4.0.0",
    "express": "^4.16.3"
  },
  "devDependencies": {
    "c8": "^8.0.0",
    "chai": "^4.2.0",
    "mocha": "^10.0.0",
    "uuid": "^9.0.0"
  }
}

다음 단계