LAB06_GCP_Serverless: Deploying Cloud Functions (2nd Gen)

Serverless: Deploying Cloud Functions (2nd Gen)

☁ GCP 🟢 Beginner Cloud Functions Cloud Storage Serverless

Scenario

You want to deploy a simple serverless "Hello World" API endpoint without managing any infrastructure. In Google Cloud, Cloud Functions (2nd Gen) is the recommended serverless framework (under the hood, it deploys a Cloud Run service). You will write a Python function, zip it using Terraform, upload it to a bucket, and deploy the function.

Your Objectives
  • Write a simple main.py and requirements.txt file locally.
  • Use the archive_file data source to create a zip file of your function code.
  • Create a google_storage_bucket and upload the zip file as a google_storage_bucket_object.
  • Deploy a google_cloudfunctions2_function using the uploaded zip file as its source. Make it an HTTP trigger.
  • Create a google_cloud_run_service_iam_member to allow unauthenticated public access.
  • Output the function's HTTP deployment URI.

Additional Context

2nd Gen vs 1st Gen: Cloud Functions 2nd Gen is built on Cloud Run and Eventarc. It offers longer timeouts, larger instances, and concurrent requests per instance. Because it's built on Cloud Run, IAM permissions for HTTP triggers (to make them public) are actually applied to the underlying Cloud Run service, not the function itself.

Terraform needs to upload your source code to Cloud Storage because the Cloud Functions API pulls the code from a GCS URI during deployment/build.

src/main.py

import functions_framework

@functions_framework.http
def hello_http(request):
    """HTTP Cloud Function."""
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:
        name = request_json['name']
    elif request_args and 'name' in request_args:
        name = request_args['name']
    else:
        name = 'World'
    return f'Hello {name} from Terraform GCP Lab 6!'

src/requirements.txt

functions-framework==3.*

variables.tf

variable "project_id" { type = string }
variable "gcp_region" { type = string; default = "us-central1" }

main.tf

provider "google" {
  project = var.project_id
  region  = var.gcp_region
}

# Zip the function source code
data "archive_file" "function_zip" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/function-source.zip"
}

# Create a random suffix for the storage bucket
resource "random_id" "bucket_suffix" { byte_length = 4 }

# Bucket to store the function source code
resource "google_storage_bucket" "source_bucket" {
  name                        = "gcf-source-${random_id.bucket_suffix.hex}"
  location                    = var.gcp_region
  uniform_bucket_level_access = true
  force_destroy               = true
}

# Upload the zip code to the bucket
resource "google_storage_bucket_object" "zip_upload" {
  name   = "source-${data.archive_file.function_zip.output_md5}.zip"
  bucket = google_storage_bucket.source_bucket.name
  source = data.archive_file.function_zip.output_path
}

# Deploy the 2nd Gen Function
resource "google_cloudfunctions2_function" "function" {
  name        = "hello-world-function"
  location    = var.gcp_region
  description = "A simple HTTP triggered Python function"

  build_config {
    runtime     = "python311"
    entry_point = "hello_http"  # The python function name
    source {
      storage_source {
        bucket = google_storage_bucket.source_bucket.name
        object = google_storage_bucket_object.zip_upload.name
      }
    }
  }

  service_config {
    max_instance_count = 1
    available_memory   = "256M"
    timeout_seconds    = 60
  }
}

# Make the underlying Cloud Run service publicly accessible
resource "google_cloud_run_service_iam_member" "public_invoker" {
  location = google_cloudfunctions2_function.function.location
  project  = google_cloudfunctions2_function.function.project
  service  = google_cloudfunctions2_function.function.name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

outputs.tf

output "function_url" {
  value       = google_cloudfunctions2_function.function.service_config[0].uri
  description = "The public URL of the Cloud Function"
}

Workflow Commands

terraform init
terraform apply -var="project_id=my-gcp-project-123"

# Note: The Cloud Build process for the function takes ~2 minutes.

# Test the function without parameters
curl $(terraform output -raw function_url)
# Returns: Hello World from Terraform GCP Lab 6!

# Test the function with URL query parameters
curl "$(terraform output -raw function_url)?name=Terraform"
# Returns: Hello Terraform from Terraform GCP Lab 6!

terraform destroy -var="project_id=my-gcp-project-123"