50 real GCP interview questions with detailed answers on IAM, VPC networking, GKE, BigQuery and Cloud Run — grouped by difficulty.
A Google Cloud project is the container that every resource — VMs, buckets, databases, service accounts, APIs — lives inside; nothing in GCP exists outside of a project.
This matters because the project is GCP's real security and billing boundary, not the organization or a folder. IAM policies, enabled APIs, and quotas are all attached at the project level by default, so "which project is this resource in" is usually the first question worth asking when something is unreachable or a bill looks wrong. A project has three identifiers: a project ID (globally unique, immutable, used in gcloud commands and URLs), a project number (auto-assigned), and a display name (editable, not unique).
Reference: Creating and managing projects
Compute Engine gives you raw virtual machines you configure and manage yourself; App Engine is a fully managed platform where you deploy application code and Google handles the servers, scaling, and patching.
Choose Compute Engine when you need control over the OS, custom drivers, specific kernel versions, or licensing — basically anything that requires you to touch the machine. Choose App Engine when you have a standard web app or API in a supported language runtime and don't want to think about infrastructure at all; it handles traffic splitting, versioning, and automatic scaling for you. The trade-off is flexibility versus operational overhead — App Engine Standard in particular has real constraints (limited runtimes, no arbitrary binaries, sandboxed execution) that push complex workloads toward Compute Engine, containers, or GKE instead.
Reference: Compute Engine documentation
A bucket is the top-level container you create in Cloud Storage to hold objects (files); every object must belong to exactly one bucket, and bucket names are globally unique across all of GCP, not just your project.
The location type you pick at creation time decides where the data lives: a region stores data in a single geographic area for the lowest latency to nearby compute, a dual-region replicates across two specific regions for resilience with still-decent latency, and a multi-region spreads data across a large geographic area (like all of the US) for maximum availability. Location can't be changed after creation — moving a bucket's data to a different location means creating a new bucket and copying objects over — so this is a decision worth getting right up front, usually by putting the bucket in the same region as the compute that reads from it most.
Reference: Bucket locations
BigQuery is Google Cloud's fully managed, serverless data warehouse built for running fast SQL analytics over very large datasets without provisioning any servers.
Its defining architectural choice is that storage and compute are separated: your data sits in Google's distributed columnar storage, and queries spin up compute ("slots") only for the duration of the query. That's why it scales to scanning terabytes in seconds and why cost is usually driven by how much data a query scans, not by how much hardware you rented. It's the tool teams reach for over a traditional relational database when the workload is analytical (aggregating over huge tables) rather than transactional (many small reads/writes with strict consistency needs).
Reference: BigQuery overview
A VPC (Virtual Private Cloud) in GCP is a private, software-defined network that gives your resources isolated IP space, routing, and firewall rules — the same basic idea as any cloud VPC.
The detail that trips people up coming from AWS is scope: a GCP VPC is a global resource, and its subnets are regional, not zonal. That means one VPC can have subnets in multiple regions worldwide with no VPN or peering required to route between them privately — in AWS, by contrast, a VPC is confined to a single region and connecting regions needs peering or Transit Gateway. This global-by-default model is why GCP networking questions often hinge on "which region is the subnet in," not "which VPC."
Reference: VPC network overview
Owner, Editor, and Viewer are IAM's original "basic roles," granting broad permissions across nearly every service in a project — Viewer can read everything, Editor can read and modify everything, Owner can additionally manage IAM and billing.
They get asked about because they're a well-known anti-pattern: granting Editor to a person or service account gives it write access to dozens of services it will never touch, which fails the principle of least privilege and makes audits meaningless. The expected answer is that production access should use predefined roles (Google-curated, scoped to one service, like roles/bigquery.dataViewer) or custom roles (a hand-picked set of permissions for a specific job), with basic roles reserved for personal sandbox projects or genuine full-project ownership.
Reference: Understanding roles
GKE is Google's managed Kubernetes service — Google runs and patches the control plane (the API server, etcd, scheduler) for you, and you're responsible for the worker nodes (in Standard mode) or not even that (in Autopilot mode).
It exists because running Kubernetes yourself means operating a distributed system on top of a distributed system: upgrading the control plane, securing etcd, and handling node failures are all non-trivial. GKE takes the control plane off your plate entirely and, in Autopilot, takes node management off your plate too, billing per pod resource request instead of per VM. It's the default choice when a team already thinks in Kubernetes primitives — Deployments, Services, CRDs — rather than wanting to adopt Kubernetes for its own sake.
Reference: GKE overview
Cloud Run is Google's fully managed serverless platform for running containers — you give it a container image, and it handles scaling, load balancing, and infrastructure, including scaling all the way down to zero instances when there's no traffic.
Unlike Cloud Functions, which deploys a single function from source, Cloud Run deploys a whole container, so it can run any language, any framework, and any number of routes inside one service. It sits between Cloud Functions (simpler, event-driven, one entry point) and GKE (full Kubernetes control) on the abstraction spectrum: you get container flexibility without cluster management.
Reference: Cloud Run overview
A service account is an identity meant for a workload rather than a human — an application, a VM, or a CI/CD pipeline authenticates as a service account to call GCP APIs, instead of a person typing in a password.
It's a hybrid of two things: it's both an identity (something IAM roles can be granted to) and a resource (something IAM roles can be granted on, controlling who can impersonate it). Every service account has an email-like identifier and can be attached to compute resources or used to generate short-lived credentials. Attaching a service account with only the permissions a workload actually needs — instead of using personal credentials or long-lived keys — is the standard way automation gets access in GCP.
Reference: Service accounts overview
A region is a specific geographic area (like us-central1), and a zone is an isolated deployment area within that region (like us-central1-a) with its own power, cooling, and networking.
Every region has at least three zones, and zones within a region are connected by low-latency links but are engineered to fail independently — a power outage in one zone shouldn't take down another zone in the same region. This is why high availability in GCP usually means spreading resources across multiple zones within a region at minimum, and across multiple regions for disaster recovery from a region-level event (which is rare, but not impossible).
Reference: Regions and zones
Cloud SQL is GCP's fully managed relational database service, supporting MySQL, PostgreSQL, and SQL Server, where Google handles patching, backups, replication, and failover.
It's the go-to choice when an application needs a traditional relational database and the team doesn't want to run and patch database servers by hand. It supports read replicas for scaling reads and high-availability configurations that fail over to a standby in a different zone automatically. The main thing to know for an interview is what it isn't: it doesn't scale writes horizontally the way Cloud Spanner does, so past a certain single-instance write throughput, Spanner (or a re-architected sharding strategy) becomes the answer instead.
Reference: Cloud SQL overview
Cloud CDN caches content at Google's edge locations close to users, while a load balancer distributes incoming requests across your backend instances — they solve different problems and are normally used together, not as alternatives.
A load balancer's job is routing and health checking: deciding which healthy backend gets a given request. Cloud CDN's job is avoiding the backend entirely for content that hasn't changed, by serving a cached copy from an edge point of presence near the user. In GCP, Cloud CDN is actually enabled as a feature on a global external Application Load Balancer's backend service, not run as a separate standalone product, which is why the two show up together in architecture diagrams. The practical distinction worth naming: CDN helps with static or slow-changing content (images, JS bundles, cacheable API responses), while the load balancer is doing work on every single request regardless of whether CDN serves it, including all dynamic, uncacheable traffic.
Reference: Cloud CDN overview
Cloud Storage is object storage accessed over HTTP(S) APIs for storing and retrieving whole files (objects) with no filesystem semantics; Persistent Disk is block storage that attaches to a Compute Engine VM or GKE node and behaves like a regular disk the OS can format and mount.
The distinction that actually matters in an interview is access pattern and durability model. Cloud Storage objects are immutable once written (you replace the whole object, you don't edit part of it in place), can be accessed from anywhere with the right credentials, and scale to essentially unlimited size with 11 nines of durability built in. Persistent Disk behaves like a local drive — the OS can write to arbitrary byte offsets, mount a filesystem on it, and it's tied to a specific VM (or, for regional PDs, replicated synchronously across two zones) rather than being globally reachable. A common mistake is reaching for Persistent Disk to store files an application should be treating as objects (uploads, generated reports, backups) purely out of familiarity with "a disk," when Cloud Storage is both cheaper and the architecturally correct choice for that access pattern.
Reference: Persistent Disk overview · Cloud Storage overview
Choose Cloud Functions for small, single-purpose, event-triggered code — a webhook handler, an image-resize job, a Pub/Sub message processor — and Cloud Run when you have (or want to package) a full containerized application or API with multiple routes and custom dependencies.
The mechanical difference is what you deploy: Cloud Functions takes source code plus a dependency file and Google builds the container for you behind the scenes, with one function as the single entry point. Cloud Run takes a container image you build yourself, which can run any language or framework and expose as many routes as you like. Both scale to zero and both can respond to HTTP requests or Eventarc/Pub/Sub events, so the deciding factor in practice is usually team habits: if your team already thinks in Docker, Cloud Run fits the workflow better; if you want to skip containers entirely for a small piece of glue code, Cloud Functions removes that step. Cold starts also tend to be a bit faster on Cloud Functions because the runtime is lighter weight, though this gap has narrowed.
Reference: Cloud Run vs Cloud Functions
It comes down to how much infrastructure control you need versus how much you want Google to manage for you — the five options sit on a spectrum from "you manage everything" to "you manage nothing."
| Option | Choose it when... |
|---|---|
| Compute Engine | You need OS-level control, custom drivers, or specific licensing/performance tuning. |
| GKE | You're already using Kubernetes, need multi-container pods, a service mesh, or complex orchestration across many services. |
| Cloud Run | You have a stateless containerized service or API and want serverless scaling, including scale-to-zero, without running a cluster. |
| App Engine | You want a batteries-included PaaS with versioning and traffic splitting and don't want to build a container at all. |
| Cloud Functions | You need a small piece of code that reacts to one kind of event and doesn't hold state between calls. |
The common trap answer is picking GKE by default because it's the most "serious" option — in practice, most new stateless services should start on Cloud Run, since it's the simplest path that still gives container flexibility, and teams only move to GKE once they hit a concrete Kubernetes-specific need (StatefulSets, DaemonSets, custom schedulers, a service mesh).
Reference: Choosing a compute option
Predefined roles are curated by Google for a specific service — like roles/pubsub.publisher or roles/storage.objectViewer — bundling exactly the permissions that job needs, and they're updated automatically as Google adds new permissions to that role's purpose. Custom roles are ones you build yourself from a list of individual permissions, giving you exact control but making you responsible for maintaining them.
The usual guidance is to reach for a predefined role first, because it's maintained for you and covers the common case. You write a custom role when the closest predefined role is either too broad (it includes a permission you don't want to grant) or too narrow (it's missing one permission you need, and the next role up grants ten more you don't want). A common example: a predefined "viewer" role for a service might include the ability to list resources across the whole project, when you only want to view resources tagged for one team — a custom role solves that gap, at the cost of you now owning its upkeep as the underlying API surface evolves.
Reference: Creating and managing custom roles
GCP's resource hierarchy is Organization → Folders (optional, nestable) → Projects → individual resources, and it exists so that access and policy can be set once near the top and apply everywhere below, instead of being configured resource by resource.
An IAM policy set at the organization level applies to every folder and project underneath it; a policy set on a folder applies to every project inside that folder; a policy set on a project applies to every resource inside it. Crucially, this inheritance is additive only — a more specific level can grant additional access, but it cannot revoke a permission granted higher up. So if someone has Viewer at the org level, you cannot take that away at the project level; you can only add more roles at the project level, not subtract. This is a favorite interview trap because people assume IAM works like a firewall with allow/deny at each layer, when in practice the fix for "this user has too much access at a lower level" is almost always to narrow the grant at the higher level, or restructure the hierarchy — org policies (a separate mechanism) can enforce constraints, but IAM role bindings themselves only add up.
Reference: Resource hierarchy
Shared VPC lets multiple projects share a single VPC network owned by a designated host project, so resources in service projects deploy into subnets they don't own but can use as their own; VPC Peering instead connects two separate, independently-owned VPCs so they can talk over internal IPs while staying fully separate networks.
Shared VPC is the better fit when a central network or platform team wants to manage IP ranges, firewall rules, and routes once for many teams' projects — it gives centralized control and transitive connectivity (if A and B are both service projects on the same host, they can reach each other). It requires all participating projects to be in the same organization. VPC Peering is the better fit for connecting independently-managed networks, including across organizations — for instance, connecting your VPC to a SaaS vendor's VPC, or to a VPC in a company you're integrating with after an acquisition. The trade-off to know cold: peering is not transitive (if A peers with B and B peers with C, A cannot reach C through B), while Shared VPC's single flat network doesn't have that limitation.
Reference: Shared VPC overview
GCP's load balancers split along two axes: global vs. regional and external vs. internal, and the right one depends on where your traffic originates and what layer you need to balance at.
The question an interviewer is really testing is whether you know it's not "one load balancer" like a traditional on-prem appliance — you pick based on protocol (HTTP vs. arbitrary TCP/UDP), scope (does traffic need to span regions), and whether traffic is public or internal-only.
Reference: Choosing a load balancer
BigQuery is a serverless analytical data warehouse built for scanning huge datasets with SQL; Cloud SQL is a managed transactional relational database built for an application's day-to-day reads and writes.
The underlying difference is workload shape. BigQuery uses columnar storage and a massively parallel query engine, which makes it excellent at "aggregate this column across a billion rows" but a poor fit for "update this one row and read it back immediately" — it isn't designed for the frequent small transactions an application backend generates, and per-query costs make many tiny queries expensive. Cloud SQL is row-oriented, supports ACID transactions, indexes, and foreign keys the way a normal application database does, but doesn't scale to BigQuery's analytical throughput. In practice, teams use Cloud SQL as the system of record for an app and periodically export or stream that data into BigQuery (often via Datastream or Dataflow) to run analytics without hammering the production database.
Reference: When to use BigQuery
Object Versioning keeps prior versions of an object every time it's overwritten or deleted, instead of the change being destructive, and lifecycle management lets you set rules that automatically transition or delete objects based on age or other conditions.
With versioning enabled on a bucket, deleting an object just marks the current version as "noncurrent" rather than removing the data, so you can recover from an accidental overwrite or delete. That protection has a cost, though: every version is billed as its own object, so buckets with versioning left on indefinitely can silently accumulate storage cost. Lifecycle rules are the usual fix — for example, a rule that moves objects to Nearline storage after 30 days, Coldline after 90, and permanently deletes noncurrent versions after 365 days. Getting this pairing right (versioning for safety, lifecycle rules to bound the cost) is a common practical question because teams often turn on versioning and forget the second half.
# Example lifecycle rule: delete noncurrent versions after 30 daysrule: - action: type: Delete condition: isLive: false daysSinceNoncurrentTime: 30Reference: Object Versioning · Lifecycle management
Cloud Storage has four storage classes — Standard, Nearline, Coldline, and Archive — that trade a lower storage price for a higher retrieval cost and a longer minimum storage duration, and the right one depends on how often you expect to read the data.
Standard is for data accessed frequently (hot data, active websites, data being actively processed). Nearline suits data accessed less than once a month, like monthly backups. Coldline fits data accessed roughly once a quarter or less, such as disaster-recovery data. Archive is for data you almost never touch — long-term compliance retention — where retrieval can take longer and cost more, but storage is cheapest. All four classes have the same latency and durability; the only differences are price and minimum storage duration. The practical mistake to call out is picking a class based on how important the data is rather than how often it's read — Archive is fine for critical data you'll never read, and Standard can be wasteful for unimportant data you read constantly. Bucket-level or per-object lifecycle rules can automate the downgrade over time instead of requiring a manual choice up front.
Reference: Storage classes
Workload Identity lets a Kubernetes pod authenticate to Google Cloud APIs as a Google service account without ever holding a downloadable key file — it binds a Kubernetes Service Account (KSA) to a Google Service Account (GSA), and the pod's requests are transparently exchanged for short-lived GSA credentials.
The alternative it replaces — mounting a downloaded JSON key file into a pod, or attaching a service account key as a Kubernetes secret — creates a long-lived credential that can leak through logs, container images, or a compromised pod, and has no automatic rotation. Workload Identity's credentials are short-lived, scoped to the specific KSA-to-GSA binding, and never touch disk as a static file, which removes an entire class of key-leak incidents. This is one of the most common "why does this matter" security questions in a GKE interview: the honest trap answer is "just mount a key file, it's simpler," and the strong answer is naming the specific risk (long-lived, unrotated, exfiltratable credentials) that Workload Identity is designed to close.
Reference: Workload Identity
Spot VMs are spare Compute Engine capacity sold at a steep discount (up to ~60-91% off on-demand pricing) that Google can reclaim with very short notice whenever it needs that capacity back for on-demand customers.
They're appropriate for workloads that are stateless, fault-tolerant, and cheap to restart or reschedule — batch jobs, CI/CD runners, rendering pipelines, and fault-tolerant parts of a GKE cluster (like a node pool running non-critical or replicated services). They are not appropriate for anything that can't tolerate an abrupt interruption without data loss — a single-replica database, a long-running job with no checkpointing, or anything holding user-facing sessions with no failover. In GKE, the usual pattern is a dedicated Spot node pool with a taint, and only workloads with a matching toleration (ones you've explicitly decided can handle preemption) get scheduled there, so a stateful workload never accidentally lands on ephemeral capacity.
Reference: Spot VMs
Ingress rules control traffic coming into a resource, egress rules control traffic going out of it, and by default every GCP VPC has an implied rule that allows all egress and denies all ingress from outside the network.
That default-deny-ingress posture is intentional — nothing is reachable from outside until you explicitly write an allow rule for it, which is the opposite of some on-prem setups where "allow everything, then lock down" is the norm. Rules are evaluated by priority (lower number wins), and you can target them at specific instances by network tag or service account rather than applying them network-wide, which is the recommended way to scope access tightly (for example, an allow rule that only applies to instances tagged web-server, rather than every VM in the VPC). A common interview trap: people assume you also need to explicitly allow egress for outbound-only workloads, when in fact you'd only add an egress deny rule if you specifically want to restrict outbound traffic, since it's open by default.
Reference: VPC firewall rules overview
IAM Conditions let you attach a conditional expression to a role binding, so access is only granted when that condition is true — most commonly based on time, resource name/type, or request attributes — instead of a role being either fully granted or not granted at all.
Roles and permissions answer "what can this identity do," but they have no built-in concept of "only during this window" or "only for resources matching this pattern." Conditions fill that gap: a classic example is granting temporary access that expires automatically (request.time < timestamp("2026-12-31T00:00:00Z")), or scoping a role so it only applies to Cloud Storage objects with a particular prefix, rather than the whole bucket. This is worth mentioning in an interview specifically because it shows you know IAM isn't just "assign a role" — conditions are how you avoid creating a narrower custom role just to express a temporary or resource-scoped grant.
Reference: IAM Conditions overview
In GKE Standard, you choose and manage the node pools yourself — machine types, node counts, upgrades, security hardening; in GKE Autopilot, Google manages the nodes entirely and you only specify what your pods need, with billing based on the CPU, memory, and storage your pods actually request rather than on provisioned VM capacity.
Autopilot applies a set of secure-by-default settings automatically (Shielded Nodes, Workload Identity, no privileged pods by default) which removes a whole category of hardening work, and it removes node-level operational tasks like patching and right-sizing nodes. The trade-off is control: Autopilot restricts or disallows some node-level customizations that Standard allows — DaemonSets have restrictions, certain privileged operations aren't permitted, and you can't SSH into nodes. The practical guidance most teams follow: default to Autopilot for typical stateless workloads to cut operational overhead, and drop to Standard when you have a concrete requirement Autopilot doesn't support, like specialized hardware scheduling or node-level agents that need broader privileges.
Reference: GKE Autopilot overview
Cloud Build is GCP's managed CI/CD service that runs a sequence of build steps — each step is a container — defined in a YAML config, typically triggered by a push to a source repository.
A common pipeline looks like: a push to Cloud Source Repositories or a connected GitHub/GitLab repo triggers Cloud Build, which runs tests, builds a container image, pushes it to Artifact Registry, and then deploys it — to Cloud Run, GKE, or App Engine — often gated by a manual approval step or a canary rollout for production. Security-conscious pipelines add Binary Authorization in the deploy step, so only images that passed the pipeline's checks (and are signed accordingly) are allowed to actually run in the cluster.
push to repo → Cloud Build trigger → run tests → build image → push to Artifact Registry → Binary Authorization check → deploy to Cloud Run / GKEReference: Cloud Build overview
Partitioning splits a table into segments based on a column (usually a date or timestamp), and clustering sorts data within those segments by other columns — both exist so that a query can skip scanning data it doesn't need, which directly reduces the bytes billed for the query.
BigQuery's default pricing charges by bytes scanned, not rows returned, so a query against an unpartitioned table with a WHERE date = '2026-09-01' filter still scans the entire table unless BigQuery can prune it. Partitioning by that date column means the query only touches the one day's partition. Clustering goes further within a partition — if you frequently filter or aggregate by, say, customer_id, clustering by that column lets BigQuery skip whole blocks of non-matching rows even inside a single day's data. The interview-worthy nuance: partitioning has a hard limit on the number of partitions per table (4,000 by default) and works best on columns with natural, bounded cardinality like dates, while clustering handles higher-cardinality columns and can be combined with partitioning for compounding savings.
Reference: Partitioned tables · Clustered tables
A Managed Instance Group autoscaler watches a signal you configure — CPU utilization, a Cloud Monitoring metric, load balancing capacity, or a schedule — and adds or removes VM instances from the group to keep that signal near a target you set.
You define a minimum and maximum instance count as guardrails, and the autoscaler works within that range. A key detail interviewers probe is the cooldown period: after a new instance is added, it's excluded from the average metric calculation for a configurable window (default 60 seconds) so the autoscaler doesn't overreact to a not-yet-warmed-up instance's low initial load and immediately scale back down. MIGs also handle self-healing independently of autoscaling — if a health check fails on an instance, the MIG recreates it, which is a separate mechanism from scaling up or down for load.
Reference: Autoscaling groups of instances
Cloud SQL is a managed instance of a traditional relational database (MySQL, PostgreSQL, SQL Server) that scales vertically and via read replicas; Cloud Spanner is Google's globally distributed relational database that scales horizontally for both reads and writes while still offering strong consistency and SQL.
The practical dividing line is write throughput and geographic scale. Cloud SQL's write capacity is bounded by a single primary instance's hardware — you can add read replicas, but writes still go through one node. Spanner shards data across many nodes and regions and still guarantees external consistency across that whole distributed system, which is genuinely hard to build yourself. That capability comes at a real cost: Spanner is pricier, has a steeper schema-design learning curve (interleaved tables, careful primary key choice to avoid hotspotting), and is overkill for an application that will never outgrow a single Cloud SQL instance. The honest interview answer is "use Cloud SQL until you have measured evidence you need Spanner" — reaching for Spanner by default is a common overengineering trap.
Reference: Cloud Spanner overview
Private Google Access lets VM instances that only have internal (private) IP addresses reach Google APIs and services — like Cloud Storage or BigQuery — without needing an external IP or routing through the public internet.
It's needed whenever you've deliberately removed external IPs from your VMs for security reasons (a common hardening step) but those VMs still need to call Google APIs, such as reading from a GCS bucket or writing logs. Without it enabled on the subnet, a VM with no external IP simply can't reach *.googleapis.com at all. It's enabled per-subnet, not per-VM, and it's distinct from Private Service Connect, which is for privately reaching specific published services (including third-party ones) rather than Google APIs generally — mixing those two up is a common small mistake in networking interviews.
Reference: Private Google Access
Synchronous processing means the caller waits for the work to finish before getting a response — like an HTTP API call that does the work inline; asynchronous processing with Pub/Sub means the caller publishes a message and moves on immediately, while one or more subscribers process it independently, on their own schedule.
Pub/Sub decouples the producer from the consumer: the publisher doesn't need to know who's listening, how many subscribers there are, or whether they're currently up, and a slow or temporarily-down subscriber doesn't block the publisher. This is the right shape when work can tolerate some delay, when you want to fan a single event out to multiple independent consumers (say, one subscriber updates a search index while another sends a notification, both off the same event), or when you need to absorb bursts of traffic — messages queue up rather than overwhelming a downstream service. The trade-off is that you give up an immediate response and have to design for eventual, at-least-once delivery: subscribers can receive a message more than once, so processing needs to be idempotent.
Reference: Pub/Sub overview
GCP's observability stack has three main pieces working together: Cloud Logging collects logs, Cloud Monitoring tracks metrics and fires alerts, and Cloud Trace captures distributed request latency — and most managed services (Cloud Run, GKE, App Engine) send data to all three automatically with no extra setup.
In practice you'd set up log-based metrics or alerting policies in Cloud Monitoring for the signals that actually indicate a problem (error rate, latency percentiles, saturation), rather than trying to watch raw logs. Cloud Trace becomes valuable once a request crosses multiple services, since it shows where time is actually being spent across that chain rather than just at one hop. For anything beyond the built-in tools, both Logging and Monitoring export to BigQuery or third-party tools like Grafana or Splunk via sinks, which is the usual answer for "how do you centralize logs from a multi-cloud or hybrid environment."
Reference: Cloud Monitoring overview · Cloud Logging overview
Binary Authorization is a deploy-time security control for GKE and Cloud Run that only allows container images to run if they're signed by an authority you trust — typically proof that the image passed your CI pipeline's build and test steps, or a vulnerability scan.
It protects against a specific failure mode: someone (a compromised credential, a misconfigured pipeline, or a well-meaning engineer under pressure) deploying an image that skipped your normal process — built on a laptop, never scanned, never tested — straight to production. Without it, IAM permission to deploy is the only gate, and IAM says nothing about what's inside the image. With a policy in place, even someone with deploy permissions can't run an unsigned image; the cluster or Cloud Run service enforces the check at admission time and rejects it. It's most useful paired with a CI/CD pipeline that automatically signs images after they pass tests and a vulnerability scan, so the signature becomes proof of "this went through the real pipeline," not just an extra manual step.
Reference: Binary Authorization overview
An external load balancer has a public IP and accepts traffic from the internet; an internal load balancer has a private IP reachable only from within your VPC (or a connected network), and is used purely for service-to-service traffic that should never be internet-facing.
Both come in HTTP(S)/Layer-7 and TCP-UDP/Layer-4 flavors, so "internal vs external" and "Layer 4 vs Layer 7" are two separate, independent choices you make together — for example, an internal Application Load Balancer is a real, common option for routing HTTP traffic between microservices inside a VPC without exposing anything publicly. The reason this comes up in interviews is a real production mistake: accidentally provisioning an external load balancer in front of an internal-only service (like an admin API or a backend database proxy) exposes it to the internet even if you intended it to stay private, so knowing which type you're creating — not just trusting a firewall rule to catch it — matters.
Reference: Load balancing overview
The principle of least privilege means granting an identity only the permissions it actually needs to do its job — nothing more — so that a compromised or misused credential has the smallest possible blast radius; IAM Recommender is GCP's tool for finding where that principle has drifted, by analyzing which permissions a principal has actually used over the past 90 days and suggesting a narrower role that still covers real usage.
In practice, roles tend to accumulate over time — someone gets Editor "just to unblock a task" and it's never revoked, or a service account was granted a broad predefined role early on and the workload only ever uses a fraction of it. IAM Recommender surfaces these gaps automatically instead of requiring a manual audit of every binding, showing exactly which granted permissions were never exercised and proposing a smaller role or a custom role that would have covered the observed activity. The nuance worth mentioning: it's a recommendation, not an automatic enforcement — accepting it can break a workload that uses a permission rarely (say, a monthly batch job), so recommendations should be reviewed against known infrequent usage before being applied, not auto-approved.
Reference: IAM Recommender overview
Cloud NAT lets VM instances that have no external IP address initiate outbound connections to the internet — for things like downloading packages or calling an external API — without exposing them to inbound connections from the internet at all.
A VM with only an internal IP is, by design, unreachable from the outside; that's the whole point of removing the external IP for security. But "unreachable from outside" and "can't reach outside" are two different properties, and outbound-only internet access is often still required — patching the OS, pulling a package, calling a third-party webhook. Cloud NAT provides that one-directional capability: it translates the VM's internal IP to a shared external IP (or pool of them) for outbound traffic only, and never accepts a new inbound connection, so the security posture of "no external IP means unreachable" is preserved while still allowing egress. This is distinct from Private Google Access, which specifically covers reaching Google APIs privately — Cloud NAT is the general-purpose answer for reaching any external destination, Google or not.
Reference: Cloud NAT overview
Start from the cluster autoscaler's own reasoning rather than guessing — gcloud container clusters describe and the autoscaler events (visible via kubectl get events or the GKE console's autoscaler status) will usually tell you exactly why it refuses to remove a node, because scale-down is conservative by design and blocks for well-documented reasons.
The most common causes, roughly in order of likelihood:
PodDisruptionBudget that would be violated by removing them, or pods with the cluster-autoscaler.kubernetes.io/safe-to-evict: "false" annotation will all block a node from being drained.safe-to-evict implicitly false by default configuration, common on nodes running certain DaemonSets or system add-ons.The fix is almost always to address the blocking pod directly — add a controller, loosen or add a PodDisruptionBudget, move local storage to something the autoscaler can safely move, or explicitly mark the pod safe to evict — rather than treating it as an autoscaler bug, since the autoscaler is behaving correctly by refusing to potentially disrupt a workload it can't safely reschedule.
Reference: Cluster autoscaler — scale down
The shape that satisfies "stateless service + relational backend + multi-region HA" on GCP is: a global external Application Load Balancer in front, Cloud Run (or a regional MIG behind a backend service) deployed in at least two regions, and a database layer chosen based on how strict the availability requirement really is.
┌──────────────────────────┐ users ─────► │ Global external HTTP(S) │ │ Load Balancer (anycast) │ └───────────┬───────────────┘ ┌─────────────┴─────────────┐ ┌──────▼──────┐ ┌──────▼──────┐ │ Cloud Run │ │ Cloud Run │ │ us-central1 │ │ europe-west1 │ └──────┬──────┘ └──────┬──────┘ │ │ ┌──────▼─────────────────────────────▼──────┐ │ Database layer (see options below) │ └──────────────────────────────────────────────┘For the database, there's a real trade-off to walk through rather than one right answer:
The load balancer's global anycast IP and health checks route users to the nearest healthy region automatically, and because the app tier is stateless, any region can serve any user — the database choice is really the whole decision here, and the honest answer in an interview is naming the RPO/RTO the business actually needs before picking one, since Spanner solves a problem Cloud SQL replicas genuinely can't, but at a cost many services don't need to pay.
Reference: Cloud Architecture Center — reliability
Start with the INFORMATION_SCHEMA.JOBS view (or the BigQuery Admin resource charts / billing export), sorted by total_bytes_billed, to find which specific queries scanned the most data in the window where the spike happened — a cost spike is almost always a handful of expensive queries, not every query getting slightly worse.
Once you find the offending query or queries, the usual culprits are:
SELECT * against a large table, or a query missing a partition filter on a partitioned table (which forces a full-table scan instead of pruning to one partition) — this is the single most common cause.Once the cause is identified, the fix is usually one of: add a required partition filter to the table (require_partition_filter: true forces every query against it to include one), rewrite the query to select only needed columns, or move a runaway scheduled query onto a materialized view so repeated runs don't re-scan the base data. Longer term, setting a custom cost control — a per-query or per-project bytes-scanned limit — catches this class of incident before it becomes a bill, which is worth mentioning as the preventive half of the answer, not just the reactive fix.
Reference: Control costs in BigQuery
Work through the layers in order, because a "permission denied" on GCS can come from several independent places, not just the IAM role binding you'd check first.
storage.objects.get denied is a genuine IAM/permission problem; a 403 that mentions the organization policy engine, or a 404 where you expected a 403, points somewhere else entirely.gcloud storage buckets get-iam-policy gs://bucket and gcloud projects get-iam-policy. A binding that was on the bucket can be accidentally removed by someone re-running Terraform or gsutil with a stale policy that doesn't include it — check recent changes via Cloud Audit Logs (setIamPolicy events) filtered to that resource, which will show who changed what, when.constraints/iam.allowedPolicyMemberDomains rule that now blocks the service account's identity type, or a VPC Service Controls perimeter that started denying cross-perimeter access. This is a very common cause of "it worked yesterday" because org policies apply org-wide and can be changed by someone with no connection to your pipeline at all.Audit Logs are the fastest way to actually answer "what changed," since checking every layer by hand is slow — searching Admin Activity logs for setIamPolicy or UpdatePolicy events around the time it broke usually points straight at the change.
Reference: Troubleshoot access · Cloud Audit Logs
The honest framing is that this is rarely a pure technology choice — it's a question of what the monolith actually needs that Cloud Run can't give it, because Cloud Run is the lower-effort path when it fits.
Reasons Cloud Run is enough:
Reasons GKE is the real requirement:
The trap answer is defaulting to GKE because it's the "more capable, more serious" platform — for a genuinely stateless HTTP monolith without deep Kubernetes-specific dependencies, that adds real operational burden (cluster upgrades, node management, more moving parts) for capability the app will never use. The stronger answer names the specific requirement (state, multi-container coordination, custom networking, or sustained-traffic cost) that forces GKE, rather than treating it as the default "production-grade" choice.
Reference: Choosing a compute option
A 5-minute RPO means you can afford to lose at most 5 minutes of writes in a disaster, which rules out relying on scheduled backups alone (typically daily, sometimes hourly at best) and requires continuous or near-continuous replication of every transaction.
The mechanism that actually achieves this is cross-region read replicas with continuous replication, combined with Cloud SQL's point-in-time recovery (which requires binary/transaction logging to be enabled). In a real failure:
For an RTO (how fast you're back up) alongside this RPO, you'd also want the application tier already deployed in the DR region (idle or warm), since standing up compute and promoting the database sequentially after a disaster is much slower than having the app tier ready and just repointing it. The honest caveat worth stating out loud in an interview: if the RPO requirement were 0 (zero data loss, ever), Cloud SQL's asynchronous replication can't guarantee that, and the answer becomes Cloud Spanner, which offers synchronous multi-region replication with no data-loss window by design — that's the real line between "Cloud SQL DR is good enough" and "you actually need Spanner."
Reference: Cloud SQL high availability and disaster recovery
Shared VPC alone doesn't isolate firewall rules between teams by default — firewall rules live in the host project and, without further scoping, any Shared VPC Admin on the host can see and edit all of them. The isolation has to come from combining Shared VPC's structure with hierarchical firewall policies and tightly scoped IAM roles, not from Shared VPC's basic mechanics alone.
The design:
roles/compute.networkAdmin for whoever manages the shared network resources (typically the central platform team only), and roles/compute.networkUser granted to each team scoped to their specific subnet, not project-wide — this is the key detail, since networkUser can be bound at the subnet level, meaning Team A's networkUser grant on their subnet gives them zero visibility into Team B's subnet.networkAdmin on the host project, since that role can create, modify, or delete firewall rules and routes affecting everyone; only the central network/platform team holds it.The IAM detail that's easy to miss and worth calling out explicitly: granting compute.networkUser at the subnet level rather than the project level is what actually achieves "team A can deploy into their subnet but can't touch team B's" — granting it project-wide on the host defeats the whole point of Shared VPC's isolation model.
Reference: Shared VPC provisioning · Hierarchical firewall policies
They operate at completely different layers and don't know about each other: VPC firewall rules control traffic at the network layer between VMs (GKE nodes) based on IP, tags, or service accounts, while Kubernetes NetworkPolicies control traffic between pods, enforced inside the cluster's networking layer (on GKE, via Dataplane V2's eBPF-based enforcement, or the older Calico-based enforcement on clusters that predate it).
A VPC firewall rule has no visibility into which pod on a node a packet is destined for — from the VPC's perspective, all pods on a given node share that node's IP for many purposes, so firewall rules can restrict traffic to and from the cluster's nodes (say, blocking all external access to the node pool except through the load balancer) but cannot express "pod A can talk to pod B but not pod C," since that distinction only exists inside the cluster's pod network. That's exactly what NetworkPolicies are for.
You need both, doing different jobs, in most real clusters:
Skipping NetworkPolicies because "the VPC firewall already restricts access" is a common and serious gap: the firewall stops external attackers from reaching the cluster, but does nothing to limit lateral movement between pods once something inside the cluster is compromised — that's a separate threat model, and only NetworkPolicies address it.
Reference: GKE network policy · Dataplane V2
503s under moderate load on Cloud Run almost always mean requests are arriving faster than the service can accept them given its current concurrency and instance settings — not that the service is broken — so the investigation starts with Cloud Run's own scaling and concurrency configuration before looking at application code.
maxInstances. If it's capped too low, Cloud Run simply can't scale out further once demand exceeds what the current instances can handle at their configured concurrency, and incoming requests get rejected with a 503 once the queue backs up. This is the single most common cause and the first thing to check.minInstances and cold starts. If minInstances is 0 and traffic is bursty, a burst can arrive faster than new instances can cold-start (especially for a heavier container image or a runtime with slow initialization), causing requests to queue and time out as 503s during the ramp-up window specifically, then recover once instances catch up. Setting a small minInstances above zero removes the cold-start gap for the baseline traffic level.The pattern that separates this from a genuine application bug: it's load-dependent and intermittent rather than consistent, which points at a scaling/concurrency configuration mismatch rather than broken logic — the fix is almost always in the Cloud Run service's scaling settings, not the code.
Reference: Cloud Run container runtime contract · About instance autoscaling
The core idea for minimal downtime is to do a bulk historical load once, then keep the target continuously in sync via change data capture (CDC) until the cutover moment, rather than a single big-bang export/import that requires the source to be frozen while it runs.
on-prem DB │ ├── (1) Bulk historical export ──► GCS ──► BigQuery load job │ (one-time, can run over hours/days) │ └── (2) Datastream (CDC) ──► ongoing change stream ──► BigQuery (continuous, starts once bulk load begins) ... once (2) has caught the target fully up to the source ... (3) Cutover: pause writes to on-prem briefly, confirm BigQuery is fully caught up, redirect readers to BigQueryConcretely: Datastream is GCP's managed CDC service for exactly this case — it reads the source database's change log (binlog for MySQL, WAL for PostgreSQL, etc.) and streams inserts/updates/deletes into BigQuery continuously, without needing custom polling logic. In parallel, a one-time bulk export (via bq load, Dataflow, or a database-native export to GCS then load into BigQuery) seeds the historical data that predates when the CDC stream started. Once the CDC stream has fully caught up — verified by comparing row counts or checksums between source and target for a sample of tables — the actual cutover becomes a short window: briefly pause writes on the source (or route them read-only), let the last few changes flush through the stream, confirm the two are in sync, and switch consumers over to BigQuery. That downtime window is minutes, not the hours or days a full stop-the-world dump-and-load would take.
The details worth naming explicitly in an interview: CDC requires the source database to have the appropriate logging enabled (binlog format, WAL retention settings) before starting, and schema differences (data types that don't map 1:1 between the source engine and BigQuery) need to be resolved as part of the pipeline design, not discovered during cutover.
Reference: Datastream overview
The structural foundation is separate projects per environment (and often per environment-and-team), organized under folders like dev/, staging/, prod/ — because IAM and Organization Policy both apply at the project or folder level, environment separation has to exist in the resource hierarchy before it can exist in access control.
IAM layer:
dev folder, since velocity matters more than tight control in dev and blast radius is naturally small.staging and prod — in prod, most engineers get read-only or no standing access at all, with elevated access granted through a break-glass process or time-bound IAM Conditions rather than a permanent role binding.dev-engineers@, prod-oncall@) so access changes when someone joins or leaves a team, instead of requiring a manual IAM audit per person.Organization Policy layer (this is the part IAM alone can't do — org policies constrain what's possible, regardless of who has a role):
constraints/iam.disableServiceAccountKeyCreation enforced org-wide (or at minimum in staging/prod) to force Workload Identity Federation instead of long-lived keys.constraints/compute.vmExternalIpAccess restricted in prod to prevent accidentally internet-exposed VMs.The distinction worth stating clearly in an interview: IAM answers "can this identity perform this action," while Organization Policy answers "is this action even allowed to happen in this part of the hierarchy, regardless of who's asking." A prod folder that relies only on IAM to keep engineers from creating public buckets is one misconfigured role binding away from an incident; an org policy constraint blocking public bucket creation in that folder closes the gap even if IAM is misconfigured.
Reference: Organization Policy Service overview · IAM best practices
This set of requirements — encryption in transit, mutual authentication (mTLS), and auditability, all without touching application code — is exactly what a service mesh is for, and on GKE the standard answer is Cloud Service Mesh (Google's managed Istio-based mesh), which handles all three by injecting a sidecar proxy alongside each pod rather than requiring the application itself to implement TLS or auth logic.
Here's why each requirement maps to the mesh rather than something else:
localhost or the service name, unaware that the actual wire traffic between pods is now encrypted.The trade-off worth naming: a service mesh adds real operational complexity and a small amount of latency per hop (the sidecar proxy sits in the request path), so this is the right tool when the combination of all three requirements is genuinely needed — for a single requirement in isolation there are lighter options (NetworkPolicies alone for traffic restriction, or TLS termination at an internal load balancer for encryption alone), but none of those lighter options gets you mutual authentication and per-hop audit logging together without code changes the way a mesh does.
Reference: Cloud Service Mesh overview