Table of Contents#
- Why This Course Exists — GCP Cloud Engineer Foundations and the ACE Exam
- Meet Meridian Logistics — This Course's Running Example
- How GCP's Geography Differs From AWS's and Azure's
- The Project — GCP's Real Unit of Isolation
- The Resource Hierarchy: Organization, Folders, and Projects
- Organization Policies — GCP's Guardrails
- Policy Inheritance — How Rules Flow Down the Hierarchy
- The Architecture Framework and the Shared Responsibility Model
- Labels, Network Tags, and Resource Manager Tags — Three Different Things
- The gcloud CLI, Cloud Shell, and Client Libraries
- APIs and Services — Explicit Enablement Required
- Quotas and Essential Contacts
- Cloud Asset Inventory and Gemini Cloud Assist
- Workforce Identity Federation — A First Look
- A Full Worked Landing Zone Bootstrap
- Real-World Scenario: The Region-Lock Policy That Almost Blocked a Disaster Recovery Test
- Part 1 gcloud Cheat Sheet
- Pre-Flight Checklist: Is a New GCP Environment Actually Ready?
- Common Mistakes and Interview Traps
- Worked Practice Problems
- Summary and What's Next
Why This Course Exists — GCP Cloud Engineer Foundations and the ACE Exam#
This course teaches the operational core of Google Cloud that every other GCP specialization builds on top of. Google Cloud Platform (GCP) is Google's public cloud — compute, storage, networking, and managed data services rented by the hour instead of bought as hardware. Whether you end up doing CI/CD pipelines, network engineering, or security work on GCP, you will spend every working day inside the resource hierarchy, IAM, compute, storage, and networking primitives this course covers.
This is the first of six GCP courses on this site, each aligned to a real Google Cloud certification so the depth you build has an external, verifiable checkpoint:
| Course | Certification | Focus |
|---|---|---|
| 1. GCP Cloud Engineer Foundations (this one) | Associate Cloud Engineer (ACE) | Projects, IAM, compute, storage, networking, day-2 operations |
| 2. GCP DevOps & CI/CD Platform | Professional Cloud DevOps Engineer (PCDE), part 1 | CI/CD pipelines, GitOps, secrets, supply-chain security |
| 3. GCP SRE & Observability | Professional Cloud DevOps Engineer (PCDE), part 2 | SLOs, error budgets, telemetry, FinOps |
| 4. GCP Network Engineering | Professional Cloud Network Engineer (PCNE) | VPC design, hybrid connectivity, network security |
| 5. GCP Security Engineering | Professional Cloud Security Engineer (PCSE) | Identity, perimeters, encryption, detection, compliance |
| 6. GCP Architecture & Design | Professional Cloud Architect (PCA) | End-to-end solution design, migration, operational excellence |
The Associate Cloud Engineer (ACE) exam this course targets is a 2-hour, 50-60 question exam covering four weighted domains: setting up a cloud environment (~20%), planning and implementing a solution (~30%), operating a solution (~30%), and configuring access and security (~20%). This course's eight chapters map directly onto those four domains, expanded far past exam-cram depth into what you actually need to run GCP workloads in production.
Note
If you've already worked through this site's AWS Cloud Architecture series, you'll recognize the shape of a lot of this — a resource hierarchy, an IAM system, compute/storage/network primitives. GCP's underlying decisions are different enough (a genuinely different resource model, a differently-shaped IAM, per-project rather than per-account billing) that porting AWS mental models directly onto GCP is one of the most common ways experienced cloud engineers stumble on their first GCP project. This course calls out those differences explicitly wherever they matter.
🎯 By the end of this chapter, you'll be able to stand up a correctly-structured GCP resource hierarchy — organization, folders, projects, org policies, billing, and labeling — the same foundation a real platform team would lay before any workload goes on top of it.
Meet Meridian Logistics — This Course's Running Example#
Every chapter in this course comes back to the same fictional company so examples build on each other instead of starting from zero each time. Meridian Logistics is a mid-size freight-tracking company: dispatchers plan routes, drivers report GPS pings from a mobile app, and customers track shipments on a public web app. Its platform team is migrating from a single rented data center into GCP, one workload at a time — a shipment-api service (the customer-facing tracking API), a Postgres-backed orders database, a Pub/Sub pipeline ingesting GPS pings from ~4,000 vehicles, and a BigQuery warehouse for route-efficiency analytics.
Meridian's platform team is three people: Priya, the platform lead who owns the resource hierarchy and IAM; Devon, who owns compute and networking; and Ana, who owns data and observability. You'll see all three show up across this series making real, specific decisions — not generic "a team decided" hand-waving.
Meridian's whole system in one picture — keep this shape in mind, since every later chapter attaches new GCP services onto exactly these four pieces rather than inventing a new example each time.
How GCP's Geography Differs From AWS's and Azure's#
GCP's physical infrastructure is organized into regions and zones, the same basic shape as every major cloud, but with real differences in how failure domains and networking are built underneath. A region is an independent geographic area (us-central1, europe-west1); a zone is an isolated location inside a region with its own power, cooling, and networking (us-central1-a, us-central1-b). Resources in different zones of the same region can fail independently but talk to each other over a low-latency network.
The first real difference from AWS shows up immediately: GCP's network is a single global, software-defined network Google built and privately peers across the planet, not a set of regional networks stitched together with the public internet or paid transit. A Virtual Private Cloud (VPC) network in GCP is, by default, global — one VPC can have subnets in every region on Earth, with no VPC peering or transit gateway required to route between them. AWS and Azure both scope a VPC/VNet to a single region; if you're coming from either, expect to unlearn "one VPC per region" as a hard rule (Part 7 of this course covers VPC design in full).
A single VPC is global by default — it isn't scoped to one region the way an AWS VPC or Azure VNet is.
Meridian Logistics runs its dispatch API in us-central1 (close to its Midwest headquarters) with a standby deployment in us-east1, and its GPS-ingestion pipeline reads from Pub/Sub topics that are themselves regionless — a design only possible because the messaging and networking layers underneath aren't boxed into one region.
Tip
Best practice: pick regions based on where your users and compliance requirements actually are, not the cheapest region. Compute pricing varies by region (us-central1 is typically one of the cheapest US regions), but latency to your actual users and data-residency requirements (GDPR data staying in an EU region, say) should decide the primary region first — cost optimization is Part 3's FinOps material, not a reason to put a customer-facing API 2,000 miles from its users.
Choosing a Region: The Real Decision Factors#
Picking a region is rarely a single-factor decision, and treating it as one (usually "pick whichever is cheapest") is how a customer-facing service ends up with avoidable latency for years:
| Factor | Why it matters | Meridian's answer |
|---|---|---|
| User proximity | Round-trip latency is bounded by physics — no amount of compute fixes a customer being 2,000 miles from the nearest region | us-central1 (Iowa) — central to its US customer base |
| Compliance/data residency | Some contracts or regulations require data to stay within a specific jurisdiction | Not yet a factor — revisit if Meridian signs an EU customer |
| Service availability | Not every GCP service launches in every region simultaneously | Confirmed GKE, Cloud Run, Pub/Sub, and BigQuery all GA in both target regions before committing |
| Price tier | Compute/storage/egress pricing varies meaningfully by region | A secondary factor after the above three, not the primary one |
| Disaster-recovery distance | A DR region sharing the same power grid or seismic zone as primary defeats the point | us-east1 chosen specifically for geographic separation from us-central1 |
From the Trenches: The "One VPC, Many Regions" Surprise#
A team migrating from AWS to GCP built what they assumed was a like-for-like network: one VPC per region, mirroring their AWS account's per-region VPCs, then spent a sprint building VPC peering between them before a GCP-native engineer pointed out the peering was solving a problem that didn't exist — a single global VPC with regional subnets would have given them the exact same routing without any peering configuration at all. The deeper cause wasn't ignorance of the feature; it was that the AWS mental model ("a VPC is regional") was strong enough to make them build the workaround before questioning the premise. Whenever you port an architecture between clouds, write down which assumptions are provider-specific facts versus genuinely universal networking truths before you start — the ones that turn out to be provider-specific are exactly the ones that cost a sprint.
The Project — GCP's Real Unit of Isolation#
A GCP project is the base container for every resource you create — every Compute Engine VM, every Cloud Storage bucket, every IAM policy binding lives inside exactly one project. This is a real structural difference from AWS, where an account is the isolation boundary and resources within it share IAM and billing by default. In GCP, a project is simultaneously:
- A billing boundary — a project links to exactly one Cloud Billing account at a time (Part 2 covers billing accounts in depth).
- An IAM boundary — IAM policies attach to a project (or a folder/org above it) and apply to everything inside.
- A quota boundary — most GCP service quotas (API calls, VM cores, IP addresses) are tracked per project.
- An API-enablement boundary — a service's API must be explicitly enabled per project before anything in that project can call it (covered later in this chapter).
Every project has three identifiers, and getting them confused is one of the most common early-GCP mistakes:
| Identifier | Example | Mutable? | Used for |
|---|---|---|---|
| Project ID | meridian-shipment-prod | No, permanent once set | Referenced in almost every gcloud command and API call |
| Project name | "Meridian Shipment — Production" | Yes | Human-readable display label only |
| Project number | 847213590482 | No, auto-assigned | Used internally by some APIs and service-to-service auth |
A project's relationship to billing is worth seeing as a diagram before Part 2 covers it in full — it's a genuinely different shape from how AWS or Azure tie an account/subscription directly to a single payment method:
One billing account can pay for many projects — and a project can be re-linked to a different billing account later, something neither an AWS account nor an Azure subscription lets you do to an already-running set of resources.
# Create a project — the Project ID must be GLOBALLY unique across all of GCP,
# not just within your organization, because it becomes part of resource URLs
gcloud projects create meridian-shipment-prod \
--name="Meridian Shipment — Production" \
--organization=847213590482
# Link the new project to a billing account — a project with no linked billing
# account cannot use any paid service at all, only the always-free tier
gcloud billing projects link meridian-shipment-prod \
--billing-account=012345-6789AB-CDEF01Important
A Project ID is permanent and globally unique the moment it's created — you cannot rename it later, and once released (by deleting the project) it may not become available for reuse for a long time, if ever. Meridian's team learned this the easy way by adopting a naming convention (meridian-<workload>-<env>, e.g. meridian-shipment-prod, meridian-shipment-staging) before creating a single real project, specifically so nobody had to improvise a name under time pressure and regret it permanently.
Why a Single Project for Everything Is Still a Trap#
A new GCP user's first instinct is often to create one project and put everything in it — one dispatch API, one database, one CI/CD pipeline, all sharing the same project. This works for a weekend prototype and becomes a liability within a month for three concrete reasons: IAM blast radius (a contractor granted access to debug the staging database now has API access to the production compute instances in the same project too, because IAM at the project level is all-or-nothing for that project), quota collisions (a load test against staging burns through the same per-project API quota production traffic needs), and an unreadable bill (every cost — compute, storage, network egress — lands on one line with no natural boundary to attribute it to a team or environment).
The fix is environment- and workload-scoped projects, not one mega-project: Meridian ends up with meridian-shipment-prod, meridian-shipment-staging, meridian-shared-logging, and meridian-shared-networking — each with its own IAM policy, its own quota pool, and its own line of billing detail, tied together by the folder hierarchy described next.
Realistic Scenario: The Contractor Incident#
Six weeks into the migration, Meridian brought on a short-term contractor to fix a slow query in the staging Postgres instance. Because everything still lived in one project at the time (meridian-shipment-all), Priya granted the contractor roles/cloudsql.admin — scoped, she thought, to "the database." Cloud SQL's admin role at the project level doesn't distinguish staging from production instances living in the same project, so the grant silently covered the production database too. The contractor never touched production, and nothing went wrong — but the exposure existed for the full three weeks of the engagement, discovered only when Priya ran a Cloud Asset Inventory IAM query ahead of a security review and found a stale grant she couldn't immediately explain.
The fix that came out of the incident was exactly the project split described above: once meridian-shipment-prod and meridian-shipment-staging became separate projects, "grant access to staging only" became a true statement enforceable by IAM at the project boundary, instead of a scoping intention that depended on the contractor's judgment and Priya's memory to hold.
Important
The blast radius of an IAM grant is exactly the resource hierarchy node it's attached to — never the narrower thing you meant when you made the grant. If "staging only" isn't a real project or folder boundary, it isn't a real access boundary either, no matter how carefully the grant is worded in a ticket or a Slack message.
Project Lifecycle: Active, Pending Deletion, and Restore#
A project isn't simply "exists" or "gone" — GCP gives you a 30-day recovery window after deletion before resources are permanently purged, specifically to survive the fat-fingered gcloud projects delete that every platform team eventually runs at least once.
A deleted project isn't gone for 30 days — undelete is the actual emergency-recovery command, not a project restore from a backup.
# Delete a project — resources stop serving almost immediately, but the
# project enters a 30-day PENDING_DELETION state, not permanent deletion
gcloud projects delete meridian-shipment-dev
# Recover a project inside the 30-day window — this is the real "oh no"
# command every platform engineer should know before they need it
gcloud projects undelete meridian-shipment-dev
# Confirm a project's current lifecycle state
gcloud projects describe meridian-shipment-dev --format="value(lifecycleState)"Warning
The 30-day window undoes the project's deletion, but it does not guarantee every resource inside comes back in the same state — some resources with their own independent deletion/retention behavior (a Cloud SQL instance without a final backup, an ephemeral Compute Engine VM's local SSD data) can still be unrecoverable even though the project itself is restorable. Treat undelete as a safety net for the deletion itself, never as a substitute for real backups of the data inside.
The Resource Hierarchy: Organization, Folders, and Projects#
The resource hierarchy is the tree GCP uses to inherit IAM policies and organization policies downward, from one root to every individual resource. It has four levels, from broadest to narrowest:
Every project sits under exactly one folder (or directly under the org); IAM and org policies set at any level flow down to everything beneath it.
- Organization — the root node, automatically created when a company sets up Cloud Identity or Google Workspace, tied to a verified domain (
meridianlogistics.com). Without an organization, projects you create are "orphan" projects with no shared governance layer above them — fine for a personal sandbox, a real liability for a company. - Folders — optional grouping nodes under the organization, and foldable into other folders (folders can nest). Folders exist purely for grouping and policy inheritance; they hold no resources of their own.
- Projects — the isolation boundary described above, sitting under a folder or directly under the org.
- Resources — the actual VMs, buckets, tables, and topics living inside a project.
Terminology Map: The Same Concept, Different Names#
If you're carrying a mental model over from another cloud, the resource hierarchy's individual pieces have direct (if imperfect) equivalents worth pinning down explicitly rather than assuming:
| Concept | GCP | AWS | Azure |
|---|---|---|---|
| Root governance node | Organization | Organization (AWS Organizations) | Tenant (Microsoft Entra ID) |
| Grouping layer | Folder (nestable) | Organizational Unit (nestable) | Management Group (nestable) |
| Isolation/billing boundary | Project | Account | Subscription |
| Guardrail mechanism | Organization Policy | Service Control Policy (SCP) | Azure Policy |
| CLI | gcloud | aws | az |
The mapping breaks down in one important way: an AWS account and an Azure subscription are both closer to "a project's billing boundary plus its own default IAM scope" combined — GCP splits billing (Cloud Billing account, covered in Part 2) and resource isolation (project) into two genuinely separate objects that don't have to have a 1:1 relationship. One Cloud Billing account routinely pays for dozens of projects; there's no equivalent "many accounts, one payer" default shape in the same way on GCP as AWS's payer-account/linked-account model, because the project is the AWS-account-equivalent and billing is deliberately decoupled from it.
# Move an existing project into a folder — reparenting a project doesn't
# touch its resources, only which IAM/org-policy inheritance chain it sits under
gcloud projects move meridian-shipment-staging \
--folder=234567890123
# List every folder directly under the organization
gcloud resource-manager folders list --organization=847213590482Folders — Grouping Projects by Purpose#
Folders can mirror whatever grouping actually matters to how you govern the environment — commonly either by environment (Production / Non-Production / Shared Services, as in Meridian's tree above) or by team/business unit (Platform / Data / Mobile), and sometimes both, nested. There's no universally correct choice; the decision framework below is what actually decides it.
| Grouping strategy | Choose it when... |
|---|---|
| By environment (Prod/Staging/Dev) | Different environments genuinely need different org policies (e.g., production forbids public IPs, dev allows them for quick testing) |
| By team/business unit | Different teams need different IAM admins and cost visibility, but environments share the same policy posture |
| Nested (env inside team, or team inside env) | A large org with both concerns — Meridian is small enough to skip nesting, but a 200-engineer company usually needs it |
Tip
Best practice: design the folder tree around what needs a different org policy or IAM boundary, not around your org chart. A folder that exists only to mirror a reporting structure but carries the exact same policies as its sibling folder adds hierarchy depth with no governance benefit — every extra level is one more place policy inheritance has to be reasoned about during an incident.
Organization Policies — GCP's Guardrails#
An organization policy is a constraint enforced automatically on every resource under whatever node it's attached to — the GCP equivalent of an AWS Service Control Policy (SCP). Unlike IAM (which controls who can do what), org policies control what's allowed to exist or happen at all, regardless of who's asking — even a project owner with full IAM permissions cannot create a resource that violates an active org policy.
# A constraint restricting which regions resources can be created in —
# directly the GCP equivalent of a region-lock SCP
gcloud resource-manager org-policies set-policy region-lock-policy.yaml \
--project=meridian-shipment-prod# region-lock-policy.yaml — restrict Compute Engine resource creation
# to Meridian's two approved US regions
constraint: constraints/gcp.resourceLocations
listPolicy:
allowedValues:
- "in:us-central1-locations"
- "in:us-east1-locations"Common constraints a platform team sets early, before any real workload exists to accidentally violate them:
| Constraint | What it prevents |
|---|---|
constraints/compute.vmExternalIpAccess | VMs from getting a public IP address by default |
constraints/iam.disableServiceAccountKeyCreation | Long-lived service account key files (Part 3 covers why these are risky) |
constraints/gcp.resourceLocations | Resources being created outside approved regions |
constraints/compute.requireOsLogin | SSH access without OS Login's centralized IAM-based auth |
constraints/iam.allowedPolicyMemberDomains | Granting IAM access to accounts outside your verified domain |
Warning
Org policies are not a substitute for IAM and don't get evaluated in isolation from the resource state that already exists. A constraint added after a violating resource already exists doesn't retroactively delete or fix that resource — it only blocks new violations going forward. A real cost leak: a team enabled resourceLocations restriction to us only, congratulated itself, and didn't notice for four months that a stray europe-west1 bucket created before the policy existed kept accumulating storage charges completely unaffected by the new rule.
Custom Constraints — When the Built-In List Isn't Enough#
The constraints shown so far are all managed constraints — predefined by Google and covering the most common guardrails. When a rule is specific to your own organization's requirements and no managed constraint covers it, you can define a custom constraint against almost any resource property GCP exposes:
# custom-constraint.yaml — require every new Compute Engine instance to
# carry a cost-center label, so Meridian's billing export never has an
# unattributed line item
name: organizations/847213590482/customConstraints/custom.requireCostCenterLabel
resourceTypes:
- compute.googleapis.com/Instance
methodTypes:
- CREATE
condition: "resource.labels.exists(l, l == 'cost-center')"
actionType: ALLOW
displayName: Require a cost-center label on every new VM# Register the custom constraint, then enforce it like any managed one
gcloud org-policies set-custom-constraint custom-constraint.yaml
gcloud resource-manager org-policies enable-enforce \
custom.requireCostCenterLabel --organization=847213590482Note
Custom constraints only cover resource types and fields the underlying API actually exposes for policy evaluation — not every property of every resource is available to condition on. Check a service's own org-policy documentation before assuming a custom constraint can enforce an arbitrary rule against it.
Policy Inheritance — How Rules Flow Down the Hierarchy#
A policy set at any level of the resource hierarchy applies to every level beneath it, and a child can only restrict further, never loosen what a parent already restricts — this is the single rule that makes the whole hierarchy predictable. If the organization enforces "no public IPs on VMs," no folder or project beneath it can override that back to "allowed," but a project can add its own additional constraint on top.
Effective policy is always the union of every level in the chain — a project can never grant back something an ancestor denied.
The one deliberate escape hatch is an explicit exception, not a loosening: a policy can be set to allow a specific, named exception at a lower level (e.g., "no public IPs, except this one bastion-host project") — but that exception has to be explicitly authored at the level that needs it, never assumed.
🔍 Investigation tip: when a resource creation fails with an org-policy violation and it's not obvious why,
gcloud resource-manager org-policies describe-effective <constraint> --project=<id>shows the actual effective policy after inheritance is applied — don't guess by reading the project-level policy alone, since the real answer might be enforced two levels up.
A Realistic Multi-Project Resource Hierarchy#
Putting the pieces together, here's Meridian Logistics's actual landing zone, six weeks into their GCP migration:
The Non-Production folder relaxes the public-IP restriction the org enforces everywhere else — an explicit exception scoped to exactly the folder that needs it, not a global loosening.
The Architecture Framework and the Shared Responsibility Model#
Google Cloud's Architecture Framework is GCP's answer to AWS's Well-Architected Framework — a set of five design pillars every workload should be evaluated against before it's called production-ready.
| Pillar | Core question it asks |
|---|---|
| Operational Excellence | Can you deploy, monitor, and recover this system efficiently? |
| Security, Privacy, and Compliance | Is data and access protected to the standard your regulators and customers require? |
| Reliability | Does the system meet its availability target under real failure conditions? |
| Cost Optimization | Are you paying for capacity you actually need, not capacity you provisioned out of caution? |
| Performance Optimization | Does the system meet its latency/throughput target, and can it scale to meet a bigger one? |
This course's later chapters (compute, networking, monitoring) each map concretely onto these pillars rather than treating the framework as an abstract checklist — Course 6 (GCP Architecture & Design) is where evaluating a whole system against all five pillars becomes the main skill being taught.
The shared responsibility model draws the line between what Google secures and what you secure, and — like every cloud provider's version of this — the line moves depending on which compute model you use:
The further left a compute model sits, the more of the "shared" middle layer Google absorbs — Cloud Run leaves you responsible mainly for your own code and data, a raw Compute Engine VM leaves you responsible for OS patching too.
Meridian's shipment-api running on Cloud Run means Devon's team never patches an OS kernel; the GPS-ingestion workers running on a Compute Engine managed instance group (Part 4) mean Devon's team owns OS patching, kernel CVEs, and VM image hygiene for that workload specifically — a real, deliberate cost/control trade-off, not an oversight.
From the Trenches: The Pillar Nobody Prioritized#
Meridian's first architecture review scored well on Reliability and Performance but was never formally checked against Cost Optimization, because nobody on a three-person platform team felt they had time for a fifth pillar during a launch crunch. Three months later, the first real bill showed the GPS-ingestion pipeline's Compute Engine fleet running at roughly 30% average CPU utilization around the clock — sized for a peak load that only happened during two hours of each business day. The immediate cause was static VM sizing; the deeper cause was that Cost Optimization got silently deprioritized as "something to look at later," and "later" only arrived because a bill forced it. The Architecture Framework's five pillars are a checklist precisely because skipping one doesn't cause an immediate failure — it causes a slow, quiet one that only shows up once someone finally looks.
Tip
Best practice: score every pillar during a design review, even the ones that feel like they can wait. A one-line "Cost Optimization: not yet evaluated, revisit before GA" is a legitimate outcome of a review — silently skipping the pillar entirely is not the same thing, because nothing then guarantees anyone ever comes back to it.
Labels, Network Tags, and Resource Manager Tags — Three Different Things#
GCP has three separate tagging-adjacent mechanisms with overlapping names and genuinely different purposes — confusing them is one of the most common early mistakes.
| Mechanism | What it is | Used for | Can it gate an org policy? |
|---|---|---|---|
| Labels | Free-form key: value annotations on almost any resource | Cost attribution, filtering in the console, queryable metadata | No |
| Network tags | Simple string tags on Compute Engine VMs | Targeting firewall rules at a group of VMs | No — firewall rules only |
| Resource Manager tags | Strongly-typed key: value pairs, IAM-controlled who can create/assign them | Conditionally enforcing org policies, fine-grained IAM conditions | Yes |
# Labels — annotate for cost tracking and filtering, not enforcement
gcloud compute instances add-labels gps-worker-1 \
--labels=team=platform,env=prod,cost-center=logistics-ops
# Network tags — target a firewall rule at exactly the VMs that need it
gcloud compute instances add-tags gps-worker-1 --tags=allow-pubsub-egress
# Resource Manager tags — IAM-governed, can gate an org policy or IAM condition
gcloud resource-manager tags keys create environment \
--parent=organizations/847213590482
gcloud resource-manager tags values create production \
--parent=847213590482/environment
gcloud resource-manager tags bindings create \
--tag-value=847213590482/environment/production \
--parent=//cloudresourcemanager.googleapis.com/projects/meridian-shipment-prodTip
Best practice: use labels for cost visibility from day one, on every project. Meridian tags every resource with team, env, and cost-center labels before a single VM boots — retrofitting labels onto an already-running fleet later means a painful audit of "whose is this?" across every resource, usually discovered during the first confusing bill.
From the Trenches: The Firewall Rule That Targeted the Wrong Thing#
Early on, Devon wrote a firewall rule intended to allow the GPS-ingestion workers to reach Pub/Sub, targeting it at instances labeled team: platform — and nothing worked, because firewall rules target network tags, not labels, and he'd conflated the two. The immediate symptom was connection timeouts that looked network-layer; the actual cause was one line of Terraform using labels where the firewall rule resource needed target_tags. The deeper lesson Meridian took from it: because GCP's console UI shows both labels and network tags on the same VM details page, in visually similar chip-style widgets, it's easy to build a mental model where they're interchangeable — they aren't interchangeable anywhere in the API, only in how casually they're described out loud.
Warning
A firewall rule's --target-tags flag matches network tags only. Labels, no matter how descriptive, are silently ignored by firewall targeting — there's no error, the rule simply matches zero instances if every instance in scope only carries labels.
The gcloud CLI, Cloud Shell, and Client Libraries#
gcloud is GCP's command-line interface, and understanding its command shape once means every service's commands become guessable rather than memorized.
# Configure the CLI interactively — sets your default project, region, zone
gcloud init
# Verify current identity — the GCP equivalent of AWS's
# `aws sts get-caller-identity`, worth running before any risky command
gcloud auth list
gcloud config list
# Every gcloud command follows roughly the same shape:
# gcloud <service> <resource> <verb> [flags]
gcloud compute instances list
gcloud storage buckets create gs://meridian-shipment-assets --location=us-central1
gcloud pubsub topics create gps-pingsgcloud isn't the only interface: Cloud Shell gives you a free, browser-based VM with gcloud, kubectl, and most tooling preinstalled — Priya's team uses it for one-off administrative tasks from a locked-down laptop where installing the SDK isn't allowed. Client libraries (Python, Go, Java, Node.js, and more) are how application code — not a human at a terminal — calls GCP APIs; the shipment-api service itself uses the Python client library to publish tracking updates to Pub/Sub, never shelling out to gcloud at runtime.
Which Interface for Which Task?#
Four different ways to talk to GCP exist for four genuinely different jobs — reaching for the wrong one is a common source of "why doesn't this reproduce" friction:
| Interface | Best for | Not great for |
|---|---|---|
| Cloud Console (web UI) | First-time exploration, one-off visual inspection, reading a resource's full detail page | Anything that needs to be repeated identically or reviewed in a pull request |
gcloud CLI | Scripted one-off tasks, quick debugging, anything you'd otherwise screenshot from the console | Managing complex, interdependent infrastructure where drift matters |
| Terraform / Infrastructure as Code | Anything meant to be repeatable, reviewable, and version-controlled — a real environment's baseline | A genuine one-off investigation task with no lasting infrastructure change |
| Client libraries | Application code that needs to call a GCP API as part of its own runtime logic | Human-run administrative tasks — using a client library from a personal script to do what gcloud already does in one line is needless code to maintain |
Meridian's rule of thumb, and a reasonable default for any team: if it changes infrastructure that should still be there next month, it goes through Terraform (Part 2 covers this properly); gcloud is for looking, debugging, and genuine one-offs, not for building anything meant to last.
Named Configurations — Switching Projects Without Losing Track#
gcloud supports multiple named configurations — separate saved sets of project/account/region defaults you switch between explicitly, instead of one global config file everyone quietly overwrites:
# Create one named configuration per environment instead of one shared default
gcloud config configurations create meridian-prod
gcloud config set project meridian-shipment-prod --configuration=meridian-prod
gcloud config set account priya@meridianlogistics.com --configuration=meridian-prod
gcloud config configurations create meridian-dev
gcloud config set project meridian-shipment-dev --configuration=meridian-dev
# Switch between them explicitly — the active configuration name is
# always visible, unlike a single mutable default
gcloud config configurations activate meridian-prod
gcloud config configurations listFrom the Trenches: The Wrong Default Project#
Devon once ran gcloud compute instances delete gps-worker-1 intending to clean up a broken test VM — and deleted the production worker instead, because his terminal's gcloud config default project was still set to meridian-shipment-prod from an unrelated task earlier that morning. The immediate cause was an unattended default project; the deeper cause was that nothing in his shell prompt showed which project was active, so a command that looked identical to the safe version he'd run ten times that day silently ran against production. Meridian's fix was adopting named configurations (one per environment, switched explicitly rather than mutated in place) plus a shell prompt that renders the active configuration's name inline, and a team rule: destructive commands always pass --project= explicitly, never rely on whichever configuration happens to be active.
APIs and Services — Explicit Enablement Required#
Every GCP service's API must be explicitly enabled per project before anything in that project can use it — a real, distinct step from having IAM permission to use the service. A user with the roles/compute.admin IAM role still gets a clear "API not enabled" error trying to create a VM in a project where the Compute Engine API hasn't been turned on.
# Enable the Compute Engine API — without this, EVERY gcloud compute
# command fails with a clear "API not enabled" error, regardless of IAM
gcloud services enable compute.googleapis.com --project=meridian-shipment-prod
# List currently enabled APIs
gcloud services list --enabled --project=meridian-shipment-prod
# Disable an API no longer needed — directly reduces the project's
# overall attack surface by removing an unused entry point
gcloud services disable sqladmin.googleapis.com --project=meridian-shipment-devNote
This step-based friction is deliberate, not an oversight: it means a brand-new project starts with almost nothing enabled, so an attacker who compromises a credential in that project can't silently start using services nobody expected to be reachable there. Treat "what APIs are enabled in this project" as part of its attack surface, worth reviewing periodically, not a one-time setup checkbox.
APIs That Get Forgotten Until Something Fails#
A handful of services have API-enablement gotchas specific to how they're consumed, not just "click enable and move on":
| API | The gotcha |
|---|---|
iam.googleapis.com | Needed even for basic role-management commands — easy to assume IAM is "always on" since every project has some IAM state by default |
cloudresourcemanager.googleapis.com | Required for gcloud projects commands themselves — a genuinely confusing first error, since you need it enabled to manage the very concept of enabling APIs elsewhere |
serviceusage.googleapis.com | Required to enable any other API programmatically — a new project sometimes needs this one turned on manually first via the console |
cloudbilling.googleapis.com | Needed for any automation that reads/writes billing config, separate from the Cloud Billing account existing at all |
From the Trenches: The Terraform Apply That Failed on API Enablement, Not Permissions#
Devon's first attempt at automating a new environment with Terraform failed on google_compute_instance.gps_worker with a permission-denied-shaped error, even though the Terraform service account had roles/compute.admin. Twenty minutes into debugging IAM bindings, the actual cause turned out to be that compute.googleapis.com simply wasn't enabled yet in the brand-new project — Terraform's error message didn't clearly distinguish "you don't have permission" from "the API doesn't exist here yet" at the time. The fix, and the pattern Meridian adopted afterward, was making API enablement its own explicit, early step in every environment's Terraform module (a google_project_service resource per required API, applied before anything that depends on it) rather than assuming a freshly-created project already has the APIs a workload needs.
Quotas and Essential Contacts#
Quotas cap how much of a resource one project can consume — VM cores per region, API requests per minute, IP addresses — both to protect Google's shared infrastructure from a single runaway project and to protect you from a misconfigured loop burning an unbounded bill.
# Check current quota usage for a specific service/region
gcloud compute regions describe us-central1 --format="table(quotas)"
# Request a quota increase proactively, before a launch — the same
# pre-launch capacity-planning discipline as any other cloud provider
gcloud alpha services quota update \
--service=compute.googleapis.com \
--consumer=projects/meridian-shipment-prod \
--metric=compute.googleapis.com/cpus \
--value=500 \
--dimensions=region=us-central1Worked Example: Sizing a Quota Increase Request Before It's Urgent#
Meridian's GPS-ingestion fleet runs 24 n2-standard-8 VMs (8 vCPUs each) in us-central1 today — 192 vCPUs against a default regional quota of 240. Ana's team is rolling out tracking to 3,000 additional vehicles over the next quarter, and back-of-envelope math (roughly 1 worker VM per 170 vehicles at current message rates) says the fleet needs to grow to about 42 VMs, or 336 vCPUs — 96 over the current quota.
The math that actually matters here isn't the target number, it's the lead time: a quota increase request for a large jump can take Google several business days to approve, especially the first time a project asks for one, because it can trigger a manual review rather than an automatic grant. Requesting the increase to 400 vCPUs (headroom past the 336 target, so the next quarter's growth doesn't trigger a second request) three weeks before the rollout, instead of the week the new vehicles are supposed to go live, is what actually made the launch land on schedule.
Tip
Best practice: request quota increases sized for your next planning horizon's peak, not exactly your current need — and request them the moment a growth plan is confirmed, not when you hit the ceiling. A quota-exhaustion error during a real launch is a self-inflicted incident; the fix was available days in advance.
Rate Quotas vs. Allocation Quotas — Two Different Failure Modes#
Not every quota behaves the same way when exceeded, and knowing which kind you're looking at changes how you respond:
| Quota type | What it limits | What happens when exceeded | Example |
|---|---|---|---|
| Allocation quota | How much of a resource can exist at once | The create/update API call fails outright until something is freed or the quota raised | VM cores per region, static IP addresses |
| Rate quota | How many requests can be made per time window | Requests are throttled or rejected (HTTP 429) until the window resets, then normal service resumes | API calls per 100 seconds, Pub/Sub publish requests per minute |
A rate-quota hit is usually transient and self-healing (retry with backoff); an allocation-quota hit blocks forward progress entirely until you act. Meridian's GPS-ingestion pipeline hit a rate quota during a load test (Pub/Sub publish requests per minute) that looked, from the error alone, identical to an allocation problem — the fix was exponential backoff in the publisher client, not a quota increase request at all.
The Always Free Tier — Real Limits, Not a Trial Period#
GCP's Always Free tier is a genuinely permanent, no-expiration allowance built into specific services — distinct from the separate new-customer trial credit, and worth knowing precisely because "free tier" gets used loosely to mean both. A few of the limits that matter most for learning and small workloads:
| Service | Always Free allowance |
|---|---|
| Compute Engine | 1 e2-micro instance per month, in specific US regions only |
| Cloud Storage | 5 GB-months of Standard storage, US regions |
| Cloud Functions / Cloud Run | 2 million invocations per month |
| BigQuery | 1 TB of query processing per month, 10 GB of storage |
| Pub/Sub | 10 GB of throughput per month |
Note
Always Free limits are per-project in some cases and per-billing-account in others, and they apply only to specific SKUs and regions — check the current official Always Free page before relying on a specific number for a real cost estimate, since Google does revise these allowances over time.
Essential Contacts is a smaller but easy-to-skip setup step: it registers named email addresses to receive specific categories of Google-initiated notifications (billing, security, technical, legal) at the organization, folder, or project level — separate from whoever happens to be an IAM Owner at the time. Ana registered Meridian's on-call rotation's shared inbox for the SECURITY and TECHNICAL categories specifically so a Google-detected compromised credential alert doesn't silently land in one departed employee's inbox.
Cloud Asset Inventory and Gemini Cloud Assist#
Cloud Asset Inventory is GCP's searchable, historical record of every resource and every IAM policy across your whole organization — the tool you reach for to answer "what exists," "who can access X," or "what changed since last Tuesday" without manually walking every project.
# Search every Compute Engine instance across the whole organization
gcloud asset search-all-resources \
--scope=organizations/847213590482 \
--asset-types=compute.googleapis.com/Instance
# Export a full point-in-time snapshot of every resource to BigQuery
# for offline analysis — genuinely useful for a security audit
gcloud asset export \
--organization=847213590482 \
--output-bigquery-table='projects/meridian-shared-logging/datasets/audit/tables/assets' \
--content-type=resourceGemini Cloud Assist layers a natural-language interface on top of this and other GCP observability data — asking it "why did the shipment-api project's cost jump 40% last week" surfaces the underlying resource-usage change instead of requiring you to manually correlate a billing export against Cloud Asset Inventory yourself. It's a genuine time-saver for investigation, not a replacement for understanding what it's querying underneath — the ACE exam expects you to know both the raw tools (Asset Inventory, Cloud Monitoring, Cloud Logging) and that Gemini Cloud Assist exists as an AI-assisted layer over them.
Using the IAM Policy Analyzer to Answer "Who Can Actually Do This?"#
Cloud Asset Inventory's IAM policy analysis goes further than "list every binding" — it answers the question a security review actually asks, which is "which identities can perform action X on resource Y," accounting for every role, every inherited binding, and every group membership in between:
# Who can delete objects in Meridian's shipment-assets bucket, accounting
# for every inherited binding from folder and org level?
gcloud asset analyze-iam-policy \
--organization=847213590482 \
--full-resource-name="//storage.googleapis.com/projects/_/buckets/meridian-shipment-assets" \
--permissions="storage.objects.delete"This single query replaced what used to be a manual, folder-by-folder IAM audit at Meridian — Priya runs a scoped version of it before every quarterly access review instead of eyeballing each project's IAM page and hoping she didn't miss an inherited grant from three levels up.
From the Trenches: The Audit Nobody Could Answer Quickly#
Before adopting Cloud Asset Inventory, a routine SOC 2 audit question — "list every identity with delete access to customer PII storage" — took Meridian's team the better part of two days: manually checking IAM on each of nine projects, then separately checking folder- and org-level bindings, then cross-referencing group memberships by hand in Workspace admin. The immediate cause was the lack of a single query surface; the deeper cause was that IAM policy inheritance is invisible unless you deliberately compute the union across the whole hierarchy — which is exactly the kind of task humans are bad at doing correctly by hand and exactly what analyze-iam-policy is built to compute instead. The same question takes under a minute today.
Workforce Identity Federation — A First Look#
Workforce Identity Federation lets external identity providers — an existing corporate Active Directory, Okta, or another SAML/OIDC provider — authenticate directly to GCP without creating a duplicate Google Identity for every employee. This chapter introduces it because it's part of the ACE exam's "setting up a cloud solution environment" domain; Part 3 (IAM & Identity) covers the full mechanics of how a federated identity maps to IAM permissions once inside GCP.
Meridian doesn't use Workforce Identity Federation yet — a 12-person company runs fine on native Google Workspace accounts — but Priya flagged it as the first thing to set up if Meridian is ever acquired by (or acquires) a company running its own separate corporate directory, since it avoids the alternative of manually provisioning and deprovisioning duplicate accounts forever.
# Skeleton of what onboarding an external Okta tenant looks like — a
# workforce pool is the container, a provider is the actual trust
# relationship to the external identity source
gcloud iam workforce-pools create meridian-corp-pool \
--organization=847213590482 --location=global
gcloud iam workforce-pools providers create-oidc okta-provider \
--workforce-pool=meridian-corp-pool --location=global \
--issuer-uri="https://meridian.okta.com" \
--client-id="<okta-app-client-id>" \
--attribute-mapping="google.subject=assertion.sub"| Mechanism | Authenticates | Typical use |
|---|---|---|
| Google Workspace account | A human, natively in Google's own identity system | Small orgs, or any org already standardized on Workspace |
| Workforce Identity Federation | A human, via an external IdP (Okta, Azure AD, generic SAML/OIDC) | Orgs with an existing corporate directory that shouldn't be duplicated |
| Workload Identity Federation | A non-human workload (a CI/CD pipeline, an app running outside GCP) | Covered in full in Part 3 — the workload equivalent of this same federation idea |
Part 3 picks this thread back up once IAM roles and bindings are covered in full — a federated identity still needs an IAM role granted to it like any other principal; federation only solves authentication, never authorization on its own.
A Full Worked Landing Zone Bootstrap#
Putting every piece of this chapter together, here's the actual sequence Priya ran to bootstrap Meridian's GCP landing zone from nothing:
# 1. Confirm the organization exists (created automatically when
# Google Workspace/Cloud Identity was set up for meridianlogistics.com)
gcloud organizations list
# 2. Create the core folder structure
gcloud resource-manager folders create --display-name="Production" \
--organization=847213590482
gcloud resource-manager folders create --display-name="Non-Production" \
--organization=847213590482
gcloud resource-manager folders create --display-name="Shared Services" \
--organization=847213590482
# 3. Create the foundational projects (folder IDs from step 2's output)
gcloud projects create meridian-shared-networking --folder=345678901234
gcloud projects create meridian-shared-logging --folder=345678901234
gcloud projects create meridian-shipment-prod --folder=456789012345
gcloud projects create meridian-shipment-staging --folder=567890123456
# 4. Link every project to the billing account (Part 2 covers this in depth)
for p in meridian-shared-networking meridian-shared-logging \
meridian-shipment-prod meridian-shipment-staging; do
gcloud billing projects link "$p" --billing-account=012345-6789AB-CDEF01
done
# 5. Set a baseline org policy at the ROOT — no public IPs, US regions only
gcloud resource-manager org-policies set-policy region-lock-policy.yaml \
--organization=847213590482
# 6. Relax the public-IP constraint specifically for Non-Production
gcloud resource-manager org-policies set-policy dev-exception-policy.yaml \
--folder=567890123456
# 7. Enable Cloud Audit Logs org-wide, routed to the log-archive project
# (Part 9's security chapter covers this in full depth)
gcloud logging sinks create org-audit-sink \
bigquery.googleapis.com/projects/meridian-shared-logging/datasets/audit_logs \
--organization=847213590482 --include-children \
--log-filter='logName:"cloudaudit.googleapis.com"'Real-World Scenario: The Region-Lock Policy That Almost Blocked a Disaster Recovery Test#
Four months after setting the organization-wide region-lock policy (us-central1 and us-east1 only), Meridian scheduled its first real disaster-recovery drill: fail the dispatch API over to a third region entirely, to prove the architecture could survive losing both approved US regions at once. The drill plan called for standing up a temporary us-west1 deployment — which the org policy immediately and correctly blocked.
This wasn't a bug in the policy; it was the policy doing exactly its job against a scenario nobody had designed the policy around. Priya had two real options, laid out here as the actual decision framework the team used:
| Option | Trade-off |
|---|---|
Grant a time-boxed exception at the DR-test project level, scoped to us-west1, removed immediately after the drill | Fast, low-risk, but requires remembering to remove the exception — a real process, not a technical guarantee |
Add us-west1 to the permanent allow-list org-wide | No forgotten-exception risk, but permanently widens the org's approved footprint for every future project, diluting the original data-residency intent |
Meridian chose the time-boxed exception, set with an explicit calendar reminder tied to the drill's end date, and — as a second safeguard — a follow-up Cloud Asset Inventory query scheduled to run a week after the drill specifically checking for any resource still running in us-west1. The drill succeeded, the exception was removed on schedule, and the org policy went back to enforcing exactly two regions with no permanent widening of the approved footprint.
🔍 Investigation tip: whenever an org policy needs a genuinely temporary exception, pair the exception with an automated check for its own removal — a policy exception with no expiry mechanism has a way of quietly becoming permanent the moment the person who set it moves on to the next fire.
Part 1 gcloud Cheat Sheet#
| Task | Command |
|---|---|
| Create a project | gcloud projects create <id> --organization=<org-id> |
| Link billing | gcloud billing projects link <id> --billing-account=<acct> |
| Create a folder | gcloud resource-manager folders create --display-name=<name> --organization=<org-id> |
| Move a project into a folder | gcloud projects move <id> --folder=<folder-id> |
| Set an org policy | gcloud resource-manager org-policies set-policy <file>.yaml --organization=<org-id> |
| Check effective policy | gcloud resource-manager org-policies describe-effective <constraint> --project=<id> |
| Enable an API | gcloud services enable <api>.googleapis.com --project=<id> |
| List enabled APIs | gcloud services list --enabled --project=<id> |
| Search resources org-wide | gcloud asset search-all-resources --scope=organizations/<org-id> |
| Show current identity/config | gcloud auth list && gcloud config list |
Pre-Flight Checklist: Is a New GCP Environment Actually Ready?#
Before handing a new project to an application team, confirm every item below — this is the exact checklist Meridian's platform team runs for every new environment:
- Project created with a permanent, convention-following Project ID (not a placeholder name)
- Project linked to the correct Cloud Billing account
- Project placed under the correct folder (Production / Non-Production / Shared Services)
- Baseline labels applied (
team,env,cost-center) - Required APIs explicitly enabled (
compute.googleapis.com,iam.googleapis.com, and every service the workload actually needs) - Org-policy inheritance confirmed via
describe-effective, not assumed from reading the project-level policy alone - Budget and alert threshold configured (Part 2)
- Essential Contacts registered for
TECHNICALandSECURITYcategories - Quota headroom confirmed for the workload's expected peak, not just its launch-day size
Common Mistakes and Interview Traps#
| Mistake | Why it happens | The fix |
|---|---|---|
| Treating a Project ID like it's renamable | Confusing it with the mutable project name | Pick a permanent naming convention (<company>-<workload>-<env>) before creating any real project |
| Assuming one VPC per region, AWS-style | Porting AWS's regional-VPC mental model | Remember GCP VPCs are global by default — Part 7 covers the implications |
| Granting IAM at the project level "to be safe" | Not yet knowing folder-level inheritance exists | Grant at the narrowest level that covers the actual need — Part 3 goes deep on this |
| Forgetting to enable a service's API before using it | IAM permission and API enablement feel like the same gate | Always check gcloud services list --enabled when a "permission denied"-shaped error appears unexpectedly |
| Assuming an org policy retroactively fixes existing resources | Confusing "blocks new violations" with "cleans up old ones" | Audit existing resources with Cloud Asset Inventory after adding a new constraint |
| Confusing labels, network tags, and Resource Manager tags | All three are called "tags" or "labels" colloquially | Use the decision table in this chapter — only Resource Manager tags gate org policies |
Worked Practice Problems#
1. Meridian's finance team wants clean per-team cost attribution across all projects without waiting for a Resource Manager tags rollout. What's the fastest correct mechanism, and why not the alternatives?
Labels. They're free-form, attach to almost every resource type, are queryable in Cloud Billing's cost breakdown reports immediately, and require no IAM setup beyond normal resource-edit permissions. Network tags only affect firewall targeting and carry no cost-reporting integration at all. Resource Manager tags can eventually support cost attribution via policy-driven automation, but they require creating tag keys/values with org-level IAM permissions first — real setup overhead that isn't justified just to answer "which team owns this instance."
2. A newly-hired engineer with the roles/compute.admin role at the organization level reports they still can't create a VM in meridian-shipment-dev. IAM looks correct. What's the most likely cause, and what command confirms it?
The Compute Engine API is very likely not enabled in that specific project — IAM permission and API enablement are two independent gates, and having the role doesn't imply the API is on. Run gcloud services list --enabled --project=meridian-shipment-dev to confirm; if compute.googleapis.com is missing from the list, gcloud services enable compute.googleapis.com --project=meridian-shipment-dev resolves it.
3. Priya wants Non-Production projects to allow public IPs on VMs while every other folder in the org forbids it. Where should each policy live, and why can't the exception simply be set at the project level directly under Production instead of at the Non-Production folder?
The base "no public IPs" constraint belongs at the organization level so it applies everywhere by default. The exception belongs at the Non-Production folder, since a folder-level policy applies to every project under it (current and future) without re-authoring the exception each time a new dev/staging project is created. Setting it at individual project level under Production would be backwards — the goal is to loosen the rule for non-production work, not for a production project, and putting it there would also mean re-doing the exception manually for every new non-prod project instead of inheriting it automatically.
4. A team wants a temporary exception to an org-wide region-lock policy for a one-week disaster-recovery drill. What's the risk of simply adding the new region to the permanent allow-list instead of scoping a time-boxed exception, and how should the exception's removal be guaranteed rather than just planned?
Adding the region permanently widens the organization's approved footprint for every future project, not just the drill — it silently weakens the original data-residency intent for good, long after the one-week need has passed. A time-boxed exception scoped to the specific project or folder running the drill avoids that permanent widening, but a time-boxed exception is only as reliable as the process that removes it — pairing it with an automated check (a scheduled Cloud Asset Inventory query, or a calendar-triggered reminder tied to an actual follow-up task) turns "we'll remember to remove it" into a verified fact instead of an intention.
Summary and What's Next#
This chapter built the ground floor: projects as GCP's real isolation unit, the organization/folder/project resource hierarchy and how policies inherit down it, org policies as guardrails IAM can't override, the labels/tags/Resource-Manager-tags distinction, and the operational basics (gcloud, API enablement, quotas, Cloud Asset Inventory) every later chapter assumes you already have running. Meridian Logistics now has a real, governed landing zone — the next step is making sure the right people have the right access to it.
Part 2 covers billing accounts and budgets in full depth, plus the infrastructure-as-code tooling (Terraform, Config Connector) and AI-assisted tooling (Gemini CLI, Gemini Cloud Assist) that turn the manual gcloud sequence in this chapter into something repeatable and version-controlled.