Cloud Functions Essentials
Cloud Functions Essentials What It Is: Event-Driven vs HTTP Google Cloud Functions is GCP's Function-as-a-Service offering — a single function deployed and invo…
Cloud Functions Essentials
What It Is: Event-Driven vs HTTP
Google Cloud Functions is GCP's Function-as-a-Service offering — a single function deployed and invoked without managing servers, GCP's answer to AWS Lambda and Azure Functions. Unlike Azure Functions' triggers-and-bindings model (where inputs/outputs are declared in config and injected), GCP Cloud Functions takes a simpler two-shape model: an HTTP function receives a plain request/response object, and an event-driven (CloudEvent) function receives a standardized event payload from an Eventarc-routed source (Pub/Sub, Cloud Storage, Firestore, etc.) — there's no binding-injection layer, you read the event object directly in your handler.
HTTP function — triggered by a direct HTTPS request; you get a raw request/response, exactly like an Express handler
CloudEvent function — triggered by an event (Pub/Sub message, Cloud Storage object change, Firestore write) routed through Eventarc, using the CNCF CloudEvents spec
Gen 2 (built on Cloud Run) — the current generation; functions run as Cloud Run services under the hood, giving longer timeouts (up to 60 min), larger instances, and concurrency (multiple requests per instance)
Writing Functions (Node.js)
const functions = require('@google-cloud/functions-framework')
// HTTP function — plain (req, res), no binding config needed
functions.http('createOrder', async (req, res) => {
if (req.method !== 'POST') {
return res.status(405).send('Method Not Allowed')
}
const { items, customerId } = req.body
if (!items || items.length === 0) {
return res.status(400).json({ error: 'items is required' })
}
const order = await createOrder({ items, customerId })
res.status(201).json(order)
})
// CloudEvent function — triggered by a Pub/Sub message via Eventarc
functions.cloudEvent('processOrderEvent', async (cloudEvent) => {
const base64Data = cloudEvent.data.message.data
const payload = JSON.parse(Buffer.from(base64Data, 'base64').toString())
console.log(`Processing order ${payload.orderId}`)
await fulfillOrder(payload.orderId)
})
// CloudEvent function — triggered by a new object landing in Cloud Storage
functions.cloudEvent('resizeUploadedImage', async (cloudEvent) => {
const file = cloudEvent.data
if (!file.contentType?.startsWith('image/')) return
console.log(`Resizing ${file.name} in bucket ${file.bucket}`)
await generateThumbnail(file.bucket, file.name)
})Deployment Tooling
Deployment is a single gcloud command per function — there's no equivalent of Azure's Function App grouping multiple functions under one deployable unit; each Cloud Function deploys and scales independently. Local testing uses the Functions Framework directly rather than a separate CLI tool (Azure's func start).
# Deploy an HTTP function (Gen 2)
gcloud functions deploy createOrder \
--gen2 \
--runtime=nodejs20 \
--region=us-central1 \
--source=. \
--entry-point=createOrder \
--trigger-http \
--allow-unauthenticated \
--memory=256Mi \
--timeout=60s \
--set-env-vars=DATABASE_URL=postgres://...
# Deploy an event-driven function, triggered by a Pub/Sub topic
gcloud functions deploy processOrderEvent \
--gen2 \
--runtime=nodejs20 \
--region=us-central1 \
--source=. \
--entry-point=processOrderEvent \
--trigger-topic=order-processing
# Deploy a function triggered by Cloud Storage object finalization
gcloud functions deploy resizeUploadedImage \
--gen2 \
--runtime=nodejs20 \
--region=us-central1 \
--source=. \
--entry-point=resizeUploadedImage \
--trigger-bucket=my-uploads-bucket
# Local dev with the Functions Framework — no separate CLI install needed
npx @google-cloud/functions-framework --target=createOrder
# Stream logs
gcloud functions logs read createOrder --region=us-central1 --gen2Scaling & Pricing Model
Cloud Functions bills per 100ms of compute time plus a per-invocation fee, with a perpetual free tier (2 million invocations/month). Because Gen 2 runs on Cloud Run, you also get concurrency — a single instance can handle multiple in-flight requests if your code is async-safe, which lowers cost for I/O-bound workloads compared to the one-request-per-instance model. This differs from Azure Functions' Consumption plan, which is billed on GB-seconds and executions but doesn't offer per-instance concurrency in the same way.
# Configure min/max instances and concurrency (Gen 2)
gcloud functions deploy createOrder \
--gen2 \
--region=us-central1 \
--min-instances=0 \
--max-instances=20 \
--concurrency=40 \
--cpu=1 \
--memory=512Mi
# min-instances=0 -> scale to zero, cold starts possible
# min-instances=1 -> always one warm instance, eliminates cold starts, costs morePitfalls & Practical Tips
Gen 1 and Gen 2 have different limits and behavior (Gen 1 max timeout is 9 minutes, no concurrency; Gen 2 supports up to 60 minutes and per-instance concurrency) — new functions should default to Gen 2 unless you have a specific Gen 1-only dependency
Pub/Sub and Storage triggers deliver at-least-once — a message or event can be redelivered after a transient failure, so handlers must be idempotent (check for already-processed IDs before acting)
--allow-unauthenticated is required to make an HTTP function publicly callable; without it, callers need a valid Google-signed identity token, which is easy to forget when a frontend suddenly gets 403s
Cold starts scale with package size and heavy top-level imports — keep dependencies lean and do expensive client initialization (DB pools, SDK clients) outside the handler so it's reused across warm invocations, not per-request
Unlike Azure's cron.yaml, Cloud Functions has no built-in scheduler — pair an HTTP function with Cloud Scheduler (a separate resource that calls the function's URL on a cron schedule)
Concurrency > 1 assumes your code has no unsafe shared mutable state between requests on the same instance — CPU-bound or blocking code should keep concurrency at 1 to avoid one request starving another