Article 26 – Google Cloud Functions

A user uploads a profile picture and you need to resize it into a thumbnail. A new entry gets written to your database and you need to send a notification. Do you really want to provision an entire VM that sits idle 99% of the time, waiting for these events? Do you want to build a full container service for a task that takes 200 milliseconds?

This is the problem Cloud Functions solves. It is the purest form of serverless — you write a small piece of code, upload it, and Google handles everything else. When a trigger fires, Google finds a server, runs your code, and shuts it down. Scales to thousands automatically. Scales to zero when idle. You pay only for the milliseconds your code actually runs.

Understanding Triggers — The “When”

A Cloud Function does nothing until it receives a signal. That signal is a Trigger. There are two categories:

HTTP Triggers — The simplest kind. Google gives you a unique HTTPS URL. When any application, user, or webhook hits that URL, your function runs. This turns your function into a lightweight, serverless API endpoint.

Event-Driven Triggers — This is where Cloud Functions really shines. Your function automatically reacts to events in other Google Cloud services.

Let me walk through a concrete example. We want to generate a thumbnail every time a user uploads an image:

  1. Event — User uploads golden-retriever.jpg to a Cloud Storage bucket called user-profile-pics
  2. Trigger fires — The function is watching that bucket. The moment the file is finalized, it wakes up
  3. Function executes — It receives event data: { "bucket": "user-profile-pics", "name": "golden-retriever.jpg" }
  4. Action — Downloads the image, resizes it, uploads thumbnail.jpg to a user-profile-thumbnails bucket
  5. Function sleeps — Job done. Goes dormant. Costs fractions of a penny.

Other popular event triggers:

  • Pub/Sub — Fires when a message is published to a topic. Great for decoupling systems.
  • Firestore — Fires when a document is created, updated, or deleted.
  • Cloud Audit Logs — Fires in response to specific API calls. Useful for automated security workflows.
Writing the Code

Cloud Functions supports Python, Node.js, Go, Java, Ruby, and more. The structure is simple — a source file with your function logic and a dependency file for external libraries.

Here is a basic HTTP function in Python:

# 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}!'
Deploying a Function

One command does it all:

# Deploy an HTTP-triggered function (Gen2)
gcloud functions deploy hello-world-http \
    --gen2 \
    --runtime=python312 \
    --region=us-central1 \
    --source=. \
    --entry-point=hello_http \
    --trigger-http \
    --allow-unauthenticated

Let me break down the important flags:
--gen2 — Use the modern 2nd Generation (built on Cloud Run under the hood)
--runtime — Language and version
--entry-point — The function name in your code to execute
--trigger-http — HTTP trigger. For a storage trigger, you would use --trigger-event=google.storage.object.finalize --trigger-resource=my-bucket
--allow-unauthenticated — Makes the endpoint public

For a storage-triggered function:

# Deploy a function triggered by Cloud Storage uploads
gcloud functions deploy generate-thumbnail \
    --gen2 \
    --runtime=python312 \
    --region=us-central1 \
    --source=. \
    --entry-point=resize_image \
    --trigger-event=google.storage.object.finalize \
    --trigger-resource=user-profile-pics

You can configure memory (128MB to 32GB) and timeout (up to 60 minutes for Gen2).

IAM — Two Sides to Understand

IAM for Cloud Functions has two distinct parts, and they are often confused:

1. The Function’s Identity (Runtime Service Account)

Your function needs permissions to interact with other services. It runs as a service account. By default, it uses the project’s default service account (which has broad permissions — not ideal).

The right approach: create a dedicated service account with minimal permissions.

# Create a dedicated service account
gcloud iam service-accounts create thumbnail-sa \
    --display-name="Thumbnail Generator SA"

# Grant it only what it needs
gcloud projects add-iam-policy-binding my-project \
    --member="serviceAccount:[email protected]" \
    --role="roles/storage.objectAdmin"

2. Who Can CALL the Function (Invoker Role)

For event-driven functions, this is automatic. For HTTP functions, you control access via the Cloud Functions Invoker role (roles/cloudfunctions.invoker).

  • Private (default) — Only authenticated principals with the Invoker role can call it
  • Public — Grant roles/cloudfunctions.invoker to allUsers (that is what --allow-unauthenticated does behind the scenes)
VPC Connectivity

By default, Cloud Functions runs in a Google-managed environment outside your VPC. If it needs to reach a Cloud SQL database or Memorystore instance with a private IP, use a Serverless VPC Access Connector — a managed bridge from serverless into your VPC.

Common Pitfalls and Best Practices

Pitfall: Giving the runtime service account the Editor role. Massive security risk.
Best Practice: Create a dedicated service account with the bare minimum permissions.

Pitfall: Writing “fat” functions that do too many things. Slow, hard to debug, inefficient.
Best Practice: Keep functions small, single-purpose, and fast. Chain complex workflows with Pub/Sub.

Pitfall: Forgetting about cold starts. First invocation after idle time is slower.
Best Practice: For latency-sensitive apps, configure a minimum number of warm instances (with cost implications).

Pitfall: Assuming an event will be delivered exactly once. Network issues can cause duplicate deliveries.
Best Practice: Design functions to be idempotent — running them twice with the same input produces the same result. For the thumbnail example, check if the thumbnail exists before creating it.

Quick Reference
# Deploy HTTP function
gcloud functions deploy [NAME] --gen2 --runtime=[RUNTIME] \
    --region=[REGION] --source=. --entry-point=[HANDLER] --trigger-http

# Deploy Storage-triggered function
gcloud functions deploy [NAME] --gen2 --runtime=[RUNTIME] \
    --region=[REGION] --source=. --entry-point=[HANDLER] \
    --trigger-event=google.storage.object.finalize \
    --trigger-resource=[BUCKET]

# Deploy Pub/Sub-triggered function
gcloud functions deploy [NAME] --gen2 --runtime=[RUNTIME] \
    --region=[REGION] --source=. --entry-point=[HANDLER] \
    --trigger-topic=[TOPIC]

# Call an HTTP function directly
gcloud functions call [NAME] --region=[REGION]

# List all functions
gcloud functions list

# View function logs
gcloud functions logs read [NAME] --region=[REGION]

# Grant public access
gcloud functions add-invoker-policy-binding [NAME] --member="allUsers"