Introduction to Cloud Functions
Cloud Functions is Google Cloud's managed Function-as-a-Service (FaaS) offering. It lets you upload a single function—written in a supported runtime—and Google Cloud handles the build, container packaging, scaling, TLS termination, and traffic routing. You pay per invocation, per GB-second of memory, and per GHz-second of CPU; idle code costs nothing. For the PCD exam, Cloud Functions sits next to Cloud Run and App Engine in the serverless compute triangle, but with a deliberately narrower programming model: one source directory, one entry point, one HTTP or CloudEvent handler.
Cloud Functions ships in two generations that share a name but differ dramatically in implementation. Cloud Functions (1st gen) runs on a legacy infrastructure managed directly by the Functions team and exposes a constrained execution environment with 540-second maximum timeouts and one concurrent request per instance. Cloud Functions (2nd gen) is, under the hood, a Cloud Run service deployed with an opinionated buildpack, fronted by Eventarc for non-HTTP triggers. That means CF2 inherits Cloud Run's scaling model, networking, IAM, and revisions. On the exam, when a scenario mentions long-running HTTP handlers, request concurrency, or Eventarc filters, the correct answer almost always involves the 2nd gen runtime.
Function-as-a-Service (FaaS): A serverless compute model where the unit of deployment is a single function attached to a trigger. The platform builds, scales, and bills per invocation. In Google Cloud, Cloud Functions is the FaaS product; in CF2, that function is wrapped into a Cloud Run container automatically.
1st Gen vs 2nd Gen: The Architecture Split
Choosing the right generation is the single most common Cloud Functions decision on the PCD exam. The two generations share a gcloud functions deploy command but produce very different runtime resources behind the scenes.
Underlying platform
1st gen functions are deployed as opaque "Cloud Function" resources managed by the legacy FaaS control plane. You cannot see the underlying container, you cannot tune concurrency, and you cannot route traffic between revisions. 2nd gen functions are deployed as a Cloud Run service plus an Eventarc trigger (for event-driven functions). After gcloud functions deploy --gen2, you can navigate to the Cloud Run console and see the service, revisions, and traffic splits. The same function appears in both gcloud functions list and gcloud run services list.
Quotas and limits
| Capability | 1st gen | 2nd gen |
|---|---|---|
| Max memory | 8 GiB | 32 GiB |
| Max vCPU | ~2 vCPU implicit | Up to 8 vCPU explicit |
| Max HTTP timeout | 540 s (9 min) | 3600 s (60 min) |
| Max event-driven timeout | 540 s | 540 s |
| Concurrency per instance | 1 | 1-1000 (default 1) |
| Max instances | 3000 | 1000 (default) |
| Min instances | Supported | Supported |
| Triggers | Pub/Sub, GCS, Firestore, HTTP, Firebase | All of those plus 90+ Eventarc sources |
When to pick which generation
The 2nd gen runtime is the default recommendation for any new workload. The only legitimate reasons to stay on 1st gen are (a) you depend on a specific 1st gen-only event source like Firebase Realtime Database direct triggers, or (b) you are extending an existing 1st gen codebase and the migration cost is not justified. For every other case—higher concurrency, longer HTTP timeouts, more memory, traffic splitting, or richer event filtering—2nd gen wins.
On the exam, "I need an HTTP handler that runs for 30 minutes" or "I need to handle 100 concurrent requests per instance" both force the 2nd gen answer. 1st gen caps at 540 seconds and concurrency 1. Memorize these two numbers; they are the most common distractors.
HTTP vs Event-Driven Functions
Cloud Functions supports two fundamental trigger styles, and the choice changes the function signature, the IAM model, and the retry semantics.
HTTP functions
HTTP functions expose an HTTPS endpoint on *.cloudfunctions.net (1st gen) or *.run.app (2nd gen via Cloud Run). They accept any HTTP method, parse the request, return a response, and are billed for the duration of the request. HTTP functions in 2nd gen support --allow-unauthenticated for public endpoints or rely on Cloud Run IAM (roles/run.invoker) to gate access. The signature in Node.js is (req, res) => {...}; in Python, it is a Flask-style def handler(request): ....
Event-driven functions and CloudEvents v1.0
Event-driven functions consume structured events. In 1st gen, the event was a legacy Google-specific payload. In 2nd gen, every event-driven function receives a CloudEvents v1.0 envelope, the CNCF-standardized event format with mandatory id, source, specversion, type, and a typed data payload. Your handler signature becomes (cloudEvent) => {...} in Node.js or @functions_framework.cloud_event in Python. This means you can write the same handler for Pub/Sub, GCS, Firestore, or any Eventarc source by switching on cloudEvent.type.
CloudEvents v1.0 is portable across vendors. If you later move a 2nd gen function to Knative, AWS EventBridge, or Azure Event Grid, the handler signature stays the same as long as you use the CloudEvents SDK. This is a frequent "future-proofing" exam justification.
Eventarc Integration in 2nd Gen
Eventarc is the universal event router that powers all non-HTTP triggers in Cloud Functions 2nd gen. Understanding the Eventarc model is mandatory for the PCD exam because the same patterns appear in Cloud Run and Workflows questions.
How Eventarc fits
When you run gcloud functions deploy my-fn --gen2 --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" --trigger-event-filters="bucket=my-bucket", Google Cloud actually creates three resources: a Cloud Run service, an Eventarc trigger, and (under the hood for GCS) a Pub/Sub subscription that Eventarc uses as the delivery transport. The trigger filters convert provider events into CloudEvents and POST them to the Cloud Run URL.
Trigger filter types
Eventarc filters accept exact-match attributes like bucket=my-bucket, methodName=storage.objects.delete, or serviceName=storage.googleapis.com. You combine multiple --trigger-event-filters flags with AND semantics. There is also a --trigger-event-filters-path-pattern flag for path-style matching, such as filtering Cloud Audit Logs by resourceName=projects/_/buckets/my-bucket/objects/*. This pattern matching is the answer when a scenario says "trigger only when an object in a specific prefix is deleted."
Direct sources vs Audit Log sources
Eventarc has two flavors of triggers. Direct sources (GCS, Firestore, Pub/Sub, Firebase Alerts, Cloud IoT) deliver native typed events with minimal latency. Audit Log sources convert any Cloud Audit Logs entry into a CloudEvent, which means you can trigger a function on any Google Cloud API call—BigQuery job completion, IAM policy change, Compute Engine VM creation—as long as Data Access or Admin Activity logs are enabled. Audit Log triggers have higher latency (10-60 seconds) but enormous breadth.
Audit Log triggers require Data Access logs to be explicitly enabled for the target service in the project's audit configuration. Admin Activity logs are on by default. A common trap question is "the trigger never fires"—the fix is gcloud projects get-iam-policy and adding the relevant audit_log_configs entry.
Trigger Sources in Depth
Each trigger source has its own delivery guarantees, latency profile, and IAM requirements. The exam typically picks one of these four high-value sources.
Pub/Sub triggers
Pub/Sub is the workhorse asynchronous trigger. Deploying with --trigger-topic=my-topic (1st gen) or --trigger-event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" --trigger-topic=my-topic (2nd gen) provisions a subscription that pushes messages into your function. Delivery is at-least-once, so handlers must be idempotent. In 2nd gen, the Pub/Sub message arrives as the data field of the CloudEvent envelope, base64-encoded. You acknowledge implicitly by returning a 2xx response; non-2xx responses or thrown exceptions cause Pub/Sub to redeliver after the subscription's ack deadline.
Cloud Storage triggers
GCS triggers fire on object-level events: google.cloud.storage.object.v1.finalized (a new or overwritten object becomes durable), deleted, archived, and metadataUpdated. 1st gen used legacy event types like google.storage.object.finalize. The CloudEvent's data field contains the GCS object metadata: bucket, name, size, contentType, generation, and metageneration. A frequent exam scenario is "process every uploaded image"—the answer is a finalize trigger filtered by bucket, with the function reading the object via the Cloud Storage client library (never inline in the event payload; the event contains metadata, not bytes).
Firestore triggers
Firestore triggers fire on document writes (created, updated, deleted, or written which matches any change). The CloudEvent payload contains the document path, the old value, and the new value. These triggers are specific to Firestore in Native mode; Datastore mode does not emit Firestore events. Use Firestore triggers for fan-out patterns: when a user document changes, the function denormalizes data into related collections or invalidates a cache.
Cloud Audit Logs triggers
As described above, Audit Log triggers convert API activity into events. The CloudEvent's data field is the full LogEntry proto, including protoPayload, resource, severity, and principalEmail. Use these triggers for compliance automation: revoke a service account key the moment it is created, alert when a BigQuery dataset's IAM is changed, or auto-tag VMs at creation.
Many candidates assume GCS triggers receive the file content. They do not. The event carries metadata only, and you must call the GCS API from inside the function to fetch the bytes. For large objects, stream the read with the client library's resumable download to avoid OOM on the function instance.
Concurrency: The 2nd Gen Game Changer
Concurrency is the single biggest performance and cost lever in Cloud Functions 2nd gen, and it does not exist in 1st gen.
What concurrency means
In 1st gen, each function instance handles exactly one request at a time. If 100 requests arrive simultaneously, the platform spins up 100 instances and you pay for 100 instance-seconds. In 2nd gen, concurrency is configurable from 1 to 1000 via --concurrency=N. With --concurrency=80 (the Cloud Run default but not the CF2 default), a single instance can handle 80 simultaneous requests, dropping your instance count and bill by up to 80x for I/O-bound workloads.
The default-1 trap
Critically, Cloud Functions 2nd gen ships with --concurrency=1 by default, mimicking the 1st gen behavior so migrations behave the same. You must explicitly raise it. The exam often presents a "why are my CF2 costs the same as 1st gen?" scenario; the answer is the unset concurrency flag.
When to raise concurrency
Raise concurrency when your function is I/O-bound: making outbound HTTP calls, querying databases, or waiting on Pub/Sub. Keep it at 1 when your function is CPU-bound (image processing, video transcoding, ML inference) because additional requests will starve each other for CPU. Also keep it at 1 if you mutate global in-memory state without locks—concurrent invocations will race.
Cloud Functions 2nd gen concurrency: range 1-1000, default 1. Cloud Run concurrency: range 1-1000, default 80. The product names are similar, the defaults are not. This is one of the highest-frequency exam distractors.
Scaling Controls: min-instances and max-instances
Scaling controls let you trade cost for latency and cap the blast radius of runaway invocations.
--max-instances
--max-instances caps the number of concurrent instances. The default is 1000 for 2nd gen (3000 for 1st gen). Setting --max-instances=10 is critical when your function calls a downstream system with limited capacity—an unsharded Cloud SQL instance, a third-party API with rate limits, or a legacy on-prem service. Without a cap, a traffic spike fans out and either melts the downstream or rings up a surprise bill. When the cap is reached, additional requests get queued briefly and then receive 429 Too Many Requests (HTTP) or remain in the Pub/Sub queue (event-driven).
--min-instances and cold starts
--min-instances=N keeps N instances warm at all times, eliminating cold-start latency for the first N concurrent requests. You pay for the idle instances at a discounted "idle" rate (about 10% of active). This is the answer for latency-sensitive HTTP endpoints, particularly Java or .NET functions where cold starts can exceed 5 seconds. For Pub/Sub-backed batch workloads where occasional latency is fine, leave min-instances at 0.
Cold-start mitigations beyond min-instances
Cold starts are also reduced by (a) using lightweight runtimes (Go and Node.js are fastest, Java and .NET slowest), (b) initializing heavy objects like database pools at module scope so they survive across invocations on the same warm instance, and (c) for CF2 specifically, enabling startup CPU boost via --cpu-boost to get extra CPU during initialization.
Supported Runtimes and the Buildpack Model
Cloud Functions supports a curated set of language runtimes, each maintained on a deprecation schedule.
Runtime list
As of the current platform, supported runtimes include Node.js (18, 20, 22), Python (3.10, 3.11, 3.12, 3.13), Go (1.21, 1.22, 1.23), Java (11, 17, 21), Ruby (3.0, 3.2, 3.3), PHP (8.1, 8.2, 8.3), and .NET (6, 8). Each runtime has a deprecation date roughly aligned with the upstream community support window; deploying with a deprecated runtime works but blocks new deployments after the end-of-life date.
Buildpacks under the hood
Both generations use Google Cloud Buildpacks to convert your source directory into an OCI container. You provide source code and a manifest file (package.json, requirements.txt, go.mod, pom.xml, etc.); the buildpack detects the runtime, installs dependencies, and bakes in the Functions Framework that wires your entry point to the HTTP server or CloudEvents listener. In 2nd gen, the resulting container is the exact image Cloud Run runs—you can pull it with gcloud artifacts docker images list and even deploy it directly to Cloud Run if you want to bypass the Functions abstraction.
Custom dependencies and native binaries
Each runtime has standard ways to pull dependencies (npm, pip, Go modules, Maven). For native binaries or system packages, 1st gen is restrictive—you have very limited control. 2nd gen, because it produces a Cloud Run container, allows much more flexibility, including --source pointing to a Dockerfile path or using Cloud Build customizations.
Secret Management: --set-secrets vs Secret Manager IAM
Hardcoding secrets in code or environment variables is a top exam trap. Cloud Functions has first-class integration with Secret Manager.
Mount secrets at deploy time
The recommended pattern is --set-secrets="DB_PASSWORD=projects/PROJECT/secrets/db-password:latest". At cold-start, the platform fetches the secret value and exposes it as the DB_PASSWORD environment variable to your function. The :latest alias pulls whatever version is current; you can also pin a specific version like :3 for rollback safety. Use --set-secrets="/app/secrets/key=projects/PROJECT/secrets/api-key:latest" to mount the secret as a file instead of an env var, which is preferred for multi-line or binary secrets like PEM keys.
IAM is non-negotiable
The function's runtime service account—[email protected] by default, or a custom service account via --service-account—must hold roles/secretmanager.secretAccessor on each referenced secret. Without it, the deployment succeeds but the function's cold start fails with a permissions error that the platform surfaces in the first invocation.
Always assign a dedicated service account to each function with [email protected] rather than relying on the default Compute Engine service account. The default account has broad Editor permissions on the entire project, violating least privilege. The exam treats this as a near-mandatory pattern for any "secure production" scenario.
Rotation pattern
Secret Manager rotation is one-sided: rotating a secret creates a new version, but warm function instances continue to read the old value from their already-injected environment variable. To pick up rotations promptly, either redeploy on rotation (via a Pub/Sub-triggered Cloud Build) or fetch the secret dynamically inside the function with the Secret Manager client library, paying a small per-invocation cost.
VPC Egress: Connectors and Direct VPC
By default, Cloud Functions runs in a Google-managed network with full public internet egress and zero access to your VPC. Many real workloads need to reach private resources—Cloud SQL with private IP, on-prem services via Cloud VPN, or internal load balancers—which requires VPC egress configuration.
Serverless VPC Access Connector
The legacy path is a Serverless VPC Access Connector, a managed pool of f1-micro-class instances in a /28 subnet that proxies traffic from your function into your VPC. You create the connector once with gcloud compute networks vpc-access connectors create, then attach it to functions with --vpc-connector=my-connector and choose egress scope via --egress-settings=private-ranges-only (only RFC1918 destinations) or --egress-settings=all-traffic (route all egress through the VPC, useful for NAT-controlled outbound IPs). Connectors cost money 24/7 whether functions are invoked or not and have throughput limits per instance.
Direct VPC egress (2nd gen)
Cloud Functions 2nd gen supports Direct VPC egress, where the function's underlying Cloud Run instance attaches directly to a VPC subnet without a connector. Configure with --network=my-vpc --subnet=my-subnet. Direct VPC eliminates the connector cost, removes the throughput bottleneck, and reduces latency by skipping the proxy hop. The trade-off is higher cold-start latency on the first connection per instance because the platform must wire up the network interface, and a one-IP-per-instance address consumption pattern that demands careful subnet sizing.
For new 2nd gen functions, prefer Direct VPC egress over connectors. Connectors are now legacy. Reserve connectors only for 1st gen functions or when you have an existing connector serving multiple workloads and adding new functions to it is operationally simpler than re-architecting.
Retry Behavior and Dead-Letter Queues
Reliability semantics differ sharply between HTTP and event-driven functions, and this is heavy exam territory.
HTTP retries (none built-in)
HTTP functions do not retry automatically. If your handler returns 5xx, that error propagates to the caller, who decides whether to retry. This means HTTP clients are responsible for their own retry policies—exponential backoff, jitter, circuit breakers—using something like Cloud Tasks or a client SDK retry policy.
Event-driven retries
Pub/Sub-triggered and Eventarc-triggered functions retry on failure when retry is enabled. For 2nd gen Pub/Sub functions, retries are governed by the underlying Pub/Sub subscription's ackDeadline (default 10s, max 600s) and retryPolicy (minimum and maximum backoff, default 10 seconds to 600 seconds). For 1st gen Pub/Sub functions, deploy with --retry to enable automatic retries within a 7-day window.
Dead-letter topics
To stop infinite retry loops, configure a dead-letter topic on the Pub/Sub subscription. After N delivery attempts (configurable from 5 to 100, default 5), the message is forwarded to the dead-letter topic where you can inspect, alert, or replay. This requires roles/pubsub.publisher on the dead-letter topic for the Pub/Sub service account [email protected].
Without a dead-letter topic and without retry caps, a poison-pill message (one that always causes the handler to crash) creates an infinite billed retry loop until the message expires after the subscription's messageRetentionDuration (max 7 days). This is a frequent "runaway cost" scenario answer—the fix is always "add a dead-letter topic and retry cap."
Memory, CPU, and Timeout Configuration
The resource and timeout knobs directly determine performance, cost, and what kinds of work the function can do.
Memory tiers
1st gen offers 128, 256, 512, 1024, 2048, 4096, and 8192 MB tiers, with CPU implicitly scaled with memory. 2nd gen, inheriting Cloud Run, allows continuous memory configuration up to 32 GiB and explicit --cpu=1 through --cpu=8. Higher memory tiers cost more per GB-second but finish faster, and finishing faster often nets a lower total bill—measure before optimizing.
Timeouts
1st gen HTTP and event-driven functions both cap at 540 seconds (9 minutes). 2nd gen HTTP functions can run up to 3600 seconds (60 minutes), the same as Cloud Run; 2nd gen event-driven functions still cap at 540 seconds because Eventarc and Pub/Sub push delivery have their own deadlines. Set the timeout to a realistic upper bound for your function's work; the platform terminates with SIGKILL on timeout, killing in-flight work without flushing buffers.
Choosing CPU explicitly
For CF2, --cpu=2 or higher is the right knob for CPU-bound workloads like image processing, ML inference, or compression. CPU is allocated per request when concurrency is 1 and shared when concurrency is greater than 1. For sub-second latency targets, also use --cpu-boost to get extra CPU during the first 10 seconds of every instance's life.
白話文解釋(Plain English Explanation)
Analogy 1: The Light Switch and the Smart Bulb
1st gen Cloud Functions is an old-fashioned light switch with one wire. You flip it, the bulb lights, you flip it off, it goes dark. One switch, one bulb, one action at a time. 2nd gen Cloud Functions is a smart bulb on a mesh network. The same physical fixture (your function code) can respond to dozens of triggers—voice commands, motion sensors, scheduled timers, app taps—because there is a router (Eventarc) translating each signal into a common protocol (CloudEvents) before the bulb sees it. And the bulb can dim, change color, and handle multiple commands simultaneously (concurrency).
Analogy 2: The Security Guard with a Walkie-Talkie
Imagine a security guard sitting in a booth. In the 1st gen world, when the alarm rings, one guard goes to investigate that one alarm. If five alarms ring at once, five separate guards have to be hired and sent out. In the 2nd gen world, the guard wears a walkie-talkie (concurrency setting) and can coordinate responses to up to 1000 alarms while sitting in the same booth—as long as those alarms are calls to other people rather than physical sprints. Pub/Sub is the dispatch radio, Eventarc is the universal translator that takes alarms from any building's wiring system, and Secret Manager is the locked drawer where the master keys live.
Analogy 3: The Restaurant Kitchen
Cloud Functions is a kitchen that opens only when an order ticket prints. The runtime (Node.js, Python, Go) is the cuisine—each kitchen specializes in one. The trigger is the ticket printer: HTTP tickets come from walk-ins (web requests), Pub/Sub tickets come from the delivery app, GCS tickets fire whenever a supplier drops off a crate of ingredients, Audit Log tickets ring when the health inspector signs paperwork. The timeout is how long the kitchen is allowed to cook a single order before management throws it out: 9 minutes in the old kitchen, an hour in the new one. Min-instances are the line cooks you keep on payroll even during slow hours so the first order does not wait for someone to clock in.
Frequently Asked Questions
Q1: What is the difference between a background function and an HTTP function?
HTTP functions are triggered by direct HTTPS requests and return a synchronous response to the caller. Background (event-driven) functions are triggered asynchronously by events from Pub/Sub, Cloud Storage, Firestore, Eventarc, or Cloud Audit Logs. In 2nd gen, background functions receive a CloudEvents v1.0 envelope, while HTTP functions receive a standard HTTP request object. IAM also differs: HTTP functions are gated by roles/run.invoker on the underlying Cloud Run service, while event-driven functions are invoked by the Eventarc service account.
Q2: How do I handle function failures and retries?
For event-driven functions, enable automatic retries (--retry in 1st gen, or via the subscription's retry policy in 2nd gen Pub/Sub) and design every handler to be idempotent—use the event's id to deduplicate, or write through a transactional store. Always configure a dead-letter topic with a max delivery attempt count (5-100) so poison messages are quarantined rather than retried forever. For HTTP functions, retries are the caller's responsibility; use Cloud Tasks for orchestrated, retried HTTP invocations.
Q3: When should I choose Cloud Functions vs Cloud Run vs App Engine?
Choose Cloud Functions when you have a single event-handling function with no HTTP routing, no custom container, and a short-to-medium runtime. Choose Cloud Run when you need full Docker container control, multi-route HTTP services, or already-containerized workloads—keeping in mind that CF2 is itself Cloud Run under the hood. Choose App Engine Standard when you want a managed framework experience and tight integration with App Engine-specific features like Memorystore-backed sessions. For most new serverless workloads, the realistic choice is between CF2 (for trigger-driven simplicity) and Cloud Run (for everything else).
Q4: How do I keep my function's startup latency low?
Use --min-instances=1 (or more) to keep warm instances ready. Pick a lightweight runtime—Go and Node.js have sub-second cold starts, Java and .NET often exceed 5 seconds. Initialize heavy objects (database pools, ML model loads) at module scope rather than inside the request handler. In CF2, add --cpu-boost to get extra CPU during initialization. If you cannot meet your latency SLO with these levers, the function is the wrong abstraction—use Cloud Run with --min-instances and --cpu-always-allocated for true low-latency steady-state behavior.
Q5: How do I securely connect a function to a private Cloud SQL or Memorystore instance?
Cloud SQL with private IP and Memorystore require VPC egress. In CF2, use Direct VPC egress (--network and --subnet) to attach the function directly to a VPC subnet. In CF1, attach a Serverless VPC Access Connector (--vpc-connector). For Cloud SQL, you can also use the Cloud SQL Auth Proxy as a sidecar pattern by using the connector library inline in your function. For all paths, ensure the function's service account has the right IAM (roles/cloudsql.client for Cloud SQL) and that secrets like database passwords come from Secret Manager via --set-secrets, never hardcoded.
Q6: What happens if my function exceeds its memory or timeout limit?
Memory exhaustion causes the function instance to be killed with an out-of-memory error; the platform returns a 500 to HTTP callers or signals failure to event-driven sources (triggering retry if enabled). Timeout exhaustion sends SIGKILL after the configured timeout (max 540s for 1st gen, 3600s for HTTP in 2nd gen, 540s for event-driven in 2nd gen) and the in-flight request fails. For long-running work that cannot be split, choose Cloud Run with up to 3600s timeouts and --cpu-always-allocated for background processing, or use Workflows / Cloud Tasks to orchestrate a sequence of short function invocations.