Asynchronous Execution: Cloud Tasks and Cloud Run
Scenario
Your serverless API application lets users generate reports. Report generation takes 3 minutes. If you run this synchronously, the user's HTTP request will time out. You need an asynchronous task queue. While Pub/Sub is great for "scatter/gather" event routing, Cloud Tasks is the correct tool for orchestrated, rate-controlled, scheduled task execution. A frontend will enqueue a task, and Cloud Tasks will issue an HTTP POST to a private background Cloud Run service containing an OIDC authentication token.
- Provision the private background google_cloud_run_v2_service.
- Provision a dedicated google_service_account for the Task Queue to use when invoking the service.
- Grant the Service Account roles/run.invoker on the background Cloud Run service specifically.
- Provision a google_cloud_tasks_queue queue. Configure the rate limits and max retries parameters.
Additional Context
Pub/Sub vs Cloud Tasks: Pub/Sub guarantees at-least-once delivery, but has virtually no rate limiting. If 10,000 users click "Buy", Pub/Sub will immediately attempt to trigger 10,000 workers. Cloud Tasks allows you to define `max_dispatches_per_second` (rate limiting) to protect fragile backend systems like old databases. You can also explicitly delay a task ("execute this in exactly 2 hours"), whereas Pub/Sub is immediate.
OIDC Authentication: Because Cloud Tasks communicates over HTTP, your backend Cloud Run service can be set to private. The client application submitting the task tells Cloud Tasks to generate an OIDC token using a specific service account. When Cloud Tasks issues the HTTP POST, it attaches `Authorization: Bearer [token]`. The `google_cloud_tasks_queue` does not define the HTTP target—the application code creating the task defines the target!
variables.tf
variable "project_id" { type = string }main.tf
provider "google" {
project = var.project_id
region = "us-central1"
}
# NOTE: Cloud Tasks requires an App Engine application to exist in the project.
# You only ever create this ONCE per project. Uncomment if running on a fresh project.
# resource "google_app_engine_application" "app" {
# project = var.project_id
# location_id = "us-central"
# }
# 1. The execution Service Account
# The client app tells Cloud Tasks to sign requests using this SA.
resource "google_service_account" "task_runner" {
account_id = "cloud-tasks-runner"
display_name = "Cloud Tasks Invoker Identity"
}
# 2. The Internal Worker Service
resource "google_cloud_run_v2_service" "report_generator" {
name = "reports-worker"
location = "us-central1"
ingress = "INGRESS_TRAFFIC_INTERNAL_ONLY" # Security constraint
template {
containers {
image = "us-docker.pkg.dev/cloudrun/container/hello"
}
}
}
# Grant the Task Runner SA permission to invoke the worker
resource "google_cloud_run_service_iam_member" "worker_invoker" {
location = google_cloud_run_v2_service.report_generator.location
project = google_cloud_run_v2_service.report_generator.project
service = google_cloud_run_v2_service.report_generator.name
role = "roles/run.invoker"
member = "serviceAccount:${google_service_account.task_runner.email}"
}
# 3. The Task Queue infrastructure
resource "google_cloud_tasks_queue" "reports_queue" {
name = "heavy-reports-queue"
location = "us-central1"
# Protect the backend Cloud Run service / DB from being overwhelmed
rate_limits {
max_dispatches_per_second = 10
max_concurrent_dispatches = 5
}
# Control how aggressively a failing task is retried
retry_config {
max_attempts = 5
max_retry_duration = "3600s" # 1 hour
min_backoff = "5s"
max_backoff = "600s"
}
# depends_on = [google_app_engine_application.app]
}Application Code Hint (Python SDK)
Terraform creates the queue. Your application code pushes the task:
from google.cloud import tasks_v2
client = tasks_v2.CloudTasksClient()
queue_path = client.queue_path("my-project", "us-central1", "heavy-reports-queue")
task = {
"http_request": {
"http_method": tasks_v2.HttpMethod.POST,
"url": "https://reports-worker-xyz.a.run.app/generate",
"oidc_token": {
# Use the Identity provisioned by Terraform
"service_account_email": "[email protected]"
},
"body": b'{"report_id": 99}'
}
}
client.create_task(request={"parent": queue_path, "task": task})Workflow Commands
terraform init
terraform apply -var="project_id=my-gcp-project-123"
# List configured queues
gcloud tasks queues list --location=us-central1
terraform destroy -var="project_id=my-gcp-project-123"
