# RAD Platform — Google Cloud Certification Guides (full text) > Single-fetch export of every certification lab map and section exploration > guide on https://docs.radmodules.dev — generated from the same markdown > source as the site. Index: https://docs.radmodules.dev/llms.txt > Note: on this site PDE = Professional Cloud DevOps Engineer (not Data > Engineer) and PCDE = Professional Cloud Database Engineer. --- # Associate Cloud Engineer (ACE) Certification Lab Map The Associate Cloud Engineer certification validates that you can deploy applications, monitor operations, and manage enterprise solutions on Google Cloud using both the console and the command line. The RAD platform's four foundation modules — `Services_GCP` (shared VPC networking, Cloud SQL, Redis, Filestore, GKE Autopilot, service accounts), `App_CloudRun` (Cloud Run v2 deployment engine), `App_GKE` (GKE deployment engine), and the `App_Common` shared library (secrets, IAM, storage, CMEK, CI/CD plumbing) — give you a live, inspectable lab: every toggle in your deployment portal maps to real GCP resources you can then explore with `gcloud`, `kubectl`, and the console. Application wrapper modules (Django, WordPress, etc.) exist on top of these but are not needed for exam preparation. ## How to use this guide - Deploy one of the profiles below from your deployment portal, then work through the matching section guide. - Every section-guide subsection has a **Try it** block — do the CLI steps, not just the console clicks. The ACE exam assumes `gcloud`/`kubectl` fluency. - Use the coverage legend to know which exam topics you must study outside the platform; the section guides flag these in **Beyond the modules** blocks. - ACE is entry-level: focus on creating, inspecting, and modifying resources, not on architecture trade-offs. **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: Baseline platform *Purpose:* the shared infrastructure layer every other profile builds on — VPC, Cloud NAT, private Cloud SQL, NFS/Redis VM, service accounts. *Modules:* `Services_GCP` only. | Variable | Value | |---|---| | `project_id` | your project ID | | `tenant_id` | `demo` (default) | | `create_postgres` | `true` (default) | | `create_network_filesystem` | `true` (default) | | `support_users` | your email address | | `resource_labels` | `{ environment = "dev", cost-center = "lab" }` | *Estimated incremental cost:* low–moderate — the dominant drivers are the `db-custom-1-3840` Cloud SQL instance and the `e2-small` NFS VM running 24/7. ### Profile: Serverless application *Purpose:* Cloud Run service with database, storage buckets, NFS mount, revisions, and scheduled backups — covers most of Sections 2 and 3. *Modules:* Baseline platform + `App_CloudRun`. | Variable | Value | |---|---| | `container_image_source` | `prebuilt` | | `container_image` | `us-docker.pkg.dev/cloudrun/container/hello` | | `min_instance_count` | `0` (default — scale to zero) | | `max_instance_count` | `3` | | `database_type` | `POSTGRES` (default) | | `storage_buckets` | one entry, e.g. `[{ name_suffix = "media" }]` | | `support_users` | your email address | *Estimated incremental cost:* low — Cloud Run scales to zero; cost is dominated by what the baseline platform already runs. ### Profile: Kubernetes application *Purpose:* GKE Autopilot cluster plus a namespaced workload with HPA, ResourceQuota, PodDisruptionBudget, and NetworkPolicy — the `kubectl` half of the exam. *Modules:* `Services_GCP` (re-applied with GKE enabled) + `App_GKE`. | Variable | Value | |---|---| | `create_google_kubernetes_engine` (Services_GCP) | `true` | | `gke_cluster_mode` (Services_GCP) | `AUTOPILOT` (default) | | `container_image_source` (App_GKE) | `prebuilt` | | `container_image` (App_GKE) | `us-docker.pkg.dev/cloudrun/container/hello` | | `enable_resource_quota` (App_GKE) | `true` | | `enable_network_segmentation` (App_GKE) | `true` | *Estimated incremental cost:* moderate — Autopilot bills per pod resource request plus the cluster management fee. ### Profile: Operations & security add-ons *Purpose:* billing budget, alerting, audit logs, edge security, and IAP for Sections 1, 3.4, and 4. Apply on top of either application profile. *Modules:* `Services_GCP` + one application module. | Variable | Value | |---|---| | `create_billing_budget` (Services_GCP) | `true` | | `budget_amount` (Services_GCP) | `100` (default) | | `configure_email_notification` (Services_GCP) | `true` | | `notification_alert_emails` (Services_GCP) | your email address | | `enable_audit_logging` (Services_GCP or app module) | `true` | | `enable_cloud_armor` (App_CloudRun) | `true`; add `application_domains` only if you want your own hostname | | `enable_iap` + `iap_authorized_users` (App_CloudRun) | `true` + `["user:you@example.com"]` | *Estimated incremental cost:* moderate — the global external load balancer (forwarding rule + Cloud Armor policy) is the dominant driver; audit logging adds Cloud Logging volume. ## Section 1: Setting up a cloud solution environment (~23% of the exam) The modules deploy into an existing project, enable ~45 APIs automatically, create least-privilege service accounts, and can create a real billing budget — but project creation, resource hierarchy, and Cloud Identity remain console/`gcloud` exercises. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Enabling APIs within projects | ✅ | `enable_services` (default `true`), `additional_apis` | [Section 1 guide](ACE_Section_1_Exploration_Guide.md#11-setting-up-cloud-projects-and-accounts) | | 1.1 Granting IAM roles within a project | ✅ | dedicated SAs + predefined-role bindings | [Section 1 guide](ACE_Section_1_Exploration_Guide.md#11-setting-up-cloud-projects-and-accounts) | | 1.1 Creating projects / resource hierarchy / Cloud Identity | 📘 | modules deploy into an existing `project_id` only | [Section 1 guide](ACE_Section_1_Exploration_Guide.md#11-setting-up-cloud-projects-and-accounts) | | 1.1 Assessing quotas | 🟡 | `max_instance_count` and friends consume quotas; no quota management | [Section 1 guide](ACE_Section_1_Exploration_Guide.md#11-setting-up-cloud-projects-and-accounts) | | 1.2 Budgets and alerts | ✅ | `create_billing_budget`, `budget_amount` (default `100`), `budget_alert_thresholds` | [Section 1 guide](ACE_Section_1_Exploration_Guide.md#12-managing-billing-configuration) | | 1.2 Linking billing accounts / billing exports | 📘 | billing account is auto-discovered, never managed | [Section 1 guide](ACE_Section_1_Exploration_Guide.md#12-managing-billing-configuration) | ## Section 2: Planning and implementing a cloud solution (~30% of the exam) The strongest section for the lab: Cloud Run and GKE deployments are fully demonstrated, along with Cloud SQL, GCS, Filestore, Memorystore, a custom-mode VPC, Cloud NAT, firewall rules, and a global external load balancer with Cloud Armor. Compute Engine appears only as the self-managed NFS VM; App Engine and Cloud Functions are not implemented. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Cloud Run deployment and autoscaling | ✅ | `min_instance_count` (default `0`), `max_instance_count` (default `1`), `container_resources` | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#21-planning-and-implementing-compute-resources) | | 2.1 GKE workloads (Deployment/StatefulSet, HPA) | ✅ | `workload_type`, `stateful_pvc_enabled`, `container_resources` | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#21-planning-and-implementing-compute-resources) | | 2.1 Compute Engine VMs / MIGs | 🟡 | the NFS VM MIG only | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#21-planning-and-implementing-compute-resources) | | 2.1 App Engine, Cloud Functions, Spot VMs | 📘 | not implemented | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#21-planning-and-implementing-compute-resources) | | 2.2 Cloud SQL, GCS, Filestore, Memorystore | ✅ | `create_postgres` (default `true`), `storage_buckets`, `create_filestore_nfs`, `create_redis` | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#22-planning-and-implementing-storage-and-data-solutions) | | 2.2 AlloyDB, Firestore | ✅ | `enable_alloydb`, `create_firestore` (both default `false`) | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#22-planning-and-implementing-storage-and-data-solutions) | | 2.2 BigQuery, Spanner, Bigtable, Pub/Sub messaging | 📘 | not implemented | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#22-planning-and-implementing-storage-and-data-solutions) | | 2.3 VPC, subnets, firewall rules, Cloud NAT, PSA | ✅ | `availability_regions`, `subnet_cidr_range` | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#23-planning-and-implementing-networking-resources) | | 2.3 Load balancing, Cloud Armor, CDN | ✅ | `enable_cloud_armor`, `application_domains`, `enable_cdn` | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#23-planning-and-implementing-networking-resources) | | 2.3 Shared VPC, Cloud DNS, VPN/Interconnect | 📘 | not implemented | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#23-planning-and-implementing-networking-resources) | | 2.4 Infrastructure as code workflow | 🟡 | the entire repository (OpenTofu modules), `deploy_application`, Cloud Build pipelines; portal abstracts state | [Section 2 guide](ACE_Section_2_Exploration_Guide.md#24-planning-and-implementing-resources-through-infrastructure-as-code) | ## Section 3: Ensuring successful operation of a cloud solution (~27% of the exam) Revision management, traffic splitting, CI/CD with Cloud Build and Cloud Deploy, scheduled database backups, GCS lifecycle rules, static IPs, a full set of preconfigured alert policies, and synthetic uptime checks on publicly reachable endpoints are all live. Log routing/sinks are study-outside topics. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Revisions, traffic splitting, canary releases | ✅ | `traffic_split`, `max_revisions_to_retain` (default `7`) | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#31-managing-compute-resources) | | 3.1 CI/CD with Cloud Build and Cloud Deploy | ✅ | `enable_cicd_trigger`, `cloud_deploy_stages` | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#31-managing-compute-resources) | | 3.1 Kubernetes operations (kubectl, HPA, PDB, quota) | ✅ | `enable_resource_quota`, `enable_pod_disruption_budget` | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#31-managing-compute-resources) | | 3.1 VM lifecycle, SSH, snapshots, node pools | 🟡 | NFS VM MIG with daily snapshots | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#31-managing-compute-resources) | | 3.2 Database backups, restore, custom SQL | ✅ | `backup_schedule` (default `0 2 * * *`), `enable_backup_import`, `enable_custom_sql_scripts` | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#32-managing-storage-and-database-solutions) | | 3.2 GCS object lifecycle and versioning | ✅ | `storage_buckets[].lifecycle_rules`, `backup_retention_days` | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#32-managing-storage-and-database-solutions) | | 3.3 Static IPs, multi-region subnets | 🟡 | `reserve_static_ip` (default `true`), `availability_regions` | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#33-managing-networking-resources) | | 3.3 Routes, peering, DNS operations | 📘 | not implemented | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#33-managing-networking-resources) | | 3.4 Alert policies, channels, dashboards | ✅ | `support_users`, `alert_policies`, `alert_cpu_threshold` (default `80`) | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#34-monitoring-and-logging) | | 3.4 Audit logs | ✅ | `enable_audit_logging` | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#34-monitoring-and-logging) | | 3.4 Uptime checks | ✅ | `uptime_check_config` (default `{ enabled = false, path = "/" }` — opt in with `enabled = true`) | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#34-monitoring-and-logging) | | 3.4 Log sinks, log-based metrics | 📘 | not implemented | [Section 3 guide](ACE_Section_3_Exploration_Guide.md#34-monitoring-and-logging) | ## Section 4: Configuring access and security (~20% of the exam) Strong coverage: every module creates dedicated least-privilege service accounts, App_GKE uses Workload Identity, Services_GCP can create a Workload Identity Federation pool, secrets live in Secret Manager with optional automatic rotation, and IAP can gate both platforms. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Viewing and creating IAM policies, role types | ✅ | predefined-role-only bindings, per-resource grants | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#41-managing-identity-and-access-management-iam) | | 4.1 Audit logs for access review | ✅ | `enable_audit_logging` | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#41-managing-identity-and-access-management-iam) | | 4.1 Custom roles, IAM Conditions, Policy Troubleshooter | 📘 | not implemented | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#41-managing-identity-and-access-management-iam) | | 4.2 Dedicated service accounts on compute | ✅ | `cloudrun-sa-*`/`gke-sa-*` runtime identities | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#42-managing-service-accounts) | | 4.2 Workload Identity (GKE) and WIF (keyless CI) | ✅ | KSA→GSA binding, `enable_workload_identity_federation` | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#42-managing-service-accounts) | | 4.2 Secret Manager and rotation | ✅ | `secret_environment_variables`, `enable_auto_password_rotation` | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#42-managing-service-accounts) | | 4.2 IAP identity-gated access | ✅ | `enable_iap`, `iap_authorized_users`/`iap_authorized_groups` | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#42-managing-service-accounts) | | 4.2 SA key management, short-lived tokens | 📘 | deliberately keyless — study `gcloud iam service-accounts keys` separately | [Section 4 guide](ACE_Section_4_Exploration_Guide.md#42-managing-service-accounts) | --- # ACE Certification Preparation Guide: Section 1 — Setting up a cloud solution environment (~23% of the exam) ACE Certification Preparation Guide: Section 1 — Setting up a cloud solution environment (~23% of the exam) > 📚 **Official exam guide:** [Associate Cloud Engineer certification](https://cloud.google.com/learn/certification/cloud-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 1 using the RAD platform foundation modules as a hands-on lab. The module exercised here is almost entirely `Services_GCP` — the platform layer deployed once per project. Deploy the **Baseline platform** profile from the [Lab Map](ACE_Certification_Guide.md) before starting; add the **Operations & security add-ons** profile (specifically `create_billing_budget = true`) for subsection 1.2. --- ## 1.1 Setting up cloud projects and accounts > ⏱ ~60 min · 💰 no additional cost beyond the baseline profile · ⚙️ Requires: default Baseline platform deployment **Why the exam cares** — The exam tests whether you understand the project as the fundamental billing, IAM, and API boundary: how projects relate to folders and organizations, why APIs must be enabled before resources can be created, how identities (users, groups, service accounts) are granted roles, and how to check quotas before they bite you. Scenario questions often hinge on knowing that a project ID is immutable, that APIs are enabled per project, and that groups are preferred over individual user bindings. **How RAD implements it** — Every module deploys into an *existing* project named by `project_id` (required, no default); the modules never create projects, folders, or organizations. On apply, `Services_GCP` enables roughly 45 service APIs when `enable_services` (default `true`) is set — the list includes `compute.googleapis.com`, `run.googleapis.com`, `container.googleapis.com`, `sqladmin.googleapis.com`, `secretmanager.googleapis.com`, `cloudkms.googleapis.com`, and more; `additional_apis` (default `[]`) appends your own. Identity wiring: | Variable | Default | Effect | |---|---|---| | `project_id` | — (required) | Target project for all resources | | `tenant_id` | `"demo"` | Tenant suffix used in resource naming | | `enable_services` | `true` | Enables the ~45 required APIs at apply time | | `additional_apis` | `[]` | Extra APIs to enable | | `support_users` | `[]` | Emails that receive monitoring/budget notification channels | | `resource_labels` | `{}` | Labels merged onto every module-managed resource | | `resource_creator_identity` | platform deployer SA | Service account Terraform runs as | `Services_GCP` creates five dedicated service accounts per deployment — `cloudbuild-sa-{prefix}`, `clouddeploy-sa-{prefix}`, `cloudrun-sa-{prefix}`, `nfs-sa-{prefix}`, and `gke-sa-{prefix}` — each bound to predefined roles only. Organization context is auto-discovered: the platform reads `org_id` from the project data source, and org-dependent features (VPC-SC, SCC notifications) skip gracefully when the project has no organization or the caller lacks org-level permission. **Try it** 1. In the portal, set `resource_labels = { environment = "dev", team = "platform" }` and redeploy. In the console go to **Cloud SQL > your instance** and confirm the labels appear under the instance details. 2. Inspect the project and its enabled APIs from Cloud Shell: ```bash gcloud projects describe $GOOGLE_CLOUD_PROJECT gcloud services list --enabled --filter="config.name:run.googleapis.com OR config.name:sqladmin.googleapis.com" ``` Note the three identifiers in the `describe` output: project ID (immutable), project number, and display name (changeable). 3. List the service accounts the module created and inspect one binding: ```bash gcloud iam service-accounts list --filter="email:cloudrun-sa" gcloud projects get-iam-policy $GOOGLE_CLOUD_PROJECT \ --flatten="bindings[].members" \ --filter="bindings.members:cloudrun-sa" \ --format="table(bindings.role)" ``` 4. Check a quota your deployment consumes: **IAM & Admin > Quotas & System Limits**, filter by *Cloud SQL Admin API*. CLI equivalent: `gcloud compute regions describe us-central1 --format="table(quotas.metric,quotas.usage,quotas.limit)"` for Compute quotas. 5. You know it worked when the IAM policy query returns only narrow predefined roles (no `roles/editor`) and the enabled-services list contains the APIs above. **Check yourself**
Q1: A teammate deploys the Baseline platform profile into a fresh project and the apply fails with "API not enabled" errors for Compute Engine. They had set enable_services = false. What is the fastest fix, and why does the default avoid this? A: Re-enable `enable_services = true` (or run `gcloud services enable compute.googleapis.com ...` manually). GCP refuses to create any resource whose API is disabled in the project; the module's default enables all ~45 required APIs up front precisely so that downstream resources (VPC, Cloud SQL, NAT) can be created in one apply.
Q2: You need 12 operations engineers to receive monitoring alerts. Should you list 12 addresses in support_users or one Google Group address? A: Use one group address. The module creates one notification channel per entry, and IAM/notification management best practice is to bind groups, not individuals — membership changes in Cloud Identity / Google Workspace then propagate automatically without touching the deployment.
Q3: What is the difference between the project ID, project number, and project name? A: The project ID is a globally unique, immutable, human-chosen string used in APIs and URLs; the project number is a globally unique, immutable numeric identifier assigned by Google (it appears in default service account emails); the project name is a mutable display label with no uniqueness requirement.
**Beyond the modules** — The exam also tests things the modules deliberately do not do: - *Creating projects and hierarchy:* practice `gcloud projects create my-lab-project --folder=FOLDER_ID` and browse **IAM & Admin > Manage Resources** to see Organization → Folder → Project inheritance. - *Cloud Identity:* user and group lifecycle is managed in admin.google.com, not in GCP. Know that IAM policies can bind `user:`, `group:`, `serviceAccount:`, and `domain:` principals. - *Quota increases:* find a quota in **IAM & Admin > Quotas & System Limits** and walk through (without submitting) the **Edit Quotas** increase request flow; quota increases are requests, not instant changes. - *Org policies:* browse **IAM & Admin > Organization Policies** (e.g. `constraints/compute.vmExternalIpAccess`). The modules do not manage org policy constraints. **⚠️ Exam trap** — Enabling an API and granting IAM permission are independent: a user with `roles/run.admin` still cannot deploy to Cloud Run if `run.googleapis.com` is disabled in the project, and enabling the API grants no one any access. --- ## 1.2 Managing billing configuration > ⏱ ~40 min · 💰 the budget itself is free; alert emails are free · ⚙️ Requires: `create_billing_budget = true` (Operations & security add-ons profile) **Why the exam cares** — The exam expects you to link projects to billing accounts, create budgets with threshold alerts, and export billing data for analysis. Decision criteria: budgets *notify*, they never stop spending; billing exports to BigQuery are the only way to analyze historical cost by label; the Billing Account Administrator role is separate from project IAM. **How RAD implements it** — `Services_GCP` creates a real Cloud Billing budget when `create_billing_budget` (default `false`) is enabled. The billing account is *auto-discovered* from the project — there is no billing-account variable, and the module never links or unlinks projects. The budget is scoped with a `budget_filter` to the current project only. | Variable | Default | Effect | |---|---|---| | `create_billing_budget` | `false` | Creates the project-scoped budget | | `budget_amount` | `100` | Budget amount in the billing account's currency | | `budget_alert_thresholds` | `[0.5, 0.9, 1.0]` | One threshold rule per entry (50%, 90%, 100%) | | `budget_alert_emails` | `[]` | Merged with `support_users` into email notification channels | The budget wires the email channels and keeps the default IAM recipients enabled, so Billing Account Administrators/Users also get notified. Separately, `resource_labels` (default `{}`) propagates onto every module-managed resource, which is what makes label-based cost filtering in Billing Reports and BigQuery exports possible. **Try it** 1. In the portal set `create_billing_budget = true`, `budget_amount = 50`, and add your email to `budget_alert_emails`. Redeploy `Services_GCP`. 2. Verify in the console under **Billing > Budgets & alerts** — you should see "Budget for ``" with three threshold rules. CLI: ```bash BILLING_ACCOUNT=$(gcloud billing projects describe $GOOGLE_CLOUD_PROJECT \ --format="value(billingAccountName)") gcloud billing budgets list --billing-account=${BILLING_ACCOUNT##*/} ``` 3. Explore label-based cost attribution: **Billing > Reports**, open the **Labels** filter on the right and select a key you set in `resource_labels` (data appears with up to a day's delay). 4. You know it worked when `gcloud billing budgets list` shows your budget with `thresholdRules` at 0.5, 0.9, and 1.0. **Check yourself**
Q1: Your budget fired its 100% alert but resources keep running and costs keep accruing. Is something broken? A: No. Budgets only send notifications (email and optionally Pub/Sub) — they never cap spending or stop resources. Automated cost response requires you to wire a Pub/Sub budget notification to your own automation (e.g. a function that disables billing), which the exam expects you to know is a custom build, not a checkbox.
Q2: Finance wants a monthly per-team cost breakdown of everything the RAD platform deploys. Which two pieces make this possible? A: (1) Consistent `resource_labels` (e.g. `team = "platform"`) on every resource, which the modules apply automatically, and (2) a billing export to BigQuery, configured at the billing-account level under **Billing > Billing export**, which you then query grouping by the label key. Billing Reports filtering by label works for ad-hoc views, but BigQuery is the answer for programmatic/chargeback reporting.
**Beyond the modules** — Not implemented by the foundation modules; practice these directly: - *Linking a project to a billing account:* `gcloud billing projects link my-project --billing-account=0X0X0X-0X0X0X-0X0X0X` (requires Billing Account User on the account + Project Billing Manager or Owner on the project). - *Billing exports:* enable the BigQuery export (standard usage cost) under **Billing > Billing export**; there is no Terraform in this repo doing it. - *Billing IAM:* know `roles/billing.admin`, `roles/billing.user` (can link projects), and `roles/billing.viewer` and that they live on the billing account, not the project. **⚠️ Exam trap** — Budget thresholds can alert on *forecasted* spend as well as actual spend; also, a budget scoped to a billing account is not the same as one scoped to a project — the module's budget uses a project filter, so other projects on the same billing account are not counted. --- # ACE Certification Preparation Guide: Section 2 — Planning and implementing a cloud solution (~30% of the exam) ACE Certification Preparation Guide: Section 2 — Planning and implementing a cloud solution (~30% of the exam) This guide covers exam Section 2 using the RAD platform foundation modules as a hands-on lab. All four foundation modules are exercised: `Services_GCP` provides the VPC, databases, and (optionally) the GKE Autopilot cluster; `App_CloudRun` and `App_GKE` are the two deployment engines; the `App_Common` shared library handles storage, secrets, and builds. Deploy the **Serverless application** profile first, then the **Kubernetes application** profile from the [Lab Map](ACE_Certification_Guide.md). --- ## 2.1 Planning and implementing compute resources > ⏱ ~90 min · 💰 low for Cloud Run (scale-to-zero); moderate for GKE Autopilot · ⚙️ Requires: Serverless application profile; Kubernetes application profile for the GKE half **Why the exam cares** — The biggest single skill in Section 2 is *choosing* the right compute platform — Compute Engine for full OS control, GKE for container orchestration, Cloud Run for stateless request-driven containers — and then configuring scaling and resources correctly on each. Expect scenarios contrasting scale-to-zero economics, cold starts, machine-type selection, and preemptible/Spot pricing. **How RAD implements it** — The same application can be deployed through `App_CloudRun` or `App_GKE`, making the comparison concrete: *Cloud Run* (`App_CloudRun`): | Variable | Default | Notes | |---|---|---| | `container_image_source` | `"custom"` | `prebuilt` deploys `container_image` directly; `custom` builds with Cloud Build | | `min_instance_count` | `0` | 0 = scale to zero; ≥1 eliminates cold starts | | `max_instance_count` | `1` | Cost ceiling (1–1000) | | `container_resources` | `cpu_limit = "1000m"`, `memory_limit = "512Mi"` | Per-instance capacity | | `container_port` | `8080` | Port Cloud Run routes requests to | | `execution_environment` | `"gen2"` | gen2 is required for NFS and GCS Fuse volumes | | `timeout_seconds` | `300` | Request timeout, 0–3600 | | `cpu_always_allocated` | `false` | Default is request-based billing (CPU only during request processing); set `true` to keep CPU allocated between requests | `enable_image_mirroring` (default `true`) copies public images into Artifact Registry first (a digest-aware copy), so the service never pulls directly from Docker Hub. *GKE* (`App_GKE`): `min_instance_count` (default `1`) and `max_instance_count` (default `3`) become an HPA's min/max replicas; `container_resources` becomes pod requests/limits (Autopilot bills by requests). `workload_type` (default `null`) resolves to a Deployment, but setting `stateful_pvc_enabled = true` auto-selects a StatefulSet with per-pod PVCs (`stateful_pvc_size`, `stateful_pvc_mount_path` required); explicitly combining `workload_type = "Deployment"` with `stateful_pvc_enabled = true` fails at plan time. `enable_vertical_pod_autoscaling` (default `false`) adds a VPA. The cluster itself comes from `Services_GCP`: `gke_cluster_mode` (default `AUTOPILOT`, or `STANDARD` with an explicit `e2-standard-4` node pool autoscaling 1–5 nodes via `gke_node_min_count`/`gke_node_max_count`), regional location, REGULAR release channel, Workload Identity, and Managed Prometheus. *Compute Engine appears once:* `create_network_filesystem` (default `true`) runs an `e2-small` Ubuntu VM in a managed instance group of size 1, with a stateful data disk, TCP health checks with auto-healing, and a daily snapshot schedule (7-day retention) — a small but real MIG to inspect. **Try it** 1. Deploy the Serverless application profile, then inspect the service and its revisions: ```bash gcloud run services list --region=us-central1 gcloud run services describe --region=us-central1 \ --format="yaml(spec.template.spec.containers[0].resources, spec.template.metadata.annotations)" ``` In **Cloud Run > service > Revisions**, confirm CPU/memory match `container_resources`. 2. Change `max_instance_count` to `5` in the portal and redeploy; watch the new revision appear with `gcloud run revisions list --service= --region=us-central1`. 3. Deploy the Kubernetes application profile, connect, and inspect the workload: ```bash gcloud container clusters get-credentials gke-cluster-1 --region=us-central1 kubectl get deployments,hpa,pods -n kubectl describe hpa -n ``` 4. Inspect the one Compute Engine VM and its MIG: `gcloud compute instance-groups managed list` and `gcloud compute instances list --filter="name~nfs"`. 5. You know it worked when the HPA shows `MINPODS 1 / MAXPODS 3` (or your overrides) and the Cloud Run revision shows your CPU/memory limits. **Check yourself**
Q1: A stateless HTTP API has unpredictable, bursty traffic and the team wants to pay nothing during idle nights. Cloud Run or GKE — and which RAD variable expresses the decision? A: Cloud Run with `min_instance_count = 0` — Cloud Run scales to zero between requests and bills only while serving. GKE Autopilot pods (HPA minimum of 1 in this module) keep billing for their resource requests around the clock. The trade-off is cold-start latency on the first request after idle.
Q2: You set stateful_pvc_enabled = true in App_GKE without touching workload_type. What gets deployed and why? A: A StatefulSet. The module auto-selects StatefulSet whenever per-pod PVCs are requested, because Deployments cannot give each replica its own stable volume and identity. Forcing `workload_type = "Deployment"` alongside it fails validation at plan time.
Q3: On GKE Autopilot, what happens if a container spec has no CPU/memory requests, and why does the module always set them? A: Autopilot requires resource requests — it either rejects the pod or applies defaults, and it bills per requested resource. The module always renders `container_resources` into requests/limits so scheduling and billing are deterministic.
**Beyond the modules** — General-purpose Compute Engine, App Engine, and Cloud Functions are not implemented. For the exam: - Create a VM yourself: `gcloud compute instances create test-vm --zone=us-central1-a --machine-type=e2-micro`, then SSH with `gcloud compute ssh test-vm --zone=us-central1-a`. Study machine families (E2/N2/C3), Spot VMs, instance templates, and MIG autoscaling/rolling updates. - App Engine: deploy a hello-world with `gcloud app deploy` in a scratch project; know Standard vs Flexible and that the region is permanent per project. - Cloud Run functions (Cloud Functions): `gcloud functions deploy` with an HTTP or Pub/Sub trigger; know that 2nd gen runs on Cloud Run. - GKE Standard node-pool operations (`gcloud container node-pools create/resize`) — the RAD cluster defaults to Autopilot where node pools are invisible. **⚠️ Exam trap** — Cloud Run `max_instance_count` defaults to 1 in this module: a load test will plateau quickly and is not a Cloud Run limitation, just a deliberate cost ceiling. On the exam, "service stops scaling" scenarios are usually a max-instances setting, not quota. --- ## 2.2 Planning and implementing storage and data solutions > ⏱ ~75 min · 💰 Cloud SQL is the dominant baseline cost; Filestore (`BASIC_HDD` 1 TiB) and Redis add meaningful cost — destroy after the lab · ⚙️ Requires: Baseline platform; toggle `create_redis` / `create_filestore_nfs` for those labs **Why the exam cares** — Section 2.2 tests product selection: object storage (GCS) vs file storage (Filestore) vs block storage, relational (Cloud SQL/AlloyDB/Spanner) vs NoSQL (Firestore/Bigtable), and caching (Memorystore). It also tests basic creation parameters: storage classes, regional vs zonal availability, and private connectivity to managed databases. **How RAD implements it** — `Services_GCP` provisions the data layer; the app modules consume and mount it. | Variable (Services_GCP) | Default | What it creates | |---|---|---| | `create_postgres` | `true` | Cloud SQL PostgreSQL (`postgres_database_version` default `POSTGRES_17`, `postgres_tier` default `db-custom-1-3840`) | | `postgres_database_availability_type` | `ZONAL` | Set `REGIONAL` for HA with a synchronous standby | | `create_postgres_read_replica` | `false` | Zonal read replicas (`postgres_read_replica_count` default `1`) | | `create_mysql` | `false` | Cloud SQL MySQL (`mysql_database_version` default `MYSQL_8_4`) | | `enable_alloydb` | `false` | AlloyDB cluster + primary (`alloydb_cpu_count` default `2`) | | `create_firestore` | `false` | Firestore database (Native mode) | | `create_redis` | `false` | Memorystore Redis (`redis_tier` default `BASIC`, `redis_memory_size_gb` default `1`, AUTH enabled) | | `create_filestore_nfs` | `false` | Filestore (`filestore_tier` default `BASIC_HDD`, `filestore_capacity_gb` default `1024`) | The Cloud SQL instance is private-IP only (no public IPv4, encrypted-only SSL mode), reachable through Private Services Access, with automated daily backups (7 retained, 04:00 UTC) and point-in-time recovery enabled. Redis persistence (`redis_persistence_mode`, default `DISABLED`) is only configurable on `STANDARD_HA` tier, and plan-time preconditions reject `BASIC` tier when `resource_labels.environment = "production"` — and also reject `redis_persistence_mode = "DISABLED"` on a production `STANDARD_HA` instance, so production caches must enable `RDB` or `AOF` persistence. On the application side, `storage_buckets` (default `[]`, with `create_cloud_storage` default `true`) provisions GCS buckets per entry — `storage_class` default `STANDARD`, `versioning_enabled` default `false`, `public_access_prevention` default `"enforced"`, optional `lifecycle_rules` and CORS (the platform's object-storage layer). `gcs_volumes` mounts buckets into the container via GCS Fuse, `enable_nfs` (default `true`) mounts the NFS share at `nfs_mount_path` (default `/mnt/nfs`), and `database_type` (default `POSTGRES`; `MYSQL`/`NONE`) selects which Cloud SQL engine the app binds to, connected through the Cloud SQL Auth Proxy (a unix-socket volume on Cloud Run via `enable_cloudsql_volume` default `true`; a proxy sidecar on GKE). **Try it** 1. Inspect the database from the CLI and confirm it has no public IP: ```bash gcloud sql instances list gcloud sql instances describe \ --format="yaml(settings.availabilityType, settings.ipConfiguration, settings.backupConfiguration)" ``` Note `pointInTimeRecoveryEnabled: true` and the absence of a public address. 2. In the portal, add a bucket: `storage_buckets = [{ name_suffix = "media", versioning_enabled = true, storage_class = "NEARLINE" }]`, redeploy, then verify: ```bash gcloud storage buckets list --format="table(name, storageClass, versioning_enabled)" gcloud storage cp /etc/hostname gs:///test.txt && gcloud storage ls -L gs:///test.txt ``` 3. Set `create_redis = true` and `create_filestore_nfs = true` in Services_GCP, redeploy, then: `gcloud redis instances list --region=us-central1` and `gcloud filestore instances list`. Check the Redis AUTH and private IP in the output. 4. You know it worked when the bucket shows `NEARLINE` class with versioning on, and the SQL instance shows `availabilityType: ZONAL` with backups enabled. **Check yourself**
Q1: The application needs a shared read-write filesystem mounted by 10 Cloud Run instances simultaneously. GCS, Filestore, or a persistent disk? A: Filestore (or the module's NFS server) — it is a managed NFS file share supporting concurrent multi-writer POSIX access, which RAD mounts via `enable_nfs`/`nfs_mount_path`. Persistent disks are single-writer block devices for VMs; GCS is object storage (the GCS Fuse mount is eventually-consistent object semantics, not a POSIX filesystem).
Q2: Production launch review: the Cloud SQL instance must survive a zone outage. Which single variable changes, and what does it actually do? A: `postgres_database_availability_type = "REGIONAL"`. Cloud SQL then maintains a synchronous standby in a second zone of the same region with automatic failover. It roughly doubles instance cost and is not the same as a read replica (asynchronous, zonal in this module, no automatic failover).
Q3: Why does the module reject redis_tier = "BASIC" when resource_labels.environment = "production"? A: BASIC tier is a single node with no replication and no SLA — a maintenance event or node failure flushes the cache and causes downtime. STANDARD_HA adds a replica with automatic failover, and only STANDARD_HA supports RDB/AOF persistence in this module.
**Beyond the modules** — Not implemented: BigQuery, Spanner, Bigtable, Datastore mode, Pub/Sub as an application messaging bus, Memcached, and Storage Transfer Service. For the exam: load a CSV into BigQuery (`bq load` + `bq query --dry_run` for cost estimation), create and delete a small Spanner instance, publish/pull a Pub/Sub message (`gcloud pubsub topics create t && gcloud pubsub subscriptions create s --topic=t`), and review GCS storage classes (Standard/Nearline/Coldline/Archive with 0/30/90/365-day minimums). **⚠️ Exam trap** — "Backups enabled" ≠ unlimited recovery: PITR (enabled here with 7-day transaction log retention) lets you restore to a moment in time within the window; daily backups alone only restore to backup snapshots. MySQL in this module has binary logging but no PITR configuration block — don't assume parity between engines. --- ## 2.3 Planning and implementing networking resources > ⏱ ~75 min · 💰 the Cloud Armor + global LB lab adds a forwarding-rule and policy cost · ⚙️ Requires: Baseline platform; `enable_cloud_armor = true` with a domain for the LB lab **Why the exam cares** — You must be able to create a custom-mode VPC with subnets, write firewall rules with priorities and target tags, give private instances outbound internet via Cloud NAT, and choose the right load balancer (global external Application LB for HTTP(S), passthrough Network LB for TCP/UDP). Private access to managed services (Private Services Access vs Private Google Access vs Private Service Connect) is a recurring scenario. **How RAD implements it** — `Services_GCP` builds a custom-mode VPC `vpc-network-{prefix}` (subnets are not auto-created) with one subnet per region in `availability_regions` (default `["us-central1"]`, CIDRs from `subnet_cidr_range`, default `["10.0.0.0/24"]`), a Cloud Router + Cloud NAT per region (`{network}-nat-gw-{region}`), and a Private Services Access peering range (`/16`) used by Cloud SQL, Redis, and Filestore for private IPs. Firewall rules are tag- and range-based: `{network}-fw-allow-lb-hc` admits Google health-check ranges `130.211.0.0/22` and `35.191.0.0/16`; `{network}-fw-allow-iap-ssh` admits IAP TCP forwarding range `35.235.240.0/20` on tcp:22; NFS/Redis rules target tags `nfsserver`/`redisserver`; HTTP rules target `httpserver`/`webserver` tags on 80/443/8080/8443. GKE secondary ranges (pods/services) are carved out of `gke_pod_base_cidr` (default `10.64.0.0/10`) and `gke_service_base_cidr` (default `10.8.0.0/16`) only when the cluster is enabled. On the edge: in `App_CloudRun`, `vpc_egress_setting` (default `PRIVATE_RANGES_ONLY`, or `ALL_TRAFFIC`) controls Direct VPC egress (the service gets a network interface in the subnet — no Serverless VPC Access connector is used), and `ingress_settings` (default `all`) controls who can reach the service. `enable_cloud_armor` (default `false`) provisions a global external Application Load Balancer — serverless NEG → backend service with a Cloud Armor policy (preconfigured OWASP sqli/xss/lfi/rce rules, 500 req/min/IP rate limit, Adaptive Protection) → URL map → HTTPS proxy → global static IP — with `application_domains` optional (a zero-config `.nip.io` certificate is derived when it is empty); Google-managed certificates are issued per domain when you do supply one, and `enable_cdn` (default `false`) turns on Cloud CDN at the backend service. In `App_GKE`, `enable_custom_domain` uses the Gateway API (`gke-l7-global-external-managed` GatewayClass) with Certificate Manager, `reserve_static_ip` (default `true`) holds a global address, and `enable_cloud_armor` activates the same Gateway, falling back to a derived `.nip.io` HTTPS certificate when no custom domain is set. **Try it** 1. Walk the network from the CLI: ```bash gcloud compute networks list --filter="name~vpc-network" gcloud compute networks subnets list --network= gcloud compute routers list gcloud compute routers nats list --router= --region=us-central1 gcloud compute firewall-rules list --filter="network~" \ --format="table(name, sourceRanges.list(), allowed[].map().firewall_rule().list(), targetTags.list())" ``` 2. See Private Services Access: **VPC network > VPC network peering** shows `servicenetworking-googleapis-com`; `gcloud compute addresses list --global --filter="purpose=VPC_PEERING"` shows the reserved /16. 3. Enable `enable_cloud_armor = true` with `application_domains = ["app.example.com"]` (a domain you control), redeploy, then inspect the LB and the WAF policy: ```bash gcloud compute forwarding-rules list --global gcloud compute security-policies list gcloud compute security-policies describe --format="table(rules[].priority, rules[].action, rules[].description)" ``` 4. Flip `vpc_egress_setting` to `ALL_TRAFFIC` and observe in **Cloud Run > service > Networking** that all egress now routes through the VPC (and therefore out via Cloud NAT). 5. You know it worked when the firewall list shows the health-check and IAP ranges above, and the security policy shows deny(403) WAF rules plus a rate-based ban rule. **Check yourself**
Q1: The Cloud SQL instance has no public IP, yet Cloud Run connects to it. Name the two mechanisms involved. A: Private Services Access gives the Cloud SQL instance a private IP in a peered Google-managed range, and Cloud Run reaches that RFC 1918 address through Direct VPC egress (`vpc_egress_setting = "PRIVATE_RANGES_ONLY"` routes private-range traffic into the VPC), with the Cloud SQL Auth Proxy handling authentication/encryption.
Q2: A VM in the subnet must download OS packages but must never be reachable from the internet. What provides this, and what would you check if downloads fail? A: Cloud NAT — it gives instances without external IPs outbound internet access with no inbound exposure. If downloads fail, check that the NAT gateway covers the subnet/region (`gcloud compute routers nats describe`) and that no egress-deny firewall rule outranks the default allow.
Q3: Why does enabling Cloud Armor in App_CloudRun also flip ingress away from "all"? A: Cloud Armor evaluates traffic at the load balancer. If the Cloud Run service still accepted direct `run.app` traffic (`ingress = all`), attackers could bypass the WAF entirely; restricting ingress to `internal-and-cloud-load-balancing` forces every request through the protected path.
**Beyond the modules** — Not implemented: Shared VPC (host/service projects), VPC peering between your own VPCs, Cloud DNS zones and records, Cloud VPN / Interconnect, custom static routes, VPC flow logs, and internal load balancers. For the exam: create a private Cloud DNS zone (`gcloud dns managed-zones create`), peer two scratch VPCs and verify non-transitivity, review HA VPN (99.99% SLA, requires Cloud Router/BGP), and practice `gcloud compute networks subnets expand-ip-range` (ranges can grow, never shrink). **⚠️ Exam trap** — Firewall rule priority: lower number wins, default rules sit at 65534, and an "allow" does not override a higher-priority "deny". Also remember health-check ranges (`130.211.0.0/22`, `35.191.0.0/16`) must be allowed or your load balancer marks all backends unhealthy — the module creates this rule for you, which is why it "just works". --- ## 2.4 Planning and implementing resources through infrastructure as code > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: any deployed profile **Why the exam cares** — ACE expects you to understand what IaC buys you (declarative desired state, plan-before-apply diffs, repeatability, drift correction), the basic Terraform workflow (`init → validate → plan → apply`), remote state, and Google-native tooling (Cloud Foundation Toolkit, Config Connector). You don't need to write modules from scratch. **How RAD implements it** — The entire RAD platform *is* IaC: your deployment portal collects variable values and runs OpenTofu inside Cloud Build (a create pipeline for the first deploy and an update pipeline for changes: `tofu init → plan → apply`). Every portal toggle in this guide is a Terraform variable; `deploy_application` (default `true`) is a good example of declarative control — set it to `false` and the next apply removes the workload while keeping the supporting infrastructure (VPC, database, buckets) intact. The modules also demonstrate plan-time *policy*: 23 preconditions in `App_CloudRun` and 32 in `App_GKE` reject invalid combinations (e.g. CDN without a custom domain) before any API call is made. **Try it** 1. See the IaC workflow run end-to-end: trigger any deploy from the portal, then `gcloud builds list --limit=5` and open the latest build's log to watch the `init → plan → apply` steps execute in order. 2. In that plan output, find where your portal field (e.g. `min_instance_count`) lands on the Cloud Run service — connect a portal toggle to the concrete API attribute it sets. 3. Observe drift correction: in the console, manually change your Cloud Run service's memory limit (**Edit & deploy new revision**), then trigger an update from the portal with unchanged variables and watch the plan revert your manual edit. 4. Re-read the build log's plan/apply steps to see exactly which create/update/no-op actions the apply performed. 5. You know it worked when the plan output for step 3 shows your console edit being reverted (an in-place update back to `512Mi` or your configured value). **Check yourself**
Q1: A teammate "fixed" production by editing a firewall rule in the console. The next scheduled IaC apply un-fixed it. What happened and what is the correct workflow? A: Terraform reconciles real resources to the declared configuration, so out-of-band console edits are reverted as drift. The correct workflow is to change the variable/configuration in source (or the portal) and apply through the pipeline — console edits to IaC-managed resources should be reserved for break-glass emergencies and immediately backported.
Q2: Why does `tofu plan` matter on the exam (and in this platform) before `apply`? A: `plan` computes the exact create/update/destroy diff against state without touching anything, letting you catch destructive changes (e.g. a database replacement) before they happen. The RAD pipeline always runs `plan -out=plan.tfplan` and applies that saved plan, guaranteeing what was reviewed is what executes.
**Beyond the modules** — The portal abstracts state management, so practice separately: configure a GCS backend with versioning for remote state (`terraform { backend "gcs" { bucket = "..." } }`), know why remote state + locking matters for teams, and skim Config Connector (GCP resources as Kubernetes CRDs) and the Cloud Foundation Toolkit/Terraform blueprints. Also drill the raw CLI equivalents the exam loves: `gcloud compute instances create`, `gcloud container clusters create-auto`, `gcloud run deploy` — IaC questions are often really "do you know what this automates". **⚠️ Exam trap** — `terraform destroy` (and the portal's purge) deletes *everything in state*, not just "unused" resources. Conversely, resources created outside IaC are invisible to it — deleting the Terraform deployment won't clean up your console experiments. --- # ACE Certification Preparation Guide: Section 3 — Ensuring successful operation of a cloud solution (~27% of the exam) ACE Certification Preparation Guide: Section 3 — Ensuring successful operation of a cloud solution (~27% of the exam) > 📚 **Official exam guide:** [Associate Cloud Engineer certification](https://cloud.google.com/learn/certification/cloud-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 3 — day-2 operations — using the RAD platform foundation modules. `App_CloudRun` and `App_GKE` carry most of the load (revisions, traffic, CI/CD, backups, alerts); `Services_GCP` supplies the infrastructure alerts and audit logging. Deploy the **Serverless application** profile (plus the **Kubernetes application** profile for the `kubectl` labs and the **Operations & security add-ons** profile for 3.4) from the [Lab Map](ACE_Certification_Guide.md). --- ## 3.1 Managing compute resources > ⏱ ~90 min · 💰 low; Cloud Deploy stages each run their own service — destroy after the lab · ⚙️ Requires: Serverless application profile; `enable_cicd_trigger` + a GitHub repo for the CI/CD lab **Why the exam cares** — Operating compute means deploying new versions safely (canary/blue-green via traffic splitting), scaling manually and automatically, and working a Kubernetes cluster from the CLI (`kubectl get/describe/logs/scale`). Expect questions on shifting Cloud Run traffic between revisions and on diagnosing pods. **How RAD implements it** — *Revisions and traffic (Cloud Run):* every portal update creates a new revision; `max_revisions_to_retain` (default `7`) prunes older non-serving revisions. `traffic_split` (default `[]` = 100% to latest) takes a list of `{ type, revision, percent, tag }` entries that must sum to exactly 100 (validated at plan time), e.g. 90% `TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST` / 10% to a named revision — a declarative canary. *CI/CD:* `enable_cicd_trigger` (default `false`) with `github_repository_url` and `github_token` creates a Cloud Build trigger (`cicd_trigger_config.branch_pattern` default `"^main$"`) that builds with Kaniko, pushes to Artifact Registry, and either updates the service directly or — when `enable_cloud_deploy = true` — creates a Cloud Deploy release through `cloud_deploy_stages` (default `dev` → `staging` → `prod`, with `require_approval = true` on prod). Note that `enable_cloud_deploy = true` without `enable_cicd_trigger = true` is rejected at plan time — Cloud Deploy releases only come from the CI/CD pipeline. *Kubernetes operations (App_GKE):* the HPA spans `min_instance_count` (default `1`) to `max_instance_count` (default `3`); `enable_pod_disruption_budget` (default `true`) creates a PDB with `pdb_min_available` (default `"1"`, skipped when `max_instance_count = 1`); `enable_resource_quota` (default `false`) caps the namespace at `quota_cpu_requests`/`quota_cpu_limits` (default `"4"`), `quota_memory_requests` (default `"4Gi"`) / `quota_memory_limits` (default `"8Gi"`) — memory values *must* carry a binary suffix (`Gi`/`Mi`), enforced by a plan-time validation, because Kubernetes treats a bare `"4"` as 4 bytes and would block all scheduling. `cron_jobs` deploys Kubernetes CronJobs. *Compute Engine operations:* the `Services_GCP` NFS VM runs in a MIG with auto-healing health checks and a daily snapshot schedule with 7-day retention — a live example of snapshot-based VM protection. **Try it** 1. Deploy a visible change (e.g. set an env var in `environment_variables`), then split traffic in the portal: `traffic_split = [{ type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST", percent = 90 }, { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION", revision = "-0000X", percent = 10, tag = "previous" }]`. Verify, then practice the imperative equivalent the exam tests: ```bash gcloud run revisions list --service= --region=us-central1 gcloud run services update-traffic --region=us-central1 \ --to-revisions=-0000X=10,LATEST=90 gcloud run services describe --region=us-central1 --format="yaml(status.traffic)" ``` 2. On GKE, exercise the core `kubectl` loop: ```bash gcloud container clusters get-credentials gke-cluster-1 --region=us-central1 kubectl get pods -n -o wide kubectl describe pod -n kubectl logs -n --tail=50 kubectl scale deployment -n --replicas=3 # HPA will reconcile this kubectl get pdb,resourcequota -n ``` 3. With CI/CD enabled, push a commit to `main` and watch: `gcloud builds list --limit=3`, then **Cloud Deploy > Delivery pipelines** to promote dev → staging and approve prod (`gcloud deploy rollouts approve ...` is the CLI form). 4. You know it worked when `status.traffic` shows your 90/10 split and the tagged revision serves on its own tag URL. **Check yourself**
Q1: Five minutes after a release, error rates spike on the new Cloud Run revision. Fastest rollback? A: `gcloud run services update-traffic --to-revisions==100`. Revisions are immutable, so the previous one is still deployed and warm — shifting traffic is instant and requires no rebuild or redeploy. This is exactly what `traffic_split` declares in Terraform form.
Q2: You run kubectl scale deployment app --replicas=10 but pods drop back to 3. Why? A: A HorizontalPodAutoscaler owns the replica count (the module creates one spanning `min_instance_count`–`max_instance_count`, default max 3). The HPA reconciles manual scaling back into its bounds; to scale higher you raise `max_instance_count` (or edit/remove the HPA), not the Deployment.
Q3: During cluster maintenance, why does at least one pod of the app always stay up? A: `enable_pod_disruption_budget` (default true) creates a PDB with `minAvailable: 1`, so voluntary disruptions (node drains, upgrades) cannot evict the last available pod. Note it does not protect against involuntary failures like node crashes.
**Beyond the modules** — VM SSH workflows are not part of the app modules: practice `gcloud compute ssh --tunnel-through-iap` (the module's `fw-allow-iap-ssh` rule for `35.235.240.0/20` already permits this to the NFS VM), `gcloud compute instances list --filter="status=RUNNING"`, on-demand snapshots (`gcloud compute disks snapshot`), creating images from disks, and MIG rolling updates (`gcloud compute instance-groups managed rolling-action start-update`). Also study GKE Standard node-pool resize/upgrade commands, which Autopilot hides. **⚠️ Exam trap** — `gcloud run deploy` always sends 100% of traffic to the new revision *unless* the service was previously set to manual traffic control (`--no-traffic` / explicit splits). If a question says "deploy without serving traffic", the answer involves `--no-traffic` and tags, mirroring the `traffic_split` `tag` field here. --- ## 3.2 Managing storage and database solutions > ⏱ ~60 min · 💰 negligible (backup bucket storage) · ⚙️ Requires: Serverless application profile with a database (`database_type` ≠ `NONE`) **Why the exam cares** — Day-2 data work: running and restoring backups, understanding PITR vs snapshot restore, lifecycle-managing objects, and connecting to databases to run queries. Scenario questions usually test whether you know *which* recovery mechanism fits an RPO, and how to keep storage costs down automatically. **How RAD implements it** — `App_CloudRun`/`App_GKE` provision an automated logical-backup pipeline: a Cloud Scheduler job triggers a Cloud Run job that runs the backup export on `backup_schedule` (default `"0 2 * * *"` UTC), dumping the application database to a dedicated GCS backup bucket whose lifecycle rule deletes objects older than `backup_retention_days` (default `7`). Restores are first-class: `enable_backup_import` (default `false`) with `backup_source` (`gcs` or `gdrive`), `backup_file`, and `backup_format` runs a one-time import job. `enable_custom_sql_scripts` (default `false`) executes `.sql` files from `custom_sql_scripts_bucket`/`custom_sql_scripts_path` in lexicographic order (a non-empty path is enforced at plan time), optionally as the root DB user. Independently of these logical dumps, the Cloud SQL instance itself keeps 7 automated daily backups (04:00 UTC) with PITR enabled and 7-day transaction-log retention. Bucket hygiene is demonstrated by the platform's object-storage layer: per-bucket `versioning_enabled`, `lifecycle_rules` (age, newer-version count, storage-class transitions), soft-delete policy, and `public_access_prevention` (default `enforced`). **Try it** 1. Trigger a backup right now instead of waiting for the schedule: ```bash gcloud scheduler jobs list --location=us-central1 gcloud scheduler jobs run --location=us-central1 gcloud run jobs executions list --region=us-central1 --limit=3 gcloud storage ls -l gs:/// ``` 2. Inspect the managed-backup side: `gcloud sql backups list --instance=` and `gcloud sql instances describe --format="yaml(settings.backupConfiguration)"` — note `transactionLogRetentionDays: 7`. 3. Add a lifecycle transition to a bucket in the portal (`lifecycle_rules` with an age-based `SetStorageClass` to `NEARLINE`), redeploy, and verify: `gcloud storage buckets describe gs:// --format="yaml(lifecycle_config)"`. 4. Run a custom SQL script: upload `001_create_table.sql` to a bucket, set `enable_custom_sql_scripts = true` with the bucket/path, redeploy, and read the job logs with `gcloud run jobs executions describe --region=us-central1`. 5. You know it worked when a fresh timestamped dump appears in the backup bucket and `gcloud sql backups list` shows the 7 retained automatic backups. **Check yourself**
Q1: An engineer dropped a table at 14:32. The last nightly dump is from 02:00. What's the lowest-data-loss recovery, and why is it available here? A: Point-in-time recovery — restore (clone) the Cloud SQL instance to 14:31. PITR is enabled on the instance with 7-day transaction log retention, so any second in that window is recoverable; the 02:00 GCS dump would lose 12.5 hours of writes. The exam expects you to know PITR creates a new instance rather than rewinding the existing one.
Q2: How do you keep backup-bucket costs flat without any manual cleanup? A: An object lifecycle rule that deletes objects older than N days — exactly what `backup_retention_days` configures on the backup bucket. Lifecycle management is evaluated daily by GCS itself; no jobs or cron needed on your side.
**Beyond the modules** — `gcloud sql connect` is worth practicing but won't work against these instances directly because they have no public IP — connect via the Cloud SQL Auth Proxy (`cloud-sql-proxy `) or Cloud SQL Studio in the console. Also study: on-demand backups (`gcloud sql backups create --instance=...`), cross-product backup surfaces (Firestore export/import to GCS, GKE Backup — note `Services_GCP` has `enable_gke_backup`, default `false`, schedule `0 3 * * *`, 30-day retention), BigQuery job history (`bq ls -j`), and the Pricing Calculator for storage cost estimation. **⚠️ Exam trap** — Object *versioning* and lifecycle *deletion* interact: deleting a versioned object creates a noncurrent version that still bills until a `num_newer_versions`/age rule purges it. "We enabled versioning and storage costs doubled" is a classic scenario. --- ## 3.3 Managing networking resources > ⏱ ~40 min · 💰 a reserved-but-unattached static IP bills hourly — release after the lab · ⚙️ Requires: Kubernetes application profile (for `reserve_static_ip`) or any Cloud Armor deployment **Why the exam cares** — Operations on live networks: reserving static internal/external IPs, adding subnets or expanding ranges as workloads grow, and keeping firewall rules current. The exam favors `gcloud compute addresses` and subnet-expansion commands. **How RAD implements it** — Two operational patterns are live: - *Static IPs:* `App_GKE`'s `reserve_static_ip` (default `true`) reserves a **global** static external IP for the Gateway/load balancer (`static_ip_name` optional override); `App_CloudRun` likewise creates a global static IP when its load balancer is enabled, and the `Services_GCP` NFS VM holds a reserved *internal* address so the share's IP survives instance replacement. - *Subnets per region:* adding a region to `availability_regions` in `Services_GCP` creates a new subnet, router, and NAT gateway in that region on the next apply without touching existing subnets. **Try it** 1. List reserved addresses and identify which are global vs regional, internal vs external: ```bash gcloud compute addresses list gcloud compute addresses describe --global ``` 2. In the portal, set `reserve_static_ip = false` on App_GKE and redeploy; observe the Gateway now uses an ephemeral IP that can change on recreation — then set it back to `true` (production hygiene). 3. Practice the manual exam commands in your project: ```bash gcloud compute addresses create lab-ip --region=us-central1 gcloud compute addresses delete lab-ip --region=us-central1 --quiet ``` 4. You know it worked when `gcloud compute addresses list` shows the module's global address with status `IN_USE` (attached to a forwarding rule). **Check yourself**
Q1: After a maintenance redeploy, customers report DNS no longer resolves to the application. The Gateway IP changed. What was misconfigured? A: The load balancer was using an ephemeral IP (`reserve_static_ip = false`). Ephemeral external IPs can change whenever the fronting resource is recreated; production endpoints referenced by DNS must use a reserved static address — exactly why the module defaults to `true`.
Q2: A global external Application Load Balancer needs an IP. Regional or global reservation? A: Global (`gcloud compute addresses create NAME --global`). Global LBs use a single anycast IP; regional addresses attach to regional resources (VMs, regional LB forwarding rules). Picking the wrong scope is a common wrong-answer option.
**Beyond the modules** — Not implemented: custom static routes, VPC peering management, Cloud DNS record operations, and subnet IP-range expansion (the module creates subnets but you should practice growing one): `gcloud compute networks subnets expand-ip-range --region=us-central1 --prefix-length=23` (expansion only — ranges can never shrink). Also review **VPC network > Routes** to understand system-generated routes vs custom routes with next hops. **⚠️ Exam trap** — A reserved external static IP that is *not attached* to anything still incurs charges; releasing unused addresses is a standard cost-cleanup answer (and an Active Assist recommendation). --- ## 3.4 Monitoring and logging > ⏱ ~75 min · 💰 audit logging increases Cloud Logging volume/cost · ⚙️ Requires: `support_users` set; `configure_email_notification = true` + `notification_alert_emails` on Services_GCP; `enable_audit_logging = true` for the audit lab **Why the exam cares** — You must read and filter logs in Logs Explorer, create alert policies on metrics, understand notification channels, know which audit logs exist by default (Admin Activity: always on; Data Access: opt-in), and diagnose workloads from their telemetry. **How RAD implements it** — *Metrics and alerts:* setting `support_users` creates email notification channels plus built-in alert policies — for Cloud Run, CPU utilization > 90% and memory utilization > 90% (P99 over 60s windows, via the platform's monitoring layer); `alert_policies` (default `[]`) adds custom threshold policies on any metric type, auto-filtered to your service. `Services_GCP` adds infrastructure alerts when `configure_email_notification = true` with `notification_alert_emails`: Cloud SQL CPU/memory/disk against `alert_cpu_threshold`/`alert_memory_threshold`/`alert_disk_threshold` (all default `80`), and NFS VM CPU/memory/instance-down policies — the NFS memory alert reads the Ops Agent metric `agent.googleapis.com/memory/percent_used`. A Cloud Monitoring dashboard is created per application. *Probes:* `startup_probe_config` and `health_check_config` define HTTP/TCP startup and liveness probes on both platforms — Kubernetes-style health checking you can see in the revision/pod spec. *Logging:* GKE clusters ship `SYSTEM_COMPONENTS` and `WORKLOADS` logs and enable Managed Prometheus. `enable_audit_logging` (default `false`) turns on `allServices` ADMIN_READ/DATA_READ/DATA_WRITE Data Access audit logs plus explicit Secret Manager and KMS configs. *Uptime checks:* `uptime_check_config` (default `{ enabled = false, path = "/" }` — you must set `enabled = true`; `check_interval` default `"60s"`, `timeout` default `"10s"`) creates a `-uptime-check` — an HTTP GET probe from multiple global regions — plus a `-uptime-check-alert` policy on `monitoring.googleapis.com/uptime_check/check_passed` that notifies the `support_users` channels (via the platform's monitoring layer). The check is only created when the endpoint is publicly reachable (e.g. a custom domain, the nip.io LB host, or the run.app URL with `ingress_settings = "all"`); internal-only deployments get none. The `uptime_check_names` output returns the created check's name. **Try it** 1. List what monitoring the modules created: ```bash gcloud beta monitoring channels list --format="table(displayName, labels.email_address)" gcloud alpha monitoring policies list --format="table(displayName, enabled)" ``` (Console: **Monitoring > Alerting** and **Monitoring > Dashboards**. If your service is publicly reachable, also open **Monitoring > Uptime checks** and find `-uptime-check` probing from multiple regions.) 2. Read your application's logs with exam-style filters: ```bash gcloud logging read 'resource.type="cloud_run_revision" AND severity>=ERROR' --limit=10 gcloud logging read 'resource.type="k8s_container" AND resource.labels.namespace_name=""' --limit=10 ``` 3. Enable `enable_audit_logging = true`, redeploy, perform an action (read a secret value in the console), then find it: ```bash gcloud logging read 'logName:"cloudaudit.googleapis.com%2Fdata_access" AND protoPayload.serviceName="secretmanager.googleapis.com"' --limit=5 ``` 4. Break a probe on purpose: set `health_check_config.path` to `/broken`, redeploy, and watch the revision fail to become ready (Cloud Run) or the pod restart-loop (`kubectl describe pod` shows failing liveness probes). Revert. 5. You know it worked when the policies list shows the CPU/memory alerts and step 3 returns a Data Access entry naming your principal. **Check yourself**
Q1: Security asks "who read the database password last Tuesday?" — can you answer with default settings? A: No. Secret *reads* are Data Access (DATA_READ) audit events, which are disabled by default; only Admin Activity (e.g. changing IAM, creating secrets) is always on. With `enable_audit_logging = true` the module enables Data Access logs for Secret Manager (and all services), making the question answerable from Logs Explorer.
Q2: An alert policy exists and its condition fires, but nobody is emailed. First thing to check? A: Notification channels — a policy with no (or unverified) channels evaluates conditions but notifies no one. In RAD terms: `support_users`/`notification_alert_emails` must be non-empty, which is what creates and attaches the email channels.
Q3: A GKE pod is in CrashLoopBackOff. Give the two-command diagnosis sequence. A: `kubectl describe pod -n ` (events: image pull errors, OOMKilled, failing probes) then `kubectl logs -n --previous` (output of the crashed container, not the current restart). The `--previous` flag is the detail the exam likes.
**Beyond the modules** — Not implemented: log sinks/Log Router exports (to BigQuery, GCS, Pub/Sub), log-based metrics, log bucket retention configuration, Cloud Trace/Profiler, and Ops Agent *installation* (the module assumes it on the NFS VM for the memory metric). Practice: create a log-based counter metric from a Logs Explorer query and create a sink with `gcloud logging sinks create`. Know the `_Required` sink (Admin Activity, 400-day retention, cannot be disabled) vs `_Default` (30-day retention). **⚠️ Exam trap** — Admin Activity audit logs are free and always on; Data Access audit logs are opt-in, billable, and high-volume (BigQuery is the one service with Data Access enabled by default). Mixing these up is the most common Section 3.4 error. --- # ACE Certification Preparation Guide: Section 4 — Configuring access and security (~20% of the exam) ACE Certification Preparation Guide: Section 4 — Configuring access and security (~20% of the exam) > 📚 **Official exam guide:** [Associate Cloud Engineer certification](https://cloud.google.com/learn/certification/cloud-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 4 using the RAD platform foundation modules. Security is where the modules shine as a lab: every deployment creates dedicated least-privilege service accounts (`Services_GCP` plus the platform's IAM layer), `App_GKE` uses Workload Identity, `Services_GCP` can stand up Workload Identity Federation, and secrets live exclusively in Secret Manager. Deploy the **Serverless application** profile plus the **Operations & security add-ons** profile (IAP, audit logging) from the [Lab Map](ACE_Certification_Guide.md). --- ## 4.1 Managing Identity and Access Management (IAM) > ⏱ ~60 min · 💰 no additional cost (audit logging adds log volume) · ⚙️ Requires: any deployed profile; `enable_audit_logging = true` for the audit-log lab **Why the exam cares** — IAM questions test the policy model (principal + role + resource, inherited down the hierarchy), the three role types (basic, predefined, custom) and when each is appropriate, and reading/troubleshooting effective access. The recurring decision criterion: prefer predefined roles on groups; never hand out `roles/owner`/`roles/editor` in production; basic roles are pre-IAM legacy. **How RAD implements it** — The modules are a worked example of least privilege: - `Services_GCP` creates `cloudbuild-sa-{prefix}`, `clouddeploy-sa-{prefix}`, `cloudrun-sa-{prefix}`, `nfs-sa-{prefix}`, and `gke-sa-{prefix}`, each granted only the predefined roles its job needs — no basic roles anywhere. - The platform's IAM layer narrows further: the runtime service account gets `roles/secretmanager.secretAccessor` *per secret* and `roles/storage.objectAdmin` *per bucket* (resource-level bindings, not project-level), and Cloud Build gets `roles/iam.serviceAccountUser` only on the identity it must deploy as. - `enable_audit_logging` (default `false`, available on `Services_GCP` and both app modules) enables Data Access audit logs (`ADMIN_READ`/`DATA_READ`/`DATA_WRITE`) for `allServices` plus explicit Secret Manager and KMS configs — the mechanism for answering "who did what". - `support_users` (default `[]`) is the human-principal entry point: emails become monitoring notification targets; bind your operators as groups where possible. **Try it** 1. Dump and read the project IAM policy the way the exam expects: ```bash gcloud projects get-iam-policy $GOOGLE_CLOUD_PROJECT \ --flatten="bindings[].members" \ --filter="bindings.members:serviceAccount" \ --format="table(bindings.members, bindings.role)" | sort ``` Confirm the module SAs hold only narrow predefined roles. 2. See a *resource-level* binding (a concept many candidates miss): ```bash gcloud secrets get-iam-policy gcloud storage buckets get-iam-policy gs:// ``` The runtime SA appears here, not in the project policy — least privilege in action. 3. Explore role definitions: `gcloud iam roles describe roles/secretmanager.secretAccessor` — note it contains essentially one permission (`secretmanager.versions.access`). Compare with `gcloud iam roles describe roles/editor` to see why basic roles are discouraged. 4. With audit logging enabled, change any IAM binding in the console, then find it: ```bash gcloud logging read 'protoPayload.methodName="SetIamPolicy"' --limit=5 \ --format="table(timestamp, protoPayload.authenticationInfo.principalEmail)" ``` 5. You know it worked when steps 2–4 show per-resource bindings, single-permission predefined roles, and your own email on the `SetIamPolicy` entry. **Check yourself**
Q1: A developer needs to view Cloud Run services and read their logs — nothing else. Which roles, and why not roles/viewer? A: `roles/run.viewer` plus `roles/logging.viewer` — predefined roles scoped to exactly the needed services. `roles/viewer` (a basic role) grants read access across nearly *every* service in the project, violating least privilege and exposing data (e.g. listing secrets, reading buckets' metadata) the developer has no business seeing.
Q2: A service account has roles/secretmanager.secretAccessor on secret app-db-password only, but the exam scenario says it "can't list secrets in the console". Is something wrong? A: No — `secretAccessor` permits *reading versions* of that one secret, not listing secrets (that needs `secretmanager.secrets.list`, in roles like `roles/secretmanager.viewer` at project level). Resource-level grants don't confer project-level browse access; this asymmetry is intended and frequently tested.
Q3: Access granted at the folder level — can a project-level admin in a child project remove it? A: No. IAM policies are inherited downward and the *effective* policy is the union of all levels; a child resource cannot revoke or restrict a grant made on its ancestor. You'd need to change the folder-level binding (or use IAM deny policies / conditions, managed above the project).
**Beyond the modules** — Not implemented: custom role creation, IAM Conditions, Policy Troubleshooter, and org-level policy administration. For the exam: create a throwaway custom role (`gcloud iam roles create labRole --project=$GOOGLE_CLOUD_PROJECT --permissions=run.services.list`), run **IAM & Admin > Policy Troubleshooter** against a principal/resource/permission triple, and review IAM role recommendations in **IAM** (Active Assist flags over-granted bindings based on 90-day usage). **⚠️ Exam trap** — Removing a user from IAM does not invalidate already-issued access tokens (up to ~1 hour) and does not touch resource-level bindings you may have forgotten — checking *both* project and resource policies is the complete answer. --- ## 4.2 Managing service accounts > ⏱ ~75 min · 💰 no additional cost · ⚙️ Requires: Serverless or Kubernetes application profile; `enable_iap = true` with authorized users for the IAP lab **Why the exam cares** — Service accounts are the workload identity story: creating dedicated SAs instead of using defaults, attaching them to compute, avoiding exported JSON keys (Workload Identity / Workload Identity Federation / impersonation instead), and protecting application credentials. The exam's consistent theme: *keys are a last resort*. **How RAD implements it** — *Dedicated runtime identities:* the Cloud Run service runs as its tenant-scoped `cloudrun-sa-*`, never the default Compute Engine service account. On GKE, `App_GKE` implements Workload Identity end-to-end: a Kubernetes ServiceAccount annotated `iam.gke.io/gcp-service-account`, and a `roles/iam.workloadIdentityUser` binding to `serviceAccount:{project}.svc.id.goog[namespace/ksa]` — pods get short-lived Google credentials with no key file anywhere. *Keyless CI/CD:* `Services_GCP`'s `enable_workload_identity_federation` (default `false`) creates pool `wif-pool` with a provider per `wif_provider_type` (default `"github"`; also `gitlab` or `generic` OIDC) and binds the pool's principals (`roles/iam.workloadIdentityUser`) to the Cloud Build, Cloud Deploy, and Cloud Run SAs — external CI authenticates by exchanging its OIDC token, no exported keys. *Impersonation:* the platform itself runs as `resource_creator_identity`, and `impersonation_service_account` (default `""`) makes the modules' shell scripts call GCP APIs as a target SA — the same `--impersonate-service-account` pattern the exam tests for humans. *Secrets:* `secret_environment_variables` maps env var names to Secret Manager secrets resolved at runtime (a secret-reference env source on Cloud Run; the Secret Manager CSI driver on GKE). The DB password is generated randomly (`database_password_length` default `32`), stored only in Secret Manager, and `enable_auto_password_rotation` (default `false`) deploys an Eventarc-triggered rotation job doing a dual-version, zero-downtime rotation (`rotation_propagation_delay_sec` default `90`). The plain `secret_rotation_period` (default `"2592000s"`) only publishes rotation *notifications* — it does not rotate anything by itself. *Identity-gated access:* `enable_iap` (default `false`) turns on IAP. On Cloud Run, the v2 service enables IAP (BETA launch stage) and the module grants `roles/run.invoker` to the IAP service agent and `roles/iap.httpsResourceAccessor` to `iap_authorized_users`/`iap_authorized_groups`. On GKE, IAP additionally requires `iap_oauth_client_id`, `iap_oauth_client_secret`, `iap_support_email`, and at least one authorized principal — all enforced by plan-time validations. **Try it** 1. Confirm the workload runs as a dedicated SA, not the default: ```bash gcloud run services describe --region=us-central1 \ --format="value(spec.template.spec.serviceAccountName)" ``` 2. On GKE, verify Workload Identity from inside a pod: ```bash kubectl get serviceaccount -n -o yaml | grep gcp-service-account kubectl run wi-test -n --rm -it --image=google/cloud-sdk:slim \ --overrides='{"spec":{"serviceAccountName":""}}' \ -- gcloud auth list ``` The active account is the Google SA — no key was mounted. 3. Practice impersonation (grant yourself `roles/iam.serviceAccountTokenCreator` on the SA first): ```bash gcloud storage ls --impersonate-service-account=cloudrun-sa-@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com ``` 4. Inspect secret handling: `gcloud secrets versions list ` and, after enabling `enable_auto_password_rotation`, watch a new version appear while the previous is disabled (dual-version rotation). 5. Enable IAP with `iap_authorized_users = ["user:you@example.com"]`, redeploy, then open the service URL in an incognito window — you are pushed through Google sign-in, and a non-listed account gets a 403. You know it worked when your account passes and others don't. **Check yourself**
Q1: A GKE pod must read a GCS bucket. A teammate suggests mounting a service account JSON key as a Kubernetes Secret. What is the exam-correct alternative and why? A: Workload Identity — bind the pod's KSA to a Google SA with `roles/storage.objectViewer` (the `roles/iam.workloadIdentityUser` pattern used by `App_GKE`). JSON keys never expire, can be exfiltrated by anyone who can read the namespace's secrets, and require manual rotation; Workload Identity issues short-lived tokens automatically with full audit attribution.
Q2: GitHub Actions needs to deploy to Cloud Run. Options: download a key for the Cloud Build SA, or use Workload Identity Federation. Compare. A: WIF (the module's `wif-pool` + GitHub OIDC provider) lets the workflow exchange its GitHub-issued OIDC token for short-lived GCP credentials — nothing stored in repo secrets, automatically scoped and auditable. A downloaded key is a long-lived bearer credential sitting in GitHub secrets; if leaked it works until manually destroyed. The exam answer is WIF (or impersonation) over keys, essentially always.
Q3: With IAP enabled on Cloud Run, a user authenticates successfully with their Google account but still gets a 403. What two grants must both exist? A: The *user* needs `roles/iap.httpsResourceAccessor` (via `iap_authorized_users`/`iap_authorized_groups`), and the *IAP service agent* needs `roles/run.invoker` on the service so it can forward authenticated requests. Authentication (who you are) passing while authorization (what you may access) fails is exactly this split — the module creates both bindings for you.
**Beyond the modules** — Not implemented: service account key creation/rotation workflows (deliberately — the modules avoid keys entirely), short-lived token minting (`gcloud auth print-access-token --impersonate-service-account=...`, `gcloud auth print-identity-token`), disabling/undeleting service accounts, and cross-project SA usage. Practice in a scratch project: `gcloud iam service-accounts create`, `gcloud iam service-accounts keys create` (then delete it and explain why), and `gcloud iam service-accounts add-iam-policy-binding --member=user:you@... --role=roles/iam.serviceAccountTokenCreator` to wire impersonation. Also review the default Compute Engine SA (`PROJECT_NUMBER-compute@developer.gserviceaccount.com`) and why attaching it with Editor-scope access is the canonical anti-pattern. **⚠️ Exam trap** — `roles/iam.serviceAccountUser` (attach/run *as* the SA) and `roles/iam.serviceAccountTokenCreator` (mint tokens to *impersonate* it) are different roles; questions often hinge on which one a deployer or impersonator actually needs. The modules grant `serviceAccountUser` to Cloud Build precisely so it can deploy services that run as the runtime SA. --- # Professional Cloud Architect (PCA) Certification Lab Map The Professional Cloud Architect certification validates your ability to design, plan, and manage secure, scalable, highly available cloud solutions — and to justify the trade-offs behind every design choice. The RAD platform's four foundation modules give you a live laboratory for exactly those trade-offs: `Services_GCP` (the shared platform layer — VPC, Cloud SQL, Redis, Filestore, GKE, CMEK, VPC-SC), `App_CloudRun` and `App_GKE` (two deployment engines for the *same* containerized workload, embodying the serverless-vs-orchestrated decision the PCA exam returns to again and again), and `App_Common` (shared layers implementing discovery, secrets, IAM, storage, and CI/CD patterns). Every toggle in your deployment portal is a design decision you can deploy, inspect in the GCP console, and reverse. ## How to use this guide - Deploy one of the profiles below through your deployment portal, then work through the matching section guide(s). - Each section guide pairs "why the exam cares" decision criteria with the exact variables that implement the concept, hands-on steps, and self-check questions. - Use the coverage legend honestly: 📘 topics (hybrid connectivity, migration planning, Vertex AI, org hierarchy) must be studied outside the platform — the "Beyond the modules" blocks tell you what and where. - **Case studies (📘):** the PCA exam includes scenario questions built on official case studies such as EHR Healthcare, Helicopter Racing League, Mountkirk Games, and TerramEarth. The modules are an excellent rehearsal ground: read a case study's requirements, then write down which portal variables would satisfy each one (e.g. EHR Healthcare's encryption and audit demands map to `enable_cmek`, `enable_audit_logging`, and `enable_vpc_sc`; Mountkirk Games' global, autoscaled, container-based platform maps to GKE Autopilot with a global Gateway and Cloud Armor). Where a requirement has *no* matching variable — hybrid Interconnect for TerramEarth, multi-region Spanner for Mountkirk — you have found a study gap. **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: Lean baseline *Purpose:* the lowest-cost architecture — zonal database, scale-to-zero serverless, self-managed NFS VM — your reference point for every cost/availability trade-off. *Modules:* Services_GCP, then App_CloudRun. | Variable | Value | |---|---| | `create_postgres` | `true` (default) | | `postgres_database_availability_type` | `ZONAL` (default) | | `create_network_filesystem` | `true` (default — e2-small NFS/Redis VM) | | `min_instance_count` | `0` (default, App_CloudRun) | | `max_instance_count` | `1` (default, App_CloudRun) | *Estimated incremental cost:* low — the zonal `db-custom-1-3840` Cloud SQL instance and the e2-small NFS VM dominate; Cloud Run scales to zero. ### Profile: Resilient data tier *Purpose:* upgrade the baseline to high availability so you can compare ZONAL vs REGIONAL Cloud SQL, BASIC vs STANDARD_HA Redis, and VM-based NFS vs managed Filestore side by side. *Modules:* Services_GCP (update in place), App_CloudRun unchanged. | Variable | Value | |---|---| | `postgres_database_availability_type` | `REGIONAL` | | `create_postgres_read_replica` | `true` | | `create_redis` | `true` | | `redis_tier` | `STANDARD_HA` | | `redis_persistence_mode` | `RDB` | | `create_filestore_nfs` | `true` | | `filestore_tier` | `BASIC_HDD` (default) | *Estimated incremental cost:* high — REGIONAL Cloud SQL roughly doubles instance cost, the read replica adds another instance, STANDARD_HA Redis doubles Redis cost, and Filestore bills a minimum 1024 GB. ### Profile: GKE architecture *Purpose:* deploy the same workload on GKE Autopilot to exercise the Cloud Run vs GKE decision, Kubernetes governance (quotas, PDBs, NetworkPolicy), and Gateway API exposure. *Modules:* Services_GCP (update), then App_GKE. | Variable | Value | |---|---| | `create_google_kubernetes_engine` | `true` (Services_GCP) | | `gke_cluster_mode` | `AUTOPILOT` (default) | | `enable_resource_quota` | `true` (App_GKE) | | `enable_network_segmentation` | `true` (App_GKE) | | `enable_pod_disruption_budget` | `true` (default, App_GKE) | | `stateful_pvc_enabled` | `true`, with `stateful_pvc_size = "10Gi"` and a mount path | *Estimated incremental cost:* moderate — Autopilot bills per pod resource request plus a cluster management fee; the stateful PVC adds a small persistent disk. ### Profile: Security and delivery *Purpose:* layer in defense-in-depth (CMEK, Binary Authorization, VPC-SC dry run, audit logs) and a full CI/CD pipeline with progressive delivery — the backbone for Sections 3, 4, and 6. *Modules:* Services_GCP (update), App_CloudRun (update). | Variable | Value | |---|---| | `enable_cmek` | `true` (Services_GCP) | | `enable_binary_authorization` | `true`, `binauthz_evaluation_mode = "REQUIRE_ATTESTATION"` (Services_GCP) | | `enable_vpc_sc` | `true`, `vpc_sc_dry_run = true` (Services_GCP; needs org + `admin_ip_ranges`) | | `enable_audit_logging` | `true` (Services_GCP) | | `enable_cloud_armor` | `true` (`application_domains` optional on App_CloudRun) | | `enable_iap` | `true`, plus `iap_authorized_users` (App_CloudRun) | | `enable_cicd_trigger` | `true`, plus `github_repository_url` (App_CloudRun) | | `enable_cloud_deploy` | `true` (App_CloudRun) | | `enable_auto_password_rotation` | `true` (App_CloudRun) | *Estimated incremental cost:* moderate — KMS keys, the global load balancer forwarding rule, and audit-log storage are the main drivers; VPC-SC and Binary Authorization are free. ## Section 1: Designing and planning a cloud solution architecture (~25% of the exam) The heart of the PCA exam: choosing architectures that satisfy business and technical requirements. The modules embody the canonical trade-offs — serverless vs orchestrated compute, zonal vs regional databases, managed vs self-managed file storage — but business analysis, migration planning, and futures thinking live outside any Terraform module. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Business requirements (cost, security, success measures) | 🟡 | `min_instance_count`, `create_billing_budget`, `enable_iap`, `support_users` | [Section 1 guide](PCA_Section_1_Exploration_Guide.md#11-designing-a-cloud-solution-infrastructure-that-meets-business-requirements) | | 1.2 Technical requirements (HA, scalability, reliability) | ✅ | `postgres_database_availability_type`, `redis_tier`, `create_postgres_read_replica`, HPA/PDB in App_GKE | [Section 1 guide](PCA_Section_1_Exploration_Guide.md#12-designing-a-cloud-solution-infrastructure-that-meets-technical-requirements) | | 1.3 Network, storage, and compute design | ✅ | VPC + Cloud NAT + private services access, Filestore vs self-managed NFS, App_CloudRun vs App_GKE | [Section 1 guide](PCA_Section_1_Exploration_Guide.md#13-designing-network-storage-and-compute-resources) | | 1.4 Creating a migration plan | 📘 | nearest: `enable_backup_import` data import jobs | [Section 1 guide](PCA_Section_1_Exploration_Guide.md#14-creating-a-migration-plan) | | 1.5 Envisioning future solution improvements | 📘 | nearest: layered module architecture, discovery-vs-inline pattern | [Section 1 guide](PCA_Section_1_Exploration_Guide.md#15-envisioning-future-solution-improvements) | ## Section 2: Managing and provisioning a cloud solution infrastructure (~17.5% of the exam) Provisioning is what the modules do for a living: a custom-mode VPC with Cloud NAT and private services access, four database engines, three flavors of file/object storage, and two container platforms — all declaratively. Hybrid topologies and the two Vertex AI subsections are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Configuring network topologies | 🟡 | `availability_regions`, `subnet_cidr_range`, Cloud NAT + private services access; no hybrid/Shared VPC | [Section 2 guide](PCA_Section_2_Exploration_Guide.md#21-configuring-network-topologies) | | 2.2 Configuring individual storage systems | ✅ | `storage_buckets`, `backup_schedule`, `create_filestore_nfs`, Cloud SQL PITR | [Section 2 guide](PCA_Section_2_Exploration_Guide.md#22-configuring-individual-storage-systems) | | 2.3 Configuring compute systems | ✅ | `gke_cluster_mode`, `gke_autoscaling_profile`, `container_resources`, `execution_environment` | [Section 2 guide](PCA_Section_2_Exploration_Guide.md#23-configuring-compute-systems) | | 2.4 Leveraging Vertex AI for end-to-end ML workflows | 📘 | not implemented | [Section 2 guide](PCA_Section_2_Exploration_Guide.md#24-leveraging-vertex-ai-for-end-to-end-ml-workflows) | | 2.5 Configuring prebuilt solutions or APIs with Vertex AI | 📘 | nearest: `secret_environment_variables` for API keys | [Section 2 guide](PCA_Section_2_Exploration_Guide.md#25-configuring-prebuilt-solutions-or-apis-with-vertex-ai) | ## Section 3: Designing for security and compliance (~17.5% of the exam) The Security and delivery profile turns on most of what this section tests: dedicated least-privilege service accounts, CMEK with automatic rotation, Binary Authorization attestation, VPC Service Controls in dry-run mode, IAP zero-trust access, and comprehensive audit logging. Organization hierarchy and regulatory frameworks remain study topics. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Security — IAM, secrets, encryption, supply chain, perimeters | ✅ | `enable_cmek`, `enable_binary_authorization`, `enable_vpc_sc`, `enable_iap`, the platform's secrets and IAM layers | [Section 3 guide](PCA_Section_3_Exploration_Guide.md#31-designing-for-security) | | 3.1 Security — resource hierarchy, org policies | 📘 | not implemented | [Section 3 guide](PCA_Section_3_Exploration_Guide.md#31-designing-for-security) | | 3.1 Security — Workload Identity Federation (keyless CI) | ✅ | `enable_workload_identity_federation`, `wif_provider_type` (Services_GCP) | [Section 3 guide](PCA_Section_3_Exploration_Guide.md#31-designing-for-security) | | 3.2 Compliance — auditability, ITAR/HIPAA-style controls | 🟡 | `enable_audit_logging`, `enable_security_command_center`, `vpc_sc_dry_run` | [Section 3 guide](PCA_Section_3_Exploration_Guide.md#32-designing-for-compliance) | ## Section 4: Analyzing and optimizing technical and business processes (~15% of the exam) CI/CD and release governance are fully demonstrable: a GitHub-triggered Cloud Build pipeline, Kaniko image builds into Artifact Registry, optional Binary Authorization attestation, and a Cloud Deploy pipeline whose default `prod` stage requires manual approval. SRE culture, post-mortems, and stakeholder management are people topics — study them separately. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Technical processes — SDLC, CI/CD, testing | 🟡 | `enable_cicd_trigger`, `cloud_deploy_stages`, `traffic_split` | [Section 4 guide](PCA_Section_4_Exploration_Guide.md#41-analyzing-and-defining-technical-processes) | | 4.2 Business processes — change management, decision-making | 🟡 | `require_approval` gates in `cloud_deploy_stages`; cost guardrails via `create_billing_budget` | [Section 4 guide](PCA_Section_4_Exploration_Guide.md#42-analyzing-and-defining-business-processes) | ## Section 5: Managing implementation (~12.5% of the exam) The platform *is* an implementation-management exhibit: a four-tier IaC architecture that development teams consume through a portal, with Artifact Registry hygiene policies and guardrail validations baked in. Raw SDK fluency (`gcloud`, client libraries, emulators) requires hands-on practice beyond the portal. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 5.1 Advising development and operation teams | 🟡 | layered module architecture, `max_images_to_retain`, plan-time validations | [Section 5 guide](PCA_Section_5_Exploration_Guide.md#51-advising-development-and-operation-teams) | | 5.2 Interacting with Google Cloud programmatically | 🟡 | OpenTofu workflow, `gcloud`-based provisioners and discovery scripts | [Section 5 guide](PCA_Section_5_Exploration_Guide.md#52-interacting-with-google-cloud-programmatically) | ## Section 6: Ensuring solution and operations excellence (~12.5% of the exam) Day-2 operations: every deployment ships a monitoring dashboard and email alert channels; Cloud SQL and the NFS VM get CPU/memory/disk alerts; releases can be canaried with `traffic_split` and promoted through Cloud Deploy. Publicly reachable deployments also get a synthetic uptime check and alert policy via `uptime_check_config` (provisioned by the platform's monitoring layer); support processes and chaos engineering are concept-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 6.1 Operational excellence pillar (Well-Architected Framework) | 🟡 | automation throughout; auto-healing NFS MIG, plan-time CMEK key recovery | [Section 6 guide](PCA_Section_6_Exploration_Guide.md#61-operational-excellence-pillar-well-architected-framework) | | 6.2 Familiarity with Google Cloud Observability solutions | ✅ | `support_users`, `alert_policies`, `uptime_check_config`, the platform monitoring layer | [Section 6 guide](PCA_Section_6_Exploration_Guide.md#62-familiarity-with-google-cloud-observability-solutions) | | 6.3 Deployment and release management | ✅ | `traffic_split`, `cloud_deploy_stages`, `max_revisions_to_retain` | [Section 6 guide](PCA_Section_6_Exploration_Guide.md#63-deployment-and-release-management) | | 6.4 Assisting with the support of deployed solutions | 📘 | nearest: `support_users` notification channels | [Section 6 guide](PCA_Section_6_Exploration_Guide.md#64-assisting-with-the-support-of-deployed-solutions) | | 6.5 Evaluating quality control measures | 🟡 | `enable_vulnerability_scanning`, Binary Authorization attestation step, the App_GKE plan-time validation suite | [Section 6 guide](PCA_Section_6_Exploration_Guide.md#65-evaluating-quality-control-measures) | | 6.6 Ensuring the reliability of solutions in production | 🟡 | PDBs, topology spread, auto-healing NFS MIG, Redis production-tier guardrail | [Section 6 guide](PCA_Section_6_Exploration_Guide.md#66-ensuring-the-reliability-of-solutions-in-production) | --- *Application wrapper modules (Django, WordPress, and others) exist on the platform but are out of scope for these guides — everything here is demonstrated with the four foundation modules alone.* --- # PCA Certification Preparation Guide: Section 1 — Designing and planning a cloud solution architecture (~25% of the exam) PCA Certification Preparation Guide: Section 1 — Designing and planning a cloud solution architecture (~25% of the exam) > 📚 **Official exam guide:** [Professional Cloud Architect certification](https://cloud.google.com/learn/certification/cloud-architect) — always confirm section weightings against the current Google Cloud exam guide. This is the heaviest-weighted PCA section, and it is about *choices*: which compute platform, which availability tier, which storage type — and why. All four foundation modules are exercised here. Deploy the **Lean baseline** profile from the [Lab Map](PCA_Certification_Guide.md) first, then apply the **Resilient data tier** and **GKE architecture** profiles as you reach 1.2 and 1.3, so you can compare the cheap architecture and the resilient one in the same project. --- ## 1.1 Designing a cloud solution infrastructure that meets business requirements > ⏱ ~60 min · 💰 low — baseline profile only · ⚙️ Requires: Lean baseline profile **Why the exam cares** — PCA scenarios open with business constraints, not technical ones: "minimize cost," "the security team requires zero-trust access," "leadership needs spend visibility." You are tested on translating those into platform decisions — scale-to-zero vs warm capacity, identity-based access vs network-based access, budgets and alerts as financial guardrails — and on recognizing which requirement drives which knob. **How RAD implements it** | Business requirement | Variable (default) | Module | |---|---|---| | Minimize idle cost | `min_instance_count` (default `0`) — Cloud Run scales to zero | App_CloudRun | | Cap maximum spend on compute | `max_instance_count` (default `1`) | App_CloudRun | | CPU billing trade-off | `cpu_always_allocated` (default `false` — request-based billing) — set `true` to keep CPU allocated between requests for in-process background work | App_CloudRun | | Financial guardrails | `create_billing_budget` (default `false`), `budget_amount` (default `100`), `budget_alert_thresholds` (default `[0.5, 0.9, 1.0]`) | Services_GCP | | Zero-trust access for internal apps | `enable_iap` (default `false`) + `iap_authorized_users` / `iap_authorized_groups` | App_CloudRun | | Stakeholder visibility | `support_users` — becomes Cloud Monitoring email notification channels | App_CloudRun / App_GKE | Cost-relevant platform choices also live in Services_GCP: the default database is a single zonal `db-custom-1-3840` PostgreSQL 17 instance (`postgres_tier`), and the default shared filesystem is a single `e2-small` VM (`create_network_filesystem`, default `true`) rather than managed Filestore — a deliberate cost-over-resilience default you will invert in 1.2. **Try it** 1. Deploy the Lean baseline profile. In **Console > Cloud Run**, open your service and confirm "Min instances: 0" on the service details. 2. Watch the instance count fall to zero after an idle period on the service's **Metrics** tab, then send a request and observe the cold start. 3. Verify the scaling bounds from the CLI: ```bash gcloud run services describe \ --region=us-central1 \ --format="yaml(spec.template.scaling)" ``` 4. Set `create_billing_budget = true` on the Services_GCP deployment and inspect **Console > Billing > Budgets & alerts**. 5. You know it worked when the Metrics tab shows the instance count touching zero and a budget with 50%/90%/100% thresholds exists. **Check yourself**
Q1: A startup runs an internal admin tool used a few hours per day and wants the lowest possible bill without exposing it to the internet. Which two settings from this platform meet both requirements? A: `min_instance_count = 0` (scale-to-zero eliminates idle compute cost) and `enable_iap = true` with an authorized-users list (identity-based zero-trust access instead of a VPN or IP allowlist). IAP authenticates every request at Google's edge before it reaches the service, so no always-on network infrastructure is needed.
Q2: Finance wants to be warned before, not after, the monthly cloud budget is exhausted. What do you configure? A: A billing budget with multiple alert thresholds — here `budget_alert_thresholds = [0.5, 0.9, 1.0]` notifies at 50% and 90% of `budget_amount`, before the 100% mark. Budgets alert but do not stop spending; pair them with `max_instance_count` caps if hard limits matter.
**Beyond the modules** — The exam also tests business analysis the modules cannot show: defining KPIs and success measures, CapEx-vs-OpEx framing, total cost of ownership, and build/buy/modify/deprecate workload disposition. Study the Google Cloud pricing calculator, "Cloud Billing reports" docs, and the Architecture Framework's cost optimization pillar. Try `gcloud billing accounts list` and explore **Billing > Reports** grouped by SKU in a scratch project. **⚠️ Exam trap** — Budgets never *stop* spending; they only notify. If a scenario demands spend *enforcement*, the answer involves quotas, instance caps, or programmatic budget-response automation — not the budget alone. --- ## 1.2 Designing a cloud solution infrastructure that meets technical requirements > ⏱ ~90 min · 💰 high — REGIONAL Cloud SQL roughly doubles instance cost; HA Redis and a read replica add more · ⚙️ Requires: Resilient data tier profile (+ GKE architecture profile for the HPA/PDB steps) **Why the exam cares** — High availability, scalability, and reliability requirements ("99.95% uptime," "survive a zone failure," "handle 10× Black Friday traffic") each map to a specific mechanism with a specific cost. The exam expects you to know that zonal→regional Cloud SQL buys automatic zone failover, that read replicas buy read throughput but *not* HA, and that BASIC-tier Redis offers no replication at all. **How RAD implements it** | Requirement | Mechanism | Variable (default) | |---|---|---| | Database survives zone failure | Cloud SQL REGIONAL = synchronous standby in a second zone, automatic failover | `postgres_database_availability_type` (default `ZONAL`) | | Read scale-out | Read replicas — always ZONAL in this module | `create_postgres_read_replica` (default `false`), `postgres_read_replica_count` (default `1`) | | Point-in-time recovery | PITR with 7-day transaction log retention, 7 retained daily backups starting 04:00 UTC | always on for PostgreSQL | | Cache survives instance failure | Memorystore `STANDARD_HA` = replica + automatic failover | `redis_tier` (default `BASIC`) | | Cache survives restart | RDB snapshots or AOF | `redis_persistence_mode` (default `DISABLED`) | | App scales with traffic (GKE) | HPA targeting 70% CPU / 80% memory utilization — created only when `max_instance_count` > 1 and `enable_vertical_pod_autoscaling = false` | `min_instance_count` (default `1`), `max_instance_count` (default `3`) in App_GKE | | Voluntary-disruption protection | PodDisruptionBudget, skipped when `max_instance_count = 1` | `enable_pod_disruption_budget` (default `true`), `pdb_min_available` (default `"1"`) | A guardrail worth studying: the platform's Redis layer carries two plan-time preconditions — one **blocks `redis_tier = "BASIC"` when `resource_labels.environment = "production"`**, and a second **blocks `redis_persistence_mode = "DISABLED"` on a production `STANDARD_HA` instance**, so production caches must enable `RDB` or `AOF`. That is the exam's "BASIC tier is not production-grade" lesson — and "cached state must survive failover" — encoded as validations. **Try it** 1. Apply the Resilient data tier profile. In **Console > SQL**, open the instance — the overview shows high availability (regional) and a failover option. 2. Confirm from the CLI and find the standby zone: ```bash gcloud sql instances describe \ --format="value(settings.availabilityType, gceZone, secondaryGceZone)" ``` 3. In **Console > Memorystore > Redis**, verify the instance tier shows Standard. 4. On the GKE profile, inspect the autoscaler: ```bash kubectl get hpa -n -o wide ``` 5. You know it worked when `availabilityType` returns `REGIONAL` with a populated `secondaryGceZone`, and `kubectl get hpa` shows utilization targets of 70% (CPU) and 80% (memory). **Check yourself**
Q1: An e-commerce database must survive a zone outage with no manual intervention, and the reporting team's heavy queries are slowing checkout. What two changes do you make? A: Set the instance to REGIONAL availability (synchronous standby plus automatic failover handles the zone outage) and add a read replica, pointing the reporting workload at it (offloads reads). Neither substitutes for the other: replication to a read replica is asynchronous with no automatic failover, and a REGIONAL standby serves no read traffic.
Q2: A session cache on BASIC-tier Memorystore loses all data during maintenance, breaking user logins. Cheapest fix that survives both maintenance and instance failure? A: Move to `STANDARD_HA`, which adds a replica and automatic failover — exactly what the module's production guardrail enforces. Persistence (`RDB`/`AOF`) additionally protects against full restarts. BASIC tier has no replica, so any failure event means a cold cache.
Q3: Why does the module skip creating a PodDisruptionBudget when `max_instance_count = 1`? A: A PDB with `minAvailable: 1` on a single-replica workload would make the one pod unevictable, blocking node drains and GKE upgrades indefinitely. PDBs only make sense when spare replicas can keep serving during voluntary disruption — a validation in App_GKE also requires `pdb_min_available` to be less than `max_instance_count` (percentages exempt).
**⚠️ Exam trap** — Backups ≠ PITR ≠ HA. Backups recover to a snapshot time, PITR replays transaction logs to any moment within retention, and REGIONAL HA prevents the outage in the first place. A scenario asking to "recover the database to 14:32 yesterday" needs PITR; "no downtime during zone failure" needs REGIONAL; neither solves the other. --- ## 1.3 Designing network, storage, and compute resources > ⏱ ~90 min · 💰 moderate — Filestore's 1024 GB minimum and the GKE cluster are the drivers · ⚙️ Requires: GKE architecture profile, optionally `create_filestore_nfs = true` **Why the exam cares** — This is the product-selection subsection: VPC layout and private access patterns, file vs object vs block vs relational storage, and the serverless-vs-Kubernetes compute decision. The exam rewards knowing the *decision criteria* — POSIX semantics demand file storage, per-pod persistent state demands a StatefulSet, operational simplicity favors Cloud Run. **How RAD implements it** *Network*: a custom-mode VPC (subnets are not auto-created) with one subnet per entry in `availability_regions` (default `["us-central1"]`) sized by `subnet_cidr_range` (default `["10.0.0.0/24"]`); a Cloud Router and Cloud NAT per region (covering all subnets and IP ranges) so private workloads get outbound-only internet; and private services access (a global VPC-peering address range plus a service networking connection) carrying Cloud SQL and Memorystore traffic privately — the PostgreSQL instance has no public IP and uses encrypted-only SSL connections. GKE clusters are VPC-native (alias-IP), with pod/service secondary ranges derived per cluster from `gke_pod_base_cidr` (default `10.64.0.0/10`) and `gke_service_base_cidr` (default `10.8.0.0/16`). *Storage* — the module set is a storage-selection matrix: | Need | Service | Variable (default) | |---|---|---| | Relational, transactional | Cloud SQL PostgreSQL/MySQL, AlloyDB | `create_postgres` (default `true`), `create_mysql` (`false`), `enable_alloydb` (`false`) | | Document / NoSQL | Firestore (Native mode, Enterprise edition) | `create_firestore` (default `false`) | | Shared POSIX filesystem, managed | Filestore (`BASIC_HDD`/`BASIC_SSD`/`ENTERPRISE`) | `create_filestore_nfs` (default `false`), `filestore_capacity_gb` (default `1024`) | | Shared POSIX filesystem, cheap | Self-managed NFS on an `e2-small` MIG with a stateful pd-ssd data disk, auto-healing, daily snapshots | `create_network_filesystem` (default `true`), `network_filesystem_capacity` (default `10` GB) | | Object storage | GCS buckets with versioning, lifecycle rules, CMEK | `storage_buckets` list in App_CloudRun / App_GKE | | In-memory cache | Memorystore Redis | `create_redis` (default `false`) | | Per-pod block storage | StatefulSet PVCs (GKE only) | `stateful_pvc_enabled`, `stateful_pvc_size` | The Filestore-vs-NFS-VM pair is a textbook managed-vs-self-managed trade-off: Filestore costs more (1 TiB minimum on BASIC tiers) but removes patching, healing, and snapshot management; the VM is cheap but is a single zonal instance whose resilience is only MIG auto-healing and daily disk snapshots. The platform's NFS-discovery layer prefers Filestore when both exist. *Compute* — the same App_Common wiring deploys to either engine. Cloud Run: request-driven, `execution_environment` default `gen2` (required — and validated — for NFS and GCS Fuse mounts), `timeout_seconds` default `300`, no per-instance persistent volumes. GKE: `workload_type` (default `null`) auto-resolves — `stateful_pvc_enabled = true` selects a StatefulSet, otherwise a Deployment; setting `workload_type = "Deployment"` *alongside* `stateful_pvc_enabled = true` fails at plan time because Deployments do not honor per-replica volume claim templates. **Try it** 1. In **Console > VPC network > VPC networks**, open the platform VPC; note custom subnet mode and the GKE subnet's two secondary ranges. 2. List the private services access allocation: ```bash gcloud compute addresses list --global --filter="purpose=VPC_PEERING" gcloud services vpc-peerings list --network= ``` 3. Deploy App_GKE with `stateful_pvc_enabled = true`, `stateful_pvc_size = "10Gi"`, and a `stateful_pvc_mount_path`; leave `workload_type` unset. Then: ```bash kubectl get statefulset,pvc -n ``` 4. Now set `workload_type = "Deployment"` while keeping `stateful_pvc_enabled = true` and run a plan — read the validation error, then revert. 5. You know it worked when a StatefulSet with a bound PVC exists, and the deliberate misconfiguration was rejected at plan time, not at runtime. **Check yourself**
Q1: A legacy CMS needs a shared writable filesystem across six replicas, with a strict SLA and no ops staff to babysit a file server. Which option here, and why not the default? A: Filestore (`create_filestore_nfs = true`) — a managed service with no VM to patch or heal. The default self-managed NFS VM is far cheaper but is a single zonal `e2-small` whose recovery depends on MIG auto-healing and daily snapshots; "no ops staff + strict SLA" rules it out.
Q2: Why does Cloud Run gen2 matter for this platform's NFS support? A: NFS and GCS Fuse volume mounts require Cloud Run's gen2 execution environment (full Linux kernel compatibility); gen1 does not support them. The module encodes this as a plan-time validation: `enable_nfs = true` with `execution_environment = "gen1"` is rejected before anything deploys.
Q3: A team must run a container with one persistent volume per replica and stable network identities. Cloud Run or GKE, and which workload type? A: GKE with a StatefulSet — per-replica PVCs (`volumeClaimTemplates`) and stable pod identities are StatefulSet features. Cloud Run instances are ephemeral and share-nothing; its volume options (Cloud SQL socket, NFS, GCS Fuse) are shared, not per-instance block storage.
**Beyond the modules** — Not implemented here: Shared VPC host/service projects, VPC Network Peering between VPCs, Cloud DNS, internal load balancers, Spanner, Bigtable, and BigQuery. For the exam, be able to place each: Spanner for globally consistent relational scale, Bigtable for high-throughput wide-column time series, BigQuery for analytics. Read "Choose a storage option" and "Compare Google Cloud database services" in the official docs. **⚠️ Exam trap** — "NoSQL" is not one answer. Firestore (the only NoSQL engine deployable here) suits document data with mobile/web sync; Bigtable suits petabyte time series; Memorystore is a cache, not a system of record — especially with persistence `DISABLED`, the default. --- ## 1.4 Creating a migration plan > ⏱ ~30 min reading + a short lab on the import jobs · 💰 no additional cost · ⚙️ Requires: default deployment **Why the exam cares** — Migration scenarios test sequencing (assess → plan → migrate → optimize), choosing rehost/replatform/refactor per workload, and data-transfer mechanics: online vs offline, downtime windows, dependency order. **How RAD implements it** — The foundation modules deploy greenfield infrastructure; there is no migration tooling. The nearest adjacent capability is the data-import path in App_CloudRun/App_GKE: `enable_backup_import` (default `false`) with `backup_source` (`gcs` or `gdrive`), `backup_file`, and `backup_format` runs a containerized job that restores an existing database dump into the new Cloud SQL instance — a miniature "migrate the data, then cut over" exercise. Notice the real plan-time constraint: `backup_format = "auto"` is rejected when `backup_source = "gdrive"`. **Try it** 1. Export a small PostgreSQL dump from any existing system and upload it to a GCS bucket. 2. Redeploy with `enable_backup_import = true`, `backup_source = "gcs"`, and the `backup_file` path; watch the import job in **Console > Cloud Run > Jobs**. 3. Verify activity on the target instance: ```bash gcloud sql operations list --instance= --limit=5 ``` 4. You know it worked when the import job execution succeeds and your tables exist in the application database. **Check yourself**
Q1: A company must move a 400 TB on-premises archive to GCS over a 100 Mbps link within a month. Which transfer approach? A: Transfer Appliance (offline hardware). At 100 Mbps, 400 TB takes roughly a year online — far beyond the window. Storage Transfer Service or `gcloud storage` suits online transfers only when bandwidth × time covers the volume.
Q2: In a phased migration, which workloads move first? A: Rehost (lift-and-shift) stateless, low-dependency workloads first for quick wins; refactor strategically valuable apps where cloud-native gains justify the effort; defer tightly coupled legacy systems until dependencies are mapped. The exam rewards "assess and map dependencies before moving anything."
**Beyond the modules** — Study Migration Center (discovery and assessment), Migrate to Virtual Machines, Database Migration Service (continuous replication into Cloud SQL with minimal downtime), and Storage Transfer Service vs Transfer Appliance selection. Also review the network prerequisites for migration — HA VPN and Cloud Interconnect — none of which the modules provision. Walk the **Migration Center** console flow in a scratch project. --- ## 1.5 Envisioning future solution improvements > ⏱ ~30 min reading · 💰 no additional cost · ⚙️ Requires: default deployment **Why the exam cares** — Architects design for change: new regions, new compliance regimes, replacing components without rework. The exam probes whether your design has seams — abstraction layers, loose coupling, declarative definitions — that let it evolve. **How RAD implements it** — Not a deployable feature, but the repository itself is the exhibit. Two patterns are worth internalizing as exam-ready talking points. First, the **layered module architecture** (Platform → Foundation → Application): swapping Cloud Run for GKE is a one-layer change because both foundation engines consume the same shared configuration. Second, the **discovery-vs-inline pattern**: App_CloudRun and App_GKE probe for Services_GCP-managed resources (subnets carrying the description `managed-by=services-gcp`, labeled Artifact Registry repos) and provision inline equivalents only when the platform layer is absent — with `require_services_gcp_module` (default `true`) able to enforce platform presence. That is "design for evolving deployment topologies" in working code. **Try it** 1. Note that the platform discovers shared subnets by the `managed-by=services-gcp` description filter. 2. Reproduce the discovery query the module runs: ```bash gcloud compute networks subnets list \ --filter="description~managed-by=services-gcp" \ --format="table(name,network,region,description)" ``` 3. You know it worked when the subnets your Services_GCP deployment created appear — the same signal a future App_GKE deployment would use to attach to them. **Check yourself**
Q1: A platform team wants application teams to deploy onto shared infrastructure when it exists, but self-provision in isolated sandboxes when it does not. What architectural pattern supports this? A: Discovery with inline fallback — probe for tagged/labeled shared resources at plan time and provision local equivalents only when absent, exactly as App_CloudRun does for VPC, SQL, NFS, and Artifact Registry. A policy flag (`require_services_gcp_module`) converts the fallback into a hard requirement for production.
**Beyond the modules** — Study the evolution mechanisms the modules do not show: event-driven decoupling with Pub/Sub and Eventarc, strangler-fig migration off monoliths, API versioning behind API Gateway/Apigee, and tracking Google Cloud release notes ("What's new") as ongoing architectural input. --- # PCA Certification Preparation Guide: Section 2 — Managing and provisioning a cloud solution infrastructure (~17.5% of the exam) PCA Certification Preparation Guide: Section 2 — Managing and provisioning a cloud solution infrastructure (~17.5% of the exam) > 📚 **Official exam guide:** [Professional Cloud Architect certification](https://cloud.google.com/learn/certification/cloud-architect) — always confirm section weightings against the current Google Cloud exam guide. This section tests whether you can actually stand infrastructure up — network topology, storage configuration, compute provisioning — and the two Vertex AI subsections added to the current exam guide. The modules exercised are `Services_GCP` (network, databases, GKE) and the two deployment engines. Deploy the **Lean baseline** profile from the [Lab Map](PCA_Certification_Guide.md), then enable GKE (**GKE architecture** profile) before 2.3. Subsections 2.4 and 2.5 are study-only. --- ## 2.1 Configuring network topologies > ⏱ ~45 min · 💰 low — Cloud NAT and a static IP are minor costs · ⚙️ Requires: default Services_GCP deployment **Why the exam cares** — Topology questions hinge on traffic direction and trust: how do private workloads reach the internet (NAT, egress-only), how do managed services get private connectivity (private services access), and how is east-west traffic constrained (firewall rules, tags). The exam then extends this to hybrid and multi-VPC designs, which you must study separately. **How RAD implements it** — Here is how the platform implements it: | Topology element | Implementation | |---|---| | Custom-mode VPC | one subnet per region in `availability_regions` (default `["us-central1"]`), CIDRs from `subnet_cidr_range` (default `["10.0.0.0/24"]`) | | Outbound-only internet | a Cloud Router and Cloud NAT per region, covering all subnets and IP ranges | | Private access to managed services | a global VPC-peering address plus a service networking connection (used by Cloud SQL and Memorystore private IP) | | Health-check ingress | firewall `fw-allow-lb-hc` allowing `130.211.0.0/22` and `35.191.0.0/16` | | Admin SSH without public IPs | firewall `fw-allow-iap-ssh` allowing `35.235.240.0/20` on tcp:22 (IAP TCP forwarding range) | | Tag-based segmentation | tags `nfsserver`/`redisserver` open tcp 111/2049/6379 + udp 2049 only to tagged VMs; `httpserver`/`webserver` open 80/443/8080/8443 | On the application side, App_CloudRun uses **Direct VPC egress** (a network interface on the subnet — no Serverless VPC Access connector), with `vpc_egress_setting` (default `PRIVATE_RANGES_ONLY`, or `ALL_TRAFFIC` to force everything through the VPC and NAT). App_GKE can add Kubernetes NetworkPolicies via `enable_network_segmentation` (default `false`) — covered in Section 3. **Try it** 1. In **Console > VPC network > VPC networks**, open the platform VPC and review subnets; then **Console > Network services > Cloud NAT** for the NAT gateway. 2. List the firewall rules and map each to a trust decision: ```bash gcloud compute firewall-rules list \ --filter="network=" \ --format="table(name,direction,sourceRanges.list(),allowed[].map().firewall_rule().list(),targetTags.list())" ``` 3. In **Console > Cloud Run**, open your service's **Networking** tab and confirm the VPC egress setting. 4. You know it worked when you can explain every rule's source range — Google LB health checks, the IAP range, or intra-VPC — and the Cloud SQL instance shows only a private IP. **Check yourself**
Q1: VMs in a private subnet must download OS packages but must never accept inbound internet connections. Which component, and why not external IPs? A: Cloud NAT — it provides source NAT for egress with no inbound path, and removes the per-VM external IP attack surface. External IPs would work for egress but expose every VM to inbound scanning and violate the requirement.
Q2: Why does Cloud SQL need a "private services access" peering range instead of just living in your subnet? A: Cloud SQL instances run in a Google-managed producer VPC, not yours. Private services access allocates an IP range from your VPC and peers it to the producer network, so the instance gets an RFC-1918 address reachable from your subnets — which is why this module can disable the public IP entirely and keep the database off the public internet.
Q3: An auditor asks how admins SSH to the NFS VM with no public IP and no VPN. What is the answer in this topology? A: IAP TCP forwarding — the `fw-allow-iap-ssh` rule admits tcp:22 only from Google's IAP range `35.235.240.0/20`, and admins use `gcloud compute ssh --tunnel-through-iap`. Identity is verified by IAP before any packet reaches the VM.
**Beyond the modules** — Not implemented: Shared VPC, VPC peering between customer VPCs, Cloud VPN / Cloud Interconnect (hybrid), Network Connectivity Center, Cloud DNS, VPC flow logs, and hierarchical firewall policies. These are heavily examined — study "Choosing a Network Connectivity product" (Dedicated vs Partner Interconnect vs HA VPN decision tree, 99.99% SLA requires HA VPN or redundant Interconnect attachments), and Shared VPC host/service project IAM. In a scratch project, try `gcloud compute networks subnets update --enable-flow-logs`. **⚠️ Exam trap** — Private Google Access, private services access, and Private Service Connect are three different things. This module uses private *services* access (VPC peering to managed-service producers). Don't pick PSC endpoints when the scenario describes Cloud SQL private IP via an allocated peering range. --- ## 2.2 Configuring individual storage systems > ⏱ ~60 min · 💰 low–moderate — bucket storage is cheap; Filestore adds ~1 TiB minimum if enabled · ⚙️ Requires: App_CloudRun with `create_cloud_storage = true` and a `storage_buckets` entry **Why the exam cares** — Storage configuration questions are about durability and cost mechanics: lifecycle transitions between storage classes, object versioning vs backups, retention for compliance, and database backup/PITR settings. You should be able to configure each and predict its cost behavior. **How RAD implements it** *Object storage* — the `storage_buckets` list (available on both App_CloudRun and App_GKE) provisions GCS buckets through the platform's object-storage layer. Each entry supports `storage_class` (default `"STANDARD"`), `versioning_enabled` (default `false`), `lifecycle_rules` (age, newer-version counts, storage-class transitions), CORS, `public_access_prevention` (default `"enforced"`), and `uniform_bucket_level_access`. The platform also sets a zero-retention soft-delete policy so destroys are not blocked, empties buckets at destroy time, and applies a lifecycle delete rule on the backup bucket driven by `backup_retention_days` (default `7`). *Database protection* — PostgreSQL gets 7 retained daily automated backups (04:00 UTC) plus PITR with 7-day log retention; disks are PD_SSD with autoresize. Application-level dumps are separate: a Cloud Scheduler job (`backup_schedule`, default `"0 2 * * *"`) triggers a containerized export job that writes dumps to the backup bucket. *File storage* — Filestore (`create_filestore_nfs`, `filestore_tier` default `BASIC_HDD`, share name `share`, no-root-squash) vs the self-managed NFS VM with daily pd-ssd snapshots and 7-day snapshot retention. **Try it** 1. Deploy with a bucket entry such as `{ name_suffix = "media", versioning_enabled = true, lifecycle_rules = [...] }` including a transition to `NEARLINE` after 30 days. 2. In **Console > Cloud Storage > Buckets**, open the bucket's **Lifecycle** tab and verify the rule; check **Protection** for versioning and public access prevention. 3. Confirm from the CLI: ```bash gcloud storage buckets describe gs:// \ --format="yaml(lifecycle,versioning,publicAccessPrevention)" ``` 4. In **Console > Cloud Scheduler**, find the backup schedule; trigger it manually ("Force run") and watch the export job in **Cloud Run > Jobs**, then verify the dump file landed in the backup bucket. 5. You know it worked when the lifecycle rule shows in the describe output and a fresh dump object exists after the forced run. **Check yourself**
Q1: Compliance requires keeping uploaded documents for 90 days in fast storage, then cheap storage for 7 years, then deletion — with no application changes. How? A: Object Lifecycle Management on the bucket: a SetStorageClass transition (e.g. to COLDLINE/ARCHIVE) at age 90 days and a Delete action at ~2,645 days. Lifecycle rules run server-side, so the application never changes. If regulators require *immutability*, add a retention policy with Bucket Lock — lifecycle alone does not prevent deletion.
Q2: The team enables object versioning "as a backup" but the bucket bill triples in a month. What happened and what fixes it? A: Every overwrite/delete keeps a noncurrent version billed at full storage rates. Add lifecycle rules keyed on `num_newer_versions` (or noncurrent age) to prune old versions — exactly what `lifecycle_rules` in the `storage_buckets` entry expresses. Versioning protects against accidental deletion; it is not a managed backup with retention built in.
**⚠️ Exam trap** — The scheduled SQL-dump job and Cloud SQL automated backups are different layers: automated backups + PITR restore the *instance*; dumps in GCS are portable, survive instance deletion, and can seed migrations. A scenario about "restore after the instance was deleted" needs the export, not PITR. --- ## 2.3 Configuring compute systems > ⏱ ~75 min · 💰 moderate — the GKE cluster fee and node/pod resources dominate · ⚙️ Requires: GKE architecture profile; try `gke_cluster_mode = "STANDARD"` in a scratch project if budget allows **Why the exam cares** — Provisioning questions test the configuration surface of each compute platform: Autopilot vs Standard GKE (who manages nodes, how billing works), machine/resource sizing, autoscaling profiles, and serverless runtime settings. The exam loves "which mode/setting reduces ops burden vs grants control." **How RAD implements it** *GKE*: `create_google_kubernetes_engine` (default `false`) provisions 1–10 clusters (`gke_cluster_count`, default `1`). `gke_cluster_mode` (default `"AUTOPILOT"`) is the headline trade-off: Autopilot is fully managed per-pod billing with node auto-provisioning; `"STANDARD"` removes the default pool and creates an explicit node pool with `gke_node_machine_type` (default `e2-standard-4`), autoscaling `gke_node_min_count` (default `1`) to `gke_node_max_count` (default `5`), `gke_node_disk_type` (default `pd-balanced`), and Shielded nodes (secure boot + integrity monitoring). Both modes share: Dataplane V2, VPC-native IP allocation, the standard Gateway API channel, vertical pod autoscaling, managed Prometheus, the `REGULAR` release channel (fixed), cost allocation, and `gke_autoscaling_profile` (default `BALANCED`, or `OPTIMIZE_UTILIZATION` for aggressive scale-down). Note these clusters DO use `private_cluster_config { enable_private_nodes = true }` (nodes get no external IPs — required because RAD-managed projects deny `compute.vmExternalIpAccess`) with a dedicated `master_ipv4_cidr_block` from `gke_master_base_cidr`; only `enable_private_endpoint` stays `false`, so the control plane is still reachable on its public endpoint for CI/CD. *Cloud Run*: `container_resources` (defaults `cpu_limit = "1000m"`, `memory_limit = "512Mi"`), startup CPU boost always on, `execution_environment` (default `gen2`), `timeout_seconds` (default `300`), session affinity enabled, and startup/liveness probes via `startup_probe_config` / `health_check_config`. *Workload provisioning on GKE*: the same `container_resources` shape, Cloud SQL Auth Proxy sidecar injected when `enable_cloudsql_volume = true` (default) and a database exists, and CronJobs via the `cron_jobs` list. **Try it** 1. In **Console > Kubernetes Engine > Clusters**, open the cluster — note "Mode: Autopilot", the release channel, and the enabled features list. 2. Compare via CLI: ```bash gcloud container clusters describe \ --location=us-central1 \ --format="value(autopilot.enabled, releaseChannel.channel, autoscaling.autoscalingProfile)" ``` 3. Change `gke_autoscaling_profile` to `OPTIMIZE_UTILIZATION` in the portal and re-apply; re-run the describe to see the profile change. 4. On Cloud Run, raise `container_resources.memory_limit` to `1Gi` and observe a new revision roll out in **Console > Cloud Run > Revisions**. 5. You know it worked when the describe output reflects each portal change without you touching a node, VM, or YAML file. **Check yourself**
Q1: A team with no Kubernetes operations experience needs GKE for its API ecosystem but must not manage nodes, upgrades, or bin-packing. Which mode and why? A: Autopilot (`gke_cluster_mode = "AUTOPILOT"`, the default here). Google manages nodes, repairs, and upgrades; billing is per pod resource request, so right-sizing requests is the only capacity task left. Standard mode is for workloads needing specific machine types, GPUs/local SSDs, or scheduling control — the module's own variable description calls out latency-sensitive gRPC workloads as the Standard-mode use case.
Q2: On Autopilot, what is the cost lever equivalent to "choosing a smaller machine type" on Standard? A: Pod resource *requests* (`container_resources`) — Autopilot bills what pods request, not nodes. Over-requested CPU/memory is pure waste; VPA (enabled at the cluster level, and exposed per-workload via `enable_vertical_pod_autoscaling` in App_GKE) right-sizes requests from observed usage. `gke_autoscaling_profile = "OPTIMIZE_UTILIZATION"` additionally tightens scale-down.
Q3: When would you accept Standard mode's extra operational burden in this platform? A: When workloads need explicit node control: a specific machine series (`gke_node_machine_type`), disk types, or scheduling behavior Autopilot constrains. The module then provisions a single explicit node pool with autoscaling 1–5 nodes and Shielded VM protections — operational burden traded for hardware control.
**⚠️ Exam trap** — Autopilot ≠ "no capacity planning." You still set requests/limits, and HPA/quota math still applies — App_GKE's `quota_memory_requests`-style values must use binary suffixes (`"4Gi"`), because a bare `"4"` is parsed by Kubernetes as 4 *bytes* and blocks all scheduling. --- ## 2.4 Leveraging Vertex AI for end-to-end ML workflows > ⏱ ~study only · 💰 no platform cost · ⚙️ Requires: nothing deployable **Why the exam cares** — The current PCA guide tests Vertex AI workflow architecture: pipelines for orchestration, feature consistency between training and serving, and choosing infrastructure (GPUs/TPUs, on-demand vs reserved) for training and inference. **How RAD implements it** — Not implemented by the foundation modules. The closest adjacency is architectural: a trained model served as a container would deploy on these modules like any other workload (Cloud Run for spiky low-ops inference, GKE for GPU-backed or high-throughput serving). **Beyond the modules** — Study Vertex AI Pipelines (Kubeflow Pipelines / TFX as pipeline definitions), Vertex AI Feature Store (online vs batch serving, preventing training-serving skew), BigQuery as the training data source and BigQuery ML for in-warehouse models, and accelerator selection (GPU vs TPU, on-demand vs reservations). In a scratch project, run the Vertex AI "Hello custom training" quickstart and `gcloud ai custom-jobs list` to see the job lifecycle. **Check yourself**
Q1: A model performs well offline but degrades in production; investigation shows features are computed differently at serving time. Which Vertex AI component addresses this? A: Vertex AI Feature Store — it centralizes feature definitions and serves the same feature values for training (batch) and prediction (online), eliminating training-serving skew caused by duplicated feature logic.
--- ## 2.5 Configuring prebuilt solutions or APIs with Vertex AI > ⏱ ~study only + 20 min secret-handling lab · 💰 no platform cost · ⚙️ Requires: default App_CloudRun deployment **Why the exam cares** — You must select the right pre-trained API per use case (Vision, Video Intelligence, Speech-to-Text/Text-to-Speech, Dialogflow, Vertex AI Search, Gemini models via Model Garden) versus training a custom model — and integrate them *securely*. **How RAD implements it** — The AI APIs themselves are not provisioned, but the secure-integration pattern is fully demonstrated: `secret_environment_variables` injects credentials into Cloud Run via Secret Manager references (never plaintext env vars), and App_GKE materializes secrets through the GKE Secret Manager CSI add-on (`SecretProviderClass` + secret sync). An application calling Gemini or Vision would receive its API key exactly this way. **Try it** 1. Add a dummy secret to `secret_environment_variables` and redeploy. 2. In **Console > Cloud Run > (service) > Revisions > Containers**, confirm the variable shows as a secret reference, not a value; then: ```bash gcloud run services describe --region=us-central1 \ --format="yaml(spec.template.containers[0].env)" ``` 3. You know it worked when the env entry shows `valueSource.secretKeyRef` rather than a literal value. **Check yourself**
Q1: A product needs OCR on scanned invoices and a conversational support agent. Which pre-trained APIs, and when would you switch to custom training? A: Cloud Vision API (OCR/document text detection) and Dialogflow CX (conversational agents). Switch to custom training (Vertex AI) only when the pre-trained model's quality on your domain data is insufficient — e.g. specialized invoice layouts needing Document AI custom processors or fine-tuned models. Pre-trained first is the exam's default posture.
**Beyond the modules** — Study the official API categories (Vision, Imagen, Video Intelligence, Speech, Dialogflow, Vertex AI Search), Model Garden deployment options, and grounding/RAG patterns with Vertex AI Search. Try `gcloud ml vision detect-text ` in a scratch project for a two-minute taste of a pre-trained API. --- # PCA Certification Preparation Guide: Section 3 — Designing for security and compliance (~17.5% of the exam) PCA Certification Preparation Guide: Section 3 — Designing for security and compliance (~17.5% of the exam) Security design is where the RAD modules are at their densest: dedicated service accounts everywhere, secrets that never touch Terraform state, CMEK with automatic rotation and even automatic key *recovery*, Binary Authorization, VPC Service Controls with a deliberately staged dry-run rollout, and zero-trust access via IAP. Deploy the **Security and delivery** profile from the [Lab Map](PCA_Certification_Guide.md) on top of a baseline deployment. Modules exercised: `Services_GCP`, `App_CloudRun` (or `App_GKE`), and the `App_Common` security layers (secrets, IAM, CMEK, and VPC-SC). --- ## 3.1 Designing for security > ⏱ ~2–3 h · 💰 moderate — KMS keys and the Cloud Armor load balancer; Binary Authorization and VPC-SC are free · ⚙️ Requires: Security and delivery profile (VPC-SC additionally needs the project in a GCP organization and non-empty `admin_ip_ranges`) **Why the exam cares** — PCA security questions are layered-defense design: who can act (IAM, separation of duties), how data is protected (encryption at rest/in transit, CMEK control), what can run (supply-chain integrity), where data can flow (perimeters, network segmentation), and who can reach the app (zero-trust access). The exam tests choosing the right layer for a requirement — e.g. data-exfiltration prevention is VPC-SC, not firewall rules. **How RAD implements it** *Identity and least privilege.* Services_GCP creates dedicated service accounts per duty — `cloudrun-sa-{prefix}`, `cloudbuild-sa-{prefix}`, `clouddeploy-sa-{prefix}`, `gke-sa-{prefix}`, `nfs-sa-{prefix}` — never the default compute SA. The platform's IAM layer grants resource-scoped bindings: `roles/secretmanager.secretAccessor` *per secret* and `roles/storage.objectAdmin` *per bucket*, plus `roles/iam.serviceAccountUser` for controlled impersonation by the build SA. On GKE, Workload Identity binds a per-namespace KSA to the GCP SA via `roles/iam.workloadIdentityUser` — no key files anywhere. *Secrets.* The platform's secrets layer generates the 32-character database password and stores it in Secret Manager; the GitHub token is written with `gcloud secrets versions add` precisely so it never enters deployment state. `secret_rotation_period` (default `2592000s` = 30 days) and `enable_auto_password_rotation` (default `false`) drive a dual-version, zero-downtime rotation flow: an Eventarc-triggered dispatcher runs a rotator job that executes `ALTER USER`, adds the new secret version, and disables the old one only after a propagation delay. *Encryption.* `enable_cmek` (default `false`) creates a keyring with separate keys for Cloud SQL, Artifact Registry, and GCS, rotating every `cmek_key_rotation_period` (default `7776000s` = 90 days), and grants `roles/cloudkms.cryptoKeyEncrypterDecrypter` to each service agent. A plan-time recovery step in the platform's object-storage layer detects key versions scheduled for destruction or disabled and restores them before any encrypted resource is provisioned — operational self-healing for the classic "someone scheduled the key for destruction" incident. *Supply chain.* `enable_binary_authorization` (default `false`) with `binauthz_evaluation_mode` (default `ALWAYS_ALLOW`; set `REQUIRE_ATTESTATION` to enforce) creates a KMS RSA-2048 signing key, a Container Analysis note and attestor, and an additive policy that blocks and audit-logs non-conforming images. GKE clusters enforce the project's singleton Binary Authorization policy when enabled. `enable_vulnerability_scanning` turns on Artifact Registry scanning. *Perimeters.* `enable_vpc_sc` (default `false`, `vpc_sc_dry_run` default `true`) builds a perimeter restricting ~15 services with four access levels (VPC CIDRs, `admin_ip_ranges`, the IAP SA, CI/CD SAs). The organization ID is resolved from the project (with an explicit `organization_id` variable override available in App_CloudRun/App_GKE — needed when the project sits under a *folder*, where auto-discovery returns nothing), and a permission probe checks the caller's Access Context Manager rights, skipping with a warning instead of failing the apply. *Edge and runtime.* `enable_iap` grants `roles/run.invoker` to the IAP service agent and `roles/iap.httpsResourceAccessor` to `iap_authorized_users`/`iap_authorized_groups` (validation requires at least one). `enable_cloud_armor` deploys OWASP preconfigured WAF rules (sqli/xss/lfi/rce, v33-stable), Adaptive Protection, and a 500 req/min/IP rate limit with a 300 s ban. `application_domains` is optional — the module derives a `nip.io` managed certificate when it is empty. On GKE, `enable_network_segmentation` (default `false`) creates default-deny-shaped NetworkPolicies on Dataplane V2: ingress only from the same namespace plus Google LB health-check and IAP ranges; egress only to DNS, HTTPS (including the restricted/private googleapis ranges `199.36.153.4/30` and `199.36.153.8/30`), Cloud SQL on 3307, the metadata server, and NFS when enabled. **Try it** 1. In **Console > IAM & Admin > Service Accounts**, list the five platform SAs; pick one and inspect its bindings: ```bash gcloud projects get-iam-policy \ --flatten="bindings[].members" \ --filter="bindings.members:cloudrun-sa-" \ --format="table(bindings.role)" ``` 2. In **Console > Security > Secret Manager**, open the database password secret — confirm versions exist but values are never shown in plan output or the portal. 3. Enable `REQUIRE_ATTESTATION`, then attempt to deploy an unsigned image and watch the denial in **Console > Logging > Logs Explorer** (filter on Binary Authorization audit events). 4. With VPC-SC enabled in dry-run, review the perimeter: ```bash gcloud access-context-manager perimeters list --policy= \ --format="table(name,title,spec.restrictedServices.list():label=DRY_RUN_SERVICES)" ``` 5. On GKE with segmentation enabled: `kubectl describe networkpolicy -n ` and trace each rule to a trust decision. 6. You know it worked when the unsigned image is blocked, the perimeter shows in dry-run (`spec` populated, not `status`), and every IAM binding you find is scoped to a specific resource or duty. **Check yourself**
Q1: A regulator requires that your company control — and be able to revoke — the encryption keys protecting customer data, with rotation at least quarterly. Which platform settings satisfy this, and what is the operational risk the module mitigates? A: `enable_cmek = true` with the default `cmek_key_rotation_period = "7776000s"` (90 days) gives customer-managed keys with quarterly rotation; disabling/destroying the key revokes access to the data. The operational risk is self-inflicted denial of service — a key version scheduled for destruction bricks every encrypted bucket and repo — which the platform's plan-time key-recovery step mitigates by restoring versions that were scheduled for destruction or disabled.
Q2: A security team wants to guarantee that only images built by the official CI pipeline run in production. Which control, and what mode? A: Binary Authorization with `binauthz_evaluation_mode = "REQUIRE_ATTESTATION"` — the CI pipeline signs (attests) each image digest with the KMS key, and the policy blocks unattested images at deploy time while audit-logging the decision. Vulnerability scanning alone only *reports*; IAM alone controls who deploys, not *what* is deployed.
Q3: Why is `vpc_sc_dry_run = true` the default, and what is the rollout sequence the exam (and this module's variable description) expects? A: An enforced perimeter with wrong access levels instantly breaks CI/CD, deployments, and admin access — VPC-SC denials are hard failures at the API layer. Correct sequence: deploy in dry-run, monitor audit logs for would-be violations for days, add missing IPs/SAs to access levels, then flip `vpc_sc_dry_run = false`. Dry-run logs violations without blocking.
**Beyond the modules** — Two examined areas are absent. (1) **Resource hierarchy and organization policies**: folders, inheritance, constraints like `iam.disableServiceAccountKeyCreation` — study "Organization Policy Service" and try `gcloud resource-manager org-policies list` in an org-attached project. (2) **Hierarchical firewall policies and Cloud NGFW** — the modules use classic VPC firewall rules only. (Workload Identity Federation, formerly absent, is now live: `enable_workload_identity_federation` in Services_GCP creates pool `wif-pool` with a GitHub Actions / GitLab CI / generic OIDC provider per `wif_provider_type` — a working keyless-CI lab.) **⚠️ Exam trap** — IAP and Cloud Armor answer different questions: IAP authenticates *identities* (who are you?); Cloud Armor filters *traffic* (is this request malicious?). "Only employees may access the app" → IAP; "block SQL injection and DDoS" → Cloud Armor. Scenarios often need both, but never one as a substitute for the other. --- ## 3.2 Designing for compliance > ⏱ ~60 min · 💰 low–moderate — Data Access audit logs can grow log storage costs · ⚙️ Requires: `enable_audit_logging = true`; SCC steps need `enable_security_command_center = true` (org-level roles required for notifications) **Why the exam cares** — Compliance questions test evidence and control mapping: which logs prove who did what (Admin Activity vs Data Access), how findings are surfaced and routed, and how regional/regulatory constraints (HIPAA, PCI-DSS, data residency) shape architecture. **How RAD implements it** *Audit trail.* `enable_audit_logging` (default `false`) configures `allServices` audit logs for `ADMIN_READ`, `DATA_READ`, and `DATA_WRITE` — Admin Activity (`ADMIN_WRITE`) is always on and free, but Data Access logs must be explicitly opted in, which is exactly what the platform does, plus explicit per-service configs for Secret Manager and Cloud KMS so secret and key access is provably logged. *Findings and posture.* `enable_security_command_center` (default `false`) plus `enable_scc_notifications` route findings to a Pub/Sub topic (`scc-{prefix}-findings`). The notification config is gated by an org-permission probe — if the deploying SA lacks org-level SCC roles, the feature is skipped with a warning rather than failing the apply. GKE clusters additionally enable security posture management (mode `BASIC`, vulnerability mode `VULNERABILITY_BASIC`). *Exfiltration and residency controls.* VPC-SC (3.1) is also the compliance answer for data-boundary requirements; region placement is controlled by `availability_regions`. **Try it** 1. Enable `enable_audit_logging = true`, then read or write a secret version and find the access event: ```bash gcloud logging read \ 'logName:"cloudaudit.googleapis.com%2Fdata_access" AND protoPayload.serviceName="secretmanager.googleapis.com"' \ --limit=5 --format="table(timestamp, protoPayload.methodName, protoPayload.authenticationInfo.principalEmail)" ``` 2. In **Console > IAM & Admin > Audit Logs**, verify Data Read/Write are enabled for "All services" with per-service rows for Secret Manager and KMS. 3. If SCC is active, browse **Console > Security > Security Command Center > Findings** and check for the `scc-{prefix}-findings` topic in **Pub/Sub**. 4. You know it worked when the log query returns the principal email and method for your secret access — audit evidence on demand. **Check yourself**
Q1: An auditor asks for proof of every read of patient-data secrets in the last 30 days. Default project logging cannot provide it — why, and what does this platform change? A: Reads are Data Access (`DATA_READ`) events, which Google disables by default for cost reasons; only Admin Activity is always on. `enable_audit_logging = true` opts all services into `ADMIN_READ`/`DATA_READ`/`DATA_WRITE`, with explicit Secret Manager coverage — making the read trail queryable in Logs Explorer (and exportable to BigQuery for long retention).
Q2: Why does the SCC notification feature "silently skip with a warning" instead of failing, and what design principle is that? A: SCC notification configs require org-level permissions the deploying service account may not hold in every tenant project. The permission probe degrades gracefully — partial security posture rather than a failed platform deployment. The principle: separate *mechanism availability* from *privilege availability*, and never let an optional control block the critical path. Know for the exam that full SCC management is organization-scoped.
**Beyond the modules** — Not covered: Assured Workloads (regulated regions/personnel controls), Access Transparency (logs of *Google* personnel access), org-policy-based data residency (`gcp.resourceLocations`), DLP/Sensitive Data Protection for PII discovery and masking, and formal compliance mappings (HIPAA BAA, PCI-DSS responsibility splits). Study the "Compliance resource center" and run a DLP inspection template against a sample bucket in a scratch project. **⚠️ Exam trap** — Admin Activity logs are free, always on, and immutable; Data Access logs are opt-in, high-volume, and billable (BigQuery and some services differ). A scenario about "who *changed* the firewall" needs no configuration at all; "who *read* the data" needs Data Access logs enabled *before* the incident — you cannot enable them retroactively. --- # PCA Certification Preparation Guide: Section 4 — Analyzing and optimizing technical and business processes (~15% of the exam) PCA Certification Preparation Guide: Section 4 — Analyzing and optimizing technical and business processes (~15% of the exam) > 📚 **Official exam guide:** [Professional Cloud Architect certification](https://cloud.google.com/learn/certification/cloud-architect) — always confirm section weightings against the current Google Cloud exam guide. This section is about *process* architecture: software delivery lifecycle, testing and release strategy, and the organizational controls (approvals, cost governance, decision-making) wrapped around them. The deployable half lives in `App_CloudRun`'s CI/CD surface and the platform's Cloud Deploy layer; the people half — SRE culture, stakeholder management, post-mortems — must be studied from the SRE books and Architecture Framework. Deploy the **Security and delivery** profile from the [Lab Map](PCA_Certification_Guide.md) with `enable_cicd_trigger = true` and `enable_cloud_deploy = true`. --- ## 4.1 Analyzing and defining technical processes > ⏱ ~90 min · 💰 low — Cloud Build minutes and Artifact Registry storage · ⚙️ Requires: Security and delivery profile + a GitHub repository (`github_repository_url`, token or App installation) **Why the exam cares** — The exam tests SDLC design choices: where builds happen, how artifacts gain provenance, how releases progress through environments, and how risk is contained per stage (canary percentages, approval gates, rollback paths). Expect questions distinguishing CI (build/test/integrate) from CD (release/promote) and asking which Google tool owns which step. **How RAD implements it** | SDLC stage | Implementation | Variables (defaults) | |---|---|---| | Source trigger | Cloud Build GitHub trigger on push | `enable_cicd_trigger` (default `false`), `cicd_trigger_config.branch_pattern` (default `"^main$"`, plus included/ignored file filters) | | Build | Kaniko executor `v1.23.2` builds in-cluster-less (no Docker daemon) and pushes to Artifact Registry tagged `latest`, the app version, and `COMMIT_SHA` | `enable_cicd_trigger` | | Provenance | optional `gcloud beta container binauthz attestations sign-and-create` step signs the `COMMIT_SHA` digest with the KMS attestor key | `enable_binary_authorization` | | Deploy (simple) | `gcloud run services update` to the new image | default path | | Deploy (progressive) | Cloud Deploy pipeline with per-stage targets and skaffold configs in GCS | `enable_cloud_deploy` (default `false`), `cloud_deploy_stages` — default `dev` → `staging` → `prod` where **`prod` has `require_approval = true`** | | Canary / blue-green | Cloud Run revision traffic splitting; entries must sum to exactly 100 (validated) | `traffic_split` (default `[]` = all traffic to latest) | | Artifact hygiene | AR cleanup policies | `max_images_to_retain` (default `7`), `delete_untagged_images` (default `true`), `image_retention_days` (default `30`) | Two details to internalize as exam material: builds tag every image with the immutable `COMMIT_SHA` (the digest the attestation signs — `latest` is never the deployment contract), and the default pipeline encodes the governance asymmetry the exam expects: pre-production promotes freely, production requires a human. **Try it** 1. Push a commit to the configured branch and watch **Console > Cloud Build > History** — identify the Kaniko build step and (if enabled) the attestation step. 2. Inspect the resulting tags: ```bash gcloud artifacts docker images list \ -docker.pkg.dev/// \ --include-tags --limit=5 ``` 3. With Cloud Deploy enabled, open **Console > Cloud Deploy > Delivery pipelines**, promote the release from `dev` to `staging`, then observe that `prod` waits in "Needs approval". 4. Configure a canary: set `traffic_split` to 90% LATEST / 10% a previous revision (with a `tag = "canary"`), apply, and verify: ```bash gcloud run services describe --region=us-central1 \ --format="yaml(status.traffic)" ``` 5. You know it worked when the prod stage is blocked pending approval and `status.traffic` shows the 90/10 split with the tagged canary URL. **Check yourself**
Q1: A team wants new releases validated on 5% of production traffic with instant rollback. Which mechanism here, and what is the rollback action? A: Cloud Run `traffic_split` — e.g. 95% to the stable revision, 5% to the new one (optionally with a stable `tag` URL for targeted testing). Rollback is a traffic reassignment to the previous revision, not a redeploy, because Cloud Run retains prior revisions (`max_revisions_to_retain`, default `7`). This is the serverless analogue of canary deployments the exam describes.
Q2: Why does the pipeline sign the image's COMMIT_SHA tag rather than `latest`? A: Attestations bind to an immutable digest. `latest` is a moving pointer — signing it would attest "whatever this tag points to," defeating supply-chain integrity. The Binary Authorization policy verifies the digest being deployed carries a valid signature from the attestor, which only holds for the specific built artifact.
Q3: Where is the CI/CD boundary in this platform's pipeline? A: CI = Cloud Build (trigger → Kaniko build → push to Artifact Registry → attest): producing a verified artifact. CD = Cloud Deploy (release → dev → staging → approval → prod): promoting that artifact through environments. The exam expects you to assign testing/build failures to CI and promotion/approval/rollout strategy to CD.
**Beyond the modules** — The exam also covers testing strategy (unit vs integration vs load; the pipeline here runs no test step — adding one is a good exercise), post-mortem/root-cause culture, and troubleshooting tooling (Cloud Profiler, Cloud Trace). Study the DORA metrics (deployment frequency, lead time, change-failure rate, MTTR) and the "Application deployment and testing strategies" architecture doc — rolling vs blue-green vs canary trade-offs are recurring exam material. **⚠️ Exam trap** — Don't conflate Cloud Build triggers with Cloud Deploy. A scenario about "build on every merge" is Cloud Build; "promote the same artifact through dev/staging/prod with approvals" is Cloud Deploy. Rebuilding the image per environment (instead of promoting one artifact) is the anti-pattern the exam wants you to reject. --- ## 4.2 Analyzing and defining business processes > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: Cloud Deploy enabled (Security and delivery profile) **Why the exam cares** — Architects operate change-management and governance processes, not just infrastructure: enforced approvals for regulated environments, cost accountability, skills-based platform choices (a team that cannot run Kubernetes should not be handed Kubernetes), and data-driven decision frameworks like SRE error budgets. **How RAD implements it** — The deployable artifacts here are governance encoded as configuration. The default `cloud_deploy_stages` makes production promotion a human decision (`require_approval = true` on `prod`) — an auditable change-management gate satisfying separation-of-duties expectations, with `auto_promote` available per stage where velocity matters more. Cost accountability comes from `create_billing_budget` + `budget_alert_thresholds` (Section 1.1) and from GKE cost allocation (enabled on every cluster, supporting namespace-level cost breakdown in billing). And the platform's *existence* demonstrates a skills-readiness decision: the portal lets a team choose Cloud Run (low Kubernetes skill requirement) or GKE (full orchestration) for the same application — the choice itself is the business-process artifact. **Try it** 1. Create a release and promote it to the `prod` stage; in **Console > Cloud Deploy > Delivery pipelines > (pipeline) > Releases**, click into the pending rollout and use **Approve** (or reject it). 2. Review the audit trail of that approval: ```bash gcloud deploy rollouts list \ --delivery-pipeline= --release= \ --region=us-central1 \ --format="table(name,state,approvalState,deployStartTime)" ``` 3. You know it worked when the rollout shows `approvalState: APPROVED` with a timestamp — evidence a change-advisory process can consume. **Check yourself**
Q1: A regulated insurer requires documented sign-off before production changes, but wants zero friction in lower environments. How is this expressed in this platform — and in exam terms, what process is being implemented? A: `cloud_deploy_stages` with `require_approval = false` (optionally `auto_promote = true`) on dev/staging and `require_approval = true` on prod — exactly the module default. This implements change management with separation of duties: the deployer and the production approver are distinct, and Cloud Deploy records both, producing the audit evidence (SOC 2-style change control) the scenario demands.
Q2: Leadership must choose between Cloud Run and GKE for a new product; the team has strong app developers and no platform engineers. What does the exam expect you to weigh? A: Team skills readiness is a first-class architectural input. With no Kubernetes operations capability, Cloud Run's managed model (no clusters, quotas, PDBs, upgrades) reduces operational risk even if GKE offers more control; choosing GKE would require hiring or training (a cost and timeline factor). The exam consistently rewards matching platform complexity to organizational capability, not maximal flexibility.
**Beyond the modules** — Study what no module can show: SRE error budgets as a decision mechanism (feature velocity vs reliability), SLI/SLO definition with stakeholders, incident communication, and translating technical metrics into business KPIs. The free Google SRE book chapters "Embracing Risk" and "Service Level Objectives" are the canonical exam preparation here. **⚠️ Exam trap** — An approval gate is change *management*, not change *validation*. If the scenario asks how to catch bad releases automatically, the answer is canary analysis/testing in the pipeline — a human approval button does not verify correctness, it assigns accountability. --- # PCA Certification Preparation Guide: Section 5 — Managing implementation (~12.5% of the exam) PCA Certification Preparation Guide: Section 5 — Managing implementation (~12.5% of the exam) > 📚 **Official exam guide:** [Professional Cloud Architect certification](https://cloud.google.com/learn/certification/cloud-architect) — always confirm section weightings against the current Google Cloud exam guide. Managing implementation means making other teams successful: providing paved paths, guardrails, and programmatic access patterns. The RAD platform is itself the exhibit — a four-tier Terraform/OpenTofu architecture that development teams consume through a portal, with validations that fail bad configurations at plan time and registry hygiene baked in. Deploy any profile from the [Lab Map](PCA_Certification_Guide.md); the **Security and delivery** profile makes the most artifacts visible. Modules exercised: all four, with emphasis on the platform's shared scripts and plan-time validations. --- ## 5.1 Advising development and operation teams > ⏱ ~60 min · 💰 no additional cost · ⚙️ Requires: any deployed profile **Why the exam cares** — Architects are advisors: they codify standards so teams cannot easily do the wrong thing, choose API-management and testing approaches, and set artifact and dependency policies. Exam scenarios ask what guidance or guardrail prevents a described failure. **How RAD implements it** — Three advisory patterns are observable in the code: *Paved path with guardrails.* The foundation modules expose a curated variable surface and reject misconfigurations at plan time — App_GKE carries 32 preconditions (min ≤ max instances, IAP completeness, PVC requirements, CDN/Armor prerequisites, name-length limits ≤ 55 chars, `gateway_backend_stage` must exist). Teams get expressive power; the platform team gets enforced invariants. This is "advising through tooling," and it is how the exam expects standards to scale beyond documentation. *Artifact policy.* Artifact Registry is auto-discovered or created (`shared-repo-{prefix}`), with cleanup policies — `max_images_to_retain` (default `7`), `delete_untagged_images` (default `true`), `image_retention_days` (default `30`) — and optional CMEK (`enable_artifact_registry_cmek`) and vulnerability scanning. Third-party dependencies are not pulled from the internet at runtime: required images (e.g. the Cloud SQL Auth Proxy) are copied into AR using Crane with **digest comparison** — an existing tag is overwritten when its digest no longer matches the source, so a stale or tampered mirror is never silently used. *Operational defaults.* Database client tooling ships as a purpose-built image, initialization jobs are first-class (`initialization_jobs` with `depends_on_jobs` ordering), and revision/image pruning keeps environments tidy without team effort. **Try it** 1. Review five of App_GKE's plan-time preconditions; for each, write the production incident it prevents. 2. Deliberately violate one in the portal (e.g. `min_instance_count = 5`, `max_instance_count = 2`) and observe the plan-time error — the message names the variables and the fix. 3. Inspect the artifact policy in effect: ```bash gcloud artifacts repositories describe \ --location= \ --format="yaml(cleanupPolicies,vulnerabilityScanningConfig)" ``` 4. You know it worked when the bad configuration never reached an apply, and the repository shows cleanup policies a developer never had to write. **Check yourself**
Q1: Development teams keep deploying containers that pull a third-party sidecar from Docker Hub at runtime, causing outages during registry rate-limiting. What do you advise, and what subtlety makes a naive mirror dangerous? A: Mirror required third-party images into your own Artifact Registry and deploy only from there — as this platform does for the Cloud SQL Auth Proxy. The subtlety: a tag can silently drift upstream, so the mirror must compare digests (as this platform does with Crane) rather than assume "tag exists = up to date"; otherwise you pin to a stale or wrong image forever.
Q2: A platform team's written standards are ignored. What does this repository demonstrate as the scalable alternative? A: Encode standards as plan-time validations and curated module variables — the standard becomes impossible to violate rather than merely documented. Misconfigurations fail with actionable error messages before any resource is created, which is cheaper than failing in production and faster than review-based enforcement.
**Beyond the modules** — Study what advising covers beyond IaC guardrails: API management selection (Apigee for monetization/analytics/legacy mediation vs API Gateway for lightweight serverless fronting), testing frameworks (unit/integration/load and where each runs in CI), Database Migration Service for advising on data moves, and Service Catalog for curated solution distribution. Try creating an API Gateway in a scratch project to feel the difference from Apigee's scope. **⚠️ Exam trap** — "Store images in Container Registry" is a stale answer: Container Registry is deprecated in favor of Artifact Registry, which adds per-repository IAM, cleanup policies, CMEK, and scanning — the features this platform depends on. --- ## 5.2 Interacting with Google Cloud programmatically > ⏱ ~60 min · 💰 no additional cost · ⚙️ Requires: any deployed profile + Cloud Shell or a workstation with `gcloud` **Why the exam cares** — The exam tests fluency across the programmatic surface: declarative IaC vs imperative CLI, when each is appropriate, and how authentication works without key files. Expect "which command/approach" questions. **How RAD implements it** — The portal compiles your variable choices and runs the OpenTofu lifecycle (`tofu init → plan → apply`) for you — every deployment you have done in these guides was a programmatic interaction. The modules also demonstrate the *boundary* of declarative IaC: where the provider has gaps, they shell out to `gcloud` deliberately — e.g. GKE add-ons are enabled via `gcloud container clusters update --enable-secret-manager`, Cloud Run jobs are executed with `gcloud run jobs execute --wait`, and discovery runs `gcloud compute networks subnets list --filter=...` inside external data scripts. Service-account impersonation (`--impersonate-service-account`, and the `impersonation_service_account` variable) is used throughout instead of key files. **Try it** 1. Trigger a deployment from the portal — behind the scenes it runs the read-only half of the lifecycle (init → validate → plan) before any apply, rejecting invalid configurations at plan time. 2. Cross-check the declared state against live state imperatively: ```bash gcloud run services list --region=us-central1 gcloud sql instances list gcloud container clusters list ``` 3. Note where the platform deliberately steps outside declarative IaC: discovery is fed by `gcloud ... --format=json` calls (e.g. subnet discovery), and add-ons are toggled with imperative `gcloud` commands where the provider has gaps. 4. You know it worked when the plan shows no unexpected diff (declarative truth) and the `gcloud` listings match it (imperative observation). **Check yourself**
Q1: An operator "quickly fixed" a service's memory limit with `gcloud run services update`. What happens on the next platform deployment, and what does the exam call this? A: Configuration drift — the next `tofu apply` reverts the manual change to the declared value (or surfaces it as a diff at plan time). The exam expects drift to be resolved by changing the declaration (the portal variable), never by repeated imperative patching; IaC is the source of truth.
Q2: A CI system needs to call GCP APIs as a privileged service account without storing a JSON key. Which patterns does this platform use? A: Service-account impersonation — callers with `roles/iam.serviceAccountUser`/token-creator rights act as the target SA via `--impersonate-service-account`, receiving short-lived tokens (the modules pass `impersonation_service_account` into provider auth and gcloud calls). On GKE, Workload Identity binds Kubernetes service accounts to GCP SAs the same keyless way. Long-lived JSON keys are the anti-answer.
**Beyond the modules** — The exam's programmatic surface is wider: Cloud Shell and Cloud Code, `gcloud storage` (the modern `gsutil` replacement), `bq` for BigQuery, client libraries (Python/Java/Node) with Application Default Credentials resolution order, local emulators (Pub/Sub, Firestore, Spanner, Bigtable), and API quota/retry behavior (exponential backoff on `429`/`5xx`). Practice in Cloud Shell: `gcloud config list`, `gcloud auth application-default login`, and one client-library quickstart end to end. **⚠️ Exam trap** — `gcloud auth login` (your user) and Application Default Credentials (`gcloud auth application-default login`, what client libraries see) are separate credential stores. A script that works in your terminal but fails with "could not find default credentials" inside code is the classic symptom — and a recurring exam distractor. --- # PCA Certification Preparation Guide: Section 6 — Ensuring solution and operations excellence (~12.5% of the exam) PCA Certification Preparation Guide: Section 6 — Ensuring solution and operations excellence (~12.5% of the exam) > 📚 **Official exam guide:** [Professional Cloud Architect certification](https://cloud.google.com/learn/certification/cloud-architect) — always confirm section weightings against the current Google Cloud exam guide. Day-2 operations: observing systems, releasing safely, controlling quality, and keeping production reliable. Every RAD deployment ships with a dashboard and alerting wired to your email, and publicly reachable deployments add a synthetic uptime check (see 6.2) — so most of this section is observable on the **Lean baseline** profile from the [Lab Map](PCA_Certification_Guide.md); add the **Security and delivery** profile for release management (6.3) and the **GKE architecture** profile for the reliability mechanics in 6.6. Modules exercised: all four, with emphasis on the monitoring layers of `Services_GCP` and `App_CloudRun`, plus the platform's shared monitoring and dashboard layers. --- ## 6.1 Operational excellence pillar (Well-Architected Framework) > ⏱ ~30 min reading + console review · 💰 no additional cost · ⚙️ Requires: default deployment **Why the exam cares** — The Architecture Framework's operational excellence pillar — automate everything, make changes safely, prepare for failure, continuously improve — frames many scenario answers. The exam rewards recognizing operational toil and replacing it with automation. **How RAD implements it** — The pillar is visible as a set of automations that remove human toil: the NFS VM is a managed instance group with TCP health checks and auto-healing plus daily disk snapshots (no pager for a hung file server); the platform restores disabled or destruction-scheduled CMEK key versions at *plan* time (self-healing before the failure manifests); orphaned Cloud Run jobs and old revisions are cleaned automatically; secret rotation is event-driven and zero-downtime; and the entire platform is declaratively reproducible, so environment rebuilds are an apply, not a runbook. **Try it** 1. Pick three automations above (the auto-healing NFS instance group, the plan-time CMEK key recovery, and Cloud Run revision/job pruning), and write down the manual runbook each replaces. 2. Observe one in action — list the snapshot schedule protecting the NFS data disk: ```bash gcloud compute resource-policies list --format="table(name,snapshotSchedulePolicy.schedule.dailySchedule)" ``` 3. You know it worked when you can name, for each automation, the incident class it prevents rather than reacts to. **Check yourself**
Q1: A team's runbook says "if the file server stops responding, SSH in and restart nfsd; if the disk is corrupted, restore last night's copy." What does this platform replace that with? A: A managed instance group with TCP health checks (ports 2049/6379) and auto-healing — an unresponsive instance is automatically recreated with its stateful data disk reattached — plus a daily snapshot schedule with 7-day retention for the corruption case. The runbook becomes infrastructure; the exam calls this eliminating toil through automation.
**Beyond the modules** — Read the official "Google Cloud Architecture Framework: Operational excellence" pillar end to end — its principles (automate deployments, manage incidents, plan for DR) are quoted nearly verbatim in exam options. The framework's sustainability and performance pillars are also fair game and have no module analogue. --- ## 6.2 Familiarity with Google Cloud Observability solutions > ⏱ ~60 min · 💰 low — log/metric volume only · ⚙️ Requires: default deployment with `support_users` populated **Why the exam cares** — You must know the observability stack's division of labor — Monitoring (metrics, alerts, uptime checks, dashboards), Logging (Logs Explorer, log-based metrics, sinks), Trace/Profiler (latency and code-level analysis) — and design alerting that pages on symptoms with actionable thresholds. **How RAD implements it** | Capability | Implementation | Variables (defaults) | |---|---|---| | Notification channels | email channels per address | `support_users` (App modules), `configure_email_notification` + `notification_alert_emails` (Services_GCP) | | Infrastructure alerts | Cloud SQL CPU/memory/disk and NFS-VM CPU/memory/instance-down policies provisioned by the platform | `alert_cpu_threshold` / `alert_memory_threshold` / `alert_disk_threshold` (all default `80`) | | Application alerts | per-service policies filtered to the Cloud Run service | `alert_policies` list — `metric_type`, `comparison`, `threshold_value`, `duration_seconds`, `aggregation_period` (default `"60s"`) | | Synthetic monitoring | `-uptime-check` (HTTP GET from multiple global probe regions) plus a `-uptime-check-alert` policy on `monitoring.googleapis.com/uptime_check/check_passed`, created by the platform's monitoring layer when the endpoint is publicly reachable; `uptime_check_names` outputs the real check name | `uptime_check_config` (default `{ enabled = false, path = "/" }`; `check_interval` default `"60s"`, `timeout` default `"10s"`) | | Dashboards | per-deployment dashboard provisioned by the platform | App_CloudRun / App_GKE | | GKE telemetry | system + workload logging, managed Prometheus | fixed defaults in Services_GCP | **Try it** 1. In **Console > Monitoring > Alerting**, identify the platform policies (Cloud SQL CPU/memory/disk, NFS health) and your service's policies; open one and trace metric → threshold → channel. 2. Add a custom policy via the portal, e.g. `{ name = "high-latency", metric_type = "run.googleapis.com/request_latencies", comparison = "COMPARISON_GT", threshold_value = 1000, duration_seconds = 300 }`, and re-apply. 3. In **Console > Monitoring > Uptime checks**, open the module-created `-uptime-check` and watch the probe results arriving from multiple regions, then query recent application errors: ```bash gcloud logging read \ 'resource.type="cloud_run_revision" AND severity>=ERROR' \ --limit=10 --format="table(timestamp,severity,textPayload)" ``` 4. You know it worked when your custom policy appears in Alerting wired to the `support_users` email channel, and the module-created uptime check shows passing probes from multiple regions. **Check yourself**
Q1: Users report the app is down, but no alert fired — CPU and memory were normal. What monitoring gap exists in the default deployment, and what is the right kind of alert to close it? A: A synthetic uptime check probing the endpoint from outside (the modules create one via `uptime_check_config` for publicly reachable deployments — internal-only deployments get none, so this gap appears whenever ingress is locked down). Resource metrics are *cause-based* and can look healthy while the user experience is broken (bad deploy, LB misconfig, dead dependency); an external probe is *symptom-based* — it measures what users experience, which SRE practice (and the exam) says to page on.
Q2: The DB team wants warning before the database degrades. Which three platform thresholds apply, and what tuning trade-off should you explain? A: `alert_cpu_threshold`, `alert_memory_threshold`, `alert_disk_threshold` (each default `80`%) on the Cloud SQL instance. Lower thresholds buy lead time but raise false-positive load (alert fatigue); higher thresholds reduce noise but shrink reaction time. Durations (`duration_seconds`) suppress transient spikes — alert design is a precision/recall trade-off, not a single right number.
**Beyond the modules** — Not wired up: log sinks/exports to BigQuery, log-based metrics, SLO monitoring with burn-rate alerts, Cloud Trace, and Cloud Profiler. Practice creating a log-based metric and an SLO on a Cloud Run service in the Monitoring console — SLO/error-budget questions are frequent. **⚠️ Exam trap** — Uptime checks need an externally reachable endpoint. If a scenario locks ingress down (e.g. internal-only), a public uptime check fails by design — the answer is private uptime checks or internal synthetic probes, not "the service is down." RAD encodes this: the foundation modules skip uptime check creation entirely when the deployment is not publicly reachable. --- ## 6.3 Deployment and release management > ⏱ ~60 min · 💰 low · ⚙️ Requires: Security and delivery profile (Cloud Deploy + CI/CD) **Why the exam cares** — Release management questions test rollout strategies (rolling, blue-green, canary), rollback speed, and environment promotion discipline — including keeping the data layer (schemas, secrets) compatible across a rollout. **How RAD implements it** — Cloud Run retains prior revisions and prunes them to `max_revisions_to_retain` (default `7`), so rollback is re-pointing traffic, with `traffic_split` providing canary and blue-green percentages (validated to sum to 100). Cloud Deploy (`cloud_deploy_stages`, default `dev`/`staging`/`prod` with approval on `prod`) promotes one artifact through environments. On GKE, Deployments use rolling updates, StatefulSets use `stateful_update_strategy` (default `RollingUpdate`), and Cloud Deploy stages map to per-stage services selected by `gateway_backend_stage` (default `"dev"`) behind the Gateway. The data layer is covered too: secret rotation is dual-version (new version added, old disabled only after `rotation_propagation_delay_sec`, default `90`) so a rollout never races its credentials. **Try it** 1. Deploy a new application version, then roll back without rebuilding: ```bash gcloud run services update-traffic \ --region=us-central1 \ --to-revisions==100 ``` 2. Confirm in **Console > Cloud Run > Revisions** that traffic moved and the old revision still exists (pruning keeps 7). 3. On GKE, watch a rolling update: change the image/version in the portal and run `kubectl rollout status deployment/ -n `. 4. You know it worked when rollback took seconds (traffic shift) rather than minutes (rebuild + redeploy). **Check yourself**
Q1: Why does revision pruning matter to release management — isn't keeping every revision safer? A: Unbounded revisions accumulate cost (container images, config clutter) and make the rollback target ambiguous. Retaining a bounded window (7 here) keeps fast rollback to any recent version while forcing older states to be reproduced from source control — the artifact of record — rather than from stale runtime objects.
Q2: During a credential rotation mid-rollout, old pods still hold the previous password. Why doesn't this platform's rotation break them? A: Rotation is dual-version: the rotator adds the *new* secret version and changes the database password, but disables the *old* version only after a propagation delay, so both credentials briefly remain valid while revisions/pods converge. Single-version rotation (overwrite-then-pray) is the outage pattern the exam wants you to avoid.
**⚠️ Exam trap** — Blue-green and canary differ in cost and blast radius: blue-green doubles capacity for an instant full cutover; canary exposes a small percentage gradually. `traffic_split` implements both shapes on Cloud Run — pick per the scenario's tolerance for risk vs spend. --- ## 6.4 Assisting with the support of deployed solutions > ⏱ ~20 min reading · 💰 no additional cost · ⚙️ Requires: default deployment **Why the exam cares** — Architects design the support model: who is notified, with what evidence, and when to escalate to Google Cloud Customer Care (Standard/Enhanced/Premium plans, TAM engagement for P1s). **How RAD implements it** — Largely not implemented; the nearest adjacent capability is the notification plumbing: `support_users` feeds Cloud Monitoring email channels (one per address, via the platform's monitoring layer), so the on-call audience is part of the deployment definition, and every alert in 6.2 carries the metric evidence a support case needs. **Try it** 1. Add a second address to `support_users` and re-apply; verify the new channel in **Console > Monitoring > Alerting > Notification channels**: ```bash gcloud beta monitoring channels list --format="table(displayName,type,labels.email_address)" ``` 2. You know it worked when the channel list matches the variable. **Check yourself**
Q1: A customer running mission-critical production workloads asks which Google Cloud support plan they need for a 15-minute P1 response and a named technical contact. What do you recommend? A: Premium Support — it provides the fastest P1 response SLO and Technical Account Manager engagement. Enhanced suits production workloads with less aggressive response needs; Standard is for non-critical workloads. Plan selection is an architectural recommendation, not an afterthought, in exam scenarios.
**Beyond the modules** — Study the Cloud Customer Care tiers and case-priority definitions (P1–P4), escalation paths, and how to package diagnostic evidence (logs, traces, monitoring snapshots). Browse **Console > Support** in any project to see the case workflow. --- ## 6.5 Evaluating quality control measures > ⏱ ~45 min · 💰 low · ⚙️ Requires: `enable_vulnerability_scanning = true` (Services_GCP) and the Security and delivery profile **Why the exam cares** — Quality control spans the delivery chain: static checks before apply, image scanning before deploy, admission enforcement at deploy, and posture monitoring after. The exam asks which control catches which defect class, and where in the pipeline it belongs. **How RAD implements it** — The platform layers four quality gates. *Plan time*: `tofu validate` plus the modules' preconditions (32 in App_GKE alone) reject invalid configurations before any API call. *Build time*: `enable_vulnerability_scanning` enables Artifact Registry scanning (`enablement_config = INHERITED`), surfacing CVEs per image digest. *Deploy time*: Binary Authorization (`REQUIRE_ATTESTATION`) admits only pipeline-signed digests. *Run time*: GKE clusters enable `security_posture_config` (mode `BASIC`, `VULNERABILITY_BASIC`) for workload posture findings. **Try it** 1. Push an intentionally dated base image through the pipeline, then review findings in **Console > Artifact Registry > (repo) > (image)** under Vulnerabilities, or: ```bash gcloud artifacts docker images list \ -docker.pkg.dev/// \ --show-occurrences --occurrence-filter='kind="VULNERABILITY"' ``` 2. Map each defect class to its gate: bad variable → plan precondition; CVE → AR scan; unsigned image → Binary Authorization; risky workload config → security posture. 3. You know it worked when the scan lists CVEs with severities for your image, and you can state which gate would have caught each of the three other defect classes. **Check yourself**
Q1: Scanning found a critical CVE, yet the image deployed anyway. Why, and what closes the gap? A: Scanning is *detective*, not *preventive* — it reports findings but blocks nothing. Closing the gap requires an enforcement point: Binary Authorization with an attestation granted only after a passing scan (e.g. the CI step attests only when no critical CVEs are present). The exam regularly contrasts visibility controls with enforcement controls.
Q2: Which is cheaper to catch: a malformed memory quota at plan time or at pod-scheduling time — and how does this platform decide? A: Plan time. App_GKE validates that quota memory values carry binary unit suffixes (`"4Gi"`) precisely because a bare number is interpreted by Kubernetes as bytes and silently blocks *all* pod scheduling — a confusing runtime outage converted into an immediate, named plan error. Shifting defect detection left is the quality-control principle being tested.
**Beyond the modules** — Not present: automated test suites in CI (unit/integration), SAST/dependency scanning steps, Web Security Scanner, and policy-as-code on infrastructure plans (e.g. OPA/terraform-compliance). Study "Container scanning overview" and "Web Security Scanner" docs, and try adding a test step to a Cloud Build YAML in a scratch repo. --- ## 6.6 Ensuring the reliability of solutions in production > ⏱ ~75 min · 💰 moderate — needs the GKE profile with ≥2 replicas · ⚙️ Requires: GKE architecture profile (`max_instance_count ≥ 2`), `enable_topology_spread = true` **Why the exam cares** — Reliability engineering is mechanism selection: protect capacity during voluntary disruptions (PDBs), spread replicas across failure domains, gate traffic on health (probes), auto-heal infrastructure, and enforce production-grade tiers. The exam gives a failure narrative and asks which mechanism was missing. **How RAD implements it** | Failure mode | Mechanism | Variables (defaults) | |---|---|---| | Upgrade/drain evicts too many pods | PodDisruptionBudget | `enable_pod_disruption_budget` (default `true`), `pdb_min_available` (default `"1"`), skipped when `max_instance_count = 1` | | All replicas land in one zone | topology spread across zone + hostname | `enable_topology_spread` (default `false`), `topology_spread_strict` | | Traffic hits a booting container | startup probe (10 s delay/10 s period) and liveness probe (15 s delay/30 s period), HTTP or TCP | `startup_probe_config`, `health_check_config` (both engines) | | NFS VM hangs | MIG auto-healing on TCP 2049/6379 health checks (300 s initial delay), PROACTIVE/REPLACE updates | `create_network_filesystem` (default `true`) | | Production on a non-replicated cache | plan-time guardrail blocks `redis_tier = "BASIC"` when `resource_labels.environment = "production"` | Services_GCP | | Demand exceeds capacity | HPA 70% CPU / 80% memory (GKE), instance scaling (Cloud Run) | `min_instance_count` / `max_instance_count` | **Try it** 1. With ≥2 replicas, verify the PDB and then simulate a voluntary disruption: ```bash kubectl get pdb -n kubectl get pods -n -o wide # note the nodes kubectl drain --ignore-daemonsets --delete-emptydir-data --dry-run=server ``` 2. Enable `enable_topology_spread = true`, re-apply, and confirm pods land in different zones (`kubectl get pods -o wide` — compare node zones). 3. Break the liveness path deliberately (point `health_check_config.path` at a non-existent route in a test deployment) and watch pods restart in **Console > Kubernetes Engine > Workloads**. 4. You know it worked when the drain respects `minAvailable`, replicas span zones, and the bad health path produces restarts instead of silent traffic blackholing. **Check yourself**
Q1: During a GKE node upgrade, a 3-replica service briefly dropped to zero healthy pods. Which two mechanisms from this platform were missing? A: A PodDisruptionBudget (`minAvailable: 1` would have forced the drain to keep one pod serving) and topology spread (replicas concentrated on one node/zone all evict together). Defaults here provide the PDB automatically once `max_instance_count > 1`; spread must be opted into via `enable_topology_spread`.
Q2: A slow-starting JVM app gets killed in a restart loop on GKE. Which probe setting is wrong, and why are there two probes at all? A: The startup probe window is too short — it must cover worst-case boot time before the liveness probe takes over. Startup probes answer "has it finished booting?" (failure = keep waiting, within limits); liveness probes answer "is it still healthy?" (failure = restart). Tuning liveness to tolerate slow boots instead of using a startup probe weakens failure detection for the entire pod lifetime.
Q3: Leadership asks for "five nines" on the self-managed NFS option. What honest answer does this architecture support? A: It cannot deliver that: the NFS server is a single zonal VM — auto-healing and daily snapshots reduce MTTR but recovery still takes minutes, and a zone outage takes the share down. For higher availability you change architecture, not tuning: managed Filestore (or, beyond this platform, a regional/Enterprise file tier). Recognizing when an SLO requires an architectural change is core PCA material.
**Beyond the modules** — Not demonstrated: chaos engineering (fault injection), load testing at scale, multi-region failover with global traffic management, and formal SLO/error-budget operations. Study the SRE workbook's "Implementing SLOs," and practice a load test (e.g. `hey` or the distributed load-testing reference architecture) against a scratch deployment while watching the HPA respond. **⚠️ Exam trap** — A PDB protects only against *voluntary* disruptions (drains, upgrades, autoscaler consolidation). Node crashes and zone outages ignore it entirely — those require replica count, topology spread, and multi-zone/multi-region design. "We had a PDB, why did the zone outage hurt us?" is exactly the confusion the exam probes. --- # Professional Cloud Developer (PCD) Certification Lab Map The Professional Cloud Developer certification validates your ability to design, build, test, deploy, and integrate scalable applications on Google Cloud — with a strong emphasis on Cloud Run, GKE, Cloud Build, Artifact Registry, Cloud Deploy, runtime secrets, service authentication, and observability. The RAD platform's four foundation modules (`Services_GCP`, `App_CloudRun`, `App_GKE`, `App_Common`) give you a live lab for exactly these skills: `Services_GCP` provisions the shared platform (VPC, Cloud SQL, Redis, GKE Autopilot, Artifact Registry, Binary Authorization, Workload Identity Federation), `App_CloudRun` and `App_GKE` are full-featured deployment engines for Cloud Run v2 services and Kubernetes workloads, and `App_Common` supplies the shared submodules they both use (secrets and rotation, Cloud Build container builds, Cloud Deploy pipelines, IAM, storage, monitoring). Application wrapper modules (Django, Wordpress, etc.) exist on the platform but everything in these guides uses the foundation modules directly. ## How to use this guide - Deploy one of the profiles below through your deployment portal, then work through the matching section exploration guide. - Every section guide pairs portal variables with the GCP console views and `gcloud`/`kubectl` commands the exam expects you to know. - Use the coverage legend to plan study time: 🟡 and 📘 topics include a "Beyond the modules" block telling you what to practice outside the platform. - PCD is a *developer* exam: when working through the labs, always ask "what would my application code see?" — the env vars, the secret refs, the socket paths, the tokens. **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: Serverless baseline *Purpose:* a default Cloud Run v2 service with database, probes, and runtime secrets — the workhorse for Sections 1, 3.1, and 4. *Modules:* `Services_GCP` (defaults), then `App_CloudRun`. | Variable | Value | |---|---| | `create_postgres` (Services_GCP) | `true` (default) | | `deploy_application` | `true` (default) | | `min_instance_count` | `0` (default — scale to zero) | | `max_instance_count` | `3` (raise from default `1` to observe scale-out) | | `database_type` | `"POSTGRES"` (default) | | `startup_probe_config` / `health_check_config` | defaults (HTTP `/healthz`) | *Estimated incremental cost:* low — Cloud Run scales to zero; the dominant costs are the `db-custom-1-3840` Cloud SQL instance and the `e2-small` NFS VM that `Services_GCP` creates by default. ### Profile: Delivery pipeline *Purpose:* GitHub-triggered Cloud Build (Kaniko) → Artifact Registry → Cloud Deploy progressive delivery with Binary Authorization attestation. Sections 2 and 3.1. *Modules:* `Services_GCP` + `App_CloudRun`. | Variable | Value | |---|---| | `enable_cicd_trigger` | `true` | | `github_repository_url` | your repo URL | | `enable_cloud_deploy` | `true` | | `cicd_enable_cloud_deploy` | `true` | | `cloud_deploy_stages` | default (`dev`, `staging`, `prod` with `require_approval = true` on prod) | | `enable_binary_authorization` (both modules) | `true` | | `binauthz_evaluation_mode` | `"REQUIRE_ATTESTATION"` | | `enable_vulnerability_scanning` (Services_GCP) | `true` | | `enable_workload_identity_federation` (Services_GCP) | `true` | *Estimated incremental cost:* low/moderate — three per-stage Cloud Run services (all can scale to zero) plus Cloud Build minutes per push. ### Profile: Kubernetes lab *Purpose:* GKE Autopilot deployment with Workload Identity, HPA, namespace governance, and the Secret Manager CSI add-on. Sections 1.1, 3.2, and 4.2. *Modules:* `Services_GCP` with GKE enabled, then `App_GKE`. | Variable | Value | |---|---| | `create_google_kubernetes_engine` (Services_GCP) | `true` | | `gke_cluster_mode` (Services_GCP) | `"AUTOPILOT"` (default) | | `min_instance_count` / `max_instance_count` (App_GKE) | `1` / `3` (defaults) | | `enable_resource_quota` (App_GKE) | `true` | | `enable_network_segmentation` (App_GKE) | `true` | | `enable_pod_disruption_budget` (App_GKE) | `true` (default) | *Estimated incremental cost:* moderate — Autopilot bills per pod resource request plus the cluster management fee; the default 1000m/512Mi pod is the dominant driver. ### Profile: Hardened edge *Purpose:* IAP authentication, Cloud Armor WAF + global HTTPS load balancer, automatic secret rotation, and Memorystore caching. Sections 1.1, 1.2, and 4.1. *Modules:* `Services_GCP` with Redis, then `App_CloudRun`. | Variable | Value | |---|---| | `create_redis` (Services_GCP) | `true` | | `redis_tier` (Services_GCP) | `"BASIC"` (default) | | `enable_iap` | `true` (plus `iap_authorized_users`) | | `enable_cloud_armor` | `true` (`application_domains` optional — a `nip.io` cert is derived when unset) | | `application_domains` | a domain you control | | `enable_auto_password_rotation` | `true` | | `secret_rotation_period` | `"2592000s"` (default, 30 days) | *Estimated incremental cost:* moderate — the global external Application Load Balancer forwarding rule and the 1 GB Memorystore instance bill continuously even when the Cloud Run service is idle. IAP alone (without Cloud Armor) adds no LB cost. ## Section 1: Designing highly scalable, available, and reliable cloud-native applications (~36% of the exam) The largest section. The modules demonstrate platform selection (Cloud Run vs GKE), scaling behavior, revision-based traffic splitting, runtime secrets, IAP, Binary Authorization, and storage selection. API management products and application messaging are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Platform choice, scaling, cold starts | ✅ | `min_instance_count`, `max_instance_count`, `cpu_always_allocated`, `execution_environment` | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#11-designing-high-performing-applications-and-apis) | | 1.1 Traffic splitting, canary, rollback | ✅ | `traffic_split`, `max_revisions_to_retain` (App_CloudRun) | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#11-designing-high-performing-applications-and-apis) | | 1.1 Caching, CDN, session affinity | 🟡 | `create_redis`, `enable_redis`, `enable_cdn`; session affinity hardcoded on | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#11-designing-high-performing-applications-and-apis) | | 1.1 REST/gRPC APIs, API management, async messaging | 🟡 | `container_protocol = "h2c"` enables end-to-end HTTP/2 (gRPC-ready) on Cloud Run and `appProtocol kubernetes.io/h2c` on the GKE Service; API management and messaging are study-only | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#11-designing-high-performing-applications-and-apis) | | 1.2 Secrets at runtime + rotation | ✅ | `secret_environment_variables`, `enable_auto_password_rotation`, `secret_rotation_period` | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#12-designing-secure-applications) | | 1.2 End-user auth (IAP), supply-chain security | ✅ | `enable_iap`, `enable_binary_authorization`, `enable_vulnerability_scanning` | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#12-designing-secure-applications) | | 1.2 CMEK, audit logs, network segmentation | 🟡 | `enable_cmek`, `enable_audit_logging`, `enable_network_segmentation` | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#12-designing-secure-applications) | | 1.3 Relational/object/cache storage selection | ✅ | `create_postgres`, `create_mysql`, `storage_buckets`, `create_redis`, `enable_alloydb` | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#13-storing-and-accessing-data) | | 1.3 Firestore, Spanner, Bigtable, BigQuery, signed URLs | 📘 | `create_firestore` provisions the DB only — SDK usage is study-only | [Section 1 guide](PCD_Section_1_Exploration_Guide.md#13-storing-and-accessing-data) | ## Section 2: Building and testing applications (~23% of the exam) The build pipeline is the strongest coverage in the repo: every deployment runs real Cloud Build jobs (Kaniko or Docker), pushes to Artifact Registry with cleanup policies, and can sign images for Binary Authorization. Local tooling and emulators are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Local dev environment, emulators, Cloud Code/Shell/Workstations | 📘 | nearest: per-tenant isolated deployments via `tenant_id` | [Section 2 guide](PCD_Section_2_Exploration_Guide.md#21-setting-up-your-development-environment) | | 2.2 Cloud Build container builds | ✅ | `container_image_source = "custom"`, `container_build_config` | [Section 2 guide](PCD_Section_2_Exploration_Guide.md#22-building) | | 2.2 Artifact Registry, image lifecycle, mirroring | ✅ | `max_images_to_retain`, `image_retention_days`, `delete_untagged_images`, Crane digest-aware mirroring | [Section 2 guide](PCD_Section_2_Exploration_Guide.md#22-building) | | 2.2 CI triggers, Kaniko, attestation | ✅ | `enable_cicd_trigger`, `cicd_trigger_config`, Kaniko v1.23.2, pipeline image signing | [Section 2 guide](PCD_Section_2_Exploration_Guide.md#22-building) | | 2.3 Unit/integration testing in CI | 🟡 | generated build pipeline is extensible; no test step ships by default | [Section 2 guide](PCD_Section_2_Exploration_Guide.md#23-testing) | ## Section 3: Deploying applications (~20% of the exam) Both deployment targets are fully implemented. `App_CloudRun` covers revisions, scaling, probes, volumes, jobs, and Cloud Deploy promotion; `App_GKE` covers Deployments/StatefulSets, HPA/VPA, probes, quotas, PDBs, and the Gateway API. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Cloud Run service configuration (scaling, CPU, gen2, timeout) | ✅ | `min/max_instance_count`, `cpu_always_allocated`, `execution_environment`, `timeout_seconds` | [Section 3 guide](PCD_Section_3_Exploration_Guide.md#31-deploying-applications-to-cloud-run) | | 3.1 Revisions, traffic management, rollback | ✅ | `traffic_split`, `max_revisions_to_retain` | [Section 3 guide](PCD_Section_3_Exploration_Guide.md#31-deploying-applications-to-cloud-run) | | 3.1 Cloud Deploy progressive delivery | ✅ | `enable_cloud_deploy`, `cloud_deploy_stages`, `cicd_enable_cloud_deploy` | [Section 3 guide](PCD_Section_3_Exploration_Guide.md#31-deploying-applications-to-cloud-run) | | 3.1 Cloud Run jobs (migrations, init) | ✅ | `initialization_jobs`, `cron_jobs` | [Section 3 guide](PCD_Section_3_Exploration_Guide.md#31-deploying-applications-to-cloud-run) | | 3.2 GKE workloads, resources, probes | ✅ | `workload_type`, `container_resources`, `startup_probe_config`, `health_check_config` | [Section 3 guide](PCD_Section_3_Exploration_Guide.md#32-deploying-containers-to-gke) | | 3.2 HPA/VPA, quotas, PDBs, exposure | ✅ | `min/max_instance_count`, `enable_vertical_pod_autoscaling`, `enable_resource_quota`, `enable_custom_domain` | [Section 3 guide](PCD_Section_3_Exploration_Guide.md#32-deploying-containers-to-gke) | ## Section 4: Integrating applications with Google Cloud services (~21% of the exam) Database connectivity (Cloud SQL Auth Proxy on both platforms), runtime configuration injection, Workload Identity, and alerting are demonstrated live. Client-library coding, tracing, and profiling are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Cloud SQL connectivity (sockets, sidecar proxy) | ✅ | `enable_cloudsql_volume`, `cloudsql_volume_mount_path`, GKE proxy sidecar | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#41-integrating-applications-with-data-and-storage-services) | | 4.1 Storage integration (GCS Fuse, NFS, Redis) | ✅ | `gcs_volumes`, `enable_nfs`, `enable_redis` | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#41-integrating-applications-with-data-and-storage-services) | | 4.1 Pub/Sub & Firestore application code | 📘 | only rotation/SCC topics exist — no app messaging | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#41-integrating-applications-with-data-and-storage-services) | | 4.2 Service accounts, ADC, Workload Identity | ✅ | per-app SAs, KSA annotation `iam.gke.io/gcp-service-account`, `additional_cloudrun_sa_roles` | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#42-consuming-google-cloud-apis) | | 4.2 Workload Identity Federation (keyless CI) | ✅ | `enable_workload_identity_federation`, `wif_provider_type` | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#42-consuming-google-cloud-apis) | | 4.2 Service-to-service auth (ID tokens) | 🟡 | `roles/run.invoker` bindings (IAP agent, allUsers); calling code is study-only | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#42-consuming-google-cloud-apis) | | 4.3 Logging, metrics, alerting, dashboards | ✅ | `support_users`, `alert_policies` (the platform's monitoring and dashboard layers) | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#43-troubleshooting-and-observability) | | 4.3 Uptime checks | ✅ | `uptime_check_config` creates a `-uptime-check` + alert policy on publicly reachable endpoints | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#43-troubleshooting-and-observability) | | 4.3 Trace, Profiler, Error Reporting | 📘 | not implemented — study-only | [Section 4 guide](PCD_Section_4_Exploration_Guide.md#43-troubleshooting-and-observability) | --- # PCD Certification Preparation Guide: Section 1 — Designing highly scalable, available, and reliable cloud-native applications (~36% of the exam) PCD Certification Preparation Guide: Section 1 — Designing highly scalable, available, and reliable cloud-native applications (~36% of the exam) This guide covers the largest PCD exam section using the RAD platform foundation modules. You will exercise `App_CloudRun` (Cloud Run v2 service design), `App_GKE` (Kubernetes workload design), and `Services_GCP` (the shared database, cache, and security infrastructure). Deploy the **Serverless baseline** profile from the [Lab Map](PCD_Certification_Guide.md) before starting; add the **Hardened edge** profile for 1.1 (caching/CDN) and 1.2 (IAP, rotation). --- ## 1.1 Designing high-performing applications and APIs > ⏱ ~90 min · 💰 low (scale-to-zero defaults); Memorystore and the global LB bill continuously if enabled · ⚙️ Requires: Serverless baseline; Hardened edge for CDN/Redis steps **Why the exam cares** — PCD scenarios constantly ask you to pick between Cloud Run and GKE, and to tune the chosen platform: when does `min_instances > 0` beat accepting cold starts, when is CPU throttling between requests acceptable, how do you canary a new revision without redeploying, and where does a cache or CDN belong in the request path. The decision criteria are cost vs latency vs operational control: Cloud Run for stateless request/response workloads with bursty traffic, GKE for workloads needing sidecars you control, StatefulSets, or fine-grained pod networking. **How RAD implements it** — Both foundation modules expose the same scaling vocabulary with platform-appropriate defaults: | Variable | App_CloudRun default | App_GKE default | What it controls | |---|---|---|---| | `min_instance_count` | `0` (scale to zero) | `1` | scaling floor / HPA `minReplicas` | | `max_instance_count` | `1` | `3` | scaling ceiling / HPA `maxReplicas` | | `container_resources` | `cpu_limit = "1000m"`, `memory_limit = "512Mi"` | same | per-instance/pod resources | | `timeout_seconds` | `300` (0–3600) | `300` | request / LB backend timeout | Cloud Run-specific performance levers: - `cpu_always_allocated` (default `false`, i.e. request-based billing) — set `true` to keep CPU allocated between requests (schedulers, queue workers, WebSocket servers). Startup CPU boost is always on, and session affinity is always on for the service. NOTE: line 59 of the same file ("keep `cpu_always_allocated = true`") is phrased as an override and stays correct only if reworded from "keep" to "set". - `execution_environment` (default `"gen2"`) — plan-time validations require gen2 for NFS (`enable_nfs`) and GCS Fuse (`gcs_volumes`) mounts. - `traffic_split` (default `[]` = 100% to latest) takes a list of `{ type, revision, percent, tag }` entries where `type` is `TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST` or `TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION`; validation enforces that percents sum to exactly 100. The optional `tag` gives a revision a stable preview URL. - `max_revisions_to_retain` (default `7`) prunes old revisions automatically; revisions serving traffic are never deleted. - Request concurrency per instance is **not** exposed — the service uses the Cloud Run default (80 concurrent requests per instance). - `container_protocol` (default `"http1"`) sets the named port on the Cloud Run service: `"h2c"` switches Cloud Run to end-to-end HTTP/2 cleartext — required for gRPC services (gRPC is built on HTTP/2) and useful for streaming/large-payload workloads. The container must serve cleartext HTTP/2 on the container port. On GKE the same variable advertises `appProtocol kubernetes.io/h2c` on the Service port so Gateway/Ingress backends speak HTTP/2 to the pods. On GKE, the platform creates a HorizontalPodAutoscaler only when `max_instance_count > 1` **and** `enable_vertical_pod_autoscaling = false`, targeting 70% CPU and 80% memory utilization. Caching: `Services_GCP` provisions Memorystore with `create_redis` (default `false`), `redis_tier` (default `BASIC` vs `STANDARD_HA`), `redis_memory_size_gb` (default `1`), AUTH enabled. On the app side, `enable_redis` (App_CloudRun default `true`) injects `REDIS_HOST`/`REDIS_PORT` (and `REDIS_URL` when derivable) env vars — set `redis_host` to the Memorystore IP, otherwise the module falls back to the shared NFS VM's Redis. CDN: `enable_cdn` (default `false`) forces the service behind a global external Application Load Balancer (ingress is auto-overridden to `internal-and-cloud-load-balancing`). **Try it** 1. In the portal, set `max_instance_count = 3` and redeploy. In **Console > Cloud Run > your service > Revisions**, note a new revision was created — configuration changes always create revisions. 2. Deploy a trivial change (e.g., a new `environment_variables` entry), then set `traffic_split` to send 10% to the new revision: ```bash gcloud run revisions list --service= --region=us-central1 gcloud run services update-traffic --region=us-central1 \ --to-revisions==90,=10 gcloud run services describe --region=us-central1 \ --format="yaml(status.traffic)" ``` (Doing it via the `traffic_split` variable keeps Terraform state authoritative; the CLI is what the exam tests.) 3. Flip `cpu_always_allocated = false`, apply, and inspect the revision: **Console > Cloud Run > service > Revisions > Containers tab** shows "CPU is only allocated during request processing". 4. Generate load (`hey` or a loop of `curl`) and watch **Cloud Run > service > Metrics > Container instance count** climb toward 3 and fall back to 0. 5. You know it worked when the traffic chart on the Revisions tab shows the 90/10 split and instance count returns to zero after load stops. **Check yourself**
Q1: A latency-sensitive API on Cloud Run shows 4-second p99 spikes after idle periods. Which two settings fix this, and what is the cost trade-off? A: Set `min_instance_count >= 1` to keep a warm instance (eliminates cold starts, bills the idle instance continuously) and keep `cpu_always_allocated = true` so background initialization isn't throttled between requests. The trade-off is paying for instance time even with zero traffic — the opposite of the scale-to-zero default.
Q2: You must roll out a risky change to 5% of users with instant rollback. How do you do it on Cloud Run without a load balancer? A: Deploy the change as a new revision and use revision-based traffic splitting (`traffic_split` / `gcloud run services update-traffic`) to send 5% to it, optionally with a `tag` for a direct test URL. Rollback is routing 100% back to the previous revision — no rebuild or redeploy, because revisions are immutable.
Q3: When would you choose App_GKE over App_CloudRun for the same container? A: When the workload needs stable per-pod storage (StatefulSet via `stateful_pvc_enabled`), Kubernetes-native controls (NetworkPolicy, ResourceQuota, PDB, topology spread), long-lived non-HTTP protocols, or sidecars you define yourself. Cloud Run wins for bursty stateless HTTP because of scale-to-zero and per-request billing.
**Beyond the modules** — The exam also tests API design and async patterns the modules don't implement: REST versioning and OpenAPI specs behind **API Gateway** or **Apigee** (try `gcloud api-gateway gateways list` in a scratch project), gRPC application code (the platform side is covered — `container_protocol = "h2c"` is the module equivalent of `gcloud run deploy --use-http2` — but writing the gRPC service/client is study-only), **Pub/Sub** publish/subscribe and push-vs-pull decisions, **Cloud Tasks** for rate-limited dispatch, **Workflows** for multi-step orchestration, and **Eventarc** triggers (the modules use Eventarc only internally for secret rotation). Also study Cloud Run concurrency tuning (`--concurrency`) since the modules pin the default of 80. **⚠️ Exam trap** — "Min instances = 0" plus "CPU always allocated" is a contradiction candidates miss: with `min_instance_count = 0` you still pay full instance time while instances exist if CPU is always allocated. Scale-to-zero only saves money between requests if instances actually terminate. --- ## 1.2 Designing secure applications > ⏱ ~75 min · 💰 low (Secret Manager pennies; KMS keys ~$0.06/key/month; IAP free) · ⚙️ Requires: Hardened edge profile (`enable_iap`, `enable_auto_password_rotation`); add `enable_binary_authorization` on both modules **Why the exam cares** — PCD security questions are about *where credentials live and who can call what*: secrets must reach code at runtime (never baked into images or state), end-user authentication should happen before traffic reaches the app (IAP), and only provably-built images should run (Binary Authorization). You're expected to know which mechanism solves which problem, not to administer the org. **How RAD implements it** — *Secrets at runtime.* `secret_environment_variables` (map of env var name → Secret Manager secret name) is rendered on the Cloud Run service as a secret reference pinned to the `latest` version — the plaintext never enters Terraform state or the image. The database password is auto-generated (`database_password_length`, default 32 in App_CloudRun) and stored in Secret Manager by the platform's secrets layer. On GKE, secrets arrive through the Secret Manager add-on: the platform creates a `SecretProviderClass` (provider `gke`) that syncs Secret Manager secrets into a Kubernetes Secret which pods consume via `secretKeyRef` — the add-on itself (`secret-manager+secret-sync-v1`) is enabled on the cluster via gcloud. *Rotation.* `secret_rotation_period` (default `"2592000s"` = 30 days) configures the Secret Manager rotation notification. On its own it only publishes to Pub/Sub; `enable_auto_password_rotation` (default `false`) closes the loop: an Eventarc trigger fires a dispatcher Cloud Run service which runs a rotator Cloud Run job. The rotation logic is dual-version and zero-downtime: `ALTER USER` first, add the new secret version, wait `rotation_propagation_delay_sec` (default `90`), then *disable* (not destroy) the old version so `latest` is unambiguous and rollback stays possible. *IAP.* `enable_iap` (default `false`) turns on IAP for the Cloud Run v2 service (launch stage BETA). A plan-time validation requires at least one entry in `iap_authorized_users` or `iap_authorized_groups`; the platform grants `roles/run.invoker` to the IAP service agent and `roles/iap.httpsResourceAccessor` to your principals. Without IAP, public services get an `allUsers` → `roles/run.invoker` binding. On GKE, IAP additionally requires `iap_oauth_client_id`, `iap_oauth_client_secret`, and `iap_support_email`. *Supply chain.* `enable_binary_authorization` with `binauthz_evaluation_mode` (default `"ALWAYS_ALLOW"`, options include `REQUIRE_ATTESTATION` and `ALWAYS_DENY`) creates a KMS-backed attestor; the CI pipeline signs images (see Section 2). `enable_vulnerability_scanning` (Services_GCP, default `false`) turns on Artifact Analysis scanning for the shared repository. *Edge protection.* `enable_cloud_armor` (default `false`) deploys a WAF policy (`{service}-waf-policy`) with preconfigured OWASP rules (SQLi/XSS/LFI/RCE), Adaptive Protection, and a 500 req/min/IP rate limit behind a global HTTPS LB. **`application_domains` is optional** — with none set, the module derives a zero-config `.nip.io` Google-managed certificate so the LB always has a hostname. The live constraint runs the *other* way: `enable_cdn = true` requires `enable_cloud_armor = true`, because the CDN attaches to the load balancer Cloud Armor provisions. Hardening extras: `enable_cmek` (Services_GCP, default `false`) for customer-managed keys with `cmek_key_rotation_period` default `7776000s` (90 days), `enable_audit_logging` (default `false`) for DATA_READ/DATA_WRITE audit logs, and `enable_network_segmentation` (App_GKE, default `false`) for namespace-scoped NetworkPolicies. **Try it** 1. Add a custom secret: create `MY_API_KEY` in **Console > Security > Secret Manager**, then set `secret_environment_variables = { MY_API_KEY = "" }` and redeploy. Verify in **Cloud Run > service > Revisions > Variables & Secrets** that it shows "Secret reference", not a value. 2. Enable rotation (`enable_auto_password_rotation = true`) and inspect the moving parts: ```bash gcloud secrets list --filter="name~rotation OR name~password" gcloud secrets versions list gcloud eventarc triggers list --location=us-central1 gcloud run jobs list --region=us-central1 # look for the rotator job ``` 3. Enable IAP with your user in `iap_authorized_users`, then prove the boundary: ```bash curl -s -o /dev/null -w "%{http_code}\n" https:/// # 302/403 anonymous curl -s -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer $(gcloud auth print-identity-token)" https:/// ``` 4. You know it worked when the secret version list shows a new ENABLED version and a DISABLED prior version after a rotation fires, and anonymous requests stop returning 200 once IAP is on. **Check yourself**
Q1: Your app reads DB_PASSWORD from an env var sourced from Secret Manager with version "latest". A rotation writes a new version while 20 instances are running. What happens, and how does the RAD design avoid an outage? A: Running instances keep the value they resolved at startup — env-var secret references resolve when the instance starts, not per request. The rotator avoids breakage by being dual-version: the database accepts the new password (`ALTER USER`) before the new secret version is published, the old secret version is only disabled after a propagation delay, and the workload is restarted so new instances pick up "latest". An exam answer should mention that env-var secrets require a new revision/restart to refresh.
Q2: A team must guarantee only images built by their CI pipeline run in production. Which two RAD variables implement this, and what happens to a hand-pushed image? A: `enable_binary_authorization = true` plus `binauthz_evaluation_mode = "REQUIRE_ATTESTATION"`. The CI pipeline signs each image with the KMS-backed attestor after building; a locally built image pushed straight to Artifact Registry has no attestation, so admission is denied at deploy time (enforcement is block-and-audit-log).
Q3: You enable `enable_cloud_armor = true` without setting `application_domains`. What happens? A: It deploys successfully. The module auto-provisions a zero-config `.nip.io` Google-managed SSL certificate (`security.tf`, `use_nip_io` / `nip_io_cert`), so the Global HTTPS Load Balancer always has a hostname to bind to. A domain is optional, not required. The validation that once required a domain here was **removed** — `App_CloudRun/validation.tf` keeps it only as comment 19, "(removed) Cloud Armor no longer requires a custom domain." The constraint that *does* exist runs the other way: **`enable_cdn = true` requires `enable_cloud_armor = true`** (precondition 26). Cloud CDN attaches to the Global HTTPS Load Balancer that Cloud Armor provisions; without it there is no backend to attach the CDN to.
**Beyond the modules** — Study Identity Platform (end-user/CIAM auth — the modules only do IAP for Google identities), OAuth 2.0/OIDC token flows and the difference between access tokens and ID tokens, signed URLs vs IAM for object access, and Web Security Scanner. VPC Service Controls exist in the modules (`enable_vpc_sc`, dry-run by default, with graceful permission-probe skips) but perimeter design questions go deeper — read the VPC-SC ingress/egress rules documentation. **⚠️ Exam trap** — `secret_rotation_period` alone rotates nothing. It only schedules a Pub/Sub *notification*. Something must consume that notification and write a new version — in RAD that's `enable_auto_password_rotation`; on the exam it's "a rotation function/job you implement". --- ## 1.3 Storing and accessing data > ⏱ ~60 min · 💰 moderate — Cloud SQL `db-custom-1-3840` is the dominant baseline cost; REGIONAL roughly doubles it; Filestore `BASIC_HDD` 1 TiB is significant · ⚙️ Requires: Serverless baseline (Postgres is on by default) **Why the exam cares** — Storage-selection questions give you data shape, consistency, and scale requirements and expect the right product: relational OLTP → Cloud SQL/AlloyDB, documents with mobile sync → Firestore, petabyte wide-column/time-series → Bigtable, global relational → Spanner, blobs → Cloud Storage, hot ephemeral → Memorystore. PCD adds the developer angle: how does code *connect* to each (covered in 4.1), and what consistency does it observe. **How RAD implements it** — `Services_GCP` provisions the menu; the app modules consume it: | Variable (Services_GCP) | Default | What you get | |---|---|---| | `create_postgres` | `true` | Cloud SQL Postgres (`postgres_database_version` default `POSTGRES_17`), private IP only, SSL `ENCRYPTED_ONLY`, PITR with 7-day log retention, 7 daily backups | | `postgres_database_availability_type` | `ZONAL` | set `REGIONAL` for an HA standby with automatic failover | | `create_postgres_read_replica` | `false` | read replica(s) (`postgres_read_replica_count` default `1`) for read scaling | | `create_mysql` | `false` | MySQL (`MYSQL_8_4`), binlog-based recovery (no PITR config) | | `enable_alloydb` | `false` | AlloyDB cluster + primary; `enable_alloydb_read_pool` adds a read pool | | `create_firestore` | `false` | Firestore Native (Enterprise edition) database — provisioning only | | `create_redis` | `false` | Memorystore Redis; persistence `redis_persistence_mode` default `DISABLED` | | `create_filestore_nfs` | `false` | Filestore (`filestore_tier` default `BASIC_HDD`, `filestore_capacity_gb` default `1024`) | | `create_network_filesystem` | `true` | self-managed e2-small NFS+Redis VM with stateful disk and daily snapshots | Object storage lives in the app modules: `storage_buckets` (a list of bucket definitions handled by the platform's object-storage layer) creates GCS buckets with versioning, lifecycle rules (age, newer-version counts, storage-class transitions), CORS, per-bucket `public_access_prevention`, and least-privilege IAM (`roles/storage.objectAdmin` granted per bucket to the app SA). `gcs_volumes` mounts buckets into the container via GCS Fuse (gen2 required), so code can use plain filesystem calls. Consistency facts to anchor: Cloud SQL is strongly consistent on the primary; read replicas lag asynchronously. The RAD Postgres instance enables PITR (restore to a timestamp) *in addition to* daily backups — these are different exam answers. Redis `BASIC` tier has no replication and loses data on restart unless RDB/AOF persistence is enabled; the module even enforces at plan time that a production STANDARD_HA instance must not have persistence `DISABLED`. **Try it** 1. Inspect the database the baseline profile created: ```bash gcloud sql instances describe \ --format="yaml(settings.availabilityType, settings.backupConfiguration, ipAddresses)" ``` Confirm `availabilityType: ZONAL`, `pointInTimeRecoveryEnabled: true`, and that there is no public IP. 2. Set `postgres_database_availability_type = "REGIONAL"` in the portal and re-apply; the describe output now shows a secondary zone. (This restarts the instance — do it in a lab window.) 3. Add a bucket via `storage_buckets` with a lifecycle rule, then verify: ```bash gcloud storage buckets describe gs:// \ --format="yaml(lifecycle_config, versioning, public_access_prevention)" ``` 4. You know it worked when the bucket shows your lifecycle rule and `versioning: enabled`, and the SQL instance reports REGIONAL with a failover replica zone. **Check yourself**
Q1: An app needs to survive a zone outage with zero data loss on its relational store. Daily backups are already enabled. What change is required and why aren't backups enough? A: Set `postgres_database_availability_type = "REGIONAL"` — synchronous replication to a standby in another zone gives automatic failover with no data loss. Backups (and even PITR) are recovery mechanisms with restore time and potential data loss back to the last transaction logs; they don't provide availability.
Q2: A product catalog is read 50:1 vs writes and the Cloud SQL primary is CPU-saturated. Rank the RAD options. A: First add Memorystore caching (`create_redis = true` + app-side `enable_redis`) — it removes repeated reads entirely and is cheapest. Second, `create_postgres_read_replica = true` to offload remaining reads (code must route reads to the replica and tolerate replication lag). Vertical scaling (`postgres_tier`) is the fallback because it has a ceiling and scales cost linearly.
Q3: Why might a developer choose `gcs_volumes` (GCS Fuse) over the Cloud Storage client library? A: Fuse lets unmodified code use filesystem semantics (good for legacy apps, ML model files, static assets at startup) at the cost of object-storage performance characteristics and POSIX edge cases. The client library is the right answer for high-throughput object I/O, signed URLs, and metadata operations. Fuse requires `execution_environment = "gen2"` on Cloud Run — validated at plan time.
**Beyond the modules** — Spanner (interleaved tables, avoiding hotspotting primary keys), Bigtable (row-key design, single-index model, eventual consistency across replicated clusters), BigQuery write paths (Storage Write API vs batch loads), and signed URL generation (`blob.generate_signed_url`, requires `roles/iam.serviceAccountTokenCreator` or a key) are all absent from the modules and all examined. The Firestore database can be created here (`create_firestore = true`) but SDK usage — documents, composite indexes, real-time listeners, transactions — must be practiced with the client libraries or the emulator. **⚠️ Exam trap** — Backups ≠ PITR. Daily backups restore to a snapshot moment; PITR replays transaction logs to an arbitrary timestamp. The RAD Postgres instance has both; the RAD MySQL instance relies on binary logging and has no PITR configuration — a distinction the exam loves. --- # PCD Certification Preparation Guide: Section 2 — Building and testing applications (~23% of the exam) PCD Certification Preparation Guide: Section 2 — Building and testing applications (~23% of the exam) > 📚 **Official exam guide:** [Professional Cloud Developer certification](https://cloud.google.com/learn/certification/cloud-developer) — always confirm section weightings against the current Google Cloud exam guide. This section maps to the RAD platform's build machinery: the platform's Cloud Build container builds, the Cloud Build CI trigger (present in both App_CloudRun and App_GKE), Artifact Registry management, and image mirroring. Deploy the **Delivery pipeline** profile from the [Lab Map](PCD_Certification_Guide.md). Local development tooling (2.1) and test authoring (2.3) are mostly study-only — honest pointers are given. --- ## 2.1 Setting up your development environment > ⏱ ~45 min (mostly outside the platform) · 💰 no additional cost · ⚙️ Requires: any deployed profile + a workstation or Cloud Shell **Why the exam cares** — The exam tests whether you know the developer toolchain: `gcloud` auth flows (user credentials vs Application Default Credentials), local emulators for unit testing without cloud cost, Cloud Code/Cloud Shell/Cloud Workstations trade-offs, and how to reproduce a cloud environment locally (e.g., Cloud SQL Auth Proxy on your laptop). **How RAD implements it** — Not directly: the foundation modules run server-side and assume the portal performs the deploy. The nearest adjacent capabilities are real and useful, though: - **Isolated per-developer environments.** `tenant_id` feeds the deterministic naming scheme (`app<8-hex-hash>`), so each developer can deploy a complete, non-colliding copy of the same application into a shared project — the cloud-native answer to "works on my machine". - **Database tooling image.** The platform builds a psql/mysql client image into Artifact Registry, which the modules' jobs use; you can run the same image locally for parity. - **Local DB access pattern.** Cloud SQL has private IP only, so the local equivalent of the deployed setup is running the Cloud SQL Auth Proxy yourself from a machine with VPC access (or via IAP tunneling) — the same binary the GKE module runs as a sidecar. **Try it** 1. Deploy a second copy of `App_CloudRun` with a different `tenant_id` and confirm both stacks coexist: ```bash gcloud run services list --region=us-central1 gcloud sql databases list --instance= ``` 2. Set up ADC locally the way the exam expects developer machines to authenticate: ```bash gcloud auth application-default login gcloud config set project ``` 3. Start a Pub/Sub emulator and point a test at it (no RAD involvement — this is the exam skill): ```bash gcloud beta emulators pubsub start --project=test-project & export PUBSUB_EMULATOR_HOST=localhost:8085 ``` 4. You know it worked when `gcloud run services list` shows two services with different tenant suffixes, and your local client library calls hit the emulator (no credentials needed). **Check yourself**
Q1: A developer's laptop code calls `storage.Client()` and gets a 403 in the office but works on Cloud Run. Why, and what's the fix? A: On Cloud Run the client library resolves Application Default Credentials from the metadata server (the service's service account). Locally there are no ambient credentials until the developer runs `gcloud auth application-default login` (or sets `GOOGLE_APPLICATION_CREDENTIALS` — discouraged because key files are long-lived). The fix is establishing local ADC; the code itself shouldn't change.
Q2: Your CI unit tests must exercise Pub/Sub and Firestore logic without network access or cost. What do you use? A: The local emulators (`gcloud beta emulators pubsub start`, the Firestore emulator) with the `PUBSUB_EMULATOR_HOST` / `FIRESTORE_EMULATOR_HOST` environment variables set so client libraries transparently target them. Emulators need no credentials, which is exactly what hermetic CI wants.
**Beyond the modules** — Study Cloud Code (IDE deploy/debug for Cloud Run and GKE, including a local Cloud Run emulator), Cloud Shell (ephemeral, pre-authenticated, 5 GB persistent home), and Cloud Workstations (managed, persistent, IAP-fronted dev VMs for regulated teams) — know which to recommend for a given constraint. Also practice `gcloud run deploy --source .` (Buildpacks-based source deploy) since the RAD pipeline always builds an explicit container instead. **⚠️ Exam trap** — `gcloud auth login` and `gcloud auth application-default login` are different credentials: the first authorizes the `gcloud` CLI, the second writes the ADC file client libraries read. Tests that pass for CLI commands but 401 in code usually mean the second was skipped. --- ## 2.2 Building > ⏱ ~75 min · 💰 low — Cloud Build per-minute billing plus Artifact Registry storage · ⚙️ Requires: Delivery pipeline profile (`enable_cicd_trigger = true`, `github_repository_url` set) **Why the exam cares** — PCD expects fluency in the container supply chain: building images in Cloud Build (and why a daemonless builder like Kaniko or Buildpacks beats `docker build` in CI), tagging strategy (mutable `latest` vs immutable commit-SHA tags), Artifact Registry storage and cleanup, and attaching provenance (attestations) so Binary Authorization can gate deploys. **How RAD implements it** — Two distinct build paths, both real Cloud Build: 1. **Terraform-driven build** (every deploy with `container_image_source = "custom"`, the default): the platform renders a build config and runs `gcloud builds submit`. Kaniko builds with layer caching (`--cache=true`, `--cache-ttl=24h`) and pushes three tags: the app version, `latest`, and the commit SHA. Rebuilds are *hash-triggered*: the platform hashes the build context files, the Dockerfile (or inline `dockerfile_content`), and `build_args`, so an unchanged source tree never rebuilds. 2. **Git-driven CI trigger** (`enable_cicd_trigger`, default `false`): the platform creates a Cloud Build trigger bound to `github_repository_url`, filtered by `cicd_trigger_config` (`branch_pattern` default `"^main$"`, plus `included_files`/`ignored_files`/`substitutions`). The generated pipeline runs Kaniko `v1.23.2`, optionally signs the image (`gcloud beta container binauthz attestations sign-and-create` against the `pipeline-attestor` using the `binauthz-signer` KMS key in `{project}-binauthz-keyring`), then either runs `gcloud run services update --image=...:$COMMIT_SHA` directly or creates a Cloud Deploy release (Section 3.1). Registry management: the module discovers the `Services_GCP` shared repository or creates one, and applies cleanup policies — `max_images_to_retain` (default `7`), `delete_untagged_images` (default `true`), `image_retention_days` (default `30`), scoped to this deployment's package names. `enable_image_mirroring` (default `true`) copies external base images into Artifact Registry using Crane digest comparison, comparing source/target SHA256 digests and only copying (or overwriting a stale tag) when digests differ — protecting you from registry rate limits and tag drift. `enable_vulnerability_scanning` (Services_GCP) makes Artifact Analysis scan everything pushed. **Try it** 1. Push a commit to the configured branch and watch the trigger fire: **Console > Cloud Build > History**, open the build, and identify the Kaniko step and (if Binary Authorization is on) the attestation step. ```bash gcloud builds list --limit=5 gcloud builds log ``` 2. Inspect the resulting tags and scan results: ```bash gcloud artifacts docker images list \ us-central1-docker.pkg.dev// --include-tags gcloud artifacts docker images describe \ us-central1-docker.pkg.dev///:latest \ --show-package-vulnerability ``` 3. Verify the attestation exists for the new digest: ```bash gcloud container binauthz attestations list \ --attestor=pipeline-attestor --attestor-project= ``` 4. Re-apply the deployment *without* changing source and confirm no new Cloud Build job runs (the build's content hash was unchanged). 5. You know it worked when the image shows three tags (version, `latest`, commit SHA), vulnerabilities are listed, and an attestation references the new digest. **Check yourself**
Q1: Why does the pipeline deploy by commit-SHA tag rather than `latest`, even though `latest` is also pushed? A: `latest` is mutable — it points to whatever was pushed most recently, so a deployment referencing it is not reproducible and rollbacks are ambiguous. The commit SHA tag is effectively immutable and ties the running revision to exact source provenance, which is also what the Binary Authorization attestation signs (the digest). `latest` is kept only as a developer convenience.
Q2: A build fails with Docker daemon errors inside Cloud Build. The RAD pipeline never hits this — why? A: It uses Kaniko, which builds OCI images entirely in userspace from the Dockerfile without a Docker daemon — the standard answer for daemonless, cacheable container builds in CI. (Buildpacks are the other daemonless exam answer, used by `gcloud run deploy --source`.)
Q3: Artifact Registry storage costs are growing without bound in a busy repo. Which three RAD controls address it? A: `delete_untagged_images = true` removes dangling layers, `image_retention_days = 30` ages out old images, and `max_images_to_retain = 7` keeps the most recent N regardless of age (a keep-guard, not a deleter). Together they implement the recommended AR cleanup-policy pattern: delete-by-age plus keep-most-recent.
**Beyond the modules** — The exam also covers Buildpacks/source deploys, build provenance and SLSA levels (Cloud Build generates SLSA provenance viewable under a build's **Security insights** tab), private pools, and build substitutions/secrets in a Cloud Build config (try `gcloud builds submit --substitutions=_FOO=bar` in a scratch repo). The RAD trigger supports GitHub only (token or App installation) — know that Cloud Build also connects GitLab and Bitbucket repos. **⚠️ Exam trap** — Pushing an image to Artifact Registry does *not* deploy it. The pipeline's explicit `gcloud run services update` (or Cloud Deploy release) step is what changes the running revision — a missing deploy step is a classic "build succeeded, app unchanged" troubleshooting scenario. --- ## 2.3 Testing > ⏱ ~45 min · 💰 low (extra Cloud Build minutes) · ⚙️ Requires: Delivery pipeline profile + write access to the app repository **Why the exam cares** — Tests must run *inside* the pipeline so a failure blocks promotion: unit tests early (cheap, hermetic, emulator-backed), integration tests against real or staged services after build, and smoke tests after deploy to a non-prod stage. The exam tests where each belongs and what a failing step does to the pipeline. **How RAD implements it** — Honestly: the generated pipelines contain **no test step by default** — the CI flow is build → (optional attestation) → deploy/release. The hooks for adding tests are real, though: - The Cloud Build trigger executes the generated build config; steps run sequentially and any non-zero exit fails the build, so a test step inserted between Kaniko and the deploy step gates deployment exactly as the exam describes. - `cicd_trigger_config.branch_pattern` (default `"^main$"`) controls which pushes build at all; `included_files`/`ignored_files` keep doc-only commits from burning build minutes. - The foundation modules themselves ship native OpenTofu/Terraform tests exercising the plan-time validations — a useful example of testing infrastructure code, which occasionally appears on the exam as "shift-left for IaC". - Cloud Deploy stages (Section 3.1) provide the post-deploy verification surface: promote to `dev`, run smoke tests against the per-stage service URL, then promote. **Try it** 1. In your application repo, add a test step to the build config between the build and deploy steps, e.g.: ```yaml - name: 'python:3.12-slim' entrypoint: 'bash' args: ['-c', 'pip install -r requirements.txt && pytest -q'] ``` 2. Push a commit with a deliberately failing test and observe: **Console > Cloud Build > History** shows the red step, and the deploy step never runs. ```bash gcloud builds list --filter="status=FAILURE" --limit=3 ``` 3. Confirm the Cloud Run service still runs the previous image: ```bash gcloud run services describe --region=us-central1 \ --format="value(spec.template.spec.containers[0].image)" ``` 4. You know it worked when the failed build leaves the deployed image untouched and the deploy step is skipped. **Check yourself**
Q1: Integration tests need a real Postgres but must not touch production data. How would you structure this with the RAD stack? A: Deploy a separate tenant (`tenant_id = "ci"`) so the pipeline gets its own isolated Cloud SQL database and service, run integration tests against that stage from a Cloud Build step, and tear down or reuse it per run. Unit tests stay on emulators/mocks; only the integration layer touches the real (isolated) database.
Q2: Where do smoke tests belong in a Cloud Deploy pipeline, and what stops a bad release from reaching prod? A: After the rollout to a non-prod target (dev/staging) — run them against that stage's URL, and gate `prod` with `require_approval = true` (the RAD default) so a human (or an automated verification you wire in) confirms before promotion. A failed rollout or withheld approval keeps the release from advancing.
**Beyond the modules** — Practice writing emulator-backed unit tests (Pub/Sub, Firestore, Spanner emulators), Cloud Build test reporting, and load testing against Cloud Run revisions (e.g., `hey`/`k6` against a tagged canary URL). Cloud Deploy *verify* (post-deploy verification jobs declared in the Skaffold config) is the managed version of step 2's smoke-test idea and worth reading about — the RAD Cloud Deploy configs use hooks for IAM and jobs, not verification. **⚠️ Exam trap** — Cloud Build steps share the `/workspace` volume but are otherwise isolated containers; a test step can't reach a server started in a previous step unless you background it within the *same* step or use the `docker` network. "Why can't step 4 see the service step 3 started?" is a recurring question shape. --- # PCD Certification Preparation Guide: Section 3 — Deploying applications (~20% of the exam) PCD Certification Preparation Guide: Section 3 — Deploying applications (~20% of the exam) > 📚 **Official exam guide:** [Professional Cloud Developer certification](https://cloud.google.com/learn/certification/cloud-developer) — always confirm section weightings against the current Google Cloud exam guide. This section is where the RAD foundation modules shine: `App_CloudRun` deploys a fully configured Cloud Run v2 service (scaling, probes, volumes, traffic management, jobs, optional Cloud Deploy pipeline) and `App_GKE` deploys the equivalent Kubernetes workload on GKE Autopilot (Deployment/StatefulSet, HPA, probes, quotas, Gateway API). Deploy the **Serverless baseline** and **Delivery pipeline** profiles for 3.1, and the **Kubernetes lab** profile for 3.2 (see the [Lab Map](PCD_Certification_Guide.md)). --- ## 3.1 Deploying applications to Cloud Run > ⏱ ~90 min · 💰 low — per-stage services scale to zero; Cloud Deploy itself is free (you pay for the Cloud Build it runs) · ⚙️ Requires: Serverless baseline; Delivery pipeline profile for the Cloud Deploy steps **Why the exam cares** — Cloud Run questions probe the service resource model: every deploy creates an immutable *revision*; traffic is routed across revisions by percentage and tag; scaling, CPU allocation, execution environment, and probes are revision properties. Progressive delivery questions then layer Cloud Deploy on top: releases, targets, promotion, and approval gates. You should be able to predict what a given configuration does to cold starts, cost, and rollback time. **How RAD implements it** — App_CloudRun builds the Cloud Run v2 service from portal variables: | Concern | Variables (defaults) | |---|---| | Lifecycle | `deploy_application` (`true`) — `false` provisions infra only | | Image | `container_image_source` (`"custom"` = Cloud Build; `"prebuilt"` = use `container_image` as-is), `enable_image_mirroring` (`true`) | | Scaling | `min_instance_count` (`0`), `max_instance_count` (`1`); plan-time check min ≤ max | | Runtime | `container_port` (`8080`), `container_resources` (`1000m`/`512Mi`), `timeout_seconds` (`300`), `execution_environment` (`"gen2"`), `cpu_always_allocated` (`false`), `startup_cpu_boost` hardcoded on | | Probes | `startup_probe_config` (enabled, HTTP `/healthz`, delay 10s, period 10s, failure threshold 10) and `health_check_config` → liveness probe (enabled, HTTP `/healthz`, delay 15s, period 30s, failure threshold 3); both support TCP | | Volumes | Cloud SQL socket (`enable_cloudsql_volume` `true`, mount `cloudsql_volume_mount_path` `/cloudsql`), NFS (`enable_nfs` `true`, `nfs_mount_path` `/mnt/nfs`), GCS Fuse (`gcs_volumes`) — NFS and Fuse require gen2 (validated) | | Traffic | `traffic_split` (default all-to-latest), revision `tag` for preview URLs, `max_revisions_to_retain` (`7`) | | Networking | `ingress_settings` (`"all"`), Direct VPC egress with `vpc_egress_setting` (`"PRIVATE_RANGES_ONLY"`) — no Serverless VPC Access connector is used | | Jobs | `initialization_jobs` (Cloud Run v2 Jobs with `depends_on_jobs` ordering, `execute_on_apply`, NFS/GCS mounts); `cron_jobs` | Progressive delivery: `enable_cloud_deploy` (default `false`) creates a Cloud Deploy delivery pipeline — **setting it without `enable_cicd_trigger = true` is rejected at plan time** by a precondition in App_CloudRun (the pipeline would never receive a release without a CI trigger). `cloud_deploy_stages` defaults to `dev` → `staging` → `prod` with `require_approval = true` on prod and `auto_promote = false` everywhere (per-stage `auto_promote` creates a Cloud Deploy automation). Each stage gets its own Cloud Run service named `-`; only the prod stage inherits your `ingress_settings` (non-prod stages stay `"all"` so their `*.run.app` URLs work). Skaffold configs live in a GCS bucket named `{project}-{8-char-md5}-cd-configs`; skaffold post-deploy hooks grant `allUsers` invoker on public stages and execute initialization jobs with `gcloud run jobs execute --wait`. With `cicd_enable_cloud_deploy = true`, the Cloud Build trigger ends with `gcloud deploy releases create` instead of `gcloud run services update`. **Try it** 1. Deploy the baseline, then list revisions and confirm probe wiring: ```bash gcloud run services describe --region=us-central1 \ --format="yaml(spec.template.spec.containers[0].startupProbe, spec.template.spec.containers[0].livenessProbe)" ``` 2. With the Delivery pipeline profile, push a commit and follow the release: ```bash gcloud deploy releases list --delivery-pipeline= --region=us-central1 gcloud deploy rollouts list --delivery-pipeline= \ --release= --region=us-central1 ``` 3. Promote to staging, then approve prod (it is gated by default): ```bash gcloud deploy releases promote --release= \ --delivery-pipeline= --region=us-central1 gcloud deploy rollouts approve --release= \ --delivery-pipeline= --region=us-central1 ``` Watch **Console > Cloud Deploy > Delivery pipelines** render the stage graph as each rollout completes. 4. Inspect the per-stage services: `gcloud run services list --region=us-central1` shows `-dev`, `-staging`, `-prod`. 5. You know it worked when the prod rollout sits in "Pending approval" until you approve it, and the prod service serves the new image afterward. **Check yourself**
Q1: A release passed dev and staging but the prod rollout is stuck. No errors anywhere. What's the most likely cause in the default RAD pipeline? A: The prod stage has `require_approval = true` by default — the rollout is waiting in `PENDING_APPROVAL` for `gcloud deploy rollouts approve` (or a console approval). This is the intended manual gate, not a failure; the exam phrases this as "deployment requires manager sign-off before production".
Q2: You set `enable_cloud_deploy = true` without `enable_cicd_trigger = true` and the plan fails with a precondition error. Why does the module insist on the trigger? A: Cloud Deploy releases are only created by the CI/CD pipeline; a delivery pipeline with nothing to create releases is meaningless, so the module rejects the combination at plan time instead of provisioning a dead pipeline. Generalized exam lesson: progressive delivery sits *downstream* of CI — Cloud Deploy consumes artifacts, it doesn't build them.
Q3: How do you give QA a URL for an unreleased revision without sending it any production traffic? A: Add a `traffic_split` entry for the revision with `percent = 0` and a `tag` (e.g., `"qa"`). Cloud Run exposes a stable tagged URL (`https://qa----.run.app`) that routes directly to that revision while the main URL keeps serving the stable split.
**Beyond the modules** — The exam also expects Cloud Run *event-driven* invocation (Eventarc triggers delivering CloudEvents, Pub/Sub push subscriptions authenticating with OIDC tokens and `roles/run.invoker`) — the modules only use Eventarc internally for secret rotation. Also study canary/automated rollback strategies in Cloud Deploy (canary deployment strategy with traffic percentages per phase) — the RAD pipeline uses the standard strategy with manual promotion. Try `gcloud deploy rollouts retry` and `gcloud run services update-traffic --to-latest` in a scratch project. **⚠️ Exam trap** — The startup probe and the liveness probe fail differently: a failing *startup* probe means the instance never receives traffic and Cloud Run keeps retrying/replacing instances (deploys appear to hang); a failing *liveness* probe restarts a previously healthy container. "New revision stuck at 0% serving" is almost always the startup probe (wrong `path` or port), not liveness. --- ## 3.2 Deploying containers to GKE > ⏱ ~90 min · 💰 moderate — Autopilot bills summed pod resource *requests* plus a cluster fee; the quota/PDB/probe exercises add nothing · ⚙️ Requires: Kubernetes lab profile (`create_google_kubernetes_engine = true` in Services_GCP, then App_GKE) **Why the exam cares** — GKE questions test the Kubernetes resource model through a Google lens: requests vs limits (and how Autopilot bills them), Deployment vs StatefulSet selection, HPA vs VPA, probe semantics, disruption budgets, and modern exposure via the Gateway API. Autopilot specifics matter: you size pods, not nodes. **How RAD implements it** — `Services_GCP` provisions the cluster: `gke_cluster_mode` default `"AUTOPILOT"`, Dataplane V2, VPC-native (alias-IP) pod/service secondary ranges, Workload Identity (`{project}.svc.id.goog`), the standard Gateway API channel, release channel `REGULAR`, and the Secret Manager add-on. `App_GKE` then deploys into it (discovering the cluster via `gke_cluster_selection_mode`, or provisioning an inline Autopilot cluster when the platform module is absent): - **Workload type.** `workload_type` (default `null`) auto-resolves: `stateful_pvc_enabled = true` → StatefulSet (with required `stateful_pvc_size` and `stateful_pvc_mount_path`, `stateful_pod_management_policy` default `OrderedReady`, `stateful_update_strategy` default `RollingUpdate`); otherwise Deployment. Explicitly setting `workload_type = "Deployment"` together with `stateful_pvc_enabled = true` fails at plan time. - **Autoscaling.** The HPA is created only when `max_instance_count > 1` (default `3`) **and** `enable_vertical_pod_autoscaling = false` (its default); it targets 70% CPU and 80% memory utilization. Turning VPA on therefore replaces horizontal scaling with request right-sizing — they are mutually exclusive here because both would act on the same CPU/memory signals. - **Probes.** `startup_probe_config` (enabled, HTTP `/healthz`, delay 10s, period 10s, failure threshold 3) and `health_check_config` → liveness probe (delay 15s, period 30s, failure threshold 3). **No readiness probe is configured** — the module relies on the startup probe to gate first traffic; be ready to explain on the exam why a dedicated readiness probe still matters for temporarily-overloaded pods. - **Sidecar.** When a database exists and `enable_cloudsql_volume = true`, a `cloud-sql-proxy` sidecar (image mirrored into Artifact Registry) runs with `--private-ip` and a preStop hook calling `/quitquitquit` for graceful shutdown. - **Namespace governance.** `enable_resource_quota` (default `false`) creates a ResourceQuota — `quota_cpu_requests`/`quota_cpu_limits` default `"4"`, `quota_memory_requests` default `"4Gi"`, `quota_memory_limits` default `"8Gi"` (binary unit suffix is *validated*: a bare `"4"` would be read as 4 bytes by Kubernetes and block all scheduling), `quota_max_pods` `"20"`. `enable_pod_disruption_budget` (default `true`) creates a PDB with `pdb_min_available` default `"1"`, skipped when `max_instance_count = 1` and validated to be < `max_instance_count`. `enable_network_segmentation` (default `false`) adds NetworkPolicies (requires Dataplane V2). `enable_topology_spread` spreads pods across zones/hosts. - **Exposure.** The Service is `service_type` default `"LoadBalancer"`, `service_port` `80` → `container_port` `8080`, `session_affinity` default `"ClientIP"`. `enable_custom_domain` (default `false`) switches to the Gateway API: a `Gateway` with `gatewayClassName: gke-l7-global-external-managed`, an `HTTPRoute` (plus a `ReferenceGrant` for cross-namespace backends), Certificate Manager Google-managed certs for `application_domains`, and a reserved global static IP (`reserve_static_ip` default `true`). Cloud Armor and CDN attach via `GCPBackendPolicy`. **Try it** 1. Get credentials and inspect what the module deployed (the namespace is auto-generated from `application_name` + `tenant_id` unless `namespace_name` is set): ```bash gcloud container clusters get-credentials --region=us-central1 kubectl get ns kubectl -n get deploy,hpa,pdb,resourcequota,svc ``` 2. Confirm probe and sidecar wiring, and watch a rolling update: ```bash kubectl -n get deploy -o yaml | grep -A6 -E "startupProbe|livenessProbe|cloud-sql-proxy" kubectl -n rollout status deploy/ kubectl -n rollout history deploy/ ``` 3. Trigger the HPA: run a load generator against the Service IP and watch replicas climb toward `max_instance_count`: ```bash kubectl -n get hpa -w ``` 4. Set `enable_resource_quota = true` with `quota_max_pods = "2"` while `max_instance_count = 3`, generate load, and observe pods blocked by the quota in `kubectl -n get events --sort-by=.lastTimestamp`. 5. You know it worked when the HPA shows `cpu: %/70%` scaling events and the quota event reads `exceeded quota` when the cap is hit. **Check yourself**
Q1: On Autopilot, a team sets limits of 4 CPU/8Gi "to be safe" while actual usage is 200m/300Mi. What is the cost effect and the fix? A: Autopilot bills the pod's resource *requests* (and defaults requests from limits when unset), so over-declaring inflates cost ~20× regardless of usage. Fix: set realistic `container_resources` requests (`cpu_request`/`mem_request`) below the limits, or enable `enable_vertical_pod_autoscaling = true` and let VPA right-size requests — accepting that the module then drops the HPA.
Q2: A maintenance event evicts pods and the app briefly serves 0 replicas. Which RAD default should have prevented this, and when does it silently not apply? A: The PodDisruptionBudget (`enable_pod_disruption_budget = true`, `pdb_min_available = "1"`) makes voluntary evictions keep at least one pod running. It is intentionally skipped when `max_instance_count = 1` — a PDB of minAvailable 1 on a single-replica workload would block node upgrades entirely. Single-replica workloads therefore have no disruption protection by design.
Q3: You need stable per-pod volumes and ordered startup for a clustered datastore. What do you set, and what happens if you also force `workload_type = "Deployment"`? A: Set `stateful_pvc_enabled = true` with `stateful_pvc_size` and `stateful_pvc_mount_path` — the workload auto-resolves to a StatefulSet with `OrderedReady` pod management. Forcing `workload_type = "Deployment"` alongside it fails at plan time, because Deployments share volumes and have no stable identity — the validation encodes the exam's own decision rule.
**Beyond the modules** — Study `maxSurge`/`maxUnavailable` tuning on rolling updates and blue/green via label-switching Services (the module always uses default RollingUpdate parameters), GKE Standard node-pool management (`gcloud container node-pools create`), and fine-grained canary traffic on GKE (requires a mesh or Gateway API traffic splitting across two Services — the module's HTTPRoute targets a single backend, selectable per Cloud Deploy stage via `gateway_backend_stage`, default `"dev"`). Also know `kubectl rollout undo` for instant Deployment rollback. **⚠️ Exam trap** — Requests vs limits on Autopilot: scheduling, quota accounting (`quota_*_requests`), and *billing* all key off requests, while OOM kills key off memory limits. "Reduce the limit" does not reduce Autopilot cost if the request stays high — and a memory limit below actual usage turns a working pod into a CrashLoopBackOff. --- # PCD Certification Preparation Guide: Section 4 — Integrating applications with Google Cloud services (~21% of the exam) PCD Certification Preparation Guide: Section 4 — Integrating applications with Google Cloud services (~21% of the exam) > 📚 **Official exam guide:** [Professional Cloud Developer certification](https://cloud.google.com/learn/certification/cloud-developer) — always confirm section weightings against the current Google Cloud exam guide. This section uses the integration surfaces the foundation modules wire up for you: database connectivity and runtime configuration (App_CloudRun's Cloud Run service and the GKE proxy sidecar), identity (the GKE service-account wiring, Workload Identity Federation in `Services_GCP`, and the platform's IAM layer), and monitoring (the platform's monitoring and dashboard layers). Deploy the **Serverless baseline** profile; add the **Kubernetes lab** profile for the Workload Identity exercises (see the [Lab Map](PCD_Certification_Guide.md)). --- ## 4.1 Integrating applications with data and storage services > ⏱ ~60 min · 💰 no additional cost over the deployed profile · ⚙️ Requires: Serverless baseline (Postgres + Cloud SQL volume are on by default) **Why the exam cares** — Integration questions are concrete: what connection string does the code use, where does the password come from, which IAM role does the service account need, and what happens at scale (connection limits, proxy behavior). The Cloud SQL Auth Proxy pattern — IAM-authenticated, TLS-encrypted, no IP allowlists — is the canonical answer, and you should know both its Cloud Run form (managed socket volume) and its GKE form (sidecar container). **How RAD implements it** — *Database connectivity.* On Cloud Run, `enable_cloudsql_volume` (default `true`) attaches the managed Cloud SQL volume mounted at `cloudsql_volume_mount_path` (default `/cloudsql`); the app connects via the Unix socket `/cloudsql/::`. On GKE, the same flag injects a `cloud-sql-proxy` sidecar (image mirrored into Artifact Registry, started with `--private-ip`, graceful preStop via `/quitquitquit`) and the app connects to localhost. Disable the flag to connect over private IP directly — the module then sets `DB_HOST` to the instance's private address. *Runtime configuration injection.* App_CloudRun assembles env vars the container sees without any code knowing about Terraform: `APP_NAME`, `APP_VERSION`, `DB_NAME`, `DB_USER`, `DB_PORT`, `DB_HOST` (socket path or private IP), `CLOUDRUN_SERVICE_URL`, plus `NFS_SERVER_IP` when NFS is enabled and `REDIS_HOST`/`REDIS_PORT`/`REDIS_URL` when `enable_redis` is on. The password never appears in plaintext: `DB_PASSWORD` arrives as a Secret Manager reference. All the env var *names* are overridable (`db_password_env_var_name`, `db_host_env_var_name`, etc.) so existing application images need no changes. *Schema and data lifecycle.* `initialization_jobs` (default: a `db-init` job running a database-init script on `postgres:15-alpine` with `execute_on_apply = true`) handles migrations/seeding with `depends_on_jobs` ordering. `enable_backup_import` restores a dump from GCS or Google Drive (`backup_source`, `backup_file`, `backup_format`); `enable_postgres_extensions`/`enable_mysql_plugins` install database extensions; `enable_custom_sql_scripts` runs arbitrary SQL from a bucket. `Services_GCP` additionally offers `enable_cloudsql_iam_auth` (default `false`), which sets the IAM-auth database flag (`cloudsql.iam_authentication` on PostgreSQL, `cloudsql_iam_authentication` on MySQL) and grants `roles/cloudsql.instanceUser` — the passwordless IAM database authentication the exam mentions. *File and object integration.* `gcs_volumes` mounts buckets via GCS Fuse (filesystem semantics; gen2 only), `enable_nfs` (default `true`) mounts the shared NFS export at `/mnt/nfs` for multi-instance shared writes, and `storage_buckets` provisions buckets with per-bucket `roles/storage.objectAdmin` for the app SA — the client-library path. **Try it** 1. See exactly what your code sees: ```bash gcloud run services describe --region=us-central1 \ --format="yaml(spec.template.spec.containers[0].env, spec.template.spec.containers[0].volumeMounts)" ``` Identify `DB_HOST` (a `/cloudsql/...` path), the `DB_PASSWORD` secret reference, and the volume mounts. 2. Verify the IAM that makes the proxy work — the service account needs `roles/cloudsql.client`: ```bash gcloud projects get-iam-policy \ --flatten="bindings[].members" \ --filter="bindings.members~cloudrun-sa" \ --format="table(bindings.role)" ``` 3. Watch the default initialization job run and read its logs: ```bash gcloud run jobs executions list --job= --region=us-central1 gcloud logging read 'resource.type="cloud_run_job"' --limit=20 ``` 4. On the GKE profile, confirm the sidecar: `kubectl -n get pod -o jsonpath='{.spec.containers[*].name}'` should list your app and `cloud-sql-proxy`. 5. You know it worked when the app container resolves `DB_HOST` to the socket path, the db-init execution shows `Succeeded`, and the GKE pod runs two containers. **Check yourself**
Q1: Cloud Run scaled to 50 instances and Postgres started rejecting connections. The instance has the module default flags. What happened and what are the fixes? A: Each instance holds its own pool; 50 instances × even a small pool exceeds the default `max_connections=200` flag set on the RAD Postgres instance. Fixes in exam order: cap `max_instance_count`, shrink the per-instance pool, raise `max_connections` (costs memory), or introduce server-side pooling. The Auth Proxy authenticates and encrypts — it does not pool for you.
Q2: Why does the platform run schema migrations as a Cloud Run *job* instead of at service startup? A: A service can scale to N concurrent instances — running migrations in the entrypoint races N copies against each other and slows cold starts. A job (`initialization_jobs` with `execute_on_apply`) runs exactly `task_count` tasks once, can be ordered with `depends_on_jobs`, retried independently (`max_retries`), and keeps the serving path fast. This separation of "run-once" from "serve" is a standard PCD design answer.
Q3: An app needs shared writable storage across all Cloud Run instances. Compare the two RAD options. A: `enable_nfs` mounts a real POSIX filesystem (Filestore or the platform NFS VM) — correct for apps needing file locking/rename semantics, but it's a single capacity/throughput point. `gcs_volumes` (GCS Fuse) backs the mount with an object store — effectively unlimited and cheaper, but writes are object uploads (no partial writes/locking). Both require `execution_environment = "gen2"`.
**Beyond the modules** — The modules create no application messaging or document-store code paths: practice the **Pub/Sub** client libraries (publish with attributes, pull vs push subscriptions, ack deadlines, dead-letter topics), **Firestore** SDK usage (documents, queries needing composite indexes, real-time listeners, transactions), and **Cloud Storage** client-library patterns including signed URLs for direct browser upload/download. The only Pub/Sub in the platform is internal (secret-rotation and SCC topics) — useful to inspect (`gcloud pubsub topics list`) but not an application pattern. **⚠️ Exam trap** — The Cloud SQL Auth Proxy replaces *network* allowlisting and TLS cert management, not database authentication: code still presents a DB user and password (unless IAM database authentication is enabled). "We added the proxy, why do we still need the password?" distinguishes `roles/cloudsql.client` (connect) from `roles/cloudsql.instanceUser` + IAM auth (login). --- ## 4.2 Consuming Google Cloud APIs > ⏱ ~60 min · 💰 no additional cost · ⚙️ Requires: Serverless baseline; Kubernetes lab profile for Workload Identity; `enable_workload_identity_federation = true` in Services_GCP for the WIF steps **Why the exam cares** — Every PCD scenario about calling Google APIs reduces to identity: code should use Application Default Credentials backed by the runtime's service account — never JSON key files. You must know how ADC resolves on Cloud Run (metadata server), on GKE (Workload Identity), on developer machines (`gcloud auth application-default login`), and outside Google Cloud entirely (Workload Identity Federation). The second axis is authorization: least-privilege roles on the *resource* (a specific secret, a specific bucket), not the project. **How RAD implements it** — *Dedicated service accounts.* `Services_GCP` creates `cloudrun-sa-{prefix}`, `cloudbuild-sa-{prefix}`, `clouddeploy-sa-{prefix}`, `gke-sa-{prefix}`, and `nfs-sa-{prefix}` — nothing runs as the default compute SA. The platform's IAM layer applies resource-level least privilege: `roles/secretmanager.secretAccessor` granted *per secret*, `roles/storage.objectAdmin` *per bucket*, and `roles/iam.serviceAccountUser` for the impersonation chains the deployer needs. When your app needs more (say Firestore), `additional_cloudrun_sa_roles` extends the Cloud Run SA's role list declaratively. *Workload Identity on GKE.* App_GKE creates a Kubernetes ServiceAccount per namespace annotated `iam.gke.io/gcp-service-account: ` and binds `roles/iam.workloadIdentityUser` to `serviceAccount:{project}.svc.id.goog[/]`. Pods using that KSA get GSA-backed tokens from the metadata server — ADC works with zero key files, identical in code to Cloud Run. *Workload Identity Federation.* `Services_GCP` (`enable_workload_identity_federation`, default `false`) creates pool `wif-pool` with a provider chosen by `wif_provider_type` (default `"github"` → provider `github-actions`; also `gitlab` → `gitlab-ci`, or `generic` for any OIDC issuer). All pool identities (`principalSet://.../*`) may impersonate the Cloud Build, Cloud Deploy, and Cloud Run service accounts via `roles/iam.workloadIdentityUser` — keyless CI from external systems, the exam's recommended replacement for exported keys. *Service-to-service authorization.* Cloud Run access is IAM on `roles/run.invoker`: public services get an `allUsers` binding; IAP services instead grant the IAP service agent invoker rights and your principals `roles/iap.httpsResourceAccessor`. Calling a non-public service from another service means minting an *ID token* for the caller's SA — the modules establish the IAM shape; the token-fetching code is yours to learn. **Try it** 1. Prove the runtime identity from inside the deployed service (no SDK required — this is what ADC does under the hood): ```bash # from your workstation, against the metadata-backed identity: gcloud run services describe --region=us-central1 \ --format="value(spec.template.spec.serviceAccountName)" ``` 2. On GKE, inspect the Workload Identity wiring: ```bash kubectl -n get sa -o yaml | grep -B2 "iam.gke.io/gcp-service-account" gcloud iam service-accounts get-iam-policy \ --format="table(bindings.role, bindings.members)" ``` You should see the `roles/iam.workloadIdentityUser` binding for `serviceAccount:.svc.id.goog[/]`. 3. Inspect the WIF pool and provider, then test invoker enforcement: ```bash gcloud iam workload-identity-pools providers list \ --workload-identity-pool=wif-pool --location=global gcloud run services get-iam-policy --region=us-central1 curl -s -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer $(gcloud auth print-identity-token)" https:/// ``` 4. You know it worked when the KSA annotation matches the GSA whose policy contains the workloadIdentityUser binding, and the authenticated curl returns 200 where an anonymous one is rejected (on a non-public service). **Check yourself**
Q1: Service A on Cloud Run must call private Service B. Which role, on what, for whom — and which token type does A send? A: Grant A's service account `roles/run.invoker` *on Service B* (resource-level, not project-level). A fetches an **ID token** with audience = B's URL (from the metadata server, e.g. via the client library or `fetch_id_token`) and sends it as a Bearer header. An OAuth *access* token is the wrong answer — Cloud Run's IAM check validates identity tokens.
Q2: GitHub Actions needs to push images and create Cloud Deploy releases without a downloaded key. Which RAD configuration is the textbook setup? A: `enable_workload_identity_federation = true` with `wif_provider_type = "github"`. The workflow exchanges its GitHub OIDC token through pool `wif-pool` / provider `github-actions` and impersonates `cloudbuild-sa-*`/`clouddeploy-sa-*` (the module binds `roles/iam.workloadIdentityUser` for the pool). No long-lived credential exists anywhere; note the module's wildcard `principalSet` is deliberately broad — production answers scope to `attribute.repository`.
Q3: A pod's Google API calls run as the node's identity instead of the app's GSA. What's missing? A: One of the three Workload Identity legs: the cluster's workload pool, the KSA annotation `iam.gke.io/gcp-service-account`, or the `roles/iam.workloadIdentityUser` binding on the GSA for `{project}.svc.id.goog[ns/ksa]` — or the pod spec isn't using the annotated KSA (`serviceAccountName`). The RAD module wires all three; on the exam, the missing IAM binding is the most common culprit.
**Beyond the modules** — Practice the client-library mechanics the modules can't show: automatic retries with exponential backoff (built into the libraries for 429/503), pagination iterators, field masks, and choosing gRPC vs REST transports. Also study API enablement failures (`SERVICE_DISABLED` 403s — the platform pre-enables its APIs, a fresh project does not) and quota errors (`RESOURCE_EXHAUSTED` 429 → backoff or quota increase, not retry-storms). **⚠️ Exam trap** — Access tokens vs ID tokens: `gcloud auth print-access-token` authorizes Google *API* calls; `gcloud auth print-identity-token` authenticates you *to a service* (Cloud Run invoker, IAP). Swapping them produces 401s that look like missing IAM but aren't. --- ## 4.3 Troubleshooting and observability > ⏱ ~60 min · 💰 low — alerting/dashboards are free at this scale; log storage grows if you enable DATA_READ audit logs · ⚙️ Requires: any deployed profile; set `support_users` to receive notifications **Why the exam cares** — PCD troubleshooting questions hand you a symptom (5xx spike, latency regression, crash loop) and expect you to pick the right tool in the right order: Logs Explorer with resource-type filters, metrics and alerting, dashboards, then code-level tools (Trace, Profiler, Error Reporting). Structured logging and instrumentation are developer responsibilities the exam tests directly. **How RAD implements it** — Containers log to stdout/stderr and Cloud Run/GKE forward to Cloud Logging automatically — nothing to configure. The modules add the alerting layer via the platform's monitoring layer: - `support_users` creates email notification channels; monitoring resources are only created when `support_users`, `alert_policies`, or an enabled uptime config exists. - Built-in alerts: CPU utilization > 0.9 and memory utilization > 0.9 (P99-aligned over 60s), filtered to your specific service (`resource.labels.service_name` on Cloud Run; the GKE module passes Kubernetes-scoped filters). - `alert_policies` adds custom policies declaratively: each entry is `{ name, metric_type, comparison, threshold_value, duration_seconds, aggregation_period }` and the module scopes the filter to the deployed service — e.g. `run.googleapis.com/request_latencies` with `COMPARISON_GT` and `threshold_value = 1000`. - The platform provisions a per-deployment Cloud Monitoring dashboard (separate Cloud Run and GKE layouts). - `uptime_check_config` (default `{ enabled = false, path = "/" }` — set `enabled = true` to provision one; `check_interval` default `"60s"`, `timeout` default `"10s"`) provisions a real Cloud Monitoring uptime check named `-uptime-check` (HTTP GET from multiple global regions) plus a `-uptime-check-alert` policy on `monitoring.googleapis.com/uptime_check/check_passed` (fires after 300s of failure, notifies the `support_users` channels). Creation is gated at plan time on public reachability — Cloud Run probes the first `application_domains` entry, else the nip.io LB host, else the run.app URL when `ingress_settings = "all"`; GKE probes the custom domain via the Gateway (HTTPS:443) or the LoadBalancer Service ingress IP over HTTP on `service_port`. Internal-only deployments get no check, and `uptime_check_names` outputs the created check's name. **Try it** 1. Generate some traffic and read the logs the developer way: ```bash gcloud logging read \ 'resource.type="cloud_run_revision" AND resource.labels.service_name="" AND severity>=WARNING' \ --limit=20 --format="value(timestamp, severity, textPayload)" ``` In **Console > Logging > Logs Explorer**, repeat with the query builder and note that JSON log lines become filterable `jsonPayload.*` fields — emit structured logs from your app to get this for free. 2. Add a latency alert via the portal: `alert_policies = [{ name = "p-latency", metric_type = "run.googleapis.com/request_latencies", comparison = "COMPARISON_GT", threshold_value = 1000, duration_seconds = 300 }]`, apply, then verify: ```bash gcloud alpha monitoring policies list --format="table(displayName, enabled)" gcloud monitoring dashboards list --format="value(displayName)" ``` 3. Inspect the module-created uptime check (publicly reachable deployments only) and confirm the probed host matches your domain or LB: ```bash gcloud monitoring uptime list-configs --format="table(displayName, httpCheck.path, period)" gcloud monitoring uptime describe -uptime-check ``` 4. Force an error (e.g., temporarily point `health_check_config.path` at a nonexistent path) and watch the liveness restarts in **Cloud Run > service > Logs** and the CPU/memory charts on the module-created dashboard. 5. You know it worked when the alert policy appears with your email channel attached, and the module-created uptime check shows green from multiple regions in **Monitoring > Uptime checks**. **Check yourself**
Q1: Users report intermittent 503s but your application logs show nothing at those timestamps. Where do you look next on Cloud Run? A: The *request* logs and platform metrics, not app logs: filter `resource.type="cloud_run_revision" AND httpRequest.status=503` — 503s with no app log usually mean the request never reached your code (instance startup failures, exceeded `max_instance_count` under load, or request timeout/probe failures). Correlate with `container/instance_count` and startup-probe failures; raising `max_instance_count` or fixing the startup probe is the usual fix.
Q2: An alert should fire when error rate exceeds 5% for 5 minutes, notifying the on-call list. Map this to RAD variables. A: Put the on-call addresses in `support_users` (creates the notification channels) and add an `alert_policies` entry on `run.googleapis.com/request_count` filtered to 5xx — though for a *ratio*, the honest answer is that the module's single-metric threshold policies can't express it; you'd build a ratio-based condition (MQL/PromQL) directly in Cloud Monitoring. Knowing when declarative simple thresholds stop being enough is itself exam-relevant.
Q3: Checkout takes 4s; the database team swears their queries are fast. Which tool proves where the time goes across your two Cloud Run services? A: Cloud Trace with distributed trace context propagation — instrument both services with OpenTelemetry (Cloud Trace exporter), propagate the `traceparent` header on the service-to-service call, and read the waterfall to see which span (handler, downstream call, DB query) owns the latency. Logs and metrics aggregate; only tracing shows the per-request breakdown.
**Beyond the modules** — Nothing in the modules instruments application code: study OpenTelemetry setup and trace propagation (Cloud Trace), continuous profiling (`google-cloud-profiler`, flame graphs, <1% overhead — safe in prod), Error Reporting's automatic stack-trace grouping (works from stdout logs for major runtimes), and log-based metrics for alerting on log patterns. Also try Cloud Run's built-in SLO monitoring (**Cloud Run > service > SLOs**) — none of this is provisioned by the platform. **⚠️ Exam trap** — Don't assume an input variable means a provisioned resource — always verify in the source (earlier platform releases accepted `uptime_check_config` without creating any check; today it provisions one, but only for publicly reachable endpoints). On the exam the analogous trap is assuming Cloud Run "has" tracing/profiling because the agent *could* run — Trace gets automatic spans for inbound requests, but cross-service propagation and custom spans require you to instrument the code. --- # Professional Cloud Database Engineer (PCDE) Certification Lab Map > 📚 **Official exam guide:** [Professional Cloud Database Engineer certification](https://cloud.google.com/learn/certification/cloud-database-engineer) — always confirm section weightings against the current Google Cloud exam guide. The Professional Cloud Database Engineer certification validates your ability to design, manage, migrate, and deploy scalable, highly available database solutions on Google Cloud. The RAD foundation modules — `Services_GCP`, `App_CloudRun`, `App_GKE`, and `App_Common` — serve as a live lab for the bulk of this exam: `Services_GCP` provisions Cloud SQL (PostgreSQL and MySQL), AlloyDB, Firestore Enterprise, and Memorystore Redis behind a private VPC, while `App_CloudRun` and `App_GKE` demonstrate how real applications connect, authenticate, back up, monitor, and rotate credentials against those databases — all driven by infrastructure-as-code, which is itself the exam's "automate database instance provisioning" objective made concrete. > **Abbreviation note:** in this repository **PDE** refers to the Professional Cloud **DevOps** Engineer guides. This certification — Professional Cloud **Database** Engineer — uses the abbreviation **PCDE** throughout. ## How to use this guide - Deploy one of the profiles below through your deployment portal, then work through the matching section guide while the infrastructure is live. - Each section guide pairs a portal change with what to observe in the GCP console and a real `gcloud`/`psql`/`kubectl` command. - Use the coverage legend to know which exam topics must be studied outside the platform — Spanner, Bigtable, BigQuery, and Database Migration Service are *not* implemented by these modules, and the section guides say so plainly. - Destroy or scale down expensive profiles (REGIONAL Cloud SQL, AlloyDB) when you finish a study session. **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: relational-baseline *Purpose:* The minimum-cost lab — a zonal private-IP PostgreSQL instance plus a Cloud Run application that connects to it through the Cloud SQL connector volume. *Modules:* `Services_GCP` + `App_CloudRun`. | Variable | Value | |---|---| | `create_postgres` | `true` (default) | | `postgres_database_availability_type` | `ZONAL` (default) | | `postgres_tier` | `db-custom-1-3840` (default) | | `database_type` (App_CloudRun) | `POSTGRES` (default) | | `enable_cloudsql_volume` (App_CloudRun) | `true` (default) | *Estimated incremental cost:* low — one 1-vCPU Cloud SQL Enterprise instance with a 10 GB PD_SSD disk is the dominant cost. ### Profile: ha-production *Purpose:* Section 1.2 and Section 4 — REGIONAL high availability, a cross-region read replica, CMEK, IAM database authentication, and database alerting. *Modules:* `Services_GCP` (redeploy/update the baseline). | Variable | Value | |---|---| | `availability_regions` | `["us-central1", "us-east1"]` | | `subnet_cidr_range` | one CIDR per region, e.g. `["10.0.0.0/24", "10.0.1.0/24"]` | | `postgres_database_availability_type` | `REGIONAL` | | `create_postgres_read_replica` | `true` | | `postgres_read_replica_count` | `1` | | `enable_cloudsql_iam_auth` | `true` | | `enable_cmek` | `true` | | `configure_email_notification` | `true` | | `notification_alert_emails` | `["you@example.com"]` | *Estimated incremental cost:* moderate-to-high — REGIONAL roughly doubles the primary's instance cost, and each read replica bills like another primary-sized instance. ### Profile: multi-engine *Purpose:* Section 1.4 and Section 2 — run PostgreSQL, MySQL, Memorystore Redis, and Firestore Enterprise (MongoDB-compatible) side by side to compare engines. *Modules:* `Services_GCP`. | Variable | Value | |---|---| | `create_postgres` | `true` (default) | | `create_mysql` | `true` | | `create_redis` | `true` | | `redis_tier` | `STANDARD_HA` | | `redis_persistence_mode` | `RDB` | | `create_firestore` | `true` | *Estimated incremental cost:* moderate — a second Cloud SQL instance plus a STANDARD_HA Redis instance (~2× BASIC); Firestore Enterprise bills per operation and is negligible at lab scale. ### Profile: alloydb-ai *Purpose:* Sections 1.1, 1.4, and 2.4 — an AlloyDB cluster with a primary and a horizontally scalable read pool, for analytics/vector-workload study. *Modules:* `Services_GCP`. | Variable | Value | |---|---| | `enable_alloydb` | `true` | | `alloydb_cpu_count` | `2` (default; allowed: 2, 4, 8, 16, 32, 64) | | `enable_alloydb_read_pool` | `true` | | `alloydb_read_pool_node_count` | `1` (default; 1–20) | *Estimated incremental cost:* high — AlloyDB has no shared-core tier; the 2-vCPU primary plus each read-pool node is the dominant cost. Tear down after each session. ### Profile: app-dataops *Purpose:* Sections 2.3, 2.5, and 3.1 — scheduled database exports, one-time backup imports, automated password rotation, and database users managed by initialization jobs. *Modules:* `App_CloudRun` (or `App_GKE`) on top of relational-baseline. | Variable | Value | |---|---| | `database_type` | `POSTGRES` (default) | | `backup_schedule` | `"0 2 * * *"` (default) | | `backup_retention_days` | `7` (default) | | `enable_backup_import` | `true` (after staging a file; see Section 3 guide) | | `backup_source` / `backup_file` / `backup_format` | `gcs` / `backup.sql` / `sql` | | `enable_auto_password_rotation` | `true` | | `secret_rotation_period` | `"2592000s"` (default, 30 days) | *Estimated incremental cost:* low — Cloud Run jobs, Cloud Scheduler, and Secret Manager versions cost cents; the backup GCS bucket is lifecycle-pruned. ## Section 1: Design innovative, scalable, and highly available cloud database solutions (~32% of the exam) The heaviest section. `Services_GCP` is the star: every design decision the exam tests — machine tier, zonal vs regional availability, private connectivity, encryption, engine selection — is a variable you can flip and observe. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Database capacity and usage planning | ✅ | `postgres_tier`, `alloydb_cpu_count`, `redis_memory_size_gb`, disk autoresize | [Section 1 guide](PCDE_Section_1_Exploration_Guide.md#11-analyze-relevant-variables-to-perform-database-capacity-and-usage-planning) | | 1.2 HA and DR options | ✅ | `postgres_database_availability_type`, `create_postgres_read_replica`, PITR/backup settings, `sql_maintenance_window_day`/`_hour` + `sql_maintenance_update_track` | [Section 1 guide](PCDE_Section_1_Exploration_Guide.md#12-evaluate-database-high-availability-and-disaster-recovery-options-given-the-requirements) | | 1.3 Application connectivity, encryption, auditing | ✅ | private IP via PSA, `ssl_mode`, `enable_cmek`, `enable_cloudsql_volume`, Auth Proxy sidecar (App_GKE), `enable_audit_logging` (session poolers 📘) | [Section 1 guide](PCDE_Section_1_Exploration_Guide.md#13-determine-how-applications-will-connect-to-the-database) | | 1.4 Evaluating database solutions (SQL/NoSQL/vector, managed vs unmanaged, gen-AI) | 🟡 | Cloud SQL vs AlloyDB vs Firestore Enterprise (MongoDB compat) vs Redis vs self-managed Redis VM; Spanner/Bigtable/BigQuery 📘 | [Section 1 guide](PCDE_Section_1_Exploration_Guide.md#14-evaluate-appropriate-database-solutions-on-google-cloud) | ## Section 2: Manage a solution that can span multiple database technologies (~25% of the exam) Day-2 operations: users and IAM, monitoring, backup/recovery, scaling, and automation. The application modules (`App_CloudRun`/`App_GKE`) carry most of this section — db-init jobs, export schedulers, rotation pipelines, and alert policies. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Connectivity and access management (IAM, database users) | ✅ | `enable_cloudsql_iam_auth`, `roles/cloudsql.instanceUser` grants, the db-init user-creation script, per-secret IAM | [Section 2 guide](PCDE_Section_2_Exploration_Guide.md#21-determine-database-connectivity-and-access-management-considerations) | | 2.2 Monitoring and troubleshooting | ✅ | Cloud SQL CPU/memory/disk alert policies (Services_GCP), `alert_policies` + `uptime_check_config` in App modules, `enable_query_insights` (Services_GCP); slow-query analysis 📘 | [Section 2 guide](PCDE_Section_2_Exploration_Guide.md#22-configure-database-monitoring-and-troubleshooting-options) | | 2.3 Backup and recovery (RTO/RPO/PITR, retention) | ✅ | managed backup configuration, PITR + 7-day log retention, export/import jobs, `backup_retention_days` | [Section 2 guide](PCDE_Section_2_Exploration_Guide.md#23-design-database-backup-and-recovery-solutions) | | 2.4 Cost and performance optimization | ✅ | scale up (`postgres_tier`, `alloydb_cpu_count`) vs out (`postgres_read_replica_count`, `alloydb_read_pool_node_count`), `postgres_database_flags`; query optimization 📘 | [Section 2 guide](PCDE_Section_2_Exploration_Guide.md#24-optimize-database-cost-and-performance) | | 2.5 Automating common database tasks | ✅ | Cloud Scheduler export job, `db-export` CronJob (GKE), the password-rotation pipeline, scheduled maintenance via `sql_maintenance_window_*`; managed upgrades 📘 | [Section 2 guide](PCDE_Section_2_Exploration_Guide.md#25-automate-common-database-tasks) | ## Section 3: Migrate data solutions (~23% of the exam) The modules implement the export/import (extended-outage) migration path end to end, but Database Migration Service, Datastream, and continuous replication from external sources are concept-only — budget real study time here. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Design and implement data migration and replication | 🟡 | `enable_backup_import` + `backup_source` (gcs/gdrive) import jobs, scheduled logical export jobs, `enable_custom_sql_scripts`; DMS / Datastream / zero-downtime migration / reverse replication 📘 | [Section 3 guide](PCDE_Section_3_Exploration_Guide.md#31-design-and-implement-data-migration-and-replication) | ## Section 4: Deploy scalable and highly available databases in Google Cloud (~20% of the exam) This section is the repository's home turf: "automate database instance provisioning" is literally what these infrastructure-as-code modules do. Deploy the ha-production profile and practice failover, replica scaling, and HA monitoring against real instances. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Implement scalable and highly available databases (provision HA, test HA/DR, read replicas, automated provisioning, monitoring) | ✅ | `postgres_database_availability_type = REGIONAL`, `postgres_read_replica_count`, `gcloud sql instances failover`, the infrastructure-as-code modules themselves, the Cloud SQL alert policies (cross-region promotion workflow 🟡) | [Section 4 guide](PCDE_Section_4_Exploration_Guide.md#41-apply-concepts-to-implement-scalable-and-highly-available-databases-in-google-cloud) | --- # PCDE Certification Preparation Guide: Section 1 — Design innovative, scalable, and highly available cloud database solutions (~32% of the exam) PCDE Certification Preparation Guide: Section 1 — Design innovative, scalable, and highly available cloud database solutions (~32% of the exam) > 📚 **Official exam guide:** [Professional Cloud Database Engineer certification](https://cloud.google.com/learn/certification/cloud-database-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 1 of the Professional Cloud Database Engineer (PCDE) exam — the largest section, weighted at roughly a third of the questions. It exercises `Services_GCP` (which provisions Cloud SQL PostgreSQL/MySQL, AlloyDB, Firestore Enterprise, and Memorystore Redis) with supporting connectivity patterns from `App_CloudRun` and `App_GKE`. Before starting, deploy the **relational-baseline** profile from the [PCDE Lab Map](PCDE_Certification_Guide.md); subsections 1.2 and 1.4 additionally use the **ha-production**, **multi-engine**, and **alloydb-ai** profiles. --- ## 1.1 Analyze relevant variables to perform database capacity and usage planning > ⏱ ~45 min · 💰 no additional cost beyond the relational-baseline profile · ⚙️ Requires: default deployment (`create_postgres = true`) **Why the exam cares** — Capacity questions test whether you can translate workload metrics (connections, working-set size, IOPS, read/write ratio) into a machine tier and storage configuration, and whether you understand the cost consequences: vCPU/RAM drive instance cost linearly, SSD vs HDD trades IOPS for price, and over-provisioned storage cannot be shrunk. Expect scenarios like "the buffer cache hit ratio is low — add memory or add vCPUs?" where the right answer is the cheaper targeted change. **How RAD implements it** — Sizing is fully parameterized in `Services_GCP`: | Variable | Default | What it sizes | |---|---|---| | `postgres_tier` / `mysql_tier` | `db-custom-1-3840` | Cloud SQL machine: `db-custom--` — the default is 1 vCPU / 3.75 GB | | `postgres_database_flags` | `[{ name = "max_connections", value = "200" }]` | Connection capacity, tunable per workload | | `alloydb_cpu_count` | `2` (validated: 2, 4, 8, 16, 32, 64) | AlloyDB primary *and* read-pool node size | | `redis_memory_size_gb` | `1` (validated 1–300) | Memorystore working-set capacity | Storage is deliberately *not* a variable: the PostgreSQL instance is fixed to a PD_SSD disk starting at 10 GB with disk autoresize enabled and no upper limit (unlimited), so the disk grows automatically as data arrives — a managed answer to "size storage for growth". The instance edition is fixed to Enterprise. The same shape applies to MySQL. **Try it** 1. In your deployment portal, change `postgres_tier` from `db-custom-1-3840` to `db-custom-2-7680` (2 vCPU / 7.5 GB) and apply. This is an in-place `PATCH` that restarts the instance. 2. Observe the change in **Console > SQL > cloudsql-\-postgres > Edit > Machine configuration**, then confirm from the CLI: ```bash gcloud sql instances describe cloudsql--postgres \ --format="table(settings.tier, settings.dataDiskType, settings.dataDiskSizeGb, settings.storageAutoResize)" ``` 3. Check the connection ceiling the flag default gives you: ```bash gcloud sql instances describe cloudsql--postgres \ --format="value(settings.databaseFlags)" ``` 4. You know it worked when the describe output shows the new tier and `storageAutoResize: True` with `PD_SSD`. **Check yourself**
Q1: A reporting workload on a db-custom-1-3840 instance shows 95% memory utilization and frequent disk reads, but CPU sits at 20%. Which single change in this module addresses it most cost-effectively? A: Change `postgres_tier` to a custom shape with more RAM (e.g. `db-custom-2-13312`) rather than more vCPUs. Cloud SQL custom tiers let you scale memory to enlarge the buffer cache — which converts disk reads to cache hits — without paying for unused CPU. Adding storage or replicas would not fix a working-set-doesn't-fit-in-RAM problem.
Q2: Why does the module enable disk autoresize instead of provisioning a large disk up front, and what is the one-way street you must remember for the exam? A: Autoresize means you pay only for storage actually used while never hitting a disk-full outage. The trap: Cloud SQL storage can grow but **never shrink** — once autoresize (or a manual edit) enlarges the disk, the only way back to a smaller disk is to export and import into a new instance.
**Beyond the modules** — The modules always use `PD_SSD`; the exam also tests when HDD storage is acceptable (rarely — archival/low-IOPS only) and how per-GB SSD pricing compares to instance pricing. They also fix the Cloud SQL edition to `ENTERPRISE`; study the Enterprise Plus edition (higher per-instance limits, data cache, near-zero-downtime maintenance) in the "Cloud SQL editions" docs page. Practice estimating with the official pricing calculator and `gcloud sql tiers list`. **⚠️ Exam trap** — `max_connections` is bounded by instance memory: cranking the flag up without resizing the tier causes per-connection memory pressure and OOM restarts. Size memory first, then connections (or use a pooler — see 1.3). --- ## 1.2 Evaluate database high availability and disaster recovery options given the requirements > ⏱ ~60 min · 💰 REGIONAL roughly doubles instance cost; each replica adds an instance-sized cost · ⚙️ Requires: ha-production profile **Why the exam cares** — HA and DR questions hinge on matching the *blast radius* a requirement tolerates to the cheapest topology that survives it: zonal (no failover) → regional/HA (synchronous standby in a second zone, same region) → cross-region read replica (asynchronous, survives region loss but needs promotion). You must also know what each option does and does not protect: REGIONAL HA protects against zone failure, not against a bad `DELETE` — that is what PITR is for (see 2.3). **How RAD implements it** — In `Services_GCP`: | Variable | Default | Behavior | |---|---|---| | `postgres_database_availability_type` | `ZONAL` | Set `REGIONAL` for an HA primary with an automatic-failover standby | | `mysql_database_availability_type` | `ZONAL` | Same choice for the MySQL instance | | `create_postgres_read_replica` / `create_mysql_read_replica` | `false` | Adds read replicas (instance type `READ_REPLICA_INSTANCE`) | | `postgres_read_replica_count` / `mysql_read_replica_count` | `1` | Replica fan-out | | `availability_regions` | `["us-central1"]` | List ≥2 regions and replicas are placed in `availability_regions[1]` — a **cross-region** DR replica | Replica placement follows the region list: when two or more regions are configured, replicas land in the second region; otherwise they stay in the primary region. Replicas are always ZONAL, and each replica's private IP is published to Secret Manager as `cloudsql--postgres-replica-host` so applications can split reads. Backups/PITR (the DR time machine) are hardcoded on the primary — see Section 2.3. Maintenance windows **are** configured on the Cloud SQL instances: `sql_maintenance_window_day` (1–7, Monday-based, default `7` = Sunday), `sql_maintenance_window_hour` (0–23 UTC, default `3`), and `sql_maintenance_update_track` (`"stable"`/`"canary"`/`"week5"`, default `"stable"`) configure the maintenance window on both the PostgreSQL and MySQL primaries. Memorystore Redis similarly pins maintenance to Sunday 02:00 UTC. **Try it** 1. Apply the ha-production profile (`postgres_database_availability_type = "REGIONAL"`, `create_postgres_read_replica = true`, `availability_regions = ["us-central1", "us-east1"]`). 2. In **Console > SQL**, the primary now shows "High availability (regional)" and a replica `cloudsql--postgres-replica` appears in us-east1. Verify topology: ```bash gcloud sql instances list \ --format="table(name, region, gceZone, settings.availabilityType, instanceType)" ``` 3. Trigger a manual failover (the exam expects you to know this command — it flips the primary to the standby zone): ```bash gcloud sql instances failover cloudsql--postgres ``` 4. You know it worked when `gcloud sql instances describe cloudsql--postgres --format="value(gceZone)"` reports a different zone than before the failover, and the replica still lists `instanceType: READ_REPLICA_INSTANCE` in the secondary region. **Check yourself**
Q1: A customer requires the database to survive a complete region outage with an RPO of minutes, but reads/writes during normal operation must stay in one region for latency. Which two module settings deliver this, and what manual step remains in a disaster? A: `postgres_database_availability_type = "REGIONAL"` (zone-level HA with automatic failover) plus `availability_regions = ["primary", "secondary"]` with `create_postgres_read_replica = true` (asynchronous cross-region replica, RPO = replication lag, usually seconds-to-minutes). In a region loss you must still **promote** the replica (`gcloud sql instances promote-replica`) and repoint applications — cross-region failover is not automatic.
Q2: Why is enabling REGIONAL availability alone insufficient for a "we accidentally dropped a table" recovery requirement? A: The HA standby is a synchronous copy — the `DROP TABLE` is replicated to it instantly. Logical/operator errors are recovered with point-in-time recovery (enabled in this module with 7 days of transaction logs) or backups, not with HA. HA addresses infrastructure failure; PITR addresses data failure.
Q3: Where would you configure when Cloud SQL applies maintenance, and what does this module do about it? A: Via the instance's maintenance window (`gcloud sql instances patch --maintenance-window-day=SUN --maintenance-window-hour=2`) and optional deny-maintenance periods. This module sets one declaratively — `sql_maintenance_window_day`/`sql_maintenance_window_hour`/`sql_maintenance_update_track` (defaults: Sunday, 03:00 UTC, `stable`) on both engines. For exam purposes know that HA instances get rolling maintenance on the standby first, and that maintenance notifications can be subscribed to per instance.
**Beyond the modules** — Deny-maintenance periods and maintenance notifications are not configured here (the window itself is — see above): practice `gcloud sql instances patch --deny-maintenance-period-start-date/--deny-maintenance-period-end-date` and the "About maintenance on Cloud SQL instances" docs page. Truly multi-regional *write* topologies (Spanner multi-region configurations, AlloyDB secondary clusters with switchover) are also out of scope for these modules — study "Spanner instance configurations" and "AlloyDB cross-region replication" docs. **⚠️ Exam trap** — A read replica is **not** an HA standby. The REGIONAL standby is synchronous, invisible (no connection string), and fails over automatically; a replica is asynchronous, readable, and must be promoted manually. Questions that say "automatic failover" point to REGIONAL availability, never to replicas. --- ## 1.3 Determine how applications will connect to the database > ⏱ ~60 min · 💰 no additional cost · ⚙️ Requires: relational-baseline profile (+ optionally `enable_cmek = true`, `enable_audit_logging = true` on Services_GCP) **Why the exam cares** — Connectivity questions test the decision between private IP, public IP with authorized networks, and the Cloud SQL Auth Proxy/connectors; how encryption is enforced in transit (SSL modes) and at rest (Google-managed vs CMEK); where credentials live; and how access is audited. The Auth Proxy + private IP + Secret Manager combination demonstrated here is Google's recommended production pattern. **How RAD implements it** — three layers: *Network path.* The platform allocates a /16 internal range reserved for VPC peering and establishes a private services access (PSA) connection to the Service Networking service. Every database then attaches privately: PostgreSQL and MySQL disable the public IPv4 address and bind to the VPC's private network and allocated range; AlloyDB attaches to the same VPC; Redis uses the VPC as its authorized network with `redis_connect_mode` (default `DIRECT_PEERING`). There is no public IP on any database. *In-transit encryption.* PostgreSQL enforces SSL mode `ENCRYPTED_ONLY`; MySQL is relaxed to `ALLOW_UNENCRYPTED_AND_ENCRYPTED` (a deliberate engine-by-engine difference worth noticing). *Application attach + credentials.* In `App_CloudRun`, `enable_cloudsql_volume` (default `true`) mounts a Cloud SQL volume at `cloudsql_volume_mount_path` (default `/cloudsql`) — the managed Cloud Run Cloud SQL connector exposing a Unix socket per connection name. In `App_GKE`, the same flag injects a **Cloud SQL Auth Proxy sidecar** container running with `--private-ip` and the database port. Passwords are randomly generated and stored only in Secret Manager (e.g. `secret-cloudsql--postgres-root-password`); applications receive them via secret references, never plaintext. Key management: `enable_cmek` (default `false`) encrypts the instances with a customer-managed KMS key (rotation period `cmek_key_rotation_period` default `7776000s`). Auditing: `enable_audit_logging` (default `false`) turns on `ADMIN_READ`/`DATA_READ`/`DATA_WRITE` audit logs for all services, which includes the Cloud SQL Admin API. **Try it** 1. Confirm the instance has no public address and SSL is enforced: ```bash gcloud sql instances describe cloudsql--postgres \ --format="yaml(ipAddresses, settings.ipConfiguration.sslMode, settings.ipConfiguration.ipv4Enabled)" ``` 2. In **Console > Cloud Run > \ > Revisions > Volumes**, find the `cloudsql` volume bound to the instance connection name. On GKE, `kubectl get pod -n -o jsonpath='{.items[0].spec.containers[*].name}'` lists the `cloud-sql-proxy` sidecar. 3. Connect the way an operator would — fetch the root password from Secret Manager and use psql through a Cloud SQL Auth Proxy (the instance is private-IP, so run this from a VM/workstation with VPC access, or inside a GKE pod): ```bash export PGPASSWORD=$(gcloud secrets versions access latest \ --secret=secret-cloudsql--postgres-root-password) ./cloud-sql-proxy --private-ip ::cloudsql--postgres & psql -h 127.0.0.1 -U postgres -d postgres -c "SELECT version();" ``` 4. You know it worked when psql returns the PostgreSQL 17 version string and the describe output showed `ipv4Enabled: false` with `sslMode: ENCRYPTED_ONLY`. **Check yourself**
Q1: An application running outside the VPC (a partner data center) must reach this Cloud SQL instance. The instance is private-IP only. What are the legitimate options? A: Either extend private connectivity (Cloud VPN/Interconnect into the VPC, since PSA-peered ranges are reachable through the VPC with custom route export — which this platform already enables on the peering), or run the Cloud SQL Auth Proxy somewhere with VPC reachability and let the partner connect to it. Enabling public IP plus authorized networks is possible but contradicts the security posture; the exam favors keeping private IP and fixing the network path.
Q2: Why does the GKE module deploy an Auth Proxy sidecar when the database is already on a private IP it could dial directly? A: The proxy adds IAM-checked, certificate-based TLS without managing client certificates: every connection is authorized against the pod's (Workload Identity) service account and encrypted end-to-end regardless of driver settings. Direct private-IP connections work, but the proxy gives uniform encryption + IAM enforcement + connection name stability across failovers — the Google-recommended pattern the exam expects.
**Beyond the modules** — **Session poolers are not implemented.** Cloud SQL's built-in *Managed Connection Pooling* and the PgBouncer-in-the-middle pattern are exam topics — study "Managed connection pooling" in the Cloud SQL docs, and know when a pooler (thousands of short-lived serverless connections) beats raising `max_connections`. Per-service data-access *audit policies* narrower than this module's allServices switch, and Private Service Connect endpoints for Cloud SQL (as opposed to PSA peering), are also worth a docs pass. **⚠️ Exam trap** — The Cloud SQL Auth Proxy *authenticates the connection*; it does **not** log the user into the database. You still need either a database password or IAM database authentication (Section 2.1) for the login itself. --- ## 1.4 Evaluate appropriate database solutions on Google Cloud > ⏱ ~75 min · 💰 moderate-to-high while multi-engine and alloydb-ai profiles are up — tear down after · ⚙️ Requires: multi-engine + alloydb-ai profiles **Why the exam cares** — Solution-evaluation questions give you workload adjectives — relational, global, wide-column, document, cache, vector/semantic search, analytical — plus constraints (lift-and-shift compatibility, licensing, ops headcount, compliance) and ask which product fits. The discriminators to internalize: compatibility (Cloud SQL/AlloyDB run real PostgreSQL/MySQL), horizontal write scale (Spanner/Bigtable), document model (Firestore), sub-millisecond cache (Memorystore), analytics (BigQuery), and managed-vs-self-managed cost of ownership. **How RAD implements it** — the platform lets you stand four genuinely different engines side by side, plus one self-managed contrast: | Engine | Toggle (default) | What to study on it | |---|---|---| | Cloud SQL PostgreSQL 17 | `create_postgres` (`true`) | Managed relational default; structured/transactional | | Cloud SQL MySQL 8.4 | `create_mysql` (`false`) | Engine choice driven by app compatibility (e.g. WordPress-class apps) | | AlloyDB for PostgreSQL | `enable_alloydb` (`false`) | PostgreSQL-compatible, built for mixed OLTP/analytics and AI — it provides a columnar engine and pgvector-with-ScaNN support; read pool via `enable_alloydb_read_pool` | | Firestore Enterprise | `create_firestore` (`false`) | Document/semi-structured NoSQL; the platform creates a named Firestore Native database in the Enterprise edition, then enables MongoDB-compatible data access via the REST API — MongoDB wire compatibility for lift-and-shift document apps | | Memorystore Redis | `create_redis` (`false`) | In-memory cache/session store; tier and persistence tradeoffs | | Self-managed Redis + NFS VM | `create_network_filesystem` (`true`) | Redis runs on an e2-small managed instance group you patch, snapshot, and health-check yourself — the "unmanaged" half of the managed-vs-unmanaged comparison | For the generative-AI angle: AlloyDB is the module's designated vector platform, and on Cloud SQL the application modules can install PostgreSQL extensions (including `vector`) through the extensions job that installs them (`CREATE EXTENSION` as the postgres user — see Section 2.5). Regulatory levers that influence engine *configuration* are also here: `enable_cmek`, `enable_audit_logging`, and `enable_vpc_sc` apply uniformly to whichever engines you enable. Note the platform even encodes a real-world multi-engine ops detail: a 120-second delay between creating the two Cloud SQL instances to avoid Service Networking conflicts. **Try it** 1. Apply the multi-engine profile, then inventory what one project now runs: ```bash gcloud sql instances list --format="table(name, databaseVersion, region)" gcloud redis instances list --region=us-central1 gcloud firestore databases list --format="table(name, type, locationId)" ``` 2. Apply the alloydb-ai profile and inspect the cluster: ```bash gcloud alloydb clusters describe alloydb--cluster --region=us-central1 gcloud alloydb instances list --cluster=alloydb--cluster \ --region=us-central1 --format="table(name, instanceType, machineConfig.cpuCount)" ``` 3. In **Console > Firestore > Databases**, open the named database (Enterprise edition does not support `(default)` — the module generates `firestore--db` when `firestore_database_id` is empty) and note the MongoDB compatibility setting. 4. You know it worked when the AlloyDB list shows a `PRIMARY` and a `READ_POOL` instance and Firestore shows edition Enterprise in the chosen location. **Check yourself**
Q1: A team is migrating a MongoDB application to Google Cloud and wants a managed service without rewriting the data access layer. Which option demonstrated by this platform fits, and what is its limitation? A: Firestore Enterprise with MongoDB-compatible data access (exactly what the platform provisions). It speaks the MongoDB wire protocol against a fully managed backend. Limitations: it must be a *named* database (no `(default)`), and compatibility covers the common driver surface, not every MongoDB feature — verify feature parity before committing, which is itself an exam-style answer.
Q2: When would you pick AlloyDB over Cloud SQL for PostgreSQL, given both are PostgreSQL-compatible and both appear in this module? A: When the workload mixes OLTP with heavy analytical reads or vector search: AlloyDB adds a columnar engine, ScaNN-indexed pgvector, scale-out read pools (1–20 nodes here), and higher per-instance performance — at a higher floor cost (minimum 2 vCPU, no shared-core tier, as the `alloydb_cpu_count` validation shows). Pure lightweight CRUD on a budget → Cloud SQL; HTAP/AI or aggressive read scaling → AlloyDB.
Q3: The compliance team mandates customer-managed keys and data-access audit trails for every database. Which two variables satisfy this across all engines in the platform, and what org-level concern remains? A: `enable_cmek = true` (CMEK on Cloud SQL and AlloyDB via the shared `cloudsql` KMS key) and `enable_audit_logging = true` (DATA_READ/DATA_WRITE audit logs for all services). Remaining concern: organization policy constraints (e.g. `constraints/gcp.restrictNonCmekServices`, location restrictions) are *not* managed by these modules — they live at the org/folder level and the exam expects you to know they override anything a project-level module does.
**Beyond the modules** — Not implemented, and all examinable: **Spanner** (horizontal write scaling, external consistency, multi-region configs), **Bigtable** (wide-column, time-series, single-digit-ms at scale), **BigQuery** (analytics; also *federated queries* to Cloud SQL — study `EXTERNAL_QUERY()` for the "multiple database solutions / federation" subtopic), **Memorystore for Memcached**, and **Vertex AI Vector Search** for embedding retrieval beyond pgvector. Try in a scratch project: `gcloud spanner instances create test --config=regional-us-central1 --nodes=1 --description=test` and a BigQuery federated query via `bq query --use_legacy_sql=false 'SELECT * FROM EXTERNAL_QUERY("", "SELECT 1;")'`. For decision practice, the "Google Cloud database options" decision tree page is the single highest-value read. **⚠️ Exam trap** — "PostgreSQL-compatible" appears three times in the Google catalog: Cloud SQL (actual PostgreSQL), AlloyDB (PostgreSQL-compatible, Google storage engine), and Spanner's PostgreSQL interface (PostgreSQL *dialect*, not wire-compatible with every driver/extension). Questions that mention existing PostgreSQL extensions or exotic drivers usually eliminate Spanner's PG interface. --- # PCDE Certification Preparation Guide: Section 2 — Manage a solution that can span multiple database technologies (~25% of the exam) PCDE Certification Preparation Guide: Section 2 — Manage a solution that can span multiple database technologies (~25% of the exam) > 📚 **Official exam guide:** [Professional Cloud Database Engineer certification](https://cloud.google.com/learn/certification/cloud-database-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 2 of the Professional Cloud Database Engineer (PCDE) exam: day-2 management — access control, monitoring, backup/recovery, cost/performance tuning, and task automation. It exercises all four foundation modules: `Services_GCP` (instances, IAM auth, alert policies), `App_CloudRun` and `App_GKE` (database users, export schedulers, rotation jobs), and the `App_Common` submodules and scripts that implement them. Deploy the **relational-baseline** and **app-dataops** profiles from the [PCDE Lab Map](PCDE_Certification_Guide.md) before starting; 2.2 also benefits from the **ha-production** profile's notification settings. --- ## 2.1 Determine database connectivity and access management considerations > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: relational-baseline profile; set `enable_cloudsql_iam_auth = true` on Services_GCP **Why the exam cares** — The exam separates two layers it loves to conflate in distractors: **IAM** controls who may *reach and administer* the instance (`roles/cloudsql.client`, `roles/cloudsql.instanceUser`, `roles/cloudsql.admin`), while **database users** control what happens *inside* the engine (GRANTs, ownership). IAM database authentication bridges them — short-lived OAuth tokens instead of passwords — and you must know its setup steps and limits. **How RAD implements it** — | Mechanism | Where | Detail | |---|---|---| | IAM database authentication | Services_GCP | `enable_cloudsql_iam_auth` (default `false`) adds the IAM-auth database flag to the PostgreSQL primary *and* replicas and the MySQL primary, and grants `roles/cloudsql.instanceUser` to the Cloud Run and Cloud Build service accounts | | Built-in users | Services_GCP | Root password is a 16-char random password, plus an explicit database user resource (`root`@`%` for MySQL — the explicit resource is needed for the MySQL grant model), stored only in Secret Manager (`secret--root-password`) | | Application users | the db-init job in App_CloudRun / App_GKE | Creates a per-app database user idempotently, creates the database with that user as owner, and applies `GRANT ALL PRIVILEGES ON DATABASE` / `GRANT ALL ON SCHEMA public` (PostgreSQL) or `CREATE USER ''@'%'` + grants (MySQL) | | App credential | `App_CloudRun`/`App_GKE` `database_password_length` (default `32`, validated 16–64) | Generated password stored as `secret--`; injected via secret references only | | Secret access IAM | App_Common | Per-secret `roles/secretmanager.secretAccessor` to the runtime SA — least privilege, no project-wide secret access | Note the engine nuance: the flag *name* differs per engine — PostgreSQL uses `cloudsql.iam_authentication` (dot) while MySQL uses `cloudsql_iam_authentication` (underscore), and the platform sets the underscore form for MySQL. A nice exam-trap nuance: the same feature, two different flag spellings. **Try it** 1. Set `enable_cloudsql_iam_auth = true` in the portal and apply. Verify the flag and grants: ```bash gcloud sql instances describe cloudsql--postgres \ --format="value(settings.databaseFlags)" gcloud projects get-iam-policy \ --flatten="bindings[].members" \ --filter="bindings.role:roles/cloudsql.instanceUser" \ --format="value(bindings.members)" ``` 2. Add an IAM database user (the flag alone does not create one — a deliberate two-step the exam tests): ```bash gcloud sql users create cloudrun-sa-@.iam.gserviceaccount.com \ --instance=cloudsql--postgres --type=cloud_iam_service_account gcloud sql users list --instance=cloudsql--postgres \ --format="table(name, type)" ``` 3. In **Console > SQL > \ > Users**, observe the built-in `postgres`/`root` user, the application user created by the `db-init` job, and your new `CLOUD_IAM_SERVICE_ACCOUNT` user. 4. You know it worked when `gcloud sql users list` shows the IAM principal with type `CLOUD_IAM_SERVICE_ACCOUNT` and the flags output contains `cloudsql.iam_authentication=on`. **Check yourself**
Q1: After enabling enable_cloudsql_iam_auth, a service account still cannot log in with an IAM token. The flag is on and roles/cloudsql.instanceUser is granted. What is missing? A: The database user itself. IAM authentication requires three things: the instance flag, the IAM role, *and* a database user of type `CLOUD_IAM_SERVICE_ACCOUNT` (or `CLOUD_IAM_USER`) created on the instance — plus in-database GRANTs on the objects it needs. The module automates the first two; the user creation is the step candidates forget.
Q2: Why does the platform generate a separate 32-character application user per service instead of letting applications connect as root? A: Least privilege and blast-radius control: the app user owns only its own database (the `db-init` job grants per-database privileges), its password is scoped to one secret with per-secret `secretAccessor` IAM, and it can be rotated (2.5) without touching other tenants. Root credentials in app code is a standard wrong-answer pattern on the exam.
**Beyond the modules** — IAM **group** authentication for Cloud SQL is not exercised here; read "IAM authentication" for both engines. Also study `gcloud sql generate-login-token` and the Auth Proxy `--auto-iam-authn` flag, which together replace passwords entirely. **⚠️ Exam trap** — `roles/cloudsql.client` lets a principal *connect through* the proxy/connector; `roles/cloudsql.instanceUser` is what IAM *login* requires. Distractors swap them. --- ## 2.2 Configure database monitoring and troubleshooting options > ⏱ ~45 min · 💰 negligible (alerting/log volume) · ⚙️ Requires: relational-baseline + `configure_email_notification = true`, `notification_alert_emails` set on Services_GCP **Why the exam cares** — You are expected to map symptoms to signals: high CPU → query plans/missing indexes, high memory → working set/connection count, storage growth → autoresize headroom and log retention, lock waits → contention views (`pg_stat_activity`, `INFORMATION_SCHEMA.INNODB_TRX`), and to wire alerting *before* the incident. Questions also cover Query Insights and audit logs as diagnostic sources, and quota exhaustion (connections, storage) as a failure class. **How RAD implements it** — `Services_GCP` creates three database alert policies, all filtered to `resource.type = "cloudsql_database"`: | Policy | Metric | Threshold variable (default) | |---|---|---| | `[prefix] Cloud SQL - High CPU Usage` | `cloudsql.googleapis.com/database/cpu/utilization` | `alert_cpu_threshold` (`80`) | | `[prefix] Cloud SQL - High Memory Usage` | `cloudsql.googleapis.com/database/memory/utilization` | `alert_memory_threshold` (`80`) | | `[prefix] Cloud SQL - High Disk Usage` | `cloudsql.googleapis.com/database/disk/utilization` | `alert_disk_threshold` (`80`) | Notifications fan out to email channels built from `configure_email_notification` (default `false`) + `notification_alert_emails`. The application modules add the workload side: `alert_policies` (a list of `{name, metric_type, comparison, threshold_value, duration_seconds, aggregation_period}` objects, default `[]`) in `App_CloudRun`, plus a monitoring dashboard. `uptime_check_config` (default `{ enabled = false, path = "/" }`) creates a `-uptime-check` synthetic probe plus a failure alert policy, once enabled, whenever the application endpoint is publicly reachable — symptom-based monitoring of the database-backed service from outside. On the database itself, `enable_query_insights` (Services_GCP, default `false`) adds an `insights_config` block (query strings recorded up to 1024 chars) to both the PostgreSQL and MySQL primaries, lighting up **Console > SQL > Query insights** with per-query load and plans. For audit-trail troubleshooting, `enable_audit_logging` (Services_GCP, default `false`) records ADMIN_READ/DATA_READ/DATA_WRITE. Slow-query capture is *available* through the flag mechanism — the `postgres_database_flags` variable's own example shows `log_min_duration_statement = 1000` — but no slow-query flag is set by default. **Try it** 1. Enable email notification in the portal, apply, then confirm the policies exist: ```bash gcloud alpha monitoring policies list \ --filter='displayName:"Cloud SQL"' --format="table(displayName, enabled)" ``` 2. Add a slow-query flag the way a DBA would, via `postgres_database_flags`: append `{ name = "log_min_duration_statement", value = "1000" }` in the portal and apply (this restarts the instance). Then generate a slow query through psql (`SELECT pg_sleep(2);`) and read it back: ```bash gcloud logging read \ 'resource.type="cloudsql_database" AND logName:"postgres.log" AND textPayload:"duration"' \ --limit=5 --freshness=1h ``` 3. In **Console > SQL > \ > System insights**, correlate CPU, memory, connections, and disk during your test load. Then set `enable_query_insights = true`, apply, and open **Query insights** on the same instance to see per-query load and captured query text. 4. You know it worked when the three alert policies list as enabled, your `pg_sleep` statement appears in the postgres log with its duration, and Query insights starts charting query load. **Check yourself**
Q1: Users report intermittent application timeouts. Cloud SQL CPU is at 30%, memory at 50%, but active connections spike to exactly 200 during incidents. What is happening and what are two fixes demonstrated or discussed in this platform? A: The instance is hitting the `max_connections=200` flag default — a quota/limit problem, not a resource problem; new connections queue or fail. Fixes: raise the flag via `postgres_database_flags` after sizing memory for it, or reduce connection demand with pooling (Cloud SQL managed connection pooling / PgBouncer — a "Beyond the modules" topic from 1.3). Scaling CPU would not help, a classic distractor.
Q2: Which signal tells you an index is missing — and where would you look on a Cloud SQL instance? A: Sustained high CPU and read IOPS with slow specific queries; confirm with Query Insights (per-query load, plans) or `EXPLAIN ANALYZE` showing sequential scans on large tables. In this lab you'd capture candidates via `log_min_duration_statement`, then `EXPLAIN` them in psql. The fix is `CREATE INDEX`, not a bigger tier — the exam rewards diagnosing before resizing.
**Beyond the modules** — Query Insights' *advanced* features (tagged query attribution via SQL commenter, longer retention) and the `gcloud` equivalent (`gcloud sql instances patch --insights-config-query-insights-enabled`) are worth knowing alongside the module's `enable_query_insights` toggle. Locking diagnosis (`pg_locks`, `pg_stat_activity.wait_event`, MySQL `SHOW ENGINE INNODB STATUS`) and Cloud SQL quotas/limits (connections per tier, 64 TB storage cap) are pure-docs topics here. **⚠️ Exam trap** — `database/disk/utilization` alerts at 80% can be a non-event on this platform because `disk_autoresize = true` grows the disk first — but autoresize **cannot** help when you hit the storage *quota* or when growth is caused by unpurged WAL/binlogs from a broken replica. Know the difference between "disk almost full" and "disk growing without bound." --- ## 2.3 Design database backup and recovery solutions > ⏱ ~60 min · 💰 low — backup storage + a small GCS bucket · ⚙️ Requires: relational-baseline + app-dataops profiles **Why the exam cares** — Backup questions are RTO/RPO arithmetic: automated daily backups give an RPO of up to 24 h; PITR (transaction logs) shrinks RPO to seconds within the log-retention window; exports (`pg_dump`/`mysqldump`) are portable but slow (long RTO) and are the only cross-version/cross-product option. Retention is both a compliance and a cost lever. **How RAD implements it** — Three independent layers: *Managed backups + PITR* (Services_GCP): the PostgreSQL primary is fixed to enabled automated backups with point-in-time recovery on, 7 days of transaction-log retention, 7 retained backups (count-based), a 04:00 start time, and the backup location set to the primary region. MySQL keeps 7 daily backups at 04:00 and enables binary logging — the binlog mechanism MySQL PITR relies on — but sets no PITR-specific attribute. AlloyDB gets a weekly automated backup (Sunday 04:00 UTC, a one-hour backup window, quantity-based retention of 7). Redis persistence is opt-in (`redis_persistence_mode`, default `DISABLED`; `RDB` with `redis_rdb_snapshot_period` default `ONE_HOUR`, or `AOF` — STANDARD_HA tier only), but *enforced* for production: a plan-time precondition rejects `DISABLED` persistence on a `STANDARD_HA` instance labeled `environment = "production"`. *Logical exports* (App_CloudRun, App_GKE): a `db-clients` job image (Debian 12 with `postgresql-client-14`–`17` and the MySQL 8.0 client, built by `App_Common`) runs the export script, which picks a *version-matched* `pg_dump`/`mysqldump` and writes `backup-.tar.gz` to the dedicated GCS backup bucket. Scheduling is `backup_schedule` (default `"0 2 * * *"`); bucket retention is `backup_retention_days` (default `7`) via an object lifecycle delete rule. *Imports / restore drills*: `enable_backup_import` (default `false`) runs a one-time `-backup-import` job restoring `backup_file` (default `backup.sql`, formats `sql,tar,gz,tgz,tar.gz,zip,auto`) from `backup_source` — `gcs` (the backup bucket) or `gdrive`. **Try it** 1. List the automated backups Terraform configured, then take an on-demand one: ```bash gcloud sql backups list --instance=cloudsql--postgres gcloud sql backups create --instance=cloudsql--postgres \ --description="pre-change safety backup" ``` 2. Rehearse PITR the safe way — clone to a *new* instance at a timestamp (UTC, within the 7-day log window): ```bash gcloud sql instances clone cloudsql--postgres pitr-drill-1 \ --point-in-time "2026-06-10T03:00:00Z" ``` 3. Trigger the logical export immediately instead of waiting for 02:00 UTC, then verify the artifact: **Console > Cloud Storage > \**. ```bash gcloud scheduler jobs run -backup-schedule --location=us-central1 gcloud storage ls gs:/// ``` 4. You know it worked when the clone instance reaches RUNNABLE with data as of your timestamp and a fresh `backup-.tar.gz` object exists in the bucket. **Check yourself**
Q1: A developer dropped a table at 14:32. The platform's defaults are in place. What is your recovery path and your data loss? A: Use PITR: clone the instance to a new instance at 14:31 (`gcloud sql instances clone --point-in-time`), then copy the table back or repoint the app. Data loss ≈ one minute (whatever you choose to discard), because transaction logs are retained 7 days. Restoring last night's 04:00 backup *without* PITR would lose ~10.5 hours — the distractor answer.
Q2: Compliance requires backups to survive a region-wide disaster. Does the module's configuration satisfy this, and what would you change? A: Not fully: `backup_configuration.location` is set to the primary region, so managed backups live in that region (the GCS export bucket adds a second copy, but also regional by default). To survive region loss you would set a multi-region or different-region backup location, replicate the export bucket (dual/multi-region storage), and/or keep the cross-region read replica from 1.2. The exam expects you to notice backup *location* as part of DR design.
Q3: When is a pg_dump-based export the right recovery/migration tool instead of managed backups? A: When you need portability: restoring into a different major version, a different product (AlloyDB, self-managed PG), another project/org, or keeping long-term archives independent of the instance's lifecycle (managed backups are deleted with the instance). The cost is RTO — logical restore is far slower than backup restore — and consistency is as-of dump start.
**Beyond the modules** — Cross-project backup restore, `gcloud sql export sql` (the *serverless* managed export to GCS, distinct from this module's job-based `pg_dump`), final backups on instance deletion, and Backup and DR Service for long-horizon retention are docs-only topics. Try `gcloud sql export sql cloudsql--postgres gs:///managed-export.sql --database=postgres` in a scratch project and compare it with the job-based export. **⚠️ Exam trap** — Backups ≠ PITR. Retained backups (7 here) bound how far *back* you can restore; transaction-log retention (7 days here) bounds how *precisely*. Also: restoring in place overwrites the instance — clone to a new instance for investigations. --- ## 2.4 Optimize database cost and performance > ⏱ ~45 min · 💰 experiments scale cost up — revert when done · ⚙️ Requires: relational-baseline; ha-production and alloydb-ai for scale-out **Why the exam cares** — "Scale up or scale out?" is the section's signature question: vertical scaling (bigger tier) fixes CPU/memory-bound *write* workloads but has a ceiling and a restart; horizontal read scaling (replicas/read pools) fixes read-heavy fan-out but does nothing for writes and introduces replication lag. Cost questions test right-sizing, committed-use thinking, and knowing which HA/replica choices double spend. **How RAD implements it** — every scaling axis is one variable: | Axis | Variable | Notes | |---|---|---| | Scale up (writes) | `postgres_tier` / `mysql_tier` / `alloydb_cpu_count` | In-place patch; brief restart | | Scale out (reads), Cloud SQL | `create_postgres_read_replica` + `postgres_read_replica_count` (default `1`) | Replica private IPs published as `-host` secrets so apps can route reads; replicas get a fixed `max_connections=30000` flag | | Scale out (reads), AlloyDB | `enable_alloydb_read_pool` + `alloydb_read_pool_node_count` (1–20) | One endpoint load-balanced across nodes — no per-replica routing needed | | Engine tuning | `postgres_database_flags` / `mysql_database_flags` / `alloydb_database_flags` | Defaults: `max_connections=200` (PG), `max_connections=200` + `local_infile=off` (MySQL) | | Cost floor | `ZONAL` default availability, `BASIC` default Redis tier, 10 GB autoresizing disk | The defaults *are* the cost-optimization lesson: HA, replicas, STANDARD_HA Redis, and CMEK are opt-in | The Redis plan-time preconditions are a governance example: deployments labeled `environment = "production"` are blocked from `BASIC` tier at plan time, and a second precondition blocks `redis_persistence_mode = "DISABLED"` on a production `STANDARD_HA` instance — cheap-but-fragile configurations are disallowed exactly where an SLA exists. **Try it** 1. With ha-production applied, raise read capacity without touching the primary: ```bash # portal: postgres_read_replica_count = 2, then verify gcloud sql instances list --filter="name:replica" \ --format="table(name, region, settings.tier, state)" ``` 2. Read the replica endpoint an application would use: ```bash gcloud secrets versions access latest \ --secret=cloudsql--postgres-replica-host ``` 3. Measure replication health before trusting reads — in psql against the **primary**: ```bash psql -h 127.0.0.1 -U postgres -d postgres \ -c "SELECT client_addr, state, replay_lag FROM pg_stat_replication;" ``` 4. You know it worked when both replicas show RUNNABLE and `pg_stat_replication` lists them with small `replay_lag`. **Check yourself**
Q1: An e-commerce database is write-saturated during flash sales (CPU 95%, all from INSERT/UPDATE). The team proposes adding two read replicas. Why is that wrong, and what is right? A: Replicas only serve reads — every write is still replayed on the primary *and* on each replica, so write saturation persists (and replicas may lag). The correct first move is vertical: a larger `postgres_tier`. If writes outgrow the largest tier, that is the exam's cue for re-architecture (sharding or Spanner), not more replicas.
Q2: What recurring cost do you take on per unit when you change postgres_read_replica_count from 1 to 3, and what operational cost comes with it? A: Each replica bills like an instance of the primary's tier (`postgres_tier` is reused in the replica settings) plus its own storage — 3 replicas ≈ 3 extra primaries. Operationally, applications must consume the per-replica `-host` secrets and tolerate asynchronous lag; replicas are not free HA (they are ZONAL and must be promoted manually).
**Beyond the modules** — Query optimization itself (EXPLAIN plans, index design, Query Insights recommendations) has no module surface — practice on the lab instance with `EXPLAIN (ANALYZE, BUFFERS)`. Continuous cost optimization tooling — committed use discounts for Cloud SQL, the Active Assist idle/overprovisioned instance recommenders, per-database billing labels in billing exports — is console/docs work: check **Console > SQL > Recommendations** in any long-lived project. **⚠️ Exam trap** — Changing the tier restarts the instance (downtime ≈ seconds-to-minutes, or a failover on REGIONAL instances). "Resize during the maintenance window with HA enabled" beats "resize whenever" in scenario answers. --- ## 2.5 Automate common database tasks > ⏱ ~60 min · 💰 low — jobs, scheduler, secret versions · ⚙️ Requires: app-dataops profile (`enable_auto_password_rotation = true`) **Why the exam cares** — The exam wants operations expressed as scheduled, auditable automation rather than humans with psql: scheduled exports, credential rotation, post-provision initialization, and SLO-based health monitoring. Knowing *which* GCP primitive schedules what (Cloud Scheduler → Cloud Run jobs; Kubernetes CronJobs; Secret Manager rotation topics → Eventarc) is the testable content. **How RAD implements it** — four automations, all observable in the console: 1. **Scheduled exports.** Cloud Run path: a Cloud Scheduler job (App_CloudRun) POSTs to the Cloud Run Jobs `:run` API with the runtime SA's OAuth token, on `backup_schedule` (default `"0 2 * * *"`), executing the `-db-export` job. GKE path: a Kubernetes CronJob (App_GKE), name `-db-export`, concurrency policy `Forbid`, history limits 3/3, script delivered via ConfigMap. 2. **Automated password rotation.** `enable_auto_password_rotation` (default `false`) wires Secret Manager's `rotation_period` (`secret_rotation_period`, default `"2592000s"` = 30 days) → Pub/Sub rotation topic → Eventarc trigger (`-pw-rot-trigger`) → a dispatcher Cloud Run service (`-rot-dispatch`) → the `-pw-rotator` job (App_Common). The rotator: generates a new password, runs `ALTER USER`, adds the new secret **version**, waits a propagation delay, then *disables* (not destroys) the old version — dual-version, zero-downtime, rollback-capable. 3. **Initialization & schema tasks.** The `db-init` job creates the database and user on every deploy (idempotent); `enable_custom_sql_scripts` + `custom_sql_scripts_bucket`/`custom_sql_scripts_path` runs `.sql` files from GCS in lexicographic order (optionally as root via `custom_sql_scripts_use_root`); PostgreSQL extension and MySQL plugin install jobs install the configured extensions/plugins — note that in a standalone foundation-module deployment the `enable_postgres_extensions`/`postgres_extensions` variables are validation-only, with the actual lists injected by application wrapper modules. 4. **SLO-adjacent monitoring.** `alert_policies` provides availability and latency alerting on the database-backed service, and `uptime_check_config` adds a `-uptime-check` synthetic probe plus failure alert for publicly reachable endpoints — external SLI data with no manual setup. 5. **Scheduled maintenance.** `sql_maintenance_window_day` (1–7, Monday-based, default `7` = Sunday), `sql_maintenance_window_hour` (0–23 UTC, default `3`), and `sql_maintenance_update_track` (`"stable"`/`"canary"`/`"week5"`, default `"stable"`) pin Cloud SQL maintenance to a predictable low-traffic window on both the PostgreSQL and MySQL primaries — patching becomes a declared, scheduled operation instead of Google-chosen timing. **Try it** 1. Inspect the rotation plumbing after applying app-dataops: **Console > Security > Secret Manager > secret-\-\ > Rotation**, and: ```bash gcloud scheduler jobs list --location=us-central1 gcloud run jobs list --region=us-central1 # -db-export, -db-init, -pw-rotator ``` 2. Force a rotation rehearsal by executing the rotator job directly, then confirm the version flip: ```bash gcloud run jobs execute -pw-rotator --region=us-central1 --wait gcloud secrets versions list secret-cloudsql--postgres- \ --format="table(name, state)" ``` 3. Prove the app still authenticates: connect with the *new* latest version via psql (`PGPASSWORD=$(gcloud secrets versions access latest --secret=...) psql -h -U -d -c "SELECT 1;"`). 4. On GKE, run tomorrow's export now: ```bash kubectl create job --from=cronjob/-db-export manual-export -n kubectl logs -n job/manual-export -f ``` 5. You know it worked when the secret shows a new ENABLED version with the prior one DISABLED, and the export job log ends with an upload to the backup bucket. **Check yourself**
Q1: During rotation, why does the platform disable the old secret version only after a propagation delay instead of immediately destroying it? A: Zero-downtime and rollback. Running instances may hold the old password in memory or fetch "latest" mid-rotation; the delay lets the new version propagate before "latest" becomes unambiguous, and disabling (rather than destroying) keeps the old version recoverable for rollback/audit. Destroying immediately risks authentication failures across the fleet — the exam's "what breaks" answer.
Q2: A team needs nightly logical backups of a GKE-hosted database with a guarantee that two exports never run concurrently. Which Kubernetes settings shown in this module deliver that? A: A CronJob with `schedule` (the module's `backup_schedule`) and `concurrencyPolicy: Forbid` — exactly what the module's db-export CronJob sets — plus bounded history (`successful/failedJobsHistoryLimit = 3`) and `restartPolicy: OnFailure` with a backoff limit so a stuck export cannot pile up.
**Beyond the modules** — Two 2.5 topics have no implementation here: **index maintenance** (`REINDEX`/`pg_repack`, MySQL `OPTIMIZE TABLE` — you could schedule them through `enable_custom_sql_scripts`, but nothing ships) and **managed upgrades** — there is no automation for major version upgrades; study in-place upgrades (`gcloud sql instances patch --database-version=POSTGRES_18` once available, plus the pre-upgrade checks) and Cloud SQL maintenance/patching behavior. Formal SLOs (error budgets, `gcloud monitoring` SLO API) also live outside the modules. **⚠️ Exam trap** — Secret Manager's `rotation_period` only *publishes a Pub/Sub notification* — nothing rotates unless something consumes it. This platform's Eventarc→dispatcher→job chain is that consumer; an answer that says "enable rotation on the secret and you're done" is wrong. --- # PCDE Certification Preparation Guide: Section 3 — Migrate data solutions (~23% of the exam) PCDE Certification Preparation Guide: Section 3 — Migrate data solutions (~23% of the exam) > 📚 **Official exam guide:** [Professional Cloud Database Engineer certification](https://cloud.google.com/learn/certification/cloud-database-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 3 of the Professional Cloud Database Engineer (PCDE) exam. Be warned up front: this is the section where the RAD foundation modules cover the *least* ground. The modules implement the export/import (extended-outage) migration path end to end — `App_CloudRun`/`App_GKE` backup-import jobs fed from GCS or Google Drive, driven by the `App_Common` scripts — and read replication inside Google Cloud via `Services_GCP`. Database Migration Service, Datastream, zero-downtime cutovers, and reverse replication are concept-only and carry heavy "Beyond the modules" study lists. Deploy the **relational-baseline** and **app-dataops** profiles from the [PCDE Lab Map](PCDE_Certification_Guide.md) before starting. --- ## 3.1 Design and implement data migration and replication > ⏱ ~90 min (half hands-on, half reading) · 💰 low — one import job + GCS staging · ⚙️ Requires: app-dataops profile with `enable_backup_import = true` **Why the exam cares** — Migration questions are downtime-budget questions. The decision tree the exam tests: **extended outage acceptable** → one-time export/import (dump file through GCS); **near-zero downtime** → continuous replication (Database Migration Service for homogeneous moves into Cloud SQL/AlloyDB, Datastream/CDC for heterogeneous) with a short cutover; **fallback required** → reverse replication from the new primary back to the source so you can return if the cutover fails. Heterogeneous moves add DDL/DML conversion (schema/type/dialect translation) on top. You must pick the tool *and* sequence the cutover: stop writes → drain replication lag → switch connection strings → verify → (optionally) reverse-replicate. **How RAD implements it** — the extended-outage path is real and runnable: | Step of a lift-and-shift | Module implementation | |---|---| | Export from source | the export script (App_Common) — version-matched `pg_dump`/`mysqldump` producing `backup-.tar.gz`; for an external source you run the equivalent dump yourself | | Stage the artifact | The module-provisioned GCS backup bucket (lifecycle-managed by `backup_retention_days`, default `7`), or Google Drive | | Import into Cloud SQL | `enable_backup_import = true` (default `false`) runs a one-time `-backup-import` job selected by `backup_source` (`gcs`/`gdrive`, default `gcs`), restoring `backup_file` (default `backup.sql`) with `backup_format` (`sql,tar,gz,tgz,tar.gz,zip,auto`) through the Cloud SQL connector volume | | Post-import fix-ups (DDL/DML adjustments) | `enable_custom_sql_scripts` + `custom_sql_scripts_bucket`/`custom_sql_scripts_path` (+ `custom_sql_scripts_use_root` for privileged DDL) runs `.sql` files in lexicographic order — the place to apply converted schema objects, recreate sequences, or fix collations | | Replication (inside GCP) | `create_postgres_read_replica` / `create_mysql_read_replica` in `Services_GCP` — Cloud SQL native async replication, the same mechanism a DMS migration uses for its destination sync, observable end to end | What the modules deliberately do **not** do: connect to an *external* source, run change data capture, or orchestrate a cutover. There is no DMS, Datastream, or external-server replication configuration anywhere in the four modules. **Try it** 1. Simulate a source database: connect to the lab instance (Auth Proxy + psql as in Section 1.3), create a table with rows, and dump it — or simply let the scheduled export from Section 2.5 produce one. Stage your own file explicitly: ```bash pg_dump -h 127.0.0.1 -U postgres -d postgres -f /tmp/source-dump.sql gcloud storage cp /tmp/source-dump.sql gs:///source-dump.sql ``` 2. In the portal set `enable_backup_import = true`, `backup_source = "gcs"`, `backup_file = "source-dump.sql"`, `backup_format = "sql"`, and apply. Watch the import job: ```bash gcloud run jobs executions list --job=-backup-import --region=us-central1 gcloud logging read 'resource.type="cloud_run_job" AND resource.labels.job_name="-backup-import"' \ --limit=50 --freshness=1h ``` 3. Verify row counts match the source (the migration engineer's first validation): ```bash psql -h 127.0.0.1 -U -d -c "SELECT count(*) FROM ;" ``` 4. For the replication half, apply ha-production (Section 1.2) and watch `cloudsql--postgres-replica` seed and catch up — `gcloud sql instances describe cloudsql--postgres-replica --format="value(state, replicaConfiguration)"`. This is the same replica machinery DMS drives during a continuous migration. 5. You know it worked when the import execution succeeds and the destination row counts equal the source's. **Check yourself**
Q1: A 2 TB on-premises PostgreSQL 14 database must move to Cloud SQL with under 5 minutes of downtime. Is the platform's import-job pattern appropriate? What is? A: No — a dump/restore of 2 TB takes hours, all of it downtime (the pattern is right only when an extended outage is acceptable). Use Database Migration Service: initial snapshot plus continuous CDC replication from the source, let lag drain while the source stays live, then a minutes-long cutover. DMS homogeneous migrations to Cloud SQL are free, which the exam likes to mention.
Q2: After cutover to Cloud SQL, the business demands a fallback path for two weeks. What is the mechanism, and what must remain true at the source? A: Reverse replication: replicate changes from the new Cloud SQL primary back to the old source (DMS supports configuring the old source as a replica of the migrated instance for PostgreSQL/MySQL, or you maintain logical replication yourself), so the application can be repointed back without data loss. The source must remain schema-compatible and reachable, and no writes may go to it directly during the fallback window — otherwise the two diverge.
Q3: An Oracle-to-PostgreSQL migration stalls because of incompatible PL/SQL and data types. Which class of work is this, and which tools address it? A: DDL/DML conversion — heterogeneous migrations need schema and code translation, not just data movement. Tools: DMS's Oracle-to-PostgreSQL conversion workspaces (Ora2Pg-based), manual rewrite of stored procedures, plus type-mapping decisions (NUMBER → numeric, DATE → timestamp). In this platform the converted DDL would be applied through the custom-SQL-scripts job; the conversion itself is always engineering work the exam expects you to schedule before data sync.
**Beyond the modules** — Most of Section 3 lives here; budget real study time: - **Database Migration Service (DMS)**: connection profiles, migration jobs, homogeneous (MySQL/PostgreSQL → Cloud SQL/AlloyDB, free) vs heterogeneous (Oracle/SQL Server → PostgreSQL, conversion workspaces). In a scratch project walk through `gcloud database-migration connection-profiles create postgresql ...` and `gcloud database-migration migration-jobs create ... --type=CONTINUOUS`, even if only to the validation step — the *verify* phase (`gcloud database-migration migration-jobs verify`) is exam-favored. - **Datastream** for CDC into BigQuery/GCS when the target is analytics rather than a like-for-like database. - **Cloud SQL external server replication** ("Replicating from an external server" docs): the pre-DMS pattern of making a Cloud SQL instance a replica of an external primary; promotion = cutover (`gcloud sql instances promote-replica`). - **Zero-downtime sequencing**: dual-write pitfalls, draining lag before cutover, connection-string switching via Secret Manager (this platform's secret-driven `DB_HOST`/host secrets show exactly where you would flip it). - **Validation tooling**: the open-source Data Validation Tool (DVT) for row/aggregate comparison between source and target. **⚠️ Exam trap** — "Use the import job / `gcloud sql import sql` for the migration" is the trap answer whenever the scenario states a downtime budget in minutes. Conversely, "set up DMS" is the trap when the scenario says a weekend outage is fine and the database is small — one-time export/import is simpler, cheaper, and exactly what this platform automates. --- # PCDE Certification Preparation Guide: Section 4 — Deploy scalable and highly available databases in Google Cloud (~20% of the exam) PCDE Certification Preparation Guide: Section 4 — Deploy scalable and highly available databases in Google Cloud (~20% of the exam) > 📚 **Official exam guide:** [Professional Cloud Database Engineer certification](https://cloud.google.com/learn/certification/cloud-database-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 4 of the Professional Cloud Database Engineer (PCDE) exam — and it is the section where this repository *is* the answer key. "Automate database instance provisioning" is literally what `Services_GCP` does: every Cloud SQL, AlloyDB, Redis, and Firestore deployment in the platform is declarative infrastructure-as-code applied through your deployment portal or Cloud Build. The HA, replica, and monitoring machinery comes from `Services_GCP`; application failover behavior is observed through `App_CloudRun`/`App_GKE`. Deploy the **ha-production** profile from the [PCDE Lab Map](PCDE_Certification_Guide.md) before starting. --- ## 4.1 Apply concepts to implement scalable and highly available databases in Google Cloud > ⏱ ~90 min · 💰 moderate-to-high while ha-production is applied (REGIONAL ≈ 2× instance cost; each replica ≈ +1 instance) — revert to ZONAL/no-replica afterwards · ⚙️ Requires: ha-production profile **Why the exam cares** — Section 1 asked you to *choose* an HA design; Section 4 asks you to *implement and prove* it: provision the HA topology, deploy and scale read replicas, replicate across regions, verify failover actually works (an untested DR plan is the exam's favorite anti-pattern), automate provisioning so environments are reproducible, and monitor the HA signals (replication lag, failover events, instance health) rather than just CPU. **How RAD implements it** — *Provisioning HA, declaratively.* The entire topology is variables on `Services_GCP`: | Capability | Variable (default) | Resulting resource | |---|---|---| | HA primary | `postgres_database_availability_type` (`ZONAL`) → `REGIONAL` | A Cloud SQL primary instance with synchronous standby + automatic failover (same for `mysql_database_availability_type`) | | Read replicas | `create_postgres_read_replica` (`false`), `postgres_read_replica_count` (`1`) | A Cloud SQL read replica instance (type `READ_REPLICA_INSTANCE`), always ZONAL | | Cross-region placement | `availability_regions` (`["us-central1"]`) | With ≥2 regions, replicas land in the second region; otherwise they stay in the primary region | | Read-pool scale-out | `enable_alloydb_read_pool` (`false`), `alloydb_read_pool_node_count` (`1`, 1–20) | An AlloyDB read-pool instance (type `READ_POOL`) | | Cache HA | `redis_tier` (`BASIC`) → `STANDARD_HA` | Memorystore with automatic failover replica; the platform *blocks* BASIC at plan time when `resource_labels.environment = "production"` | *Automated provisioning.* This is infrastructure-as-code end to end: `tofu init → plan → apply` (run by the platform's create/update pipeline in CI), idempotent re-application, dependency sequencing (instances are gated on the Service Networking connection; a 120 s delay separates the two Cloud SQL instances), and discovery-not-duplication in the app layer (App_Common finds the platform instance by its `managed-by = services-gcp` label; `App_CloudRun` provisions an equivalent inline ZONAL PostgreSQL 17 instance only when none exists). Replica lifecycle is also codified: replicas are rebuilt if the primary is replaced. *Monitoring for HA databases.* The platform ships CPU/memory/disk alert policies on `resource.type = "cloudsql_database"` wired to email channels (`configure_email_notification`, `notification_alert_emails`); each replica additionally publishes its endpoint as a `-host` secret so consumers fail over reads deliberately. Application-side, `uptime_check_config` (default `{ enabled = false, path = "/" }`) creates a `-uptime-check` synthetic probe plus failure alert once enabled whenever the application endpoint is publicly reachable — ready-made detection of user-visible impact during failover tests (internal-only deployments get none). **Try it** 1. Apply ha-production and map the fleet: ```bash gcloud sql instances list \ --format="table(name, region, gceZone, settings.availabilityType, instanceType, state)" ``` Expect the primary as `REGIONAL` in us-central1 and the replica as `READ_REPLICA_INSTANCE` in us-east1. 2. **Test HA** — note the current zone, force a failover, and time it: ```bash gcloud sql instances describe cloudsql--postgres --format="value(gceZone)" gcloud sql instances failover cloudsql--postgres gcloud sql operations list --instance=cloudsql--postgres --limit=3 ``` While it runs, hit the application URL (or watch the module-created `-uptime-check` in **Console > Monitoring > Uptime checks**) to observe the brief connection blip — the Cloud SQL connector reconnects to the same connection name without any configuration change. 3. **Scale reads** — raise `postgres_read_replica_count` to `2` in the portal, apply, and confirm the new replica appears in the secondary region; then check replication health from the primary via psql: `SELECT client_addr, state, replay_lag FROM pg_stat_replication;`. 4. **Test DR promotion** (destructive to the replica's replica-status — do it on the second, disposable replica): ```bash gcloud sql instances promote-replica cloudsql--postgres-replica-1 gcloud sql instances describe cloudsql--postgres-replica-1 \ --format="value(instanceType, settings.availabilityType)" ``` Note what Terraform now thinks: the promoted instance has drifted from the declared state, and the next `tofu plan` will want to reconcile it — promotion is a break-glass action, not a managed workflow in these modules. 5. **Prove reproducibility** — the automation claim of this section: re-run a plan over the unchanged deployment from your deployment portal (the platform runs `tofu plan` for you) and review the proposed changes. You know it worked when the failover operation completes with the primary in a new zone, the promoted instance reports `CLOUD_SQL_INSTANCE` (no longer a replica), and a fresh `tofu plan` over the *unmodified* configuration shows no unexpected changes (idempotence) — while the post-promotion plan visibly flags the drift. **Check yourself**
Q1: During a failover test of the REGIONAL instance, the application reconnected automatically without any configuration change. Why — and which connection pattern from this platform made that possible? A: Cloud SQL HA failover keeps the instance's identity: the connection name and private IP move to the promoted standby. Because the app connects through the Cloud SQL connector volume (Cloud Run) or Auth Proxy sidecar (GKE) addressed by *connection name*, and reads credentials from Secret Manager, nothing client-side referenced the failed zone. Hardcoded zonal IPs are the anti-pattern this design avoids.
Q2: A scenario requires read traffic served in two regions and a documented region-loss runbook. Which variables build the topology, and which two steps remain manual? A: `availability_regions = ["us-central1", "us-east1"]`, `postgres_database_availability_type = "REGIONAL"`, `create_postgres_read_replica = true`, `postgres_read_replica_count ≥ 1` — replicas are placed in the secondary region and their endpoints published as secrets. Manual in a disaster: promoting the replica (`gcloud sql instances promote-replica`) and repointing applications to the promoted endpoint (e.g. updating the host secret). Cross-region failover is never automatic for Cloud SQL — a recurring exam point.
Q3: Why is Terraform-based provisioning itself an HA control, not just a convenience? A: Reproducibility is recoverability: the entire database estate (instances, flags, networks, secrets, alerting) can be re-created in another project or region from code with `tofu apply`, and configuration drift is detected by `tofu plan`. Manual console-built instances cannot be rebuilt reliably under incident pressure. The exam frames this as "automate instance provisioning" — IaC plus idempotent re-application is the expected answer.
**Beyond the modules** — Three gaps to study: (1) **managed cross-region promotion workflows** — the modules build the replica but have no promotion/runbook automation; read "Promoting replicas" and "Cross-region replicas for disaster recovery" in the Cloud SQL docs, including how to re-establish replication after promotion; (2) **multi-region write systems** — Spanner multi-region instance configurations and AlloyDB secondary clusters (`gcloud alloydb clusters create-secondary`) with switchover/failover semantics; (3) **replication-lag alerting** — the module alerts on CPU/memory/disk but not on `cloudsql.googleapis.com/database/replication/replica_lag`; practice adding that alert in **Console > Monitoring > Alerting** or via `gcloud alpha monitoring policies create` in a scratch project, since lag is *the* HA health signal for read-replica topologies. **⚠️ Exam trap** — `gcloud sql instances failover` works only on REGIONAL (HA) instances — running it against a ZONAL instance fails because there is no standby. And promotion is one-way: a promoted replica is a standalone primary; to get a replica back you create a new one and reseed. Distractors that "fail back by demoting" the promoted instance are wrong for Cloud SQL. --- # Professional Cloud Network Engineer (PCNE) Certification Lab Map > 📚 **Official exam guide:** [Professional Cloud Network Engineer certification](https://cloud.google.com/learn/certification/cloud-network-engineer) — always confirm section weightings against the current Google Cloud exam guide. The Professional Cloud Network Engineer certification validates the ability to design, implement, and operate Google Cloud VPC networks, hybrid connectivity, network services (load balancing, CDN, DNS), and network security. The RAD platform's four foundation modules — `Services_GCP` (custom-mode VPC, subnets, firewall rules, Cloud NAT, private services access, GKE VPC-native clusters), `App_CloudRun` (Direct VPC egress, serverless NEGs, global external Application Load Balancer, Cloud Armor, Cloud CDN), `App_GKE` (Gateway API, Kubernetes NetworkPolicy, GCPBackendPolicy), and `App_Common` (network discovery, VPC-SC) — give you a live, modifiable lab for roughly half of this exam. The other half (Interconnect, VPN, BGP, Network Connectivity Center, Cloud DNS, NGFW, Network Intelligence Center) is deliberately *not* implemented by the modules; this guide is honest about those gaps and tells you exactly what to study outside the platform. Expect to lean on the "Beyond the modules" blocks more heavily here than in any other RAD certification guide. ## How to use this guide - Deploy one of the profiles below through your deployment portal, then work through the matching section exploration guide while the infrastructure is live. - Use the coverage legend to plan your study time: ✅ topics can be learned hands-on in RAD; 📘 topics need official docs and a scratch project. - The PCNE exam is scenario-heavy. After each "Try it", ask yourself *why* the modules made each choice (e.g., why a /16 PSA range, why Dataplane V2, why a global external ALB). **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: VPC Foundation *Purpose:* Custom-mode VPC, subnets, firewall rules, Cloud Router + Cloud NAT, and private services access — the core of Sections 1, 2, and 6.3. *Modules:* `Services_GCP`, then `App_CloudRun` on top. | Variable | Value | |---|---| | `availability_regions` | `["us-central1"]` (default) | | `subnet_cidr_range` | `["10.0.0.0/24"]` (default) | | `create_postgres` | `true` (default — forces the PSA peering to be exercised) | | `create_network_filesystem` | `true` (default — tag-based firewall rules + TCP health checks) | | `vpc_egress_setting` (App_CloudRun) | `PRIVATE_RANGES_ONLY` (default) | *Estimated incremental cost:* Low — a `db-custom-1-3840` Cloud SQL instance and one e2-small NFS VM dominate; the VPC, NAT gateway, and firewall rules are cents per day. ### Profile: GKE Network Lab *Purpose:* VPC-native cluster with named secondary ranges, Dataplane V2, Kubernetes NetworkPolicy, and Workload Identity — Sections 1.4, 2.4, and 6.2. *Modules:* `Services_GCP` (with GKE), then `App_GKE`. | Variable | Value | |---|---| | `create_google_kubernetes_engine` (Services_GCP) | `true` | | `gke_cluster_mode` | `AUTOPILOT` (default) | | `gke_cluster_count` | `1` (set `2` + `configure_cloud_service_mesh = true` for the multi-cluster east-west firewall variant) | | `gke_subnet_base_cidr` / `gke_pod_base_cidr` / `gke_service_base_cidr` | defaults `10.128.0.0/12` / `10.64.0.0/10` / `10.8.0.0/16` | | `enable_network_segmentation` (App_GKE) | `true` | | `service_type` (App_GKE) | `LoadBalancer` (default) | *Estimated incremental cost:* Moderate — Autopilot bills per pod resource request; a second cluster plus Cloud Service Mesh roughly doubles it. ### Profile: Global Edge *Purpose:* Global external Application Load Balancer, serverless NEG, Cloud Armor WAF, Cloud CDN, Certificate Manager, static global IPs — Sections 3.1, 3.2, 6.1. *Modules:* `App_CloudRun` (and/or `App_GKE` for the Gateway API equivalent). | Variable | Value | |---|---| | `enable_cloud_armor` | `true` | | `application_domains` | `["app.example.com"]` (required by App_CloudRun validation when Cloud Armor is on) | | `enable_cdn` | `true` | | `admin_ip_ranges` | your office/VPN CIDRs (priority-100 WAF allowlist on both engines; also a VPC-SC access level on App_CloudRun) | | `enable_custom_domain` (App_GKE) | `true` | | `reserve_static_ip` (App_GKE) | `true` (default) | *Estimated incremental cost:* Moderate — forwarding-rule hours, Cloud Armor policy + per-request charges, and CDN cache egress are the drivers. ### Profile: Locked-Down Perimeter *Purpose:* VPC Service Controls perimeter, restricted Google API egress, all-traffic VPC egress — Sections 2.1 and 6.2/6.3 defense-in-depth. *Modules:* `Services_GCP` or either app module (all carry the VPC-SC variables). | Variable | Value | |---|---| | `enable_vpc_sc` | `true` | | `admin_ip_ranges` | non-empty (required, or VPC-SC is skipped with a warning) | | `vpc_sc_dry_run` | `true` (default — audit before enforcing) | | `vpc_egress_setting` (App_CloudRun) | `ALL_TRAFFIC` | | `enable_network_segmentation` (App_GKE) | `true` (includes the `restricted.googleapis.com` 199.36.153.4/30 egress rule) | *Estimated incremental cost:* Low — VPC-SC and firewall/NetworkPolicy changes are free; the cost is operational (requires an organization and org-level Access Context Manager permission). ## Section 1: Designing and planning a Google Cloud VPC network (~21% of the exam) The modules demonstrate a complete single-VPC design: custom subnet mode, deterministic CIDR planning, private services access for managed databases, and GKE secondary-range sizing. Network tiers, Shared VPC, hybrid design, and DNS topology are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Designing an overall network architecture | 🟡 | `enable_cloud_armor` LB choice, PSA peering, Direct VPC egress; tiers/DNS/quotas 📘 | [Section 1 guide](PCNE_Section_1_Exploration_Guide.md#11-designing-an-overall-network-architecture) | | 1.2 Designing VPC networks | 🟡 | custom-mode VPC, `subnet_cidr_range`, PSA /16; Shared VPC/NCC/PSC/IPv6/MTU 📘 | [Section 1 guide](PCNE_Section_1_Exploration_Guide.md#12-designing-vpc-networks) | | 1.3 Designing a resilient and performant hybrid and multi-cloud network | 📘 | Not implemented (Cloud Router exists only as a NAT anchor) | [Section 1 guide](PCNE_Section_1_Exploration_Guide.md#13-designing-a-resilient-and-performant-hybrid-and-multi-cloud-network) | | 1.4 Designing for Google Kubernetes Engine | ✅ | deterministically computed secondary ranges, Autopilot/Standard, public endpoint | [Section 1 guide](PCNE_Section_1_Exploration_Guide.md#14-designing-for-google-kubernetes-engine) | ## Section 2: Implementing a VPC network (~20% of the exam) This is the strongest section for hands-on work: every deployment creates (or discovers) a VPC, subnets, firewall rules, a PSA peering with custom-route exchange, and a VPC-SC perimeter if enabled. Shared VPC, policy-based routing, and NCC are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Configuring VPCs | ✅ | VPC/subnets/firewall, PSA range, VPC-SC perimeters; Shared VPC 📘 | [Section 2 guide](PCNE_Section_2_Exploration_Guide.md#21-configuring-vpcs) | | 2.2 Configuring VPC routing | 🟡 | Cloud Router (NAT-only, ASN 64514), peering route import/export; policy-based routing/ILB next hop 📘 | [Section 2 guide](PCNE_Section_2_Exploration_Guide.md#22-configuring-vpc-routing) | | 2.3 Configuring Network Connectivity Center | 📘 | Not implemented | [Section 2 guide](PCNE_Section_2_Exploration_Guide.md#23-configuring-network-connectivity-center) | | 2.4 Configuring and maintaining GKE clusters | ✅ | VPC-native + Dataplane V2, Kubernetes NetworkPolicy; private clusters / Cloud DNS for GKE 📘 | [Section 2 guide](PCNE_Section_2_Exploration_Guide.md#24-configuring-and-maintaining-google-kubernetes-engine-clusters) | ## Section 3: Configuring managed network services (~16% of the exam) Both deployment engines build a global external Application Load Balancer — one from Terraform LB primitives (Cloud Run), one from the GKE Gateway API. Cloud DNS is not implemented at all. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Configuring load balancing | ✅ | Cloud Run serverless NEG chain; GKE Gateway API; ALB traffic management 📘 | [Section 3 guide](PCNE_Section_3_Exploration_Guide.md#31-configuring-load-balancing) | | 3.2 Configuring Cloud CDN | 🟡 | `enable_cdn` on the Cloud Run backend service (real); App_GKE flag provisions the Gateway but does not enable CDN | [Section 3 guide](PCNE_Section_3_Exploration_Guide.md#32-configuring-cloud-cdn) | | 3.3 Configuring Cloud DNS | 📘 | Not implemented (nip.io wildcard DNS used instead) | [Section 3 guide](PCNE_Section_3_Exploration_Guide.md#33-configuring-cloud-dns) | ## Section 4: Configuring and implementing hybrid and multicloud network interconnectivity (~16% of the exam) Entirely concept-only in RAD. The Cloud Router the modules create carries no BGP sessions — it exists solely to host Cloud NAT. Treat this section as a pure-study block; the guide gives you a structured plan and scratch-project commands. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Configuring Cloud Interconnect | 📘 | Not implemented | [Section 4 guide](PCNE_Section_4_Exploration_Guide.md#41-configuring-cloud-interconnect) | | 4.2 Configuring a site-to-site IPSec VPN | 📘 | Not implemented | [Section 4 guide](PCNE_Section_4_Exploration_Guide.md#42-configuring-a-site-to-site-ipsec-vpn) | | 4.3 Configuring Cloud Router | 🟡 | NAT-only router with ASN 64514; BGP/BFD/custom advertisement 📘 | [Section 4 guide](PCNE_Section_4_Exploration_Guide.md#43-configuring-cloud-router) | | 4.4 Configuring Network Connectivity Center | 📘 | Not implemented | [Section 4 guide](PCNE_Section_4_Exploration_Guide.md#44-configuring-network-connectivity-center) | ## Section 5: Managing, monitoring, and troubleshooting network operations (~14% of the exam) The modules enable LB request logging (sample rate 1.0) and rich health-check/auto-healing patterns, but VPC Flow Logs, NAT logging, and firewall logging are *not* enabled — turning them on manually against the deployed VPC is itself a great exercise. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 5.1 Logging and monitoring with Google Cloud Observability | 🟡 | LB request logging on the Cloud Run backend; alert policies/uptime checks; flow/NAT/DNS logs 📘 | [Section 5 guide](PCNE_Section_5_Exploration_Guide.md#51-logging-and-monitoring-with-google-cloud-observability) | | 5.2 Maintaining and troubleshooting connectivity | 🟡 | NFS MIG TCP health checks + auto-healing; VPN/Interconnect troubleshooting 📘 | [Section 5 guide](PCNE_Section_5_Exploration_Guide.md#52-maintaining-and-troubleshooting-connectivity-issues) | | 5.3 Monitoring, maintaining, and troubleshooting latency and traffic flow | 📘 | Run Network Intelligence Center tools *against* RAD resources | [Section 5 guide](PCNE_Section_5_Exploration_Guide.md#53-monitoring-maintaining-and-troubleshooting-latency-and-traffic-flow) | ## Section 6: Configuring, implementing and managing a cloud network security solution (~13% of the exam) Cloud Armor is the flagship ✅ here — both engines create a full WAF policy with preconfigured OWASP rules, Adaptive Protection, and rate limiting. Classic VPC firewall rules with tag-based micro-segmentation and Cloud NAT are also live; NGFW policies, Secure Web Proxy, and Packet Mirroring are study-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 6.1 Implementing and managing Google Cloud Armor | ✅ | both app modules: OWASP v33 rules, Adaptive Protection, rate limiting | [Section 6 guide](PCNE_Section_6_Exploration_Guide.md#61-implementing-and-managing-google-cloud-armor) | | 6.2 Configuring NGFW policies and VPC firewall rules | 🟡 | Tag-based VPC rules + K8s NetworkPolicy; hierarchical/NGFW tiers 📘 | [Section 6 guide](PCNE_Section_6_Exploration_Guide.md#62-configuring-ngfw-policies-and-vpc-firewall-rules) | | 6.3 Controlling internet egress traffic with Cloud NAT and Secure Web Proxy | 🟡 | Cloud NAT (`ALL_SUBNETWORKS_ALL_IP_RANGES`, auto IPs); Secure Web Proxy 📘 | [Section 6 guide](PCNE_Section_6_Exploration_Guide.md#63-controlling-internet-egress-traffic-with-cloud-nat-and-secure-web-proxy) | | 6.4 Implementing a self-managed network virtual appliance and Packet Mirroring | 📘 | Nearest analogue: self-managed NFS VM in a MIG; multi-NIC NVAs / Packet Mirroring 📘 | [Section 6 guide](PCNE_Section_6_Exploration_Guide.md#64-implementing-a-self-managed-network-virtual-appliance-and-packet-mirroring) | ## Suggested study sequence 1. **Week 1 — live lab (✅ topics):** Deploy VPC Foundation, work Sections 2.1–2.2, then 1.1–1.2. Add the GKE Network Lab and complete 1.4 and 2.4 while the cluster is up. These four subsections alone cover most of the hands-on weight of the exam. 2. **Week 2 — edge and security:** Deploy Global Edge; work 3.1, 3.2, 6.1 in one sitting (the LB, CDN, and Cloud Armor objects are the same deployment). Then 6.2 and 6.3 against the VPC Foundation resources, and Locked-Down Perimeter for the VPC-SC walk-through in 2.1. 3. **Week 3 — the 📘 third:** Sections 4 (all), 3.3, 2.3, 5.3, and 6.4 from docs plus the scratch-project commands in each "Beyond the modules" block. Roughly a third of the exam weight lives here; do not let the strength of the live lab tempt you into skipping it. The HA VPN lab in Section 4.2 — using the RAD VPC as one side — is the single highest-value scratch exercise. 4. **Final pass:** Re-run every "Check yourself" question cold. Tear down the lab profiles you no longer need; the GKE Network Lab and Global Edge profiles are the cost drivers. ## Key capabilities for quick reference | Area | What it demonstrates | |---|---| | Services_GCP networking | Custom-mode VPC, subnets, firewall rules, Cloud Router + NAT, PSA peering with custom-route export | | Services_GCP GKE | VPC-native clusters, deterministic secondary-range planning, Dataplane V2, Gateway API, Standard node pools | | Services_GCP NFS appliance | Tag-targeted firewall rules, TCP health checks, MIG auto-healing (appliance ops pattern) | | Services_GCP VPC-SC | VPC-SC perimeter, access levels, dry-run mode, permission probes | | App_CloudRun edge | Serverless NEG → backend service → URL map → proxies → global IP; Cloud Armor; CDN; cert management | | App_CloudRun service | Direct VPC egress, inline VPC/NAT/PSA fallback, hash-based CIDR allocation | | App_GKE edge | Gateway API global external ALB, HTTPRoute, GCPBackendPolicy, Cloud Armor for GKE | | App_GKE segmentation | Kubernetes NetworkPolicy micro-segmentation incl. restricted/private googleapis VIPs | | App_Common discovery | Network/subnet/tag discovery contract (`managed-by=services-gcp`) | --- # PCNE Certification Preparation Guide: Section 1 — Designing and planning a Google Cloud VPC network (~21% of the exam) PCNE Certification Preparation Guide: Section 1 — Designing and planning a Google Cloud VPC network (~21% of the exam) > 📚 **Official exam guide:** [Professional Cloud Network Engineer certification](https://cloud.google.com/learn/certification/cloud-network-engineer) — always confirm section weightings against the current Google Cloud exam guide. This section tests network *design decisions*: how to size and segment IP space, when to choose Shared VPC vs peering vs Private Service Connect, and how to plan GKE networking before the first cluster exists. Deploy the **VPC Foundation** profile first; add the **GKE Network Lab** profile before working through 1.4. The modules exercised are `Services_GCP` (the network itself) and `App_Common` (how downstream modules discover it). --- ## 1.1 Designing an overall network architecture > ⏱ ~45 min · 💰 no additional cost beyond the VPC Foundation profile · ⚙️ Requires: default deployment **Why the exam cares** — Architecture questions test whether you can pick the right *connectivity primitive* for a managed service: private services access (PSA) for Cloud SQL/Memorystore, Private Service Connect (PSC) for producer endpoints, Direct VPC egress or a Serverless VPC Access connector for Cloud Run. They also test load balancer selection (global external Application LB vs regional vs passthrough) and whether your design respects quotas (subnet ranges per VPC, peering route limits). **How RAD implements it** — The platform makes three deliberate architecture choices you can interrogate: | Decision | RAD's choice | |---|---| | Managed-service connectivity | PSA: a /16 internal range reserved for VPC peering plus a Service Networking connection | | Serverless-to-VPC connectivity | Cloud Run **Direct VPC egress** (a network interface on a subnet) — no Serverless VPC Access connector anywhere | | Internet-facing entry point | An external Application Load Balancer with a serverless NEG, created when `enable_cloud_armor` (default `false`) or `enable_cdn` (default `false`) is on | Memorystore Redis additionally exposes the choice directly: `redis_connect_mode` (default `DIRECT_PEERING`, option `PRIVATE_SERVICE_ACCESS`). Filestore uses `DIRECT_PEERING`. **Try it** 1. Deploy the VPC Foundation profile. In **Console > VPC network > VPC network peering**, find the `servicenetworking-googleapis-com` peering created by the PSA connection. 2. Inspect the reserved range and the peering from the CLI: ```bash gcloud compute addresses list --global \ --filter="purpose=VPC_PEERING" \ --format="table(name,address,prefixLength,network)" gcloud services vpc-peerings list \ --network=$(gcloud compute networks list --filter="name~vpc-network" --format="value(name)" | head -1) ``` 3. In **Console > Cloud Run**, open your service > **Networking** tab. Confirm "VPC" shows a network interface on the Services_GCP subnet (Direct VPC egress) rather than a connector. 4. You know it worked when the PSA address shows `prefixLength: 16` and the Cloud SQL instance's private IP (visible in **SQL > instance > Connections**) falls inside that range. **Check yourself**
Q1: A Cloud Run service must reach a Cloud SQL private IP and an on-prem CIDR via VPN. Direct VPC egress or Serverless VPC Access connector — and what egress setting? A: Either works for routing into the VPC, but Direct VPC egress (what RAD uses) avoids connector instance cost and gives higher throughput. To reach on-prem, `vpc_egress_setting = "ALL_TRAFFIC"` is not strictly required — `PRIVATE_RANGES_ONLY` (RAD's default) routes RFC 1918 destinations through the VPC, which covers a private on-prem CIDR. `ALL_TRAFFIC` is needed when *public* destinations must also traverse the VPC (e.g., for NAT-based egress IP allowlisting).
Q2: Why does the platform reserve a /16 for private services access instead of a /24? A: Each service producer (Cloud SQL, Memorystore, Filestore) carves per-region subnets out of the allocated range inside Google's producer VPC. A small allocation can be exhausted as instances, replicas, and regions are added, and growing it later requires updating the reservation. A /16 leaves headroom for every producer the platform might enable.
Q3: Which network tier do the module-created load balancers use? A: Global external Application Load Balancers with `EXTERNAL_MANAGED` scheme require Premium Tier, which is the project default. The modules never set a `network_tier`, so everything runs Premium. Standard Tier would force regional load balancing and regional forwarding rules — one reason a "global static IP + Standard Tier" design is an exam trap.
**Beyond the modules** — The exam also tests: Premium vs Standard network tiers (study "Network Service Tiers overview"; try `gcloud compute project-info describe --format="value(defaultNetworkTier)"`); DNS resolution topology (Cloud DNS private zones, split horizon — nothing in RAD); IAM roles for network design (`roles/compute.networkAdmin` vs `networkUser` vs `securityAdmin` — only `roles/compute.networkUser` appears, granted to the GKE service account); and quotas/limits (study "VPC resource quotas": subnet ranges per network, secondary ranges per subnet, peering limits). For PSC study "Private Service Connect types" — RAD uses *only* PSA peering, never PSC endpoints, despite the resource name `psconnect_private_ip_alloc`. **⚠️ Exam trap** — Private services access is implemented with VPC *peering*, so it is non-transitive: an on-prem network connected by VPN cannot reach a Cloud SQL private IP through the consumer VPC unless you export custom routes on the peering and advertise the PSA range from Cloud Router. RAD already enables custom-route export on the PSA peering — know why. --- ## 1.2 Designing VPC networks > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: VPC Foundation profile **Why the exam cares** — You must choose between standalone VPCs, Shared VPC, and multi-VPC designs joined by peering, NCC, or PSC, then defend an IPAM plan: which CIDRs, how many subnets, global vs regional resources, MTU, and what happens when address space collides. **How RAD implements it** — One standalone custom-mode VPC per project: | Variable / behavior | Default | |---|---| | VPC `vpc-network-{resource_prefix}` — custom-mode (subnets are not auto-created) | always | | `availability_regions` — one subnet per listed region | `["us-central1"]` | | `subnet_cidr_range` — one CIDR per region, validated 1–2 entries | `["10.0.0.0/24"]` | | Subnet description `managed-by=services-gcp` — the discovery contract used by app modules | always | The deterministic IPAM pattern is worth studying closely. When no Services_GCP VPC exists, `App_CloudRun` and `App_GKE` provision *inline* VPCs whose subnet CIDR is a deterministic /24 carved out of `192.168.0.0/16` — derived from a SHA-256 of the deployment suffix — so multiple standalone deployments never advertise the same CIDR into the shared PSA peering. The inline GKE path goes further: pod ranges from `10.0.0.0/8` and **service ranges from `100.64.0.0/10`** — RFC 6598 shared address space, a live example of non-RFC 1918 IP planning. The discovery layer runs `gcloud compute networks subnets list --filter="description~managed-by=services-gcp"` and also harvests network *tags* from existing ingress firewall rules so Cloud Run's VPC interface carries the right tags. **Try it** 1. In your deployment portal, redeploy Services_GCP with `availability_regions = ["us-central1", "us-west1"]` and `subnet_cidr_range = ["10.0.0.0/24", "10.0.1.0/24"]`. 2. Verify the subnet layout and that the VPC is custom mode: ```bash gcloud compute networks describe vpc-network- \ --format="value(x_gcloud_subnet_mode,routingConfig.routingMode)" gcloud compute networks subnets list \ --network=vpc-network- \ --format="table(name,region,ipCidrRange,description)" ``` 3. **Console > VPC network > VPC networks** — open the network and note the MTU column (the modules never set it, so it is the default 1460). 4. You know it worked when two subnets appear, one per region, each carrying the `managed-by=services-gcp` description. **Check yourself**
Q1: Two App_GKE deployments in one project, no Services_GCP. Why is the hashed-CIDR scheme necessary rather than a fixed 192.168.0.0/24 for both? A: Both inline VPCs peer with the same Service Networking producer via PSA. If two consumer VPCs advertise identical subnet CIDRs, the producer installs a return route for only one of them, silently black-holing reply traffic for the other deployment. Deterministic, hash-distinct /24s guarantee non-overlapping advertisements — the same reason on-prem/cloud IP plans must never overlap.
Q2: A customer needs 40 service projects to share one network with centralized firewall administration. Standalone VPCs with peering, or Shared VPC? A: Shared VPC. Peering does not scale administratively (full mesh, non-transitive, per-VPC firewall ownership) and has peering-group route limits. Shared VPC keeps subnets, routes, and firewall rules in one host project while service projects attach workloads via `roles/compute.networkUser` on specific subnets. RAD's "one VPC per project" model deliberately avoids this — know both.
**Beyond the modules** — Not implemented and heavily tested: **Shared VPC** (host/service projects, subnet-level IAM — try `gcloud compute shared-vpc enable HOST_PROJECT` in a scratch org), **VPC Network Peering** between your own VPCs (`gcloud compute networks peerings create`, remember non-transitivity and no overlapping CIDRs), **NCC star/mesh** topologies for many-VPC designs, **IPv6** (dual-stack subnets, `--stack-type=IPV4_IPV6`), **BYOIP/PUPI**, **Private NAT** for overlapping ranges, **MTU** decisions (1460 default, 8896 jumbo for intra-VPC and supported Interconnect), and **NVA insertion** with custom/policy-based routes plus internal LB. Study pages: "Shared VPC overview", "VPC Network Peering", "Create and use IPv6", "MTU of a VPC network". **⚠️ Exam trap** — Subnets are regional; VPCs and their routing tables are global. "Create one subnet per zone" is wrong, and a VM in `us-west1` reaches a `us-central1` subnet with no extra routing. Don't confuse the *dynamic routing mode* (regional vs global, affects only Cloud Router-learned routes) with subnet reach. --- ## 1.3 Designing a resilient and performant hybrid and multi-cloud network > ⏱ ~60 min study · 💰 no platform cost · ⚙️ Requires: nothing deployed — concept-only **Why the exam cares** — Choosing between Dedicated Interconnect (10/100 Gbps, your own colo presence), Partner Interconnect (50 Mbps–50 Gbps via a provider), Cross-Cloud Interconnect (to AWS/Azure), and HA VPN (encrypted, internet-transported, 99.99% with two tunnels per interface) is the single most repeated decision pattern on this exam, along with the 99.9% vs 99.99% Interconnect SLA topologies and hybrid DNS forwarding design. **How RAD implements it** — Not implemented by the foundation modules. The only adjacent artifacts: the Cloud Router (ASN `64514`, no BGP peers — it exists to anchor Cloud NAT), and the PSA peering's custom-route export, which is exactly the knob you would flip so an on-prem network could reach Cloud SQL private IPs over a future VPN/Interconnect. **Try it** 1. Even without hybrid links, you can inspect the building blocks the modules leave behind: ```bash gcloud compute routers list --format="table(name,region,network,bgp.asn)" gcloud compute routers describe vpc-network--nat-gw-us-central1 \ --region=us-central1 --format="yaml(bgp,nats[].name)" ``` 2. Note `bgp.asn: 64514` (a private ASN) and that there are no `bgpPeers` and no `interfaces` — contrast with what an HA VPN attachment would add. 3. You know you understand it when you can explain why this router could later host both NAT and VPN BGP sessions on the same network. **Check yourself**
Q1: An enterprise needs 99.99% SLA connectivity to on-prem with encryption in transit. Which design? A: HA VPN over Cloud Interconnect (or plain HA VPN if Interconnect isn't justified). The 99.99% Interconnect SLA requires four VLAN attachments across two metros (two edge availability domains each) with global dynamic routing; Interconnect alone is not encrypted, so the exam answer for "encrypted + 99.99%" is HA VPN over Interconnect, or MACsec on supported Interconnect connections.
Q2: On-prem hosts must call Google APIs privately through the Interconnect. What do you configure? A: Advertise `199.36.153.4/30` (restricted.googleapis.com) or `199.36.153.8/30` (private.googleapis.com) from Cloud Router as a custom route advertisement, create a Cloud DNS private zone for `googleapis.com` mapping `*.googleapis.com` to those VIPs, and make it resolvable on-prem via DNS forwarding/inbound server policy. Note RAD's GKE NetworkPolicy already allowlists exactly those two /30s for pod egress — same VIPs, different enforcement point.
**Beyond the modules** — Study the full 1.3 list deliberately: Dedicated vs Partner vs Cross-Cloud Interconnect ("Cloud Interconnect overview"); HA VPN topologies incl. VPN between two VPCs; regional vs global dynamic routing mode and its effect on which subnets Cloud Router advertises; accessing multiple VPCs from on-prem (per-VPC attachments vs NCC hub); hybrid DNS (forwarding zones, inbound DNS policies, DNS peering, cross-project binding); IP planning across on-prem and cloud (internal ranges, Private NAT for overlap); MTU over hybrid links (1440 typical for VPN, up to 8896 on Interconnect); MACsec. Scratch-project commands worth memorizing: `gcloud compute vpn-gateways create` (HA VPN gives two interfaces automatically), `gcloud compute interconnects attachments partner create`, `gcloud compute routers add-bgp-peer`. **⚠️ Exam trap** — "Global dynamic routing makes my VPN highly available" — no. Routing mode controls which *subnets* are advertised/learned across regions; HA comes from redundant tunnels/attachments and BGP failover (optionally accelerated with BFD). --- ## 1.4 Designing for Google Kubernetes Engine > ⏱ ~60 min · 💰 Autopilot cluster cost while deployed · ⚙️ Requires: GKE Network Lab profile **Why the exam cares** — GKE IP exhaustion is a classic incident: the exam tests sizing the node subnet, pod secondary range, and service secondary range *before* cluster creation, choosing public vs private nodes and control-plane endpoints, and matching node pools to workload needs. **How RAD implements it** — The platform is a worked example of deterministic multi-cluster IP planning. For cluster *i*, each range is sliced out of a base CIDR using the cluster index: | Range | Derivation | Cluster 1 result (defaults) | |---|---|---| | Node subnet | a /20 spaced 16 apart inside `gke_subnet_base_cidr` (`10.128.0.0/12`) | `10.128.0.0/20` (4,094 nodes) | | Pod range | a /14 slice of `gke_pod_base_cidr` (`10.64.0.0/10`) | `10.64.0.0/14` (~262k pod IPs) | | Service range | a /20 slice of `gke_service_base_cidr` (`10.8.0.0/16`) | `10.8.0.0/20` (4,094 services) | Each subnet carries two **named secondary ranges** (`gke-{prefix}-pods-{i}`, `gke-{prefix}-services-{i}`) — i.e., VPC-native/alias-IP clusters. Other verified design choices: `gke_cluster_mode` default `AUTOPILOT` (or `STANDARD` with an explicit node pool: `gke_node_machine_type` default `e2-standard-4`, autoscaling `gke_node_min_count` 1 to `gke_node_max_count` 5, `pd-balanced` disks, Shielded nodes);release channel `REGULAR`; **private nodes** — `private_cluster_config { enable_private_nodes = true }` plus a control-plane-only `/28` from `gke_master_base_cidr`, required because RAD-managed projects deny `compute.vmExternalIpAccess`; `enable_private_endpoint` is left `false`, so the control-plane endpoint stays public for CI/CD. The inline cluster additionally configures master authorized networks with Google public CIDR access enabled *plus* a `0.0.0.0/0` block so Cloud Build workers can reach the API server. **Try it** 1. Deploy the GKE Network Lab profile, then map the IP plan end to end: ```bash gcloud container clusters describe gke-cluster-1- \ --location=us-central1 \ --format="yaml(clusterIpv4Cidr,servicesIpv4Cidr,ipAllocationPolicy,datapathProvider,privateClusterConfig)" gcloud compute networks subnets describe vpc-network--gke-subnet-1-us-central1 \ --region=us-central1 \ --format="yaml(ipCidrRange,secondaryIpRanges)" ``` 2. Confirm `privateClusterConfig.enablePrivateNodes: true` (nodes have no external IPs) and `datapathProvider: ADVANCED_DATAPATH`. 3. In your portal, set `gke_cluster_count = 2` and redeploy: observe cluster 2 receive `10.68.0.0/14` pods and `10.8.16.0/20` services — non-overlapping by construction. 4. You know it worked when `kubectl get pods -o wide` shows pod IPs inside the cluster's pod CIDR rather than the node CIDR (alias IPs in action): ```bash gcloud container clusters get-credentials gke-cluster-1- --location=us-central1 kubectl get pods -A -o wide | head ``` **Check yourself**
Q1: With defaults, why can the platform support 10 clusters (gke_cluster_count max) without IP collisions? A: Each base CIDR is sliced using the cluster index: 16 possible /14 pod slices in `10.64.0.0/10`, 16 /20 service slices in `10.8.0.0/16`, and node /20s spaced 16 apart inside `10.128.0.0/12`. The arithmetic guarantees disjoint ranges for indexes 1–10 — the same precomputation discipline the exam expects for "plan IP space for N clusters".
Q2: A regulated customer demands nodes with no public IPs and a control plane reachable only from a bastion subnet. What changes relative to the RAD design? A: Private nodes are already configured (`enable_private_nodes = true` on both the `Services_GCP` cluster and App_GKE's inline fallback; Cloud NAT handles their egress). Only the control plane changes: set `enable_private_endpoint = true`, or keep the public endpoint but restrict master authorized networks to the bastion CIDR (today the inline cluster opens them to `0.0.0.0/0`). RAD's clusters are public-endpoint by design because the CI/CD path (Cloud Build) needs API-server access; the inline cluster even opens authorized networks to `0.0.0.0/0` (auth still required) — recognize that as a convenience trade-off, not a security best practice.
Q3: Pods schedule but new Services fail with "range exhausted". Which range is the problem and can you fix it in place? A: The *services* secondary range. It is fixed at cluster creation and cannot be replaced; pods get relief via additional pod ranges (`--additional-pod-ipv4-ranges` / per-node-pool pod ranges), but service-range exhaustion requires recreating the cluster with a larger range — why the exam (and RAD's /20 default) push you to size it up front.
**Beyond the modules** — Study: private clusters and the three control-plane access patterns (public endpoint, public + authorized networks, private endpoint) plus the newer **DNS-based control plane endpoint**; non-RFC 1918 and PUPI pod ranges (RAD's inline path already uses `100.64.0.0/10` for services); IPv6/dual-stack GKE; GKE load balancing options (container-native LB with NEGs — covered in Section 3); node-pool design (taints, local SSD, spot). Docs: "Alias IP ranges", "GKE address management", "About private clusters". **⚠️ Exam trap** — The pod range must be sized as *nodes × max-pods-per-node × 2* (GKE reserves a /24 per node by default on Standard; Autopilot manages it but still consumes the range). A "/24 pod range for a 100-node cluster" answer is always wrong — the node subnet and pod range are sized with different math. --- # PCNE Certification Preparation Guide: Section 2 — Implementing a VPC network (~20% of the exam) PCNE Certification Preparation Guide: Section 2 — Implementing a VPC network (~20% of the exam) > 📚 **Official exam guide:** [Professional Cloud Network Engineer certification](https://cloud.google.com/learn/certification/cloud-network-engineer) — always confirm section weightings against the current Google Cloud exam guide. Section 2 moves from design to `gcloud compute networks ...` muscle memory: creating VPCs, subnets, firewall rules, routes, and VPC-native GKE clusters. Deploy the **VPC Foundation** profile (Services_GCP + App_CloudRun) for 2.1–2.2 and add the **GKE Network Lab** profile for 2.4. Add the **Locked-Down Perimeter** profile if you want live VPC-SC resources. Modules exercised: `Services_GCP`, `App_GKE`, `App_Common`. --- ## 2.1 Configuring VPCs > ⏱ ~60 min · 💰 no additional cost · ⚙️ Requires: VPC Foundation profile (add Locked-Down Perimeter for VPC-SC) **Why the exam cares** — This is the bread-and-butter implementation domain: custom-mode networks, subnet creation and *expansion*, firewall rules vs policies, the private-services-access allocation, Private Google Access, Shared VPC attachment, and VPC-SC perimeters. **How RAD implements it** — Here is how the platform builds the network: | Resource | Key facts | |---|---| | VPC network | `vpc-network-{prefix}`, custom-mode (subnets are not auto-created) | | GCE subnetwork | one per `availability_regions` entry, CIDR from `subnet_cidr_range` (default `["10.0.0.0/24"]`), description `managed-by=services-gcp` | | Firewall rules | `{net}-fw-allow-lb-hc` (sources `35.191.0.0/16`, `130.211.0.0/22`, tcp 80/2049/6379); `{net}-fw-allow-iap-ssh` (source `35.235.240.0/20`, tcp 22); intra-VPC allow tcp/udp/icmp from all internal CIDRs; tag-scoped rules (`nfsserver`, `redisserver`, `httpserver`, `webserver`) | | PSA | global address `{net}-psconnect-ip-range` reserved for VPC peering with prefix length 16, plus a Service Networking connection (peering abandoned, not deleted, on teardown) | Private Google Access is enabled everywhere: the Services_GCP GCE and GKE subnets have it on, as do the *inline* subnets created when no Services_GCP VPC exists — so instances without external IPs (e.g. the NFS VM) reach Google APIs over the private path. For VPC-SC: `enable_vpc_sc` (default `false`) builds perimeter `vpcsc_{prefix}_perimeter` with 15 restricted services, four access levels (VPC CIDRs, `admin_ip_ranges`, the IAP service agent, CI/CD SAs), and `vpc_sc_dry_run` (default `true`). It is skipped with a console warning unless an organization ID is discoverable from the project, `admin_ip_ranges` is non-empty, and the caller passes an org-level Access Context Manager permission probe. **Try it** 1. List the rules and compare against the table above: ```bash gcloud compute firewall-rules list \ --filter="network~vpc-network" \ --format="table(name,direction,sourceRanges.list(),allowed[].map().firewall_rule().list(),targetTags.list())" ``` 2. Check Private Google Access per subnet — every module-managed subnet should show it enabled: ```bash gcloud compute networks subnets list --network=vpc-network- \ --format="table(name,region,ipCidrRange,privateIpGoogleAccess)" ``` 3. Know the manual equivalent for subnets you create yourself (the module subnets already have PGA on): ```bash gcloud compute networks subnets update \ --region=us-central1 --enable-private-ip-google-access ``` 4. If you deployed the Locked-Down Perimeter profile in an org-attached project, view the perimeter: **Console > Security > VPC Service Controls** — it appears under the org's access policy in dry-run mode. 5. You know it worked when `privateIpGoogleAccess: True` shows on every module subnet and (for VPC-SC) `gcloud access-context-manager perimeters list --policy=` shows `vpcsc__perimeter`. **Check yourself**
Q1: The 10.0.0.0/24 subnet is nearly full. Can you grow it without downtime, and what's the constraint? A: Yes — `gcloud compute networks subnets expand-ip-range vpc-network--subnet-us-central1 --region=us-central1 --prefix-length=23`. Expansion can only make the prefix *shorter* (larger range), must not overlap any other subnet or the PSA allocation, and cannot be reversed. RAD's Terraform would show drift afterward — in IaC environments, change `subnet_cidr_range` instead and let the plan handle it.
Q2: Why do the health-check firewall rules allow exactly 35.191.0.0/16 and 130.211.0.0/22? A: Those are Google's central health-check prober ranges for most load balancer types. Without an ingress allow from them, backends are marked unhealthy and the LB serves 502s even though the application is fine — one of the most common LB troubleshooting answers on the exam. RAD bakes them into both the VPC rules (`fw-allow-lb-hc`) and the GKE NetworkPolicy.
Q3: enable_vpc_sc = true but no perimeter appears and the apply succeeded. Why? A: By design the module degrades gracefully: VPC-SC is skipped (with a warning) if the project has no discoverable organization, if `admin_ip_ranges` is empty (lockout prevention), or if the caller fails the `gcloud access-context-manager policies list` permission probe. Check the apply log for the WARNING lines from the VPC-SC validators.
**Beyond the modules** — Not implemented: **Shared VPC** (`gcloud compute shared-vpc enable`, `associated-projects add`, subnet-level `roles/compute.networkUser` grants), **VPC Peering between consumer VPCs** (only the PSA producer peering exists), **private pools** for Cloud Build inside the perimeter, and global **network firewall policies** (the modules use classic per-network VPC firewall rules — see Section 6.2). Study "Provision Shared VPC" and "Migrate firewall rules to network firewall policies". **⚠️ Exam trap** — A VPC-SC perimeter is not a firewall: it controls access to Google *APIs* (who can call `storage.googleapis.com` for project data), not packet flow between VMs. Conversely, firewall rules can't stop an exfiltration via `gsutil cp` to an attacker-owned bucket — that's exactly what VPC-SC is for. --- ## 2.2 Configuring VPC routing > ⏱ ~40 min · 💰 no additional cost · ⚙️ Requires: VPC Foundation profile **Why the exam cares** — Route precedence (subnet routes beat everything; then custom static/dynamic by priority), global vs regional dynamic routing, policy-based routes, internal LB as next hop for NVAs, and custom-route exchange over peering are all fair game. **How RAD implements it** — Three verified routing artifacts: 1. **Cloud Router** — a Cloud Router named `{net}-nat-gw-{region}` with ASN 64514 and no advertised groups. It exists solely to host Cloud NAT; no BGP peers are configured. 2. **Peering route exchange** — the PSA peering imports and exports custom routes so GKE *pod* ranges reach the Cloud SQL producer network and back. 3. **Subnet-route export over PSA** — the inline GKE path goes one step deeper: because a GKE secondary range is a *subnet* route (not a custom route), the platform runs `gcloud compute networks peerings update servicenetworking-googleapis-com --export-subnet-routes-with-public-ip --import-subnet-routes-with-public-ip`. Without it, pod traffic reaches Cloud SQL on 3307 but replies have no return route. **Try it** 1. Dump the effective routing table and identify each route's origin: ```bash gcloud compute routes list \ --filter="network~vpc-network" \ --format="table(name,destRange,nextHopGateway.basename(),nextHopPeering,priority)" ``` Expect: one subnet route per subnet/secondary range, a `default-route-*` to `default-internet-gateway`, and peering routes for the PSA range. 2. Inspect the peering's route exchange flags: ```bash gcloud compute networks peerings list --network=vpc-network- \ --format="table(name,exportCustomRoutes,importCustomRoutes,exchangeSubnetRoutes)" ``` 3. Confirm the network's dynamic routing mode (the modules leave the default): ```bash gcloud compute networks describe vpc-network- --format="value(routingConfig.routingMode)" ``` 4. You know it worked when you can explain every row of the routes list — especially which routes came from the `servicenetworking` peering. **Check yourself**
Q1: Two custom static routes match a destination: 0.0.0.0/0 priority 1000 via internet gateway, and 10.50.0.0/16 priority 900 via an NVA. A packet to 10.50.1.5 — where does it go? A: Via the NVA. Longest-prefix match wins before priority is even considered (/16 beats /0); priority only breaks ties between routes of identical prefix length (lower number wins).
Q2: Why did App_GKE need `--export-subnet-routes-with-public-ip` on the PSA peering when custom-route export was already on? A: GKE secondary (alias-IP) ranges propagate as *subnet routes*, and custom-route export covers only custom static/dynamic routes. The misleadingly named subnet-routes-with-public-IP flags control export/import of subnet routes across the peering; without exporting them, the producer VPC has no return path to pod IPs. This distinction — custom vs subnet route exchange over peering — is precisely sub-topic 2.2's "configuring custom route import/export".
**Beyond the modules** — Study: **network tags on routes** (`gcloud compute routes create --tags` restricts a route to tagged instances — RAD uses tags only on firewall rules), **policy-based routes** (`gcloud network-connectivity policy-based-routes create`, match on protocol/src/dst, steer to an internal LB), **internal passthrough LB as next hop** for HA NVAs, and **regional vs global dynamic routing** effects on Cloud Router advertisements. None exist in the modules. **⚠️ Exam trap** — Deleting the default route (`0.0.0.0/0 → default-internet-gateway`) does *not* block access to Google APIs if Private Google Access is on — the PGA path still works. But it does break Cloud NAT egress, which depends on that default route. --- ## 2.3 Configuring Network Connectivity Center > ⏱ ~30 min study · 💰 none · ⚙️ Requires: nothing — concept-only **Why the exam cares** — NCC is Google's hub-and-spoke answer to "many VPCs + many sites": VPC spokes give transitive VPC-to-VPC reachability that plain peering cannot, hybrid spokes (VPN/Interconnect/router appliance) enable site-to-site data transfer through Google's backbone, and producer-VPC spokes propagate PSA networks. **How RAD implements it** — Not implemented by the foundation modules. The closest live artifact is the *problem NCC solves*: RAD's PSA peering is non-transitive, and its multi-deployment inline-VPC scheme (hash-distinct CIDRs, Section 1.2) exists precisely because there is no hub joining those VPCs. **Try it** 1. In a scratch project, create a hub and attach the RAD VPC as a spoke (read-only impact on the VPC itself): ```bash gcloud network-connectivity hubs create rad-lab-hub --description="PCNE practice" gcloud network-connectivity spokes linked-vpc-network create rad-vpc-spoke \ --hub=rad-lab-hub --global \ --vpc-network=projects//global/networks/vpc-network- gcloud network-connectivity hubs route-tables list --hub=rad-lab-hub ``` 2. You know it worked when the hub route table lists the RAD subnets as dynamic entries — that's NCC learning VPC-spoke routes. **Check yourself**
Q1: VPC-A peers with VPC-B, VPC-B peers with VPC-C. A needs to reach C. NCC or more peering? A: NCC with all three as VPC spokes on one hub (mesh topology) — peering is non-transitive, and adding A↔C peering scales O(n²). With NCC, spoke subnets are exchanged through the hub and reachability is transitive; use IP/CIDR export filters on spokes to exclude ranges (e.g., overlapping ones).
Q2: When do you choose star topology over mesh for VPC spokes? A: Star when branch VPCs should reach only the center (shared services) and *not* each other — e.g., per-customer VPCs that must stay mutually isolated while consuming central services. Mesh gives any-to-any.
**Beyond the modules** — Study "Network Connectivity Center overview": spoke types (VPC, hybrid VPN/Interconnect, router appliance, producer VPC), star vs mesh, Private NAT at the hub for overlapping spokes, PSC propagation through NCC, and the monitoring story (hub route tables, spoke status). Know that hybrid spokes enable *site-to-site data transfer* only in supported regions. --- ## 2.4 Configuring and maintaining Google Kubernetes Engine clusters > ⏱ ~75 min · 💰 Autopilot cluster cost · ⚙️ Requires: GKE Network Lab profile (`enable_network_segmentation = true` on App_GKE) **Why the exam cares** — The implementation flip side of 1.4: VPC-native clusters with alias IPs, Dataplane V2 vs Calico network policies, private endpoints/authorized networks, SNAT/IP masquerade, and cluster DNS choices. **How RAD implements it** — Verified cluster wiring: | Concern | RAD implementation | |---|---| | VPC-native | alias-IP clusters with named secondary ranges per cluster | | Dataplane V2 | enabled on all Services_GCP clusters; the inline cluster enables it only when `enable_network_segmentation = true` | | Control-plane access | Public endpoint; inline cluster adds master authorized networks with Google public CIDR access enabled and an explicit `0.0.0.0/0` block (auth still enforced by credentials) | | NetworkPolicy | `enable_network_segmentation` (default `false`) creates a namespace-wide policy: ingress from same namespace + LB health-check ranges + `35.235.240.0/20`, plus `0.0.0.0/0` on the container port whenever `service_type` is `LoadBalancer` or `NodePort` (an L4 NLB preserves the client IP, so real traffic matches no Google range); egress limited to DNS (53), HTTPS incl. `199.36.153.4/30` and `199.36.153.8/30`, Cloud SQL proxy loopback and `3307 → 10.0.0.0/8`, metadata `169.254.169.254:80`, NFS 2049 | | Service exposure | `service_type` default `LoadBalancer` with annotation `networking.gke.io/load-balancer-type: External`, `session_affinity` default `ClientIP` | | DNS | Cluster default kube-dns/Cloud DNS per GKE defaults — the modules configure nothing DNS-specific | **Try it** 1. Deploy, then verify Dataplane V2 and the secondary ranges in one pass: ```bash gcloud container clusters describe gke-cluster-1- --location=us-central1 \ --format="yaml(datapathProvider,ipAllocationPolicy.clusterSecondaryRangeName,ipAllocationPolicy.servicesSecondaryRangeName,masterAuthorizedNetworksConfig)" ``` 2. Inspect the NetworkPolicy the module created and test enforcement: ```bash gcloud container clusters get-credentials gke-cluster-1- --location=us-central1 kubectl get networkpolicy -A kubectl describe networkpolicy -n -namespace-isolation # Negative test: a pod in a *different* namespace cannot reach the app kubectl run probe --rm -it --image=busybox --restart=Never -n default \ -- wget -qO- --timeout=5 http://..svc.cluster.local || echo "BLOCKED (expected)" ``` 3. Toggle `enable_network_segmentation = false` in your portal, redeploy, and rerun the probe — it now succeeds. 4. You know it worked when the cross-namespace probe times out with the policy on and succeeds with it off. **Check yourself**
Q1: Why does the egress policy allow 443 to 199.36.153.4/30 and also an unrestricted 443 rule? A: `199.36.153.4/30` is restricted.googleapis.com — it only serves traffic when a Cloud DNS zone maps `*.googleapis.com` to those VIPs. RAD does not create that DNS zone, so kube-dns returns public Google IPs (e.g., for `sqladmin.googleapis.com`), and the cloud-sql-proxy sidecar would deadlock without a general HTTPS egress allowance. The module documents this dual-path reasoning in the NetworkPolicy — and the exam loves the "restricted VIP requires the DNS zone" dependency.
Q2: A Standard cluster's pods must reach an on-prem 172.16.0.0/12 range, but traffic arrives on-prem with node IPs, breaking source-based ACLs. What's happening? A: The IP masquerade agent SNATs pod IPs to node IPs for destinations outside its `nonMasqueradeCIDRs` (default covers RFC 1918, but custom configs often shrink it). Fix by adding 172.16.0.0/12 to nonMasqueradeCIDRs (or configuring Dataplane V2's equivalent) so pod IPs are preserved — then ensure on-prem routes back to the pod CIDR. RAD doesn't configure masquerade; know the default behavior.
Q3: Why is `0.0.0.0/0` in the inline cluster's authorized networks not equivalent to "no authentication"? A: Authorized networks is a *network-layer* filter on who may open a TCP session to the control plane; every request still requires valid IAM/OIDC credentials. The module opens it because Cloud Build's worker IPs are unpredictable. The hardening alternatives are private endpoints with private pools, or the DNS-based control-plane endpoint, which authorizes via IAM instead of CIDR.
**Beyond the modules** — Study: **Shared VPC clusters** (secondary ranges live in the host project; GKE service agents need `roles/compute.networkUser` + Host Service Agent User); **private clusters** and control-plane private endpoints; the **DNS-based endpoint** (`gcloud container clusters update --enable-dns-access`); **additional pod ranges** for IP relief; **NodeLocal DNSCache** and **Cloud DNS for GKE** (`--cluster-dns=clouddns`); SNAT/`ip-masq-agent` details. Docs: "About cluster networking", "Use Cloud DNS for GKE". **⚠️ Exam trap** — Kubernetes NetworkPolicy on GKE requires an enforcement engine: Dataplane V2 (or legacy Calico on Standard). On a cluster created with no datapath provider specified and no Calico, policies are accepted by the API server but silently unenforced — exactly why RAD ties Dataplane V2 to `enable_network_segmentation`, and why flipping that flag later forces cluster *recreation* (the field is immutable). --- # PCNE Certification Preparation Guide: Section 3 — Configuring managed network services (~16% of the exam) PCNE Certification Preparation Guide: Section 3 — Configuring managed network services (~16% of the exam) This section covers load balancing, Cloud CDN, and Cloud DNS. RAD gives you *two* complete global external Application Load Balancer builds to compare: the Cloud Run engine assembles one explicitly from load-balancing primitives (NEG → backend service → URL map → proxies → forwarding rules), while the GKE engine lets the Gateway controller assemble the same chain from Kubernetes manifests. Deploy the **Global Edge** profile before starting. Cloud DNS is not implemented — budget real study time for 3.3. --- ## 3.1 Configuring load balancing > ⏱ ~90 min · 💰 forwarding-rule + LB request charges while deployed · ⚙️ Requires: Global Edge profile (`enable_cloud_armor = true` on App_CloudRun (`application_domains` optional); `enable_custom_domain = true` on App_GKE) **Why the exam cares** — The LB decision tree (internal/external × regional/global × application/proxy/passthrough) plus backend mechanics — NEG types, balancing modes, session affinity, health checks, URL maps — is the highest-yield topic in Section 3. GKE adds the Gateway vs Ingress controller choice and container-native load balancing with NEGs. **How RAD implements it** — **Cloud Run path**: when `enable_cloud_armor` (default `false`) or `enable_cdn` (default `false`) is true, the module sets the service ingress to `internal-and-cloud-load-balancing` and builds: a *regional* serverless NEG pointing at the Cloud Run service → a backend service (HTTPS protocol, 30s timeout, external managed scheme, request logging at full sample rate) → URL map → HTTPS target proxy with a Certificate Manager certificate map (per-domain Google-managed certs) → global forwarding rules on a reserved global static IP (`{service}-lb-ip`), plus an HTTP→HTTPS redirect (permanent). With no custom domain (CDN-only path), a Google-managed SSL cert is issued for a `.nip.io` hostname instead. **GKE path**: when `enable_custom_domain = true`, a `Gateway` with `gatewayClassName: gke-l7-global-external-managed` (global external ALB), listeners HTTP/80 (+HTTPS/443 when `application_domains` is set), a `NamedAddress` pointing at a reserved global address, certificate map via the `networking.gke.io/cert-map` annotation, an `HTTPRoute` to the Service, and a `GCPBackendPolicy` carrying the backend timeout, optional IAP, and the Cloud Armor security policy. Without the Gateway, the default exposure is a `LoadBalancer` Service (regional external *passthrough* Network LB) with `session_affinity` default `ClientIP` and an optional regional static IP (`reserve_static_ip`, default `true`). Health checking: Cloud Run relies on the platform plus container `startup_probe_config`/`health_check_config`; GKE backends get probes from the same variables, and the shared VPC firewall (`fw-allow-lb-hc`) admits Google's prober ranges. **Try it** 1. Deploy the Global Edge profile, then walk the Cloud Run LB chain in **Console > Network services > Load balancing**: ```bash gcloud compute network-endpoint-groups list --format="table(name,networkEndpointType,region)" gcloud compute backend-services describe -backend --global \ --format="yaml(loadBalancingScheme,protocol,backends,securityPolicy,enableCDN,logConfig)" gcloud compute url-maps list gcloud compute forwarding-rules list --global \ --format="table(name,IPAddress,portRange,target)" ``` 2. On the GKE side, compare what the Gateway controller generated: ```bash kubectl get gateway,httproute,gcpbackendpolicy -n kubectl describe gateway -gateway -n # note the programmed IP gcloud compute backend-services list --format="table(name,loadBalancingScheme)" # gkegw1-* entries ``` 3. Curl the static IP with a Host header before DNS exists: ```bash curl -sk -H "Host: app.example.com" https:/// -o /dev/null -w "%{http_code}\n" ``` 4. You know it worked when the Gateway resource shows a `Programmed: True` condition and both LBs appear in the console's load balancing list with global scope. **Check yourself**
Q1: Why is the serverless NEG regional while the load balancer is global? A: Serverless NEGs are always regional objects (they wrap a regional Cloud Run/Functions service), but a global external ALB can attach regional NEGs from multiple regions to one backend service and route clients to the nearest healthy region via anycast. That's RAD's pattern with a single region; multi-region would add one NEG per region to the same backend service.
Q2: Traffic must split 90/10 between two app versions. Where does RAD do this, and where would the *exam* do it on an ALB? A: RAD splits at the Cloud Run *revision* level via `traffic_split` (LATEST/REVISION percentages) — the LB is unaware. The ALB-native answer is URL-map `routeRules` with `weightedBackendServices` (plus `requestMirrorPolicy` for mirroring and `urlRewrite` for rewrites), or, on GKE Gateway, multiple `backendRefs` with `weight` in the HTTPRoute. Know both layers and that they compose.
Q3: A client's requests keep landing on different pods despite `sessionAffinity: ClientIP` on the GKE Service. The app is reached through the Gateway. Why? A: Gateway/ALB traffic reaches pods via NEGs, bypassing kube-proxy Service semantics — the Service's ClientIP affinity applies to passthrough/cluster traffic, not to the ALB's backend selection. For the Gateway path, configure affinity on the backend via `GCPBackendPolicy` (`sessionAffinity`), which RAD leaves unset.
**Beyond the modules** — Not implemented: internal ALB/NLB (no `INTERNAL_MANAGED`/`INTERNAL` schemes anywhere, no proxy-only subnets), MIG backends with balancing modes (UTILIZATION/RATE/CONNECTION — the modules' only backend is a serverless NEG, which takes no balancing mode), global access on internal LBs, the legacy **GKE Ingress controller** with `BackendConfig` (RAD chose Gateway API), and ALB **traffic management** (weighted splits, mirroring, URL rewrites). Study "Choose a load balancer" (memorize the decision tree) and "Traffic management overview for global external Application Load Balancers"; in a scratch project create an internal ALB to see the required proxy-only subnet (`gcloud compute networks subnets create ... --purpose=REGIONAL_MANAGED_PROXY`). **⚠️ Exam trap** — Passthrough LBs (internal/external Network LB) preserve client source IPs and require backend firewall rules for *client* ranges; proxy LBs (ALB, proxy NLB) terminate connections, so backends see proxy ranges and you must allow `130.211.0.0/22` + `35.191.0.0/16` for health checks and read client IPs from `X-Forwarded-For`. Mixing these up breaks both firewalling and logging answers. --- ## 3.2 Configuring Cloud CDN > ⏱ ~30 min · 💰 cache-egress charges while testing · ⚙️ Requires: Global Edge profile with `enable_cdn = true` (App_CloudRun) **Why the exam cares** — Knowing which origins Cloud CDN supports (backend services with MIGs, backend buckets for GCS, serverless NEGs for Cloud Run, internet NEGs for external origins), how cache modes and invalidation work, and that CDN hangs off the *backend service/bucket* of a global external ALB. **How RAD implements it** — On the Cloud Run engine this is real and verifiable: `enable_cdn` (default `false`) turns on Cloud CDN directly on the Cloud Run backend service and forces creation of the global external ALB, demonstrating "Cloud CDN for Cloud Run via serverless NEG". Note it is **not** usable on its own: `validation.tf` precondition 26 requires `enable_cloud_armor = true` alongside it, because the CDN attaches to the load balancer Cloud Armor provisions. On the GKE engine, be careful: `enable_cdn` (default `false`; a custom domain is **not** required — that validation was relaxed) switches the module onto the Gateway path, but **no resource actually enables CDN** — the `GCPBackendPolicy` CRD does not support a CDN field, so CDN for the GKE Gateway must be enabled out-of-band on the controller-generated backend service. **Try it** 1. With the Global Edge profile deployed, confirm CDN on the Cloud Run backend and exercise the cache: ```bash gcloud compute backend-services describe -backend --global \ --format="yaml(enableCDN,cdnPolicy)" curl -s -D- -o /dev/null https:/// | grep -iE "age|cache|via" ``` Repeat the curl — a growing `Age:` header indicates a cache hit. 2. Invalidate the cache the way the exam expects: ```bash gcloud compute url-maps invalidate-cdn-cache -lb --path "/*" --async ``` 3. On GKE, prove the gap, then close it manually (out-of-band exercise): ```bash BS=$(gcloud compute backend-services list --format="value(name)" --filter="name~gkegw1") gcloud compute backend-services describe $BS --global --format="value(enableCDN)" # False/empty gcloud compute backend-services update $BS --global --enable-cdn --cache-mode=CACHE_ALL_STATIC ``` 4. You know it worked when **Console > Network services > Cloud CDN** lists the origin(s) and the second curl returns an `Age` header. **Check yourself**
Q1: Content must be served from an origin running in AWS behind the Google ALB with CDN. How? A: Create an internet NEG (`gcloud compute network-endpoint-groups create --network-endpoint-type=INTERNET_FQDN_PORT`) referencing the external origin, attach it to a backend service on the global external ALB, and enable CDN on that backend service. Cloud CDN supports external backends via internet NEGs — no VPN/Interconnect required for this pattern.
Q2: After deploying a fix, users still see the old asset for hours. Cache invalidation or shorter TTL? A: Immediate remediation is `gcloud compute url-maps invalidate-cdn-cache --path` (path-pattern based, takes effect in minutes but is rate-limited and not for routine use). The durable fix is versioned URLs or correct `Cache-Control` headers / cache-mode TTLs. Exam answers that "invalidate on every deploy" are wrong.
**Beyond the modules** — Study backend *buckets* (`gcloud compute backend-buckets create --gcs-bucket-name --enable-cdn` — RAD's GCS buckets are never CDN origins), cache modes (`USE_ORIGIN_HEADERS`, `CACHE_ALL_STATIC`, `FORCE_CACHE_ALL`), signed URLs/cookies, and negative caching. For the GKE gap above, the supported long-term pattern for Gateway is configuring CDN via `GCPBackendPolicy`'s sibling mechanisms or managing the backend service setting out-of-band; for the legacy Ingress controller it's `BackendConfig.spec.cdn`. **⚠️ Exam trap** — `FORCE_CACHE_ALL` caches *everything*, including responses with `Set-Cookie` or private data, and breaks dynamic content. Choose it only for pure-static backends; the safe default with well-behaved origins is `USE_ORIGIN_HEADERS`. --- ## 3.3 Configuring Cloud DNS > ⏱ ~45 min study · 💰 pennies for a test zone · ⚙️ Requires: nothing — concept-only **Why the exam cares** — Zone types (public/private), split-horizon, routing policies (weighted, geolocation, failover), DNSSEC, hybrid DNS (forwarding zones, server policies, DNS peering, cross-project binding), and the GKE external-DNS pattern are all enumerated exam topics. **How RAD implements it** — Not implemented: no Cloud DNS resources exist anywhere in the four foundation modules. The modules sidestep DNS in two verifiable ways: the Cloud Run engine derives a zero-configuration hostname from the LB's static IP via the public nip.io wildcard DNS service (IP `34.56.78.90` → `34-56-78-90.nip.io`) and issues a Google-managed certificate for it, and Certificate Manager domain certs for `application_domains` stay `PROVISIONING` until *you* create the DNS records pointing at the LB IP — an external dependency the deployment portal expects you to satisfy. **Try it** 1. Create a public zone in a scratch project and point it at your deployed LB: ```bash gcloud dns managed-zones create pcne-lab --dns-name="lab.example.com." \ --description="PCNE practice" gcloud dns record-sets create app.lab.example.com. --zone=pcne-lab \ --type=A --ttl=300 --rrdatas= ``` 2. Build the hybrid-relevant private-zone pattern against the RAD VPC: ```bash gcloud dns managed-zones create internal-zone --dns-name="internal.lab." \ --visibility=private --networks=vpc-network- --description="split horizon demo" ``` 3. Try a failover routing policy (exam favorite): ```bash gcloud dns record-sets create svc.lab.example.com. --zone=pcne-lab --type=A --ttl=60 \ --routing-policy-type=FAILOVER \ --routing-policy-primary-data= \ --routing-policy-backup-data-type=GEO \ --routing-policy-backup-data="us-central1=" ``` 4. You know it worked when `dig app.lab.example.com @8.8.8.8` resolves once registrar NS delegation is in place, and the private record resolves only from a VM inside the RAD VPC. **Check yourself**
Q1: On-prem resolvers must resolve records in a Cloud DNS private zone. What do you configure? A: An inbound DNS server policy on the VPC (`gcloud dns policies create --enable-inbound-forwarding --networks=...`), which allocates inbound forwarder IPs in each subnet region; point on-prem conditional forwarders at those IPs over VPN/Interconnect. The reverse direction (cloud → on-prem names) uses a *forwarding zone* targeting on-prem DNS servers.
Q2: Two VPCs that are NOT peered must both resolve a private zone owned by a hub project. Options? A: Either bind the private zone to additional networks (cross-project binding — the zone lives in one project but lists VPCs from others), or create DNS *peering* zones in the consumer VPCs targeting the producer VPC. DNS peering works without VPC peering — DNS and data-plane connectivity are independent, a recurring exam distinction.
**Beyond the modules** — Work through "Cloud DNS overview", "DNS server policies", "DNS routing policies and health checks" (geolocation + failover with health-checked internal LB targets), DNSSEC enablement (`gcloud dns managed-zones update --dnssec-state=on` plus DS record at the registrar), and the **external-DNS** operator for GKE (annotated Services/Ingresses auto-create Cloud DNS records — the natural automation for RAD's manually-pointed `application_domains`). **⚠️ Exam trap** — A private zone is visible only to the VPC networks it is *authorized* for — not to peered VPCs, not on-prem, not other projects — unless you add bindings, DNS peering, or forwarding. "It's private, so the peer can see it" is always wrong. --- # PCNE Certification Preparation Guide: Section 4 — Configuring and implementing hybrid and multicloud network interconnectivity (~16% of the exam) PCNE Certification Preparation Guide: Section 4 — Configuring and implementing hybrid and multicloud network interconnectivity (~16% of the exam) > 📚 **Official exam guide:** [Professional Cloud Network Engineer certification](https://cloud.google.com/learn/certification/cloud-network-engineer) — always confirm section weightings against the current Google Cloud exam guide. Honest framing up front: the RAD foundation modules implement **none** of this section. There is no Interconnect, no VPN, no BGP session, and no NCC hub anywhere in `Services_GCP`, `App_CloudRun`, `App_GKE`, or `App_Common`. What the platform *does* give you is a realistic cloud-side anchor — a custom VPC (`vpc-network-{prefix}`), a Cloud Router (ASN `64514`), and a PSA peering with custom-route export — against which every hybrid pattern in this section can be practiced in a scratch project. Deploy the **VPC Foundation** profile so those anchors exist, then treat this guide as a structured study program. Expect ~16% of the exam from material you will not see running in RAD. --- ## 4.1 Configuring Cloud Interconnect > ⏱ ~60 min study · 💰 none (Interconnect cannot be meaningfully lab'd without a circuit) · ⚙️ Requires: VPC Foundation profile for the target VPC only **Why the exam cares** — Dedicated vs Partner Interconnect selection (capacity, colocation presence, L2 vs L3 Partner models), VLAN attachment configuration, the 99.9% vs 99.99% SLA topologies, Cross-Cloud Interconnect to other clouds, and encrypting Interconnect with HA VPN over Interconnect or MACsec. **How RAD implements it** — Not implemented by the foundation modules. **Try it** 1. You can rehearse the *cloud-side* objects without a physical circuit (attachments in a scratch project remain unprovisioned but show the workflow): ```bash gcloud compute interconnects locations list --format="table(name,city,availabilityZone)" gcloud compute interconnects attachments partner create my-attachment \ --region=us-central1 \ --router=vpc-network--nat-gw-us-central1 \ --edge-availability-domain=availability-domain-1 gcloud compute interconnects attachments describe my-attachment \ --region=us-central1 --format="yaml(pairingKey,state)" ``` 2. Note the `pairingKey` — the token you hand to a Partner Interconnect provider — and the `PENDING_PARTNER` state. 3. You know you understand it when you can say why the attachment references the *Cloud Router* (BGP termination) and what changes for a Dedicated attachment (you specify the `--interconnect` instead of getting a pairing key). Delete the attachment afterward to avoid charges. **Check yourself**
Q1: 5 Gbps needed, no presence in a colocation facility, and traffic must be encrypted. Design? A: Partner Interconnect (no colo presence rules out Dedicated, which starts at 10 Gbps physical circuits) with HA VPN over Interconnect for encryption — Interconnect itself is unencrypted, and MACsec availability depends on the connection type/location. For an L3 partner, the partner's router peers with Cloud Router on your behalf; for L2, you run BGP to Cloud Router yourself.
Q2: What exactly earns the 99.99% Interconnect SLA? A: Four VLAN attachments on at least two Dedicated/Partner connections in **two metros**, attachments spread across both edge availability domains in each metro, Cloud Routers in at least two regions, and **global** dynamic routing mode — plus on-prem redundancy. Two attachments in one metro across both availability domains gets only 99.9%.
**Beyond the modules** — Study "Cloud Interconnect overview", "Partner Interconnect provisioning", "Cross-Cloud Interconnect" (Google-managed dedicated links to AWS/Azure, same VLAN-attachment + Cloud Router model), "HA VPN over Cloud Interconnect" (VPN gateways on the attachments, doubles as the encryption answer), and MACsec for Cloud Interconnect. Memorize: Dedicated = 10/100 Gbps physical, your colo; Partner = 50 Mbps–50 Gbps via provider; attachments are regional and bind to a Cloud Router. **⚠️ Exam trap** — Dataplane: an Interconnect *connection* is physical and metro-scoped; the *VLAN attachment* is the regional, routed object. A connection in Chicago can serve attachments to routers in any region (egress costs differ), but SLA math counts metros and availability domains, not regions alone. --- ## 4.2 Configuring a site-to-site IPSec VPN > ⏱ ~60 min hands-on possible in a scratch project · 💰 ~$0.05/h per tunnel + egress · ⚙️ Requires: VPC Foundation profile as one side **Why the exam cares** — HA VPN (two interfaces, 99.99% with correct tunnel topology, BGP-only) vs Classic VPN (single interface, 99.9%, supports static policy/route-based tunnels), VPN between two VPCs, and interaction with dynamic routing mode. **How RAD implements it** — Not implemented by the foundation modules. The deployed Cloud Router (`{net}-nat-gw-{region}`, ASN 64514) is technically capable of hosting VPN BGP sessions, and the VPC's subnets plus the PSA range (custom-route export already enabled on the peering) are exactly what you would advertise to a remote site. **Try it** 1. This one you *can* fully lab: create a second VPC in a scratch project and build HA VPN between it and the RAD VPC: ```bash # One HA VPN gateway per side (note: two interfaces each, automatically) gcloud compute vpn-gateways create rad-side-gw --network=vpc-network- --region=us-central1 gcloud compute vpn-gateways create remote-side-gw --network=scratch-vpc --region=us-central1 gcloud compute routers create remote-router --network=scratch-vpc --region=us-central1 --asn=65010 # Tunnels (repeat with interface 1 / peer counterpart for full HA) gcloud compute vpn-tunnels create rad-to-remote-0 \ --region=us-central1 --vpn-gateway=rad-side-gw --interface=0 \ --peer-gcp-gateway=remote-side-gw --shared-secret=SECRET \ --router=vpc-network--nat-gw-us-central1 --ike-version=2 gcloud compute routers add-interface vpc-network--nat-gw-us-central1 \ --interface-name=if-tun0 --vpn-tunnel=rad-to-remote-0 \ --ip-address=169.254.0.1 --mask-length=30 --region=us-central1 gcloud compute routers add-bgp-peer vpc-network--nat-gw-us-central1 \ --peer-name=remote-peer-0 --interface=if-tun0 \ --peer-ip-address=169.254.0.2 --peer-asn=65010 --region=us-central1 ``` 2. Verify: `gcloud compute vpn-tunnels describe rad-to-remote-0 --region=us-central1 --format="value(status)"` → `ESTABLISHED`, then check learned routes with `gcloud compute routers get-status`. 3. You know it worked when a VM (or the NFS server VM, tag `nfsserver`) in the RAD VPC can ping a scratch-VPC VM through the tunnel — remember the intra-VPC firewall rules only allow internal CIDRs, so add an ingress allow for the remote CIDR first. **Check yourself**
Q1: An on-prem device supports only policy-based VPN with static routing. HA VPN or Classic? A: Classic VPN — HA VPN requires BGP. Policy-based/route-based static tunnels exist only on Classic VPN (99.9% SLA, deprecated for new dynamic deployments). The better exam answer when the device *can* do BGP is always HA VPN with two tunnels for 99.99%.
Q2: HA VPN is up but on-prem can't reach Cloud SQL's private IP, while VMs can. Why? A: The Cloud SQL instance lives in the *producer* VPC behind PSA peering. Peering routes aren't advertised over BGP unless the consumer exports them: enable custom-route export on the PSA peering (RAD already does) **and** add the PSA /16 to Cloud Router's custom advertised routes — the PSA range is not a subnet of the consumer VPC, so default advertisement misses it.
**Beyond the modules** — Study "HA VPN topologies" (GCP↔GCP, GCP↔on-prem with 2 or 4 tunnels, active/active vs active/passive and the bandwidth-halving caveat), IKE ciphers, and tunnel troubleshooting (Section 5.2). Know link-local BGP addressing (169.254.x.x/30 per tunnel interface, as in the commands above). **⚠️ Exam trap** — Creating two tunnels from *one* HA VPN gateway interface to the peer doesn't earn 99.99% — the SLA requires tunnels from **both** interfaces of the HA VPN gateway, matched to redundant peer endpoints. --- ## 4.3 Configuring Cloud Router > ⏱ ~30 min · 💰 none · ⚙️ Requires: VPC Foundation profile **Why the exam cares** — Cloud Router is the BGP speaker behind every dynamic hybrid topology: ASN choice, MED (advertised route priority), custom advertised routes, learned-route priority (`--advertised-route-priority`, base-priority adjustment), BFD for fast failover, MD5 auth, and best-path selection mode. **How RAD implements it** — Partially. The platform creates a real Cloud Router named `{net}-nat-gw-{region}` with ASN 64514 and no advertised groups, whose only consumer is the Cloud NAT gateway (NAT applied to all subnetworks and all IP ranges). The inline path creates a plain Cloud Router with no BGP configuration at all. No BGP peers, no custom advertisements, no BFD exist anywhere. **Try it** 1. Inspect the live router and add a custom advertisement (harmless with no peers — it changes what *would* be advertised): ```bash gcloud compute routers describe vpc-network--nat-gw-us-central1 \ --region=us-central1 --format="yaml(bgp,nats[].name)" gcloud compute routers update vpc-network--nat-gw-us-central1 \ --region=us-central1 \ --advertisement-mode=CUSTOM \ --set-advertisement-groups=ALL_SUBNETS \ --set-advertisement-ranges==PSA-range gcloud compute routers get-status vpc-network--nat-gw-us-central1 \ --region=us-central1 ``` 2. You know it worked when `describe` shows `advertiseMode: CUSTOM` with your range. Revert to `--advertisement-mode=DEFAULT` afterward to avoid Terraform drift on the next platform apply. **Check yourself**
Q1: Two Interconnect attachments; you want one preferred for traffic *to* on-prem and on-prem to prefer one path *back*. Which knobs? A: Inbound to Google: on-prem influences Google's choice via MED it sends; Google-side preference for learned identical prefixes follows the route's priority (derived from MED + inter-regional cost). Outbound from Google: set `--advertised-route-priority` (MED Google sends) per BGP peer — lower MED = more preferred by on-prem. Asymmetry questions almost always resolve to "MED in each direction".
Q2: Failover between two VPN tunnels takes ~60s. How do you make it sub-second? A: Enable BFD on both BGP peers (`gcloud compute routers update-bgp-peer --bfd-session-initialization-mode=ACTIVE --bfd-min-transmit-interval=...`). BGP hold timers alone are tens of seconds; BFD detects dataplane failure in hundreds of milliseconds and tears the route down immediately.
**Beyond the modules** — Study "Cloud Router overview": ASN rules (private 64512–65534/4200000000+ ranges; Google's side of PSA-style peering vs your `--asn`), regional vs global dynamic routing and how it changes which subnets the router advertises, MD5 authentication on BGP sessions, and legacy vs standard best-path selection modes. Also note each NAT-only router (RAD's case) still counts against router quotas. **⚠️ Exam trap** — Cloud Router advertises *subnet* routes (per routing mode) by default; **custom routes, peering ranges (like RAD's PSA /16), and secondary ranges outside the mode's scope require CUSTOM advertisement mode**. "It's in the VPC so it's advertised" fails for PSA ranges. --- ## 4.4 Configuring Network Connectivity Center > ⏱ ~30 min study · 💰 none · ⚙️ Requires: nothing — concept-only **Why the exam cares** — Section 4's NCC angle is the *hybrid* one (vs Section 2.3's VPC-spoke angle): VPN/Interconnect attachments as hybrid spokes, site-to-site data transfer through Google's backbone, router appliances (SD-WAN integration) as spokes peering BGP with Cloud Router, and the transitivity rules. **How RAD implements it** — Not implemented by the foundation modules. **Try it** 1. If you built the 4.2 HA VPN lab, promote it into an NCC topology in the scratch project: ```bash gcloud network-connectivity hubs create hybrid-hub gcloud network-connectivity spokes linked-vpn-tunnels create vpn-spoke \ --hub=hybrid-hub --region=us-central1 \ --vpn-tunnels=rad-to-remote-0,rad-to-remote-1 \ --site-to-site-data-transfer gcloud network-connectivity spokes list --hub=hybrid-hub ``` 2. You know it worked when the spoke shows `ACTIVE` and the hub's route table includes prefixes learned from the tunnels. **Check yourself**
Q1: Two branch offices, each VPN'd to GCP, need branch-to-branch traffic without new circuits. Solution? A: NCC hub with both VPN tunnel sets as hybrid spokes and site-to-site data transfer enabled — branch traffic transits Google's backbone between the spokes. Without NCC, two VPN tunnels into one VPC do *not* forward traffic between each other (a VPC is not a transit router for external-to-external flows).
Q2: Where do router appliances fit? A: A router appliance spoke is a VM (typically a vendor SD-WAN appliance) in the VPC that BGP-peers with Cloud Router; NCC then treats its learned prefixes like any hybrid spoke. It's the answer pattern for "integrate our SD-WAN fabric with Google Cloud".
**Beyond the modules** — Study "NCC site-to-site data transfer" (supported regions, billing), router-appliance BGP setup, mixing VPC spokes with hybrid spokes (hub provides the transitivity that peering lacks — but VPC spokes and hybrid spokes interoperate per documented rules, not unconditionally), and Private NAT at the hub for overlapping site ranges. **⚠️ Exam trap** — "VPC peering + VPN = on-prem reaches the peered VPC" is false (non-transitive). The two supported fixes are: export/import custom routes across the peering with Cloud Router advertising them, or restructure with NCC spokes. Recognize which the scenario allows. --- # PCNE Certification Preparation Guide: Section 5 — Managing, monitoring, and troubleshooting network operations (~14% of the exam) PCNE Certification Preparation Guide: Section 5 — Managing, monitoring, and troubleshooting network operations (~14% of the exam) > 📚 **Official exam guide:** [Professional Cloud Network Engineer certification](https://cloud.google.com/learn/certification/cloud-network-engineer) — always confirm section weightings against the current Google Cloud exam guide. This section tests operating a network: which logs exist and where to enable them, which metrics matter for VPN/Interconnect/LB/NAT, and how to use Network Intelligence Center to diagnose reachability and performance. RAD gives you a live network to instrument — a global external ALB with request logging already on, health-checked managed instance groups, and alerting plumbing — but deliberately ships with VPC Flow Logs, NAT logging, and firewall logging **disabled**, which makes enabling them your lab exercise. Deploy the **VPC Foundation** and **Global Edge** profiles. Modules exercised: `Services_GCP` and `App_CloudRun`. --- ## 5.1 Logging and monitoring with Google Cloud Observability > ⏱ ~60 min · 💰 log-volume charges if you enable flow logs at high sampling · ⚙️ Requires: VPC Foundation + Global Edge profiles **Why the exam cares** — You must know which network logs are *opt-in* (VPC Flow Logs per subnet, firewall rules logging per rule, Cloud NAT logging per NAT, DNS logging per policy/zone) versus on by default, where each lands in Logs Explorer, and the headline metrics for VPN tunnels, Interconnect attachments, Cloud Routers, load balancers, and NAT gateways. **How RAD implements it** — Verified state of the deployed estate: | Telemetry | RAD state | |---|---| | LB request logs | **Enabled** — backend service request logging at full sample rate | | VPC Flow Logs | Not enabled (no flow logging on any subnet) | | Firewall rules logging | Not enabled (no logging on any firewall rule) | | Cloud NAT logging | Not enabled | | Cloud Audit Logs | `enable_audit_logging` (default `false`) → allServices ADMIN_READ/DATA_READ/DATA_WRITE | | GKE logging/monitoring | `SYSTEM_COMPONENTS` + `WORKLOADS` logging, managed Prometheus | | Alerting | `support_users` → email channels; `alert_policies` list (metric, comparison, threshold). `uptime_check_config` (default `{ enabled = false, path = "/" }`) creates a `-uptime-check` + alert policy, once enabled, when the endpoint is publicly reachable; internal-only deployments get none | **Try it** 1. Read the ALB request logs that are already flowing (generate traffic first): ```bash gcloud logging read 'resource.type="http_load_balancer"' --limit=5 \ --format="table(timestamp,httpRequest.status,httpRequest.requestUrl)" ``` 2. Enable VPC Flow Logs on the main subnet — the exam's canonical opt-in: ```bash gcloud compute networks subnets update vpc-network--subnet-us-central1 \ --region=us-central1 --enable-flow-logs \ --logging-aggregation-interval=interval-5-sec --logging-flow-sampling=0.5 gcloud logging read 'logName:"compute.googleapis.com%2Fvpc_flows"' --limit=3 ``` 3. Enable NAT logging on the platform NAT (errors-only is the cheap, high-signal choice): ```bash gcloud compute routers nats update vpc-network--nat-gw-us-central1 \ --router=vpc-network--nat-gw-us-central1 --region=us-central1 \ --enable-logging --log-filter=ERRORS_ONLY ``` 4. In **Console > Monitoring > Metrics explorer**, chart `loadbalancing.googleapis.com/https/backend_latencies` for your LB and `router.googleapis.com/nat/dropped_sent_packets_count` for the NAT. 5. You know it worked when flow-log entries show 5-tuple records with `src_instance`/`dest_instance` annotations and the NAT log stream stays empty until you exhaust ports (see 6.3). **Check yourself**
Q1: Security asks for a record of every allowed and denied connection to the NFS VM. What do you enable, and what's the catch? A: Firewall rules logging on the relevant rules (`gcloud compute firewall-rules update vpc-network--fw-allow-nfs-tcp --enable-logging`). Catches: logging is per-*rule*, only TCP/UDP rules can log, and there is no log for traffic dropped by the implied deny — you must create an explicit low-priority deny rule with logging to capture denials. VPC Flow Logs complement this but sample flows and don't record the rule decision.
Q2: Which metric tells you an Interconnect VLAN attachment is approaching capacity, and which tells you a VPN tunnel's bandwidth ceiling? A: Attachment: `interconnect.googleapis.com/network/attachment/sent_bytes_count` (vs configured capacity). VPN: `vpn.googleapis.com/network/sent_bytes_count` per tunnel against the ~3 Gbps-per-tunnel ceiling — the standard answer for "VPN slow under load" is adding tunnels (ECMP), not resizing a tunnel.
**Beyond the modules** — Study the per-product logging pages: "VPC Flow Logs" (sampling, aggregation, metadata annotations, cost levers), "Firewall Rules Logging", "Cloud NAT logging" (TRANSLATIONS_ONLY vs ERRORS_ONLY), "Cloud DNS logging" (query logs via server policies for private zones; public-zone query logging on the zone), VPC-SC audit logs (denials appear in the *org-level* policy audit log), and NCC/Cloud Router logs (`bgp_routes` status via `get-status`, router task logs). Also Firewall Insights and Flow Analyzer (5.3). **⚠️ Exam trap** — VPC Flow Logs capture only VM-attached flows in the subnet (including GKE nodes); they do not capture traffic to *global* LB frontends (use LB logs) or PSA producer-side flows. Picking "enable flow logs" to debug an LB 502 is wrong — backend service logs and health checks are the tools. --- ## 5.2 Maintaining and troubleshooting connectivity issues > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: VPC Foundation profile (NFS VM enabled, default) **Why the exam cares** — Scenario triage: LB drains and traffic redirection during maintenance, VPN tunnels that won't establish (IKE mismatch, overlapping selectors), Interconnect/BGP sessions down, and using flow logs, firewall logs, and Packet Mirroring to localize a fault. **How RAD implements it** — The platform's self-healing data path is the best live material: the NFS/Redis VM runs in a managed instance group with **TCP health checks on 2049 and 6379** and auto-healing — kill the process and watch detection, recreation, and recovery, the same observe-diagnose-recover loop the exam tests. The connection-draining concept appears on the Cloud Run side as `traffic_split` revision shifting and old-revision pruning, and the Cloud Run backend service (30s timeout) is the object you would drain in a classic ALB maintenance scenario. There is no VPN/Interconnect to troubleshoot — build the Section 4.2 scratch lab to practice those. **Try it** 1. Watch auto-healing catch a failure on the NFS VM: ```bash gcloud compute health-checks list --format="table(name,type,tcpHealthCheck.port)" # SSH via IAP (allowed by the fw-allow-iap-ssh rule) and stop the NFS service gcloud compute ssh --zone=us-central1-a --tunnel-through-iap \ --command="sudo systemctl stop nfs-kernel-server" watch -n 10 "gcloud compute instance-groups managed list-instances \ --zone=us-central1-a --format='table(name,instanceStatus,currentAction)'" ``` 2. Observe `currentAction: RECREATING` (or VERIFYING) as the health check fails, then service restoration. 3. Diagnose a deliberate firewall break: temporarily delete the health-check allow rule and watch the MIG flap, then restore it: ```bash gcloud compute firewall-rules describe vpc-network--fw-allow-lb-hc ``` 4. You know it worked when you can correlate the MIG recreation event in **Console > Compute Engine > Instance groups** with the health-check state change. **Check yourself**
Q1: You must take an ALB backend MIG out of service for maintenance with zero dropped requests. Steps? A: Set connection draining on the backend service (`--connection-draining-timeout`), then remove/abandon the backend (or set its capacity-scaler to 0): in-flight requests complete during the drain window while new requests route to remaining backends. For RAD's serverless NEG the analogue is shifting `traffic_split` to another revision before deleting the old one.
Q2: HA VPN tunnel shows ESTABLISHED but BGP session stays down. Top causes? A: Link-local interface/peer IPs mismatched between the Cloud Router interface and the peer config; wrong peer ASN; on-prem firewall blocking TCP/179 over the tunnel; or MD5 auth mismatch. `gcloud compute routers get-status --region=...` shows the BGP session state and is the first diagnostic the exam expects. (Tunnel not ESTABLISHED at all → IKE version/shared-secret/peer-IP issues instead.)
**Beyond the modules** — Practice the canonical triage flows: "Troubleshoot Cloud VPN" (IKE phase failures, rekey drops, MTU/MSS clamping — VPN MTU ~1460 minus ESP overhead, clamp MSS to ~1360), "Troubleshoot Cloud Interconnect" (LACP, light levels, attachment state), BGP flap diagnosis with BFD counters, and Packet Mirroring as the deep-inspection tool when logs aren't enough (see 6.4). **⚠️ Exam trap** — A failing LB health check is a *firewall* question more often than an application question: backends must allow `35.191.0.0/16` and `130.211.0.0/22` (or `35.235.240.0/20` for some regional paths). RAD encodes this twice — VPC rule `fw-allow-lb-hc` and the GKE NetworkPolicy ingress blocks — because both layers can independently break health checking. --- ## 5.3 Monitoring, maintaining, and troubleshooting latency and traffic flow > ⏱ ~40 min · 💰 Connectivity Tests are free in moderate use · ⚙️ Requires: VPC Foundation profile (any deployment gives you test targets) **Why the exam cares** — Network Intelligence Center's five tools each answer a specific question: **Network Topology** (what talks to what, with throughput), **Connectivity Tests** (would/does a 5-tuple reach its destination, and which rule/route decides), **Performance Dashboard** (zone-to-zone latency/loss baselines), **Firewall Insights** (shadowed/overly-permissive/unused rules), **Network Analyzer** (continuous config checks — IP exhaustion, route conflicts, misconfigured PSA), plus **Flow Analyzer** over VPC Flow Logs. **How RAD implements it** — Not implemented as resources (nothing to configure), but every tool can be pointed *at* the deployed estate, which is the realistic exam skill. The RAD VPC offers ready-made test cases: VM→Cloud SQL private IP through PSA, VM→VM under the intra-VPC rules, internet→LB frontend, and pod-range→NFS paths. **Try it** 1. Run a Connectivity Test from the NFS VM to the Cloud SQL private IP — it traverses the PSA peering and shows the full forwarding trace: ```bash gcloud network-management connectivity-tests create nfs-to-sql \ --source-instance=projects//zones/us-central1-a/instances/ \ --destination-ip-address= \ --destination-port=5432 --protocol=TCP gcloud network-management connectivity-tests describe nfs-to-sql \ --format="yaml(reachabilityDetails.result,reachabilityDetails.traces[0].steps[].description)" ``` 2. Create a deliberately blocked test (e.g., destination port 25 to an external IP) and read which step denies it. 3. Open **Console > Network Intelligence > Network Topology** and find the LB → Cloud Run edge generated by your test traffic; then **Network Analyzer** and look for insights against the GKE secondary ranges (IP-utilization warnings appear as ranges fill). 4. You know it worked when the first test returns `result: REACHABLE` with a trace step showing the peering hop, and the blocked test names the specific deny. **Check yourself**
Q1: Users in Frankfurt report slow access to us-central1 backends, but app metrics look healthy. Which NIC tool first? A: Performance Dashboard — it shows Google-measured inter-region latency and packet loss for your project's traffic versus the global baseline, separating "the network is slow" from "the app is slow". If the network is clean, move to LB `backend_latencies` vs `total_latencies` to split origin time from edge time.
Q2: A new deny rule was added and an app broke, but there are dozens of candidate rules. Fastest path to the culprit? A: A Connectivity Test for the exact 5-tuple — its trace names the matched rule (allow or deny) at each step, including implied rules. Firewall Insights complements it for hygiene (shadowed-rule detection: a rule never hit because a higher-priority rule masks it).
**Beyond the modules** — Study "Network Analyzer insights reference" (it flags exactly the things RAD's design prevents: overlapping PSA allocations, GKE pod-range exhaustion, invalid next hops), "Flow Analyzer" (BigQuery-backed analysis of VPC Flow Logs — requires you to have enabled flow logs, as in 5.1), and Connectivity Tests' *live data plane analysis* (sends real probe packets for supported paths, vs the always-available config analysis). **⚠️ Exam trap** — Connectivity Tests' configuration analysis can return REACHABLE while the workload still fails: it models VPC config (routes/firewalls/peering), not on-VM firewalls (iptables), application listeners, or Kubernetes NetworkPolicy. RAD's `enable_network_segmentation` policies are invisible to it — a denied pod connection with a green Connectivity Test is expected, not contradictory. --- # PCNE Certification Preparation Guide: Section 6 — Configuring, implementing and managing a cloud network security solution (~13% of the exam) PCNE Certification Preparation Guide: Section 6 — Configuring, implementing and managing a cloud network security solution (~13% of the exam) Network security is RAD's strongest suit in this exam after GKE networking. Both deployment engines build a production-shaped **Cloud Armor** policy (preconfigured OWASP rules, Adaptive Protection, rate-based banning), the platform VPC implements **tag-based firewall micro-segmentation**, and **Cloud NAT** handles all internet egress for private workloads. NGFW policies, Secure Web Proxy, NVAs, and Packet Mirroring are study-only. Deploy the **Global Edge** profile for 6.1 and the **VPC Foundation** profile for 6.2–6.4. Modules exercised: `App_CloudRun`, `App_GKE`, `Services_GCP`. --- ## 6.1 Implementing and managing Google Cloud Armor > ⏱ ~60 min · 💰 Cloud Armor policy + per-request charges · ⚙️ Requires: Global Edge profile (`enable_cloud_armor = true`) **Why the exam cares** — Cloud Armor questions test rule mechanics (priority, preconfigured WAF expressions, custom CEL), the edge-policy vs backend-policy split, rate limiting (`throttle` vs `rate_based_ban`), Adaptive Protection for L7 DDoS, and bot management. **How RAD implements it** — Both engines create the same verified Cloud Armor policy shape: | Priority | Rule | Action | |---|---|---| | 100 | `admin_ip_ranges` allowlist | `allow` (bypasses WAF rules) | | 1000–1003 | `evaluatePreconfiguredExpr('sqli-v33-stable')`, `xss-v33-stable`, `lfi-v33-stable`, `rce-v33-stable` | `deny(403)` | | 2000 | rate-based ban: 500 requests/60 s per IP, exceed → `deny(429)`, 300 s ban | rate-based ban | | 2147483647 | default `*` | `allow` | Plus Adaptive Protection with Layer 7 DDoS defense enabled. Attachment differs by engine: App_CloudRun sets the security policy on the backend service and **forces ingress to `internal-and-cloud-load-balancing`** so direct `*.run.app` access can't bypass the WAF; App_GKE attaches via the `GCPBackendPolicy`'s default security policy and alternatively accepts an externally managed policy through `cloud_armor_policy_name` (default `default-waf-policy`) when `enable_cloud_armor = false`. The priority-100 `admin_ip_ranges` allow rule now exists in **both** policies; on Cloud Run the same variable *additionally* feeds the VPC-SC access levels. Both engines now behave the same way here: neither requires a domain for Cloud Armor — App_CloudRun derives an `.nip.io` certificate and App_GKE's Gateway derives an `.nip.io` one. **Try it** 1. Read the deployed policy and match it to the table: ```bash gcloud compute security-policies describe -waf-policy \ --format="yaml(rules[].priority,rules[].action,rules[].match,adaptiveProtectionConfig)" ``` 2. Trigger the WAF and the rate limiter: ```bash # SQLi probe → expect 403 curl -s -o /dev/null -w "%{http_code}\n" "https:///?q=1%27%20OR%20%271%27=%271" # Burst past 500 req/min → expect 429s, then a 300 s ban for i in $(seq 1 600); do curl -s -o /dev/null -w "%{http_code} " "https:///"; done | tr ' ' '\n' | sort | uniq -c ``` 3. Inspect enforcement in **Console > Network Security > Cloud Armor policies > (policy) > Logs**, or: ```bash gcloud logging read 'resource.type="http_load_balancer" AND jsonPayload.enforcedSecurityPolicy.name!=""' \ --limit=5 --format="table(httpRequest.status,jsonPayload.enforcedSecurityPolicy.outcome,jsonPayload.enforcedSecurityPolicy.priority)" ``` 4. You know it worked when the SQLi probe logs `outcome: DENY, priority: 1000` and the burst shows 200s flipping to 429s. **Check yourself**
Q1: Legitimate admin traffic from the office keeps tripping the XSS rule on the GKE app. Fix without weakening protection for everyone? A: Populate `admin_ip_ranges` — the module inserts an `allow` rule at priority 100, which evaluates *before* the WAF rules (lower number = earlier). That is the generic Cloud Armor answer too: scoped allow rule above the blocking rule. Both engines now insert this rule; on Cloud Run the same variable also feeds the VPC-SC access levels. The manual equivalent is `gcloud compute security-policies rules create 100 --security-policy=-waf-policy --src-ip-ranges= --action=allow`.
Q2: When do you need an *edge* security policy instead of the backend policy RAD uses? A: Edge policies evaluate at Google's edge before the cache, so they can filter requests served from Cloud CDN cache hits and protect backend buckets (GCS). Backend policies (RAD's type) evaluate only on cache misses / non-CDN traffic. "Block country X from cached content" → edge policy.
Q3: Why does enabling Cloud Armor on App_CloudRun change the service's ingress setting? A: Cloud Armor enforces only on traffic that traverses the load balancer. The default Cloud Run URL (`*.run.app`) would bypass it, so the module overrides ingress to `internal-and-cloud-load-balancing` — the standard exam-grade companion control. The same logic appears as "use `internal-and-cloud-load-balancing` + LB" whenever WAF/CDN/IAP-on-LB must not be bypassable.
**Beyond the modules** — Study: rate limiting variants (throttle vs RAD's rate-based ban; enforce-on-key options beyond IP — HTTP header, cookie, XFF-IP), preconfigured rule *sensitivity levels* and opt-out fields (`evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})`), bot management with reCAPTCHA action-tokens and redirect actions, Google Threat Intelligence expressions (`evaluateThreatIntelligence('iplist-known-malicious-ips')`), and Adaptive Protection's *granular models* + automatic rule deployment (RAD enables detection; triage of its suggested rules is manual). **⚠️ Exam trap** — Rule priority 0 is the *highest*; the default rule lives at 2147483647. A "deny all then allow" design that puts the deny at a low number blocks everything — order your allows above (numerically below) the deny. --- ## 6.2 Configuring NGFW policies and VPC firewall rules > ⏱ ~45 min · 💰 no additional cost (NGFW Enterprise endpoints would cost; not created) · ⚙️ Requires: VPC Foundation profile; GKE Network Lab for the NetworkPolicy layer **Why the exam cares** — The exam now distinguishes classic VPC firewall *rules* from NGFW (Cloud Firewall) *policies* — hierarchical, global, and regional — plus tags vs service accounts as targets, L7 inspection in NGFW Enterprise, rule logging, and micro-segmentation strategy. **How RAD implements it** — Classic per-network VPC firewall rules only, but with a textbook micro-segmentation pattern in the Services_GCP network: - **Tag-scoped service access**: rules target the `nfsserver` tag (tcp 111/2049/6379, udp 2049), the `redisserver` tag (tcp 6379), and the `httpserver`/`webserver` tags (tcp 80/443/8080/8443). The NFS VM template carries the `nfsserver` and `redisserver` tags. - **Source-range strategy**: intra-VPC allows are scoped to the computed internal CIDR set (subnets + GKE base ranges when GKE is on), not 0.0.0.0/0; the standalone NFS rules use the three RFC 1918 super-ranges. - **Source-tag refinement**: the inline path goes further — NFS/Redis ingress allows are scoped to the source tag `app-nfs-client-`, the tag carried by Cloud Run's Direct VPC egress interfaces, so only that workload reaches the file server. - **Special-range allows**: `35.235.240.0/20` (IAP TCP forwarding) for SSH, `130.211.0.0/22` + `35.191.0.0/16` for health checks. - **Layer above**: Kubernetes NetworkPolicy micro-segmentation via `enable_network_segmentation` (Section 2.4) and the multi-cluster Istio east-west rules (tcp 15012/15017/15443) when `gke_cluster_count > 1`. App_GKE intentionally creates no firewall rules — Gateway/LoadBalancer controllers auto-provision their LB firewall rules, and Autopilot nodes cannot carry custom tags (so a tag-scoped HTTP rule would be useless there). No hierarchical policies, no network firewall policies, no rule logging. **Try it** 1. Map every rule to its segmentation role: ```bash gcloud compute firewall-rules list --filter="network~vpc-network" \ --format="table(name,sourceRanges.list(),sourceTags.list(),targetTags.list(),allowed[].map().firewall_rule().list())" ``` 2. Prove tag-based enforcement: remove the `nfsserver` tag from the NFS instance and watch NFS mounts fail; re-add it. ```bash gcloud compute instances remove-tags --zone=us-central1-a --tags=nfsserver gcloud compute instances add-tags --zone=us-central1-a --tags=nfsserver ``` 3. Recreate one rule as a *network firewall policy* rule in a scratch VPC to feel the difference (policies attach to networks; rules use secure tags or service accounts): ```bash gcloud compute network-firewall-policies create pcne-policy --global gcloud compute network-firewall-policies rules create 1000 \ --firewall-policy=pcne-policy --global-firewall-policy \ --direction=INGRESS --action=allow --layer4-configs=tcp:2049 \ --src-ip-ranges=10.0.0.0/24 --enable-logging gcloud compute network-firewall-policies associations create \ --firewall-policy=pcne-policy --network= --global-firewall-policy ``` 4. You know it worked when the de-tagged instance stops accepting port-2049 connections within seconds — tag changes apply live, no restart. **Check yourself**
Q1: Tags or service accounts as firewall targets for a high-security workload? A: Service accounts (or IAM-governed *secure tags* in NGFW policies). Classic network tags are mutable by anyone with `instanceAdmin` on the VM — an attacker who can edit tags can re-scope firewall rules, exactly the manipulation you performed in the Try it. Service-account targets change only with a VM identity change, and secure tags require `tagUser` IAM. RAD uses classic tags for operational simplicity; know the harder answer.
Q2: An org must guarantee "deny tcp/22 from internet" across 200 projects, with project teams unable to override. Mechanism? A: A hierarchical firewall policy at the org/folder node with a deny rule — hierarchical rules evaluate *before* network policies and VPC rules, and `goto_next` vs `allow`/`deny` controls delegation. Per-project VPC rules (RAD's mechanism) cannot enforce this centrally.
**Beyond the modules** — Study: evaluation order (hierarchical → global network policy → regional network policy → VPC rules, modulated by the network's `firewall_policy_enforcement_order`), migration tooling from VPC rules to network policies, NGFW tiers (Essentials = policies/secure tags; Standard adds FQDN/geo/Threat Intelligence objects; Enterprise adds TLS-inspecting L7 IPS via firewall endpoints), and NGFW with GKE/Cloud LB traffic. Docs: "Cloud NGFW overview", "Hierarchical firewall policies", "Migrate VPC firewall rules". **⚠️ Exam trap** — The implied rules: every VPC has implied egress-allow and ingress-deny at priority 65535. "We never wrote an egress rule, so egress is blocked" is backwards — and RAD's NetworkPolicy layer exists partly because VPC firewalls alone leave *egress* wide open. --- ## 6.3 Controlling internet egress traffic with Cloud NAT and Secure Web Proxy > ⏱ ~30 min · 💰 NAT gateway hourly + per-GB · ⚙️ Requires: VPC Foundation profile **Why the exam cares** — Cloud NAT IP addressing (auto vs manual, and why allowlisting requires manual static IPs), static vs dynamic port allocation and port-exhaustion math, and when Secure Web Proxy (URL/FQDN-aware egress policy) replaces or complements NAT. **How RAD implements it** — Two NAT deployments, both real: the platform creates `{net}-nat-gw-{region}` applied to all subnetworks and all IP ranges; the inline path creates a Cloud NAT with automatic IP allocation and the same all-subnets scope. This is what lets the private-IP-only NFS VM, GKE nodes/pods, and `ALL_TRAFFIC`-egress Cloud Run reach the internet with no public IPs anywhere. Port allocation settings are left at defaults (dynamic allocation per current GCP defaults; no per-VM minimum-port pinning). Secure Web Proxy is not implemented. **Try it** 1. Verify the gateway and watch the NFS VM's egress identity: ```bash gcloud compute routers nats list --router=vpc-network--nat-gw-us-central1 \ --region=us-central1 gcloud compute routers nats describe vpc-network--nat-gw-us-central1 \ --router=vpc-network--nat-gw-us-central1 --region=us-central1 \ --format="yaml(natIpAllocateOption,sourceSubnetworkIpRangesToNat,minPortsPerVm,enableDynamicPortAllocation)" gcloud compute ssh --zone=us-central1-a --tunnel-through-iap \ --command="curl -s ifconfig.me" ``` 2. Convert to manual static NAT IPs — the allowlisting pattern: ```bash gcloud compute addresses create nat-egress-ip --region=us-central1 gcloud compute routers nats update vpc-network--nat-gw-us-central1 \ --router=vpc-network--nat-gw-us-central1 --region=us-central1 \ --nat-external-ip-pool=nat-egress-ip ``` 3. Re-run the `curl ifconfig.me` — it now returns your reserved address. 4. You know it worked when the reported egress IP equals `nat-egress-ip` and stays stable across VM recreation. (Revert afterward; the Terraform module will otherwise show drift.) **Check yourself**
Q1: A partner allowlists your egress IP, but after traffic growth some connections fail with timeouts and NAT logs show allocation drops. Diagnosis and fixes? A: Port exhaustion: each NAT IP provides ~64k ports shared across VMs; with static allocation each VM holds a fixed block (`min_ports_per_vm`). Fixes: enable dynamic port allocation (per-VM ports grow on demand between min and max), raise `min_ports_per_vm`, or add NAT IPs. The metric/log signals are `dropped_sent_packets_count` with reason OUT_OF_RESOURCES and ERRORS_ONLY NAT logs.
Q2: Compliance requires that workloads may reach only `*.github.com` and `pypi.org`. NAT or Secure Web Proxy? A: Secure Web Proxy — NAT is L3/L4 and cannot filter by hostname/URL. SWP is an explicit (or policy-routed) proxy with rules on FQDN/URL/path and SA/secure-tag source identity, deployed per region with its own certificate and Gateway resource. NAT and SWP commonly coexist: SWP for HTTP(S) policy, NAT for everything else.
**Beyond the modules** — Study "Cloud NAT port reservation" (the math), NAT rules (different IPs per destination), Private NAT (NCC/inter-VPC overlap cases), and "Secure Web Proxy overview" (`gcloud network-services gateways create --type=SECURE_WEB_GATEWAY`, `SecurityPolicy`/`UrlList` objects, TLS inspection option). **⚠️ Exam trap** — Cloud NAT never handles *inbound* connections — it is egress-only (responses to established flows excepted). "Use Cloud NAT to expose the private VM" is always wrong; inbound is load balancers, IAP TCP forwarding (`35.235.240.0/20`, which RAD allowlists for SSH), or protocol forwarding. --- ## 6.4 Implementing a self-managed network virtual appliance and Packet Mirroring > ⏱ ~30 min study · 💰 none unless you build the scratch lab · ⚙️ Requires: VPC Foundation profile for the analogue only **Why the exam cares** — Inserting third-party firewalls/IDS into a VPC path: multi-NIC NVAs spanning VPCs, internal passthrough LB as next hop for HA, policy-based routes steering selected traffic through the appliance, and Packet Mirroring for out-of-band inspection (the only way to capture full payloads agentlessly). **How RAD implements it** — Not implemented. The honest nearest analogue is the self-managed NFS/Redis VM: a single-NIC appliance VM run in a MIG with TCP health checks, auto-healing, a static internal IP, and tag-scoped firewall rules — the *operational* half of an NVA pattern (health-checked appliance behind a stable address) without the routing half (no second NIC, no ILB-as-next-hop, no custom routes pointing at it). No Packet Mirroring resources exist. **Try it** 1. Study the analogue's moving parts, then build the missing routing half in a scratch project: ```bash gcloud compute instance-templates describe \ --format="yaml(properties.networkInterfaces,properties.tags)" # Scratch lab: route selected traffic through an appliance via ILB next hop gcloud compute forwarding-rules create nva-ilb --load-balancing-scheme=INTERNAL \ --backend-service= --ip-protocol=TCP --ports=ALL \ --network= --subnet= --region=us-central1 gcloud compute routes create via-nva --network= \ --destination-range=0.0.0.0/0 --priority=800 \ --next-hop-ilb=nva-ilb ``` 2. For out-of-band inspection, mirror the RAD subnet to a collector ILB in a scratch setup: ```bash gcloud compute packet-mirrorings create rad-mirror --region=us-central1 \ --network=vpc-network- \ --collector-ilb= \ --mirrored-subnets=vpc-network--subnet-us-central1 ``` 3. You know it worked when tcpdump on the collector VM shows cloned packets (both directions) from the mirrored subnet. **Check yourself**
Q1: An NVA must inspect traffic between a "trusted" and "untrusted" VPC. Why multi-NIC, and what's the routing rule? A: Each NIC attaches to a different VPC (NICs are fixed at VM creation), making the appliance the only L3 path between them; each VPC gets a custom route with next hop the appliance's NIC IP — or, for HA, an internal passthrough LB per VPC fronting an NVA MIG with `--next-hop-ilb`. Symmetric routing matters: replies must traverse the same appliance, which is where policy-based routes (which can also steer by source) come in for multi-NIC HA designs.
Q2: Security wants full packet capture of east-west traffic for IDS without touching workloads. Flow logs, firewall logs, or Packet Mirroring? A: Packet Mirroring — it clones entire packets (headers + payload) to a collector ILB backed by IDS instances. Flow logs are sampled 5-tuple metadata; firewall logs record rule decisions. Mirroring filters (CIDR/protocol/direction) keep collector volume manageable; mirrored traffic is charged egress.
**Beyond the modules** — Study "Packet Mirroring overview" (policy scoping by subnet/tag/instance, collector must be an internal passthrough LB with `--is-mirroring-collector` on the forwarding rule, same region), "Internal TCP/UDP load balancer as next hop" (symmetric hashing, no health-check-based failover to a different region), policy-based routes for NVA insertion with `--next-hop-ilb` and skip-rules for the appliance's own subnet, and the managed alternative positioning: Cloud IDS / NGFW Enterprise vs self-managed NVAs. **⚠️ Exam trap** — A custom static route's next-hop *instance* must have IP forwarding enabled (`--can-ip-forward`, set at creation) or packets are dropped silently. It's the most common "NVA routing doesn't work" cause — before blaming routes or firewalls, check `canIpForward` on the appliance. --- # Professional Cloud DevOps Engineer (PDE) Certification Lab Map > 📚 **Official exam guide:** [Professional Cloud DevOps Engineer certification](https://cloud.google.com/learn/certification/cloud-devops-engineer) — always confirm section weightings against the current Google Cloud exam guide. :::note PDE here means DevOps, not Data On this site **PDE** abbreviates Google's **Professional Cloud DevOps Engineer** certification (its official name). It is *not* the Professional **Data** Engineer certification, which the abbreviation sometimes refers to elsewhere in the cert-prep ecosystem. ::: The Professional Cloud DevOps Engineer certification validates your ability to build and manage CI/CD pipelines, apply SRE practices (SLOs, error budgets, incident response), implement observability, and optimize service performance and cost on Google Cloud. The RAD platform's four foundation modules — `Services_GCP` (shared platform infrastructure), `App_CloudRun` (Cloud Run v2 deployment engine), `App_GKE` (GKE Autopilot deployment engine), and `App_Common` (shared building blocks for Cloud Deploy, monitoring, and dashboards) — give you a live, inspectable lab: every Cloud Build trigger, Cloud Deploy stage, alert policy, and traffic split discussed in this guide is real infrastructure you can deploy, break, and fix. ## How to use this guide - Pick a deployment profile below and deploy it through your deployment portal. - Work through the matching section guide (`PDE_Section_N_Exploration_Guide.md`) — each subsection has hands-on steps with real `gcloud`, `kubectl`, and `tofu` commands. - Use the coverage legend honestly: 📘 topics (most pure SRE theory and incident management process) must be studied outside the platform; the section guides give pointers. - The platform itself is part of the lab — Section 1 treats the deployment modules as the IaC artifact the exam expects you to reason about. **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: Pipeline engineer *Purpose:* end-to-end CI/CD — GitHub push → Kaniko build → Artifact Registry → Binary Authorization attestation → Cloud Deploy promotion with a prod approval gate. *Modules:* `App_CloudRun` (optionally on top of `Services_GCP`). | Variable | Value | |---|---| | `enable_cicd_trigger` | `true` | | `github_repository_url` | `https://github.com//` | | `github_token` | a PAT with `repo` + `admin:repo_hook` (first apply only) | | `enable_cloud_deploy` | `true` | | `cicd_enable_cloud_deploy` | `true` | | `enable_binary_authorization` | `true` | | `binauthz_evaluation_mode` | `REQUIRE_ATTESTATION` | | `support_users` | `["you@example.com"]` | *Estimated incremental cost:* low–moderate — Cloud Build minutes and Artifact Registry storage dominate; Cloud Deploy itself adds no direct charge for Cloud Run targets, you pay for the per-stage Cloud Run services. ### Profile: GKE release engineer *Purpose:* rolling updates, HPA/VPA, PodDisruptionBudgets, and Cloud Deploy to GKE namespaces. *Modules:* `Services_GCP` + `App_GKE`. | Variable | Value | |---|---| | `create_google_kubernetes_engine` (Services_GCP) | `true` | | `gke_cluster_mode` (Services_GCP) | `AUTOPILOT` (default) | | `min_instance_count` (App_GKE) | `2` | | `max_instance_count` (App_GKE) | `4` | | `enable_pod_disruption_budget` (App_GKE) | `true` (default) | | `enable_topology_spread` (App_GKE) | `true` | | `enable_cicd_trigger` + `enable_cloud_deploy` (App_GKE) | `true` (optional, for the GKE CD path) | *Estimated incremental cost:* moderate–high — GKE Autopilot bills per pod resource request plus a cluster management fee; multiple replicas multiply the cost. ### Profile: Observability baseline *Purpose:* notification channels, threshold alert policies, auto-generated dashboards, and full audit logging to explore in Logs Explorer. *Modules:* `Services_GCP` + either application engine. | Variable | Value | |---|---| | `support_users` (app module) | `["you@example.com"]` | | `alert_policies` (app module) | one entry, e.g. on `run.googleapis.com/request_count` | | `configure_email_notification` (Services_GCP) | `true` | | `notification_alert_emails` (Services_GCP) | `["ops@example.com"]` | | `alert_cpu_threshold` / `alert_memory_threshold` / `alert_disk_threshold` (Services_GCP) | `80` (defaults) | | `enable_audit_logging` | `true` | *Estimated incremental cost:* low — audit logging (`DATA_READ`/`DATA_WRITE` on `allServices`) is the dominant driver via Cloud Logging ingestion volume. ### Profile: Cost-lean serverless *Purpose:* scale-to-zero economics, CPU throttling, revision pruning, and Artifact Registry cleanup policies for Section 5. *Modules:* `App_CloudRun` only. | Variable | Value | |---|---| | `min_instance_count` | `0` (default) | | `max_instance_count` | `3` | | `cpu_always_allocated` | `false` | | `max_revisions_to_retain` | `7` (default) | | `delete_untagged_images` | `true` (default) | | `image_retention_days` | `30` (default) | *Estimated incremental cost:* minimal — the service scales to zero between requests; only storage and per-request compute accrue. ## Section 1: Bootstrapping and maintaining a Google Cloud organization (~20% of the exam) The exam opens with organization-level design: resource hierarchy, IaC discipline, CI/CD architecture choices, and multi-environment management. The RAD modules are themselves the IaC artifact, and the Cloud Deploy stage model is the multi-environment lab. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Designing the overall resource hierarchy | 📘 | project-scoped only; `resource_labels` for governance labels | [Section 1 guide](PDE_Section_1_Exploration_Guide.md#11-designing-the-overall-resource-hierarchy) | | 1.2 Managing infrastructure | ✅ | the deployment modules themselves; `tofu plan` drift detection; Cloud Deploy owns the container image while IaC owns the rest; IaC CI checks | [Section 1 guide](PDE_Section_1_Exploration_Guide.md#12-managing-infrastructure) | | 1.3 Designing a CI/CD architecture stack | ✅ | inline Cloud Build trigger, Cloud Deploy delivery pipeline, Binary Authorization | [Section 1 guide](PDE_Section_1_Exploration_Guide.md#13-designing-a-cicd-architecture-stack) | | 1.4 Managing multiple environments | ✅ | `cloud_deploy_stages` (dev/staging/prod), per-stage services and namespaces | [Section 1 guide](PDE_Section_1_Exploration_Guide.md#14-managing-multiple-environments) | ## Section 2: Building and implementing CI/CD pipelines (~25% of the exam) The heaviest exam section and the strongest area of the RAD lab: an inline Cloud Build pipeline (Kaniko → attestation → deploy), Artifact Registry with cleanup policies, Binary Authorization, and a real Cloud Deploy pipeline with approvals, automation rules, and rollback. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Designing pipelines | ✅ | `enable_cicd_trigger`, Kaniko v1.23.2 build step, Artifact Registry cleanup policies | [Section 2 guide](PDE_Section_2_Exploration_Guide.md#21-designing-pipelines) | | 2.2 Implementing and managing pipelines | ✅ | `cloud_deploy_stages`, `traffic_split`, `kubectl set image` direct path, revision pruning | [Section 2 guide](PDE_Section_2_Exploration_Guide.md#22-implementing-and-managing-pipelines) | | 2.3 Managing pipeline configuration and secrets | ✅ | `github_token` (never in state), `secret_environment_variables`, `enable_auto_password_rotation` | [Section 2 guide](PDE_Section_2_Exploration_Guide.md#23-managing-pipeline-configuration-and-secrets) | | 2.4 Auditing and logging of code and configurations | ✅ | Data Access audit logging, Binary Authorization attestations, Cloud Deploy release history | [Section 2 guide](PDE_Section_2_Exploration_Guide.md#24-auditing-and-logging-of-code-and-configurations) | ## Section 3: Applying site reliability engineering practices (~18% of the exam) SLO/error-budget theory is mostly 📘 — the modules emit the metrics SLIs are built from but do not create SLO objects. Capacity management and incident mitigation, however, are fully hands-on: autoscaling, PDBs, traffic splitting, and instant rollback. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Balancing change, velocity, and reliability of the service | 📘 | threshold alerts as proto-SLIs; no SLO/error-budget objects | [Section 3 guide](PDE_Section_3_Exploration_Guide.md#31-balancing-change-velocity-and-reliability-of-the-service) | | 3.2 Managing service lifecycle | ✅ | `min_instance_count`/`max_instance_count`, GKE HPA (CPU 70% / memory 80%), `enable_vertical_pod_autoscaling` | [Section 3 guide](PDE_Section_3_Exploration_Guide.md#32-managing-service-lifecycle) | | 3.3 Mitigating incident impact on users | ✅ | `traffic_split` rollback, Cloud Deploy rollback, `enable_pod_disruption_budget`, probes, Cloud Armor rate limiting | [Section 3 guide](PDE_Section_3_Exploration_Guide.md#33-mitigating-incident-impact-on-users) | ## Section 4: Implementing observability practices and troubleshooting issues (~25% of the exam) The second-heaviest section. The modules provision notification channels, fixed and custom alert policies, per-platform dashboards, GKE workload logging, managed Prometheus, synthetic uptime checks (`uptime_check_config` — created for publicly reachable endpoints, with a `check_passed` alert policy), and (optionally) full data-access audit logs. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Instrumenting and collecting telemetry | 🟡 | GKE workload logging/monitoring + managed Prometheus on the Services_GCP cluster; `enable_audit_logging`; `uptime_check_config` synthetic checks | [Section 4 guide](PDE_Section_4_Exploration_Guide.md#41-instrumenting-and-collecting-telemetry) | | 4.2 Troubleshooting and analyzing issues | 🟡 | Logs Explorer over module-deployed workloads; revision/Pod diagnostics; Cloud Logging build logs | [Section 4 guide](PDE_Section_4_Exploration_Guide.md#42-troubleshooting-and-analyzing-issues) | | 4.3 Managing metrics, dashboards, and alerts | ✅ | the monitoring layer (90% CPU/memory alerts, renotify 1800s), `alert_policies`, auto-generated dashboards, Services_GCP threshold alerts | [Section 4 guide](PDE_Section_4_Exploration_Guide.md#43-managing-metrics-dashboards-and-alerts) | ## Section 5: Optimizing performance and cost (~12% of the exam) Performance tuning (execution environment, CPU allocation, resource requests) is fully demonstrated; FinOps tooling (billing export, Recommender, CUDs) is 📘, with the modules providing the levers those tools would recommend pulling. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 5.1 Collecting performance information in Google Cloud | 🟡 | `execution_environment`, `cpu_always_allocated`, `container_resources`, managed Prometheus; Trace/Profiler 📘 | [Section 5 guide](PDE_Section_5_Exploration_Guide.md#51-collecting-performance-information-in-google-cloud) | | 5.2 Implementing FinOps practices for optimizing resource utilization and costs | 🟡 | scale-to-zero, request-only CPU, VPA, AR cleanup policies, GKE cost allocation; billing export/Recommender 📘 | [Section 5 guide](PDE_Section_5_Exploration_Guide.md#52-implementing-finops-practices-for-optimizing-resource-utilization-and-costs) | --- # PDE Certification Preparation Guide: Section 1 — Bootstrapping and maintaining a Google Cloud organization (~20% of the exam) PDE Certification Preparation Guide: Section 1 — Bootstrapping and maintaining a Google Cloud organization (~20% of the exam) > 📚 **Official exam guide:** [Professional Cloud DevOps Engineer certification](https://cloud.google.com/learn/certification/cloud-devops-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 1 using the RAD platform as a lab. The foundation modules exercised here are `App_CloudRun` and `App_GKE` (the deployment engines), `Services_GCP` (the once-per-project platform layer), and the `App_Common` building blocks they share. Deploy the **Pipeline engineer** profile from the [Lab Map](PDE_Certification_Guide.md) before starting. --- ## 1.1 Designing the overall resource hierarchy > ⏱ ~30 min · 💰 no additional cost · ⚙️ Requires: default deployment **Why the exam cares** — DevOps engineers inherit the org → folder → project → resource hierarchy and must know where to attach what: organization policies and IAM at folders for environment-wide guardrails, billing accounts outside the hierarchy, projects as the isolation and quota boundary. Exam scenarios test whether you put a constraint at the right level (e.g., a folder-level policy instead of repeating it per project) and whether you isolate environments by project rather than by naming convention. **How RAD implements it** — Not meaningfully: all four foundation modules operate inside a single existing project; no folders, organization policies, or project-factory resources are created. The nearest adjacent capability is governance labeling — `resource_labels` (default `{}`) in both application engines is merged into a common label set (which always adds `application`, `deployment`, `tenant`, and `managed-by` keys) and stamped on every resource, which is the foundation for label-based cost attribution and log filtering. **Try it** 1. In the portal, set `resource_labels = { team = "payments", env = "lab" }` on a deployed application module and apply. 2. In **Console > Cloud Run > (service) > Details**, confirm the labels; then in **Billing > Reports**, group by label key `team` to see cost attribution per label. 3. Confirm from the CLI: ```bash gcloud run services describe --region=us-central1 \ --format="value(metadata.labels)" gcloud projects get-ancestors $GOOGLE_PROJECT_ID ``` 4. You know it worked when the `team` and `env` labels appear alongside the module-injected `managed-by` and `tenant` labels, and `get-ancestors` shows where your lab project sits in the hierarchy. **Check yourself**
Q1: Your company wants every non-production project to be restricted to us-central1 while production projects stay multi-region. Where do you implement this with the least ongoing effort? A: Attach a `constraints/gcp.resourceLocations` organization policy to a `non-production` folder and place all non-prod projects under it. Policies inherit down the hierarchy, so new projects get the restriction automatically — no per-project configuration or Terraform changes needed.
Q2: Why do the RAD modules stamp a `tenant` and `deployment` label on every resource instead of relying on resource names? A: Labels are queryable in billing exports, log filters, and asset inventory, while names are free-form strings. Labels give you cost showback and operational grouping across heterogeneous resource types — the same mechanism the exam expects for chargeback in a multi-team organization.
**Beyond the modules** — Study the resource hierarchy and organization policy docs directly: practice `gcloud resource-manager folders list --organization=`, `gcloud org-policies list --project=`, and review the Cloud Foundation Fabric/FAST landing-zone blueprints for how enterprises bootstrap folders, billing, and IAM with Terraform. Also know that a billing account is linked to projects but lives outside the hierarchy. **⚠️ Exam trap** — Organization policies are *not* IAM: denying a permission in IAM and constraining a resource configuration (e.g., `disableServiceAccountKeyCreation`) are different control planes, and the exam likes answers that combine both. --- ## 1.2 Managing infrastructure > ⏱ ~60 min · 💰 no additional cost · ⚙️ Requires: any deployed module **Why the exam cares** — The exam tests IaC decision criteria: declarative state-based tooling (Terraform/OpenTofu, Infrastructure Manager) vs. imperative scripts, how remote state enables collaboration and locking, how drift is detected and reconciled, and when to deliberately let another system own part of a resource. Expect scenarios on what `terraform plan` shows after someone clicks around the console. **How RAD implements it** — The deployment modules *are* the artifact: | Practice | Where you see it | |---|---| | Declarative full-stack modules | `App_CloudRun` declares a Cloud Run v2 service; `App_GKE` declares a Kubernetes Deployment | | Parameterization, no hardcoding | each engine exposes 130–160 variables with validations, e.g. `traffic_split` entries must sum to 100 | | Plan-time guardrails | `App_GKE` carries dozens of precondition checks, e.g. min ≤ max instances, binary-suffix memory quotas | | Deliberate shared ownership | the Cloud Run service that Cloud Deploy targets ignores changes to the container image, so Cloud Deploy owns image rollouts while Terraform owns everything else | | CI for the IaC itself | a repo-level Cloud Build pipeline runs convention checks, `tofu fmt -check` + `tofu validate` on every module, `tflint`, and `tofu test` against the App_CloudRun validation tests | | Discovery over duplication | the networking layer discovers Services_GCP-managed VPCs by label instead of re-declaring them | The deployment-control variable is `deploy_application` (default `true`) — setting it `false` provisions supporting infrastructure without the workload, a staged-rollout pattern worth knowing. **Try it** 1. Understand the validation gate: before any deployment, the platform's CI runs a credential-free static-analysis loop on the IaC — `tofu init -backend=false`, then `tofu validate` (type and reference checks) and a formatting check (`tofu fmt -check`) — so syntax, type, and precondition errors are caught without touching a live project. This is the code-review gate; you experience its result as a deployment that is rejected before it ever reaches `plan`/`apply`. 2. Simulate drift: in **Console > Cloud Run > (service) > Edit & deploy new revision**, change the memory limit to `1Gi` manually. The next time the platform re-applies your deployment, `terraform plan` proposes reverting memory to the declared `container_resources` memory limit (default `512Mi`) — because the console change is drift against the declared state. 3. Contrast with sanctioned drift: deploy a new image through the Cloud Deploy pipeline (Pipeline engineer profile). On the next apply, the plan shows no diff for the image, because the container image is deliberately ignored by Terraform. 4. You know it worked when step 2's plan proposes an in-place update reverting your manual change, while step 3 shows "No changes" for the image attribute. **Check yourself**
Q1: After a hotfix was deployed with `gcloud run services update --image=...`, the next `terraform apply` reverted it and re-broke production. What design prevents this class of incident? A: Either route all image changes through the pipeline that Terraform delegates to (Cloud Deploy) and have Terraform ignore the image attribute, as this platform does, or make the emergency path update the IaC source first. The root cause is two writers owning one attribute; the fix is explicitly assigning ownership.
Q2: Why does the repo run `tofu validate` and `tofu test` in CI rather than only `tofu plan` against live infrastructure? A: Validation and unit tests run without credentials or a live project (`-backend=false`), so they catch syntax, type, and precondition violations cheaply on every commit. Plans against live state are slower, need secrets, and belong to the deployment pipeline, not the code-review gate.
**⚠️ Exam trap** — `terraform plan` detects drift only for *attributes Terraform manages*. Resources created entirely outside Terraform are invisible to it; finding those requires Cloud Asset Inventory or config scanning, not a plan. --- ## 1.3 Designing a CI/CD architecture stack > ⏱ ~45 min · 💰 low (Cloud Build minutes) · ⚙️ Requires: Pipeline engineer profile **Why the exam cares** — Architecture questions test tool selection: Cloud Build for CI, Artifact Registry for artifacts, Cloud Deploy for progressive delivery, Binary Authorization for deploy-time supply-chain enforcement — and where the trust boundaries sit (which service account does what, where attestations are created and verified). **How RAD implements it** — The full stack is wired in `App_CloudRun` (the GKE engine mirrors it): - **CI**: `enable_cicd_trigger` (default `false`) creates a Cloud Build trigger with an *inline* build definition — no separate build-config file is needed in the application repo. Step 1 builds with Kaniko (`gcr.io/kaniko-project/executor:v1.23.2`, layer cache enabled with a 24h cache TTL). - **Artifact management**: images are pushed with three tags — the configured version, `latest`, and `$COMMIT_SHA` — to the shared Artifact Registry repo (`shared-repo-*`), discovered or created as a fallback. - **Supply-chain security**: when `enable_binary_authorization = true`, step 2 resolves the image *digest* and runs `gcloud beta container binauthz attestations sign-and-create` against the `pipeline-attestor` attestor, signing with the KMS key `binauthz-signer` in the `{project}-binauthz-keyring` keyring. The Cloud Run service uses the project's default Binary Authorization policy; the GKE cluster enforces the project singleton policy. Policy enforcement strength comes from `binauthz_evaluation_mode` (default `ALWAYS_ALLOW`; set `REQUIRE_ATTESTATION` to enforce). - **CD**: `enable_cloud_deploy` (default `false`) provisions a Cloud Deploy delivery pipeline plus one target per stage. Note that setting it without `enable_cicd_trigger = true` is rejected by a plan-time precondition — a delivery pipeline without a CI trigger would never receive releases. Skaffold configs live in a GCS bucket named `{project}-{8-char-hash}-cd-configs`. - **Builds run as a dedicated SA** (`cloudbuild-sa-*`), granted `roles/clouddeploy.releaser` and read access to the Skaffold bucket — not as a broad default identity. **Try it** 1. Deploy the Pipeline engineer profile, then push a commit to the connected repo's `main` branch (the trigger's `cicd_trigger_config.branch_pattern` defaults to `^main$`). 2. Watch the build: **Console > Cloud Build > History** — identify the Kaniko step, the attestation step, and the deploy step. ```bash gcloud builds list --region=us-central1 --limit=3 gcloud artifacts docker images list \ us-central1-docker.pkg.dev/$GOOGLE_PROJECT_ID// \ --include-tags --limit=5 gcloud container binauthz attestations list \ --attestor=pipeline-attestor --attestor-project=$GOOGLE_PROJECT_ID --limit=3 ``` 3. In **Console > Cloud Deploy > Delivery pipelines**, open the pipeline and confirm a release named `release-` landed in the first stage. 4. You know it worked when the image appears in Artifact Registry with the commit-SHA tag, an attestation exists for its digest, and the dev stage shows a successful rollout. **Check yourself**
Q1: Why does the attestation step sign the image digest rather than the `:latest` or commit-SHA tag? A: Tags are mutable pointers; a digest is the content-addressed identity of the image. Binary Authorization verifies attestations against the digest being deployed, so signing a tag would let a re-pushed image inherit a signature it never earned.
Q2: A teammate sets `enable_cloud_deploy = true` but leaves `enable_cicd_trigger = false`, and the plan fails with a precondition error. Bug or design? A: Design — a plan-time precondition rejects `enable_cloud_deploy = true` without `enable_cicd_trigger = true`, because a delivery pipeline without a CI trigger to feed it releases would sit empty. The exam parallel: CD is downstream of CI; design the stack as one flow.
Q3: Why Kaniko instead of a Docker daemon build step? A: Kaniko builds OCI images entirely in userspace inside the build container — no privileged Docker daemon socket — which shrinks the attack surface of the build environment and is the recommended pattern in Cloud Build.
**⚠️ Exam trap** — `binauthz_evaluation_mode = "ALWAYS_ALLOW"` (the default here) means Binary Authorization is *configured but not enforcing*. Attestations being created in the pipeline does nothing until the policy says `REQUIRE_ATTESTATION`. --- ## 1.4 Managing multiple environments > ⏱ ~45 min · 💰 low–moderate (one Cloud Run service or GKE namespace per stage) · ⚙️ Requires: Pipeline engineer profile with `enable_cloud_deploy = true` **Why the exam cares** — You must keep dev/staging/prod structurally identical while varying parameters, decide where approval gates belong, and know what isolation boundary each environment needs (namespace vs. service vs. project). Exam scenarios probe promotion mechanics: what artifact moves between stages and what must *not* be rebuilt. **How RAD implements it** — `cloud_deploy_stages` defines the promotion path. The default is: ```hcl [ { name = "dev", require_approval = false, auto_promote = false }, { name = "staging", require_approval = false, auto_promote = false }, { name = "prod", require_approval = true, auto_promote = false }, ] ``` Each stage becomes a Cloud Deploy target (with `require_approval` mapped directly) and a stage-suffixed runtime: Cloud Run services named `-`, or GKE namespaces per stage passed to Skaffold via the `NAMESPACE` deploy parameter. Stages with `auto_promote = true` get a Cloud Deploy automation with an advance-rollout rule, so a successful rollout advances automatically. Terraform provisions only the *first* stage's service/namespace; later stages materialize when Cloud Deploy promotes into them — the same rendered release, same image digest, no rebuild. Per-stage overrides (`project_id`, `region`, `service_name`) exist on each stage object, so cross-project promotion is expressible, though the lab runs all stages in one project. **Try it** 1. With the Pipeline engineer profile deployed, promote the current release out of dev: ```bash gcloud deploy releases promote \ --delivery-pipeline= \ --region=us-central1 --project=$GOOGLE_PROJECT_ID ``` 2. Promote again toward prod, then open **Console > Cloud Deploy > (pipeline)** — the prod rollout stops in **Pending approval**. Approve it: ```bash gcloud deploy rollouts list --delivery-pipeline= \ --release= --region=us-central1 gcloud deploy rollouts approve \ --delivery-pipeline= --release= \ --region=us-central1 ``` 3. Compare environments: `gcloud run services list` now shows `-dev`, `-staging`, `-prod` running the identical image digest. 4. You know it worked when the prod rollout required an explicit approval and all three services report the same image digest in `gcloud run services describe ... --format="value(spec.template.spec.containers[0].image)"`. **Check yourself**
Q1: Staging validated image digest X, but prod is running digest Y after promotion. In a correctly designed pipeline, is this possible? A: No — Cloud Deploy promotes the *release*, which pins image digests at release-creation time. If prod shows a different digest, something outside the pipeline deployed it (audit logs will show who), or the pipeline rebuilds per stage, which defeats the build-once/promote-many principle the exam expects.
Q2: Where would you add a fully automatic dev → staging hop while keeping the prod gate? A: Set `auto_promote = true` on the dev stage — the module then creates a Cloud Deploy automation with an advance-rollout rule scoped to the dev target. Prod keeps `require_approval = true`, so automation never bypasses the human gate.
**Beyond the modules** — The lab keeps all stages in one project. For exam completeness, study per-environment *project* isolation (separate IAM, quotas, VPCs per environment), Cloud Deploy deploy parameters and custom targets, and post-deployment verification (`verify` in Skaffold profiles), none of which the modules configure. **⚠️ Exam trap** — `require_approval` gates the *rollout into the target*, not release creation. A release can exist and sit unpromoted forever; approval is per-target, which is why only prod's target carries the flag. --- # PDE Certification Preparation Guide: Section 2 — Building and implementing CI/CD pipelines (~25% of the exam) PDE Certification Preparation Guide: Section 2 — Building and implementing CI/CD pipelines (~25% of the exam) > 📚 **Official exam guide:** [Professional Cloud DevOps Engineer certification](https://cloud.google.com/learn/certification/cloud-devops-engineer) — always confirm section weightings against the current Google Cloud exam guide. This is the heaviest exam section and the RAD platform's strongest lab. The pipeline is implemented in `App_CloudRun` and `App_GKE` (an inline Cloud Build definition: Kaniko build → optional Binary Authorization attestation → deploy), with the shared `App_Common` building blocks providing the Cloud Deploy pipeline and the GitHub connection. Deploy the **Pipeline engineer** profile from the [Lab Map](PDE_Certification_Guide.md) before starting; subsection 2.2 also uses the **GKE release engineer** profile for the Kubernetes path. --- ## 2.1 Designing pipelines > ⏱ ~60 min · 💰 low (Cloud Build minutes, AR storage) · ⚙️ Requires: Pipeline engineer profile **Why the exam cares** — Pipeline design questions test artifact strategy: immutable, traceable image references (digest/commit-SHA over `latest`), build caching for speed, registry hygiene (cleanup policies so storage doesn't grow unbounded), and vulnerability scanning placement. You should be able to justify each step's order and the blast radius of getting it wrong. **How RAD implements it** — One trigger, three build steps, defined inline in `App_CloudRun`: | Design decision | Implementation | |---|---| | Trigger scope | `cicd_trigger_config.branch_pattern` (default `^main$`), plus optional `included_files`/`ignored_files` path filters and custom `substitutions` | | Build tool | Kaniko `v1.23.2`, daemonless, with a 24h layer cache for reuse | | Tagging | every build pushes three tags: ``, `latest`, and `$COMMIT_SHA` — the SHA tag is what the deploy step uses, preserving commit-to-runtime traceability | | Registry | shared repo discovered automatically; if absent, the platform creates `shared-repo-` (Docker format, mutable tags) | | Cleanup | three policies scoped to this app's images: a KEEP policy retaining the `max_images_to_retain` (default `7`) most recent versions, a DELETE policy for untagged images when `delete_untagged_images` (default `true`), and a DELETE policy for images older than `image_retention_days` (default `30`) days | | Build logging | build logs land in Cloud Logging, not a GCS log bucket | | Build identity | a dedicated per-deployment Cloud Build SA, not the legacy project default | Vulnerability scanning is a platform-layer toggle: `enable_vulnerability_scanning` (default `false`) in `Services_GCP` enables the Artifact Registry repo's inherited vulnerability scanning. **Try it** 1. Push a trivial commit to the connected repo and watch **Console > Cloud Build > History**; open the build and read each step's log (Kaniko cache hits are visible on the second build — compare durations). 2. Inspect the artifact trail: ```bash gcloud builds list --region=us-central1 --limit=2 gcloud artifacts docker images list \ us-central1-docker.pkg.dev/$GOOGLE_PROJECT_ID// --include-tags gcloud artifacts repositories describe --location=us-central1 \ --format="yaml(cleanupPolicies)" ``` 3. In the portal, lower `image_retention_days` to `7` and re-apply; re-run the `describe` command and confirm the `delete-old-images` policy's `olderThan` changed to `604800s`. 4. You know it worked when each image version shows all three tags and the cleanup policies reflect your variable values. **Check yourself**
Q1: Storage costs on your registry keep climbing even though a cleanup policy deletes images older than 30 days. Builds run 40×/day. What is the likely gap? A: Untagged images (layers orphaned each time `latest` is re-pointed) aren't covered by an age-based tagged-image policy alone. The RAD platform pairs the age policy with an untagged-image DELETE policy (`delete_untagged_images`) precisely for this. Also check that the KEEP policy count isn't holding more than intended.
Q2: Why does the deploy step reference the `$COMMIT_SHA` tag rather than `latest`, given both point at the same image right after the build? A: `latest` is a moving pointer — a concurrent or later build changes what it resolves to, breaking reproducibility and rollback reasoning. The commit SHA is stable and links the running revision to the exact source commit, which is also what audit and incident investigation need.
Q3: Where in this pipeline would you add a unit-test gate, and what makes the build fail? A: As a step *before* the Kaniko step (or a test stage in the Dockerfile). Any step exiting non-zero fails the whole Cloud Build execution, so nothing is pushed or deployed — the standard fail-fast CI contract.
**⚠️ Exam trap** — Artifact Registry KEEP policies beat DELETE policies: an image matched by the `most_recent_versions` KEEP rule is never deleted even if older than the age threshold. Reason about cleanup as "DELETE rules minus KEEP rules". --- ## 2.2 Implementing and managing pipelines > ⏱ ~90 min · 💰 low–moderate (per-stage services) · ⚙️ Requires: Pipeline engineer profile; GKE release engineer profile for the Kubernetes path **Why the exam cares** — This is the deployment-strategies subsection: canary vs. blue/green vs. rolling, how Cloud Run traffic splitting implements canaries, how Cloud Deploy promotion/approval/rollback works mechanically, and what a Kubernetes rolling update actually does. Expect "errors spiked after deploy — what's the fastest safe action?" scenarios. **How RAD implements it** - **Cloud Run canary**: `traffic_split` (default `[]` = 100% to latest) is a list of `{ type, revision, percent, tag }` objects rendered into the service's traffic configuration. Validations require percentages to sum to exactly 100 and a `revision` on every revision-allocation entry. The optional `tag` gives a revision a stable URL for testing before it gets real traffic. - **Revision hygiene**: `max_revisions_to_retain` (default `7`) prunes old revisions after each apply — it lists revisions newest-first and deletes the surplus, skipping any revision currently serving traffic. - **Cloud Deploy mechanics**: targets carry `require_approval`; `auto_promote = true` on a stage creates a Cloud Deploy automation with an advance-rollout rule. The Cloud Deploy service agent gets `roles/run.admin` (Cloud Run) or `roles/container.developer` (GKE); the Cloud Build SA gets `roles/clouddeploy.releaser`. - **Two deploy paths from CI** (Cloud Run): with `cicd_enable_cloud_deploy = true` (default `false`), the trigger's deploy step creates a release with `gcloud deploy releases create release- --source=`; otherwise it calls `gcloud run services update --image=...:$COMMIT_SHA` directly. - **GKE rolling update**: the GKE trigger's direct path runs `kubectl set image / ... -n `. The Kubernetes Deployment sets no explicit strategy, so Kubernetes' default RollingUpdate (25% maxSurge / 25% maxUnavailable) applies; StatefulSets use `stateful_update_strategy` (default `RollingUpdate`, or `OnDelete` for manual control). The PodDisruptionBudget (`enable_pod_disruption_budget`, default `true`) protects availability during the node-level disruptions that accompany updates. **Try it** 1. Cloud Run canary: deploy a config change to create a second revision, list revisions, then set in the portal: ```hcl traffic_split = [ { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST", percent = 10, tag = "canary" }, { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_REVISION", revision = "-00001-xyz", percent = 90 } ] ``` Apply, then verify: ```bash gcloud run services describe --region=us-central1 \ --format="yaml(status.traffic)" ``` 2. Roll back instantly by editing the split to send 100% to the old revision and re-applying — no build, no new revision. 3. Cloud Deploy rollback: in **Console > Cloud Deploy > (pipeline) > (target)**, click **Rollback**, or: ```bash gcloud deploy targets rollback \ --delivery-pipeline= --region=us-central1 ``` 4. GKE rolling update (GKE profile): trigger one manually and watch it: ```bash kubectl set image deployment/ =: -n kubectl rollout status deployment/ -n kubectl rollout undo deployment/ -n # instant revert kubectl get pdb -n # the module-created PDB ``` 5. You know it worked when `status.traffic` shows your 90/10 split with a `canary` tag URL, and the GKE rollout replaces pods incrementally while the PDB reports `ALLOWED DISRUPTIONS` ≥ 0 throughout. **Check yourself**
Q1: Five minutes after a Cloud Run deploy, 5xx rates triple. Fastest safe mitigation? A: Shift 100% of traffic back to the previous healthy revision (console traffic manager, `gcloud run services update-traffic`, or `traffic_split` in IaC). Old revisions remain deployable instantly; this takes seconds and needs no build. Investigate the bad revision afterward via its logs — it still exists, just serves no traffic.
Q2: What's the difference between a canary on Cloud Run traffic splitting and a Cloud Deploy canary strategy? A: Traffic splitting is a *runtime* control on one service between revisions — you move percentages yourself. Cloud Deploy canary is a *pipeline* strategy that automates phased percentage progression with verification between phases. The RAD modules implement the former and use plain stage promotion (not canary strategy) in Cloud Deploy.
Q3: Why does revision pruning skip revisions serving traffic, and what failure would deleting them cause? A: A revision receiving any traffic percentage is live capacity; deleting it would break the traffic split (gcloud rejects the delete). Retention pruning must only ever remove fully drained revisions — the same reason you keep N known-good revisions as your rollback inventory.
**⚠️ Exam trap** — Blue/green ≠ canary: blue/green switches 100% of traffic between two complete environments at once (instant rollback, double capacity); canary shifts a small percentage first (gradual risk, no double capacity). Cloud Run's traffic splitting can express both, but the exam wants you to name the right strategy for the constraint given. --- ## 2.3 Managing pipeline configuration and secrets > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: Pipeline engineer profile **Why the exam cares** — Secrets in pipelines are a classic failure mode: tokens in source, passwords in Terraform state, plaintext in build logs. The exam tests where secrets should live (Secret Manager), how they reach runtime (references, not values), and how rotation happens without downtime. **How RAD implements it** - **The GitHub PAT never touches Terraform state**: `github_token` (sensitive) is required on first apply only; the platform writes it with `gcloud secrets versions add` (a provisioner, not a stored resource attribute) and the secret is abandoned rather than deleted on destroy. On later applies the stored token is reused — the trigger resolves the existing secret version rather than asking for the token again. - **Runtime secrets are references**: `secret_environment_variables` (map of env var → secret name) renders as Cloud Run secret references; the GKE engine syncs secrets via the Secret Manager CSI add-on into Kubernetes Secrets. The container sees a value; state and manifests see a reference. - **Generated, not chosen**: the database password is a randomly generated value of `database_password_length` (default `32`) chars stored straight into Secret Manager. - **Rotation**: `secret_rotation_period` (default `2592000s` = 30 days) configures Secret Manager rotation notifications to a Pub/Sub topic; `enable_auto_password_rotation` (default `false`) closes the loop with an Eventarc-dispatched rotation job that performs a dual-version, zero-downtime rotation (add new version → update DB user → disable old version after `rotation_propagation_delay_sec`, default `90`). - **Pipeline parameters that aren't secret** travel as Cloud Build substitutions (`cicd_trigger_config.substitutions`), visible in the trigger definition — the exam distinction between configuration and secrets. **Try it** 1. List the module-created secrets and confirm no value is visible anywhere in IaC outputs: ```bash gcloud secrets list --filter="name~" \ --format="table(name,createTime)" gcloud secrets versions list ``` 2. In **Console > Cloud Run > (service) > Revisions > (latest) > Variables & Secrets**, confirm `DB_PASSWORD` shows a secret *reference* (`.../versions/latest`), not a value. 3. Enable `enable_auto_password_rotation = true` in the portal and apply; after the rotation flow runs, `gcloud secrets versions list` shows a new ENABLED version and the prior one DISABLED. 4. Reason about the negative case: the GitHub PAT and the database password never appear in Terraform state — they are written directly to Secret Manager and consumed by reference, so state holds only the secret's *name*. A state inspection would never reveal the token value, which is the whole point of the reference-not-value design. 5. You know it worked when secrets have multiple versions with only the newest enabled and the runtime resolves secrets purely by reference. **Check yourself**
Q1: Why is writing the GitHub token via a `gcloud secrets versions add` provisioner better than a managed Terraform secret-version resource? A: A managed secret-version resource stores the secret payload in state; anyone with state-read access reads the token. The provisioner pushes the value directly to Secret Manager so state holds only the secret's name. The trade-off (Terraform can't detect value drift) is acceptable for write-once credentials.
Q2: During password rotation, why add the new secret version before disabling the old one instead of replacing in place? A: Running instances may hold connections authenticated with the old password and may re-read the old version until propagation completes. The dual-version window lets old and new credentials coexist (the DB user is updated, the old version stays readable), achieving zero-downtime rotation; the old version is disabled only after the propagation delay.
**⚠️ Exam trap** — Setting `secret_rotation_period` alone rotates *nothing*: Secret Manager rotation is a Pub/Sub notification schedule. Something must consume the notification and write a new version — here, that's the `enable_auto_password_rotation` machinery. --- ## 2.4 Auditing and logging of code and configurations > ⏱ ~45 min · 💰 low–moderate (audit log ingestion) · ⚙️ Requires: Pipeline engineer profile + `enable_audit_logging = true` **Why the exam cares** — After an unauthorized or broken deployment, you must reconstruct who deployed what, when, from which source. The exam tests knowledge of Admin Activity vs. Data Access audit logs (the former always on and free, the latter opt-in and billed), and how artifact provenance plus release history close the chain from commit to runtime. **How RAD implements it** - **Data Access audit logs**: `enable_audit_logging` (default `false` in both engines and `Services_GCP`) turns on project IAM audit logging for `allServices` with `ADMIN_READ`, `DATA_READ`, and `DATA_WRITE`, plus explicit per-service configs for Secret Manager and Cloud KMS — so every secret access and key use is logged. - **Deployment provenance chain**: commit SHA → image tag (build step) → attestation on the image digest (Binary Authorization step) → Cloud Deploy release pinning the digest → per-target rollout history with approver identity. Each hop is queryable. - **Build logs** are forced to Cloud Logging (`CLOUD_LOGGING_ONLY`), making build activity searchable alongside audit logs. - **Config history**: every infrastructure change flows through `tofu plan`/`apply`, so the IaC repo's git history plus state snapshots are the configuration audit trail. **Try it** 1. Enable `enable_audit_logging = true`, apply, then read your own trail. Find who deployed the last Cloud Run revision: ```bash gcloud logging read \ 'protoPayload.serviceName="run.googleapis.com" AND protoPayload.methodName:"Services.ReplaceService"' \ --limit=5 --format="table(timestamp, protoPayload.authenticationInfo.principalEmail)" ``` 2. Read a secret in the console, then prove Data Access logging caught it: ```bash gcloud logging read \ 'protoPayload.serviceName="secretmanager.googleapis.com" AND protoPayload.methodName:"AccessSecretVersion"' --limit=5 ``` 3. Walk the provenance chain for the running image: get its digest from `gcloud run services describe`, then `gcloud container binauthz attestations list --attestor=pipeline-attestor` to find its signature, then **Console > Cloud Deploy > (pipeline) > Release history** to see when it was promoted and who approved prod. 4. You know it worked when you can name the principal, timestamp, image digest, and approving user for the most recent prod deployment without leaving the console/CLI. **Check yourself**
Q1: Security asks for a record of every read of the production DB password over the last month, but Logs Explorer shows nothing. Most likely cause? A: Data Access audit logs (`DATA_READ`) for Secret Manager were not enabled — only Admin Activity logs are on by default, and reading a secret version is a data access, not an admin action. That is exactly what `enable_audit_logging` turns on; it cannot be enabled retroactively.
Q2: An image is running in prod that no Cloud Build execution produced. Which two controls in this lab would have (a) detected and (b) prevented it? A: (a) Admin Activity audit logs on `run.googleapis.com` show the out-of-band `ReplaceService` call and its principal. (b) Binary Authorization with `binauthz_evaluation_mode = "REQUIRE_ATTESTATION"` would have blocked the deploy, since only the pipeline holds the KMS signing key for `pipeline-attestor`.
**Beyond the modules** — The modules don't configure log sinks or retention: study aggregated sinks to BigQuery/GCS for long-term audit retention, log bucket retention settings (`gcloud logging buckets update _Default --retention-days=...`), and SLSA provenance generated natively by Cloud Build (`gcloud artifacts docker images describe ... --show-provenance`) — the RAD pipeline's KMS attestation is a related but distinct mechanism. **⚠️ Exam trap** — Admin Activity audit logs are always on, unconfigurable, and free; Data Access logs are off by default (except BigQuery), must be enabled per service or via `allServices`, and can be expensive at volume. Questions that hinge on "why is there no log?" usually turn on this distinction. --- # PDE Certification Preparation Guide: Section 3 — Applying site reliability engineering practices (~18% of the exam) PDE Certification Preparation Guide: Section 3 — Applying site reliability engineering practices (~18% of the exam) > 📚 **Official exam guide:** [Professional Cloud DevOps Engineer certification](https://cloud.google.com/learn/certification/cloud-devops-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 3 using the RAD foundation modules. SLO and error-budget *theory* is concept-only here (the modules emit the metrics SLIs are built from, but create no SLO objects), while service lifecycle management and incident mitigation are fully hands-on through `App_CloudRun` scaling controls, the `App_GKE` HPA/VPA/PDB stack, and instant traffic-based rollback. Deploy the **GKE release engineer** profile plus a Cloud Run service (any profile) from the [Lab Map](PDE_Certification_Guide.md). --- ## 3.1 Balancing change, velocity, and reliability of the service > ⏱ ~60 min (mostly study + one console exercise) · 💰 no additional cost · ⚙️ Requires: Observability baseline profile (for the metrics SLOs are built on) **Why the exam cares** — This is core SRE: SLIs measure behavior, SLOs set internal targets, SLAs are external contracts (always looser than the SLO), and the error budget (1 − SLO) is the objective currency that arbitrates between shipping features and hardening reliability. The exam tests the *decision* layer: what happens when the budget is exhausted, which burn rate should page, and who owns the error-budget policy. **How RAD implements it** — Not implemented as SLOs: no Cloud Monitoring SLO or service objects exist in the modules. The nearest adjacent capability is the raw SLI material and threshold alerting: the monitoring layer creates fixed CPU and memory utilization alerts at 0.9 (90%) per platform, the `alert_policies` variable lets you alert on any metric (e.g., `run.googleapis.com/request_count` or `request_latencies`), and the auto-generated dashboards chart request count and p95 latency — the exact signals you'd select as availability and latency SLIs. **Try it** 1. With a Cloud Run service deployed and receiving some traffic, create a real SLO manually on top of the module's service: **Console > Monitoring > Services > Define service**, pick the Cloud Run service, then **Create SLO** → SLI type **Availability** (request-based) → goal **99.9%** over a rolling 30 days. 2. Add the two standard burn-rate alerts on that SLO (fast burn: 14.4× over 1h; slow burn: 6× over 6h) from the SLO's **Alerts** tab. 3. Inspect what the console built, via the API: ```bash gcloud monitoring services list --project=$GOOGLE_PROJECT_ID gcloud alpha monitoring policies list \ --filter="displayName~'burn rate'" --format="value(displayName)" ``` 4. Generate traffic (e.g., `for i in $(seq 1 200); do curl -s -o /dev/null ; done`) and watch the error-budget gauge move on the SLO page. 5. You know it worked when the SLO page shows compliance %, remaining error budget, and burn-rate charts for the module-deployed service. **Check yourself**
Q1: Your SLO is 99.9% availability over 30 days and an incident just consumed 50% of the remaining error budget in 2 hours. Per standard SRE policy, what should the team do about tomorrow's planned feature release? A: Pause it. A burn that fast means the sustainable rate is massively exceeded; the error-budget policy trades release velocity for reliability work until the budget recovers. This is the whole point of the budget — an objective, pre-agreed gate instead of a judgment call mid-incident.
Q2: Why is the SLA always set looser than the SLO (e.g., SLA 99.5% vs. SLO 99.9%)? A: The SLO is the internal target with consequences you control (release freezes); the SLA carries external penalties (refunds, contracts). The gap is the operational buffer: you want to breach your internal target, react, and recover well before any contractual breach.
Q3: Why page on error-budget *burn rate* instead of on the raw error percentage? A: Burn-rate alerting scales urgency to budget impact: a 14× burn over an hour threatens the monthly budget and deserves a page, while a slow 1.5× burn is a ticket. Raw-threshold alerts either page too often (noise) or too late (budget already gone) — the multiwindow, multi-burn-rate pattern from the SRE Workbook fixes both.
**Beyond the modules** — Study: Cloud Monitoring SLO monitoring (request-based vs. windows-based SLIs), the SRE Workbook chapters on alerting on SLOs and error-budget policy, and toil measurement. In a scratch project, try `gcloud monitoring services create` / the SLO REST API to script what you clicked in the console — the exam may reference SLO definitions in JSON form. **⚠️ Exam trap** — 99.9% monthly ≈ 43 minutes of downtime, 99.99% ≈ 4.3 minutes. Exam answers often hinge on whether a proposed maintenance window or recovery time even *fits* in the stated SLO's budget. --- ## 3.2 Managing service lifecycle > ⏱ ~75 min · 💰 moderate (GKE replicas) · ⚙️ Requires: GKE release engineer profile + any Cloud Run deployment **Why the exam cares** — Capacity management questions test which knob solves which problem: horizontal scaling for load, vertical right-sizing for efficiency, minimum instances for latency, maximums for cost protection. On GKE you must know HPA vs. VPA semantics (and that they conflict on the same resource metric); on Cloud Run, scale-to-zero trade-offs. **How RAD implements it** | Control | Cloud Run (`App_CloudRun`) | GKE (`App_GKE`) | |---|---|---| | Floor | `min_instance_count` (default `0` — scale-to-zero) | `min_instance_count` (default `1`) → HPA `minReplicas` | | Ceiling | `max_instance_count` (default `1`) | `max_instance_count` (default `3`) → HPA `maxReplicas` | | Horizontal trigger | request load (managed by Cloud Run) | a Horizontal Pod Autoscaler: CPU target 70%, memory target 80% utilization | | Vertical | `container_resources` (`cpu_limit` default `1000m`, `memory_limit` default `512Mi`) | `container_resources`, or `enable_vertical_pod_autoscaling` (default `false`) → VPA with `updateMode: Auto`, floor `10m`/`32Mi` | | Readiness gating | `startup_probe_config` (HTTP `/healthz`, 10s period, 10 failures) | `startup_probe_config` (10s delay/10s period) | | Liveness | `health_check_config` (30s period, 3 failures → restart) | `health_check_config` (15s delay/30s period) | Two wiring details worth knowing: the GKE HPA is created only when `max_instance_count > 1` **and** VPA is disabled — the module never runs HPA and VPA together on the same workload; and the HPA carries a plan-time precondition that `min_instance_count <= max_instance_count`. **Try it** 1. On GKE, inspect the module's autoscaling stack: ```bash kubectl get hpa -n kubectl describe hpa -n # see the 70%/80% targets ``` 2. Load the service and watch HPA react (Autopilot provisions node capacity automatically): ```bash kubectl run loadgen --image=busybox -n --restart=Never -- \ /bin/sh -c "while true; do wget -q -O- http://; done" kubectl get hpa -n --watch ``` 3. Switch to vertical right-sizing: set `enable_vertical_pod_autoscaling = true` in the portal and apply — note in the plan that the HPA is destroyed and a `VerticalPodAutoscaler` appears. Check its recommendations after some load: `kubectl get vpa -n -o yaml`. 4. On Cloud Run, set `min_instance_count = 1` and apply, then compare cold-start latency before/after with `curl -w "%{time_total}\n" -o /dev/null -s ` after an idle period. 5. You know it worked when the HPA scales replicas toward `max_instance_count` under load, the VPA emits target requests after observation, and the warmed Cloud Run service answers without multi-second first-request latency. **Check yourself**
Q1: A GKE service OOM-kills under steady (not spiky) traffic. Do you reach for HPA or VPA, and why? A: VPA (or manually raising `container_resources` memory): the per-pod allocation is wrong, not the replica count. HPA on memory would add replicas, masking the problem expensively. VPA observes real usage and raises the request — the right vertical fix for a sizing error. Note the module enforces choosing one: enabling VPA removes the HPA.
Q2: Why does `max_instance_count` matter on a pay-per-use platform like Cloud Run where idle costs nothing? A: It caps blast radius in both directions: runaway cost under a traffic spike or retry storm, and overload protection for downstream fixed-capacity dependencies (Cloud SQL `max_connections` is 200 by default in `Services_GCP`) that unlimited Cloud Run scaling would exhaust.
**Beyond the modules** — Cloud Run concurrency tuning (requests per instance) isn't exposed as a module variable; study how concurrency interacts with CPU allocation and instance count (`gcloud run services update --concurrency=...` in a scratch project). Also study GKE cluster-level autoscaling concepts (node auto-provisioning) even though Autopilot abstracts them away. **⚠️ Exam trap** — HPA percentage targets are relative to the *request*, not the limit. A pod with a low CPU request hits "70% utilization" almost immediately; wrong requests make HPA behavior look broken. --- ## 3.3 Mitigating incident impact on users > ⏱ ~60 min · 💰 low · ⚙️ Requires: Pipeline engineer + GKE release engineer profiles **Why the exam cares** — During an incident, mitigation beats diagnosis: drain traffic away from the bad version, roll back, shed abusive load, keep capacity alive through infrastructure disruption. The exam also covers the human side — incident command roles, communication, and blameless postmortems — which no Terraform module can deploy. **How RAD implements it** - **Instant revision rollback (Cloud Run)**: every retained revision (`max_revisions_to_retain`, default `7`) is a rollback target; repoint `traffic_split` (or use the console traffic manager) — seconds, no build. - **Pipeline rollback (Cloud Deploy)**: each target retains release history; `gcloud deploy targets rollback` redeploys the prior release's pinned digests. - **Workload rollback (GKE)**: `kubectl rollout undo` reverts to the previous ReplicaSet; the direct CI/CD path (`kubectl set image`) keeps rollout history intact. - **Availability under disruption**: `enable_pod_disruption_budget` (default `true`) creates a PDB with `pdb_min_available` (default `"1"`) — automatically skipped when `max_instance_count = 1`, where a PDB would block node drains forever; created per Cloud Deploy stage namespace too. `enable_topology_spread` (default `false`) spreads replicas across zones. - **Failure containment at the edge**: `enable_cloud_armor` (default `false`) fronts Cloud Run with a global load balancer whose policy includes per-IP rate limiting — 500 requests/60s, exceed → deny with HTTP 429 and a 300s ban — plus OWASP preconfigured WAF rules and Adaptive Protection for L7 DDoS. When enabled, `ingress_settings` is forced to `internal-and-cloud-load-balancing` so the WAF can't be bypassed via the direct `*.run.app` URL. - **Self-healing probes**: liveness failures restart containers (3 consecutive failures on Cloud Run's `health_check_config`); startup probes keep traffic off instances that aren't ready. **Try it** 1. Stage a "bad deploy" on Cloud Run: push a change that returns 500s (or just treat the latest revision as bad), then execute the mitigation: ```bash gcloud run services update-traffic --region=us-central1 \ --to-revisions==100 ``` Time yourself — this is the "under a minute" mitigation the exam expects. 2. On GKE, break and revert a deployment: ```bash kubectl set image deployment/ =badregistry.example/nope:1 -n kubectl rollout status deployment/ -n # watch it stall on ImagePullBackOff kubectl rollout undo deployment/ -n ``` Note that the rolling update strategy kept the old pods serving the whole time. 3. Verify the PDB protects you during maintenance: `kubectl get pdb -n ` and confirm `MIN AVAILABLE` matches `pdb_min_available`. 4. With Cloud Armor enabled, hammer the endpoint past 500 req/min from one IP and observe 429s plus a 5-minute ban; check **Console > Network Security > Cloud Armor policies > (policy) > Logs**. 5. You know it worked when traffic shifted away from the bad revision with zero downtime, the stalled GKE rollout never reduced ready replicas below the PDB floor, and rate limiting returned 429s. **Check yourself**
Q1: A bad GKE rollout is at 50% when errors spike. Why is `kubectl rollout undo` safe to run immediately, mid-rollout? A: A rolling update keeps the previous ReplicaSet until completion; `undo` simply reverses direction, scaling the old (known-good) ReplicaSet back up under the same maxSurge/maxUnavailable constraints. No rebuild, no data risk for stateless workloads — exactly why the exam favors it as first response.
Q2: Why does the module deliberately skip creating a PDB when `max_instance_count = 1`? A: A PDB of min-available 1 over a single replica makes the pod un-evictable, blocking node drains and upgrades indefinitely — turning a reliability tool into an operational outage. With one replica, voluntary-disruption protection is meaningless anyway; the real fix is running more than one replica.
Q3: During a suspected DDoS, why is Cloud Armor's rate-based ban preferable to scaling `max_instance_count` up? A: Rate limiting sheds abusive load at the edge before it consumes compute or reaches the database; scaling up *absorbs* the attack at your expense and pushes it onto downstream fixed-capacity systems. Mitigate at the outermost layer that can distinguish bad traffic.
**Beyond the modules** — Incident *management process* is pure study: the Incident Command System roles (incident commander, communications lead, operations lead), severity classification, status communication, and blameless postmortem structure (timeline, contributing factors, action items with owners). Read the Google SRE Book chapters "Managing Incidents" and "Postmortem Culture"; practice writing one postmortem for a lab incident you stage above. **⚠️ Exam trap** — A PodDisruptionBudget protects only against *voluntary* disruptions (drains, upgrades, autoscaler consolidation). Node crashes, OOM kills, and pod evictions under node pressure ignore it — answers claiming a PDB prevents involuntary failures are wrong. --- # PDE Certification Preparation Guide: Section 4 — Implementing observability practices and troubleshooting issues (~25% of the exam) PDE Certification Preparation Guide: Section 4 — Implementing observability practices and troubleshooting issues (~25% of the exam) > 📚 **Official exam guide:** [Professional Cloud DevOps Engineer certification](https://cloud.google.com/learn/certification/cloud-devops-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 4 — the second-heaviest domain — using the RAD foundation modules. The observability surface is built from the monitoring layer (notification channels + alert policies), auto-generated per-platform dashboards, Data Access audit logging in every module, and the GKE cluster's logging/monitoring configuration. Deploy the **Observability baseline** profile from the [Lab Map](PDE_Certification_Guide.md); the GKE parts also need the **GKE release engineer** profile. One scoping note up front: the application engines create a real synthetic uptime check from `uptime_check_config` (default `{ enabled = false, path = "/" }` — you must opt in) — and then **only when the endpoint is publicly reachable**. Cloud Run probes the first `application_domains` entry, else the nip.io LB host, else the run.app URL when `ingress_settings = "all"`; GKE probes the custom domain via the Gateway (HTTPS:443) or the LoadBalancer Service ingress IP over HTTP on `service_port`. Internal-only deployments get no check, and the `uptime_check_names` output returns the created check's name (empty when skipped). --- ## 4.1 Instrumenting and collecting telemetry > ⏱ ~60 min · 💰 low–moderate (log ingestion if audit logging is on) · ⚙️ Requires: Observability baseline profile; GKE release engineer profile for cluster telemetry **Why the exam cares** — Telemetry questions test what is collected automatically vs. what needs opt-in: Cloud Run and GKE emit logs and platform metrics natively; workload metrics, data-access audit logs, Prometheus metrics, traces, and synthetic probes all require deliberate enablement. You should know which agent/config produces which signal. **How RAD implements it** | Signal | How it's produced | |---|---| | Application logs | automatic — Cloud Run revisions and GKE containers write stdout/stderr to Cloud Logging; the GKE cluster explicitly enables system-component and workload logging | | Platform metrics | automatic (`run.googleapis.com/*`, `kubernetes.io/*`); the cluster enables system-component monitoring | | Prometheus metrics | Managed Service for Prometheus is enabled on every Services_GCP cluster — it scrapes workload metrics, queryable with PromQL in Metrics Explorer | | Notification channels | `support_users` (app modules) → one email channel each (created with force-delete enabled); `notification_alert_emails` + `configure_email_notification = true` in Services_GCP for platform alerts | | Audit telemetry | `enable_audit_logging` (default `false`) → `ADMIN_READ`/`DATA_READ`/`DATA_WRITE` on `allServices` + explicit Secret Manager and KMS configs | | VM-level metrics | the Services_GCP self-managed NFS VM's memory alert uses the Ops Agent metric `agent.googleapis.com/memory/percent_used` — memory is invisible to the hypervisor without the agent | | Build/deploy telemetry | Cloud Build logs forced to `CLOUD_LOGGING_ONLY` | | Uptime checks | `-uptime-check` (HTTP GET, period from `check_interval` default `"60s"`, timeout default `"10s"`) plus `-uptime-check-alert` on `monitoring.googleapis.com/uptime_check/check_passed`, created only for publicly reachable endpoints (see note above) | Note the activation logic in `App_CloudRun`: monitoring is configured when `support_users` is non-empty, or `alert_policies` is non-empty, or `uptime_check_config.enabled` is true — but the email channels and the built-in CPU/memory alerts are only created when `support_users` has at least one entry. **Try it** 1. Apply the Observability baseline profile, then confirm the channels exist: ```bash gcloud beta monitoring channels list \ --format="table(displayName,type,labels.email_address)" ``` 2. Query workload telemetry with PromQL: **Console > Monitoring > Metrics Explorer > PromQL** and run `rate(container_cpu_usage_seconds_total[5m])` against the GKE namespace (works because managed Prometheus is enabled cluster-wide). 3. Verify the audit pipeline: read a secret, then find your own `AccessSecretVersion` entry: ```bash gcloud logging read \ 'protoPayload.serviceName="secretmanager.googleapis.com"' --limit=3 \ --format="table(timestamp,protoPayload.methodName,protoPayload.authenticationInfo.principalEmail)" ``` 4. Inspect the module-created synthetic check (publicly reachable deployments only): `gcloud monitoring uptime list-configs` shows `-uptime-check`; open it in **Console > Monitoring > Uptime checks** and trace the attached `-uptime-check-alert` policy back to your email channel. 5. You know it worked when channels list your email, PromQL returns series for your namespace, the audit entry names you, and the uptime check turns green from multiple regions. **Check yourself**
Q1: Your GKE pod's memory metrics appear in Cloud Monitoring, but your custom application metric (`orders_processed_total`) does not. The app exposes it on `/metrics`. What's missing? A: Platform metrics are automatic, but Prometheus-format application metrics need scraping. With managed Prometheus enabled (as Services_GCP does), you still must add a `PodMonitoring` custom resource targeting the pod's metrics port — collection infrastructure being enabled doesn't mean your endpoint is being scraped.
Q2: Why does the NFS VM memory alert require the Ops Agent while the CPU alert does not? A: CPU utilization (`compute.googleapis.com/instance/cpu/utilization`) is measured by the hypervisor; guest memory usage is not visible from outside the OS, so it requires the in-guest Ops Agent reporting `agent.googleapis.com/memory/percent_used`. A standard exam distinction between hypervisor and agent metrics.
**Beyond the modules** — Scripted synthetic monitors (Cloud Functions-based synthetics beyond plain uptime checks), private uptime checks against internal endpoints, Cloud Trace (distributed latency tracing — instrument via OpenTelemetry; Cloud Run propagates `X-Cloud-Trace-Context`), Cloud Profiler (continuous CPU/heap profiling), and log-based metrics are all untouched by the modules. In a scratch project: `gcloud monitoring uptime create` against a private endpoint, the Trace explorer waterfall view, and `gcloud logging metrics create` are quick to try and frequently examined. **⚠️ Exam trap** — "Monitoring is enabled" has many layers: a variable that *accepts* monitoring config is not by itself evidence the signal is collected (earlier platform releases accepted `uptime_check_config` without creating any check; verify in the console — today it provisions one, but only for public endpoints). On the exam, match each signal to its producer: agent, platform, scrape config, or audit config. --- ## 4.2 Troubleshooting and analyzing issues > ⏱ ~75 min · 💰 no additional cost · ⚙️ Requires: any deployed application; GKE profile for the Kubernetes paths **Why the exam cares** — Troubleshooting questions are scenario-driven: a revision won't start, pods crash-loop, a deploy succeeded but traffic fails. The skill tested is choosing the right diagnostic surface — Logs Explorer filters, Kubernetes events, revision status conditions, build logs — and reading them in the right order. **How RAD implements it** — The modules don't add troubleshooting tools per se; they produce richly labeled, predictable workloads to troubleshoot. Useful structure the modules guarantee: every resource carries `application`, `deployment`, `tenant`, and `managed-by` labels; Cloud Run revisions gate on a startup probe (`/healthz` by default) so misconfigured apps fail *visibly* at deploy time; GKE workloads run in a dedicated namespace with a deterministic name; init jobs (database setup, NFS setup) run as Cloud Run jobs / Kubernetes Jobs whose logs explain most first-deploy failures; and Cloud Build logs are in Cloud Logging (`CLOUD_LOGGING_ONLY`). **Try it** 1. Stage a failure: in the portal, point `container_image` at a tag that doesn't exist (or set `startup_probe_config.path` to a bogus path) and apply. 2. Cloud Run diagnosis path — revision conditions first, logs second: ```bash gcloud run revisions list --service= --region=us-central1 gcloud run revisions describe --region=us-central1 \ --format="yaml(status.conditions)" gcloud logging read \ 'resource.type="cloud_run_revision" AND resource.labels.service_name="" AND severity>=ERROR' --limit=10 ``` 3. GKE diagnosis path — events first, then pod state, then logs: ```bash kubectl get events -n --sort-by=.lastTimestamp | tail -20 kubectl get pods -n # look for ImagePullBackOff / CrashLoopBackOff kubectl describe pod -n kubectl logs -n --previous # logs from the crashed container ``` 4. In **Console > Logging > Logs Explorer**, reproduce step 2's query with the UI filters, switch on the **Histogram**, and correlate the error spike with the deploy timestamp. 5. Fix the variable, re-apply, and confirm recovery: the new revision reports `Ready: True` / pods reach `Running`. 6. You know it worked when you can state the failure cause from `status.conditions` or the event stream *before* opening application logs. **Check yourself**
Q1: A new Cloud Run revision deploys but receives 0% traffic and the previous revision still serves. The deploy command reported failure. What happened and why is this good? A: The startup probe (or container start) failed, so Cloud Run never marked the revision Ready and never shifted traffic — the previous revision keeps serving. This is fail-safe deployment: a broken image can't take an outage. Diagnosis: `status.conditions` on the revision, then its startup logs.
Q2: `kubectl logs` returns nothing for a pod stuck in `CrashLoopBackOff` with restarts climbing. What two commands get you the evidence? A: `kubectl logs --previous` (the *crashed* container's output — the current one may not have logged yet) and `kubectl describe pod ` (exit code, OOMKilled status, probe failures, events). Events and last-state often answer it without any application log at all.
Q3: A scheduled job worked for months, then silently stopped producing output. Logs show nothing at the expected time. Where do you look on this platform? A: Absence of logs at the expected time means the job never ran — check the trigger layer, not the application: CronJob status/`suspend` flag and events on GKE (`kubectl get cronjob -n `), or the Cloud Run job execution history. Then check audit logs for who changed it.
**Beyond the modules** — Error Reporting (automatic exception grouping), Log Analytics (SQL over logs), trace-correlated log views, and `gcloud builds log --stream` for live build debugging. Practice the Logs Explorer query language seriously — `resource.type`, `severity>=`, `jsonPayload.field=`, and timestamp bounds appear in exam answers verbatim. **⚠️ Exam trap** — `kubectl logs` without `--previous` shows the *current* container instance. In a crash loop, the current instance is often seconds old and empty; the evidence is in the previous instance's logs. --- ## 4.3 Managing metrics, dashboards, and alerts > ⏱ ~60 min · 💰 low · ⚙️ Requires: Observability baseline profile **Why the exam cares** — The exam tests alert policy mechanics — filters, aligners, reducers, duration windows, notification routing, renotification — and dashboard design that surfaces the four golden signals (latency, traffic, errors, saturation). You should be able to read an alert policy definition and predict exactly when it fires. **How RAD implements it** - **Fixed alerts** (the monitoring layer, created when `support_users` is non-empty): CPU and memory utilization, threshold `0.9`, greater-than comparison, duration `60s`, renotify every `1800s`. Aggregation differs by platform deliberately — Cloud Run aligns by delta and reduces with the 99th percentile over `run.googleapis.com/container/cpu/utilizations`; GKE aligns and reduces by mean grouped by pod name over `kubernetes.io/container/cpu/limit_utilization`. - **Custom alerts**: the `alert_policies` variable (list of `{name, metric_type, comparison, threshold_value, duration_seconds, aggregation_period}`) becomes one policy per entry, auto-filtered to this service/namespace, aligned by mean, and routed to the same email channels. - **Dashboards**: Cloud Run gets Request Count, Request Latency (p95), Container Instance Count, and Container CPU Utilization, pre-filtered to the service; GKE gets CPU Usage (Cores), Memory Usage (Bytes), Pod Restart Count, and Network Egress (Bytes), pre-filtered to the namespace. - **Platform-layer alerts** (Services_GCP, gated on `configure_email_notification`, default `false`): Cloud SQL CPU/memory/disk policies driven by `alert_cpu_threshold`/`alert_memory_threshold`/`alert_disk_threshold` (all default `80`, divided by 100 into ratios), plus NFS-server CPU, memory (Ops Agent metric), and an instance-down policy built on *metric absence* of CPU utilization. **Try it** 1. Add a latency alert via the portal: ```hcl alert_policies = [{ name = "p99-latency-high" metric_type = "run.googleapis.com/request_latencies" comparison = "COMPARISON_GT" threshold_value = 1000 duration_seconds = 300 }] ``` 2. Apply, then read back exactly what was created: ```bash gcloud alpha monitoring policies list \ --format="table(displayName,conditions[0].conditionThreshold.thresholdValue,conditions[0].conditionThreshold.duration)" ``` 3. Open **Console > Monitoring > Dashboards**, find the module dashboard (named ` - Cloud Run Dashboard ()` or the GKE variant), and walk each widget; note the `dashboardFilters` pinning it to your service/namespace. 4. Force a notification: temporarily set a custom alert with `threshold_value = 1` on `run.googleapis.com/request_count`, generate traffic, and confirm the email arrives; check **Monitoring > Alerting > Incidents** for the open incident, then remove the test policy. 5. You know it worked when the policy appears with your threshold and duration, the incident opens and closes as traffic starts/stops, and email lands at the `support_users` address. **Check yourself**
Q1: The Cloud Run CPU alert reduces with the 99th percentile across series while GKE reduces by mean grouped by pod. Why might the same "CPU > 90%" intent be aggregated differently? A: Cloud Run instances are interchangeable and short-lived — alerting on the p99 across instances catches the worst instances without paging on a single outlier mean shift. GKE pods are longer-lived, fewer, and individually meaningful, so a per-pod mean (grouped by pod name) identifies *which* pod is hot. Aggregation strategy should match the failure unit you'd act on.
Q2: An alert has duration 300s. CPU spikes to 95% for 90 seconds, four times an hour. Does it fire? A: No — the condition must hold continuously for the full duration window. 90-second spikes reset the clock each time. That's the false-positive defense duration provides, and also why genuinely bursty problems may need a shorter duration or a percentile aligner instead.
Q3: How does the Services_GCP "NFS instance down" alert detect an outage when a dead VM emits no metrics at all? A: It's a metric-*absence* condition on `compute.googleapis.com/instance/cpu/utilization`: no data for the window means the instance stopped reporting, which is the failure signal. Threshold conditions can't catch "no data" — absence conditions exist precisely for dead-emitter detection.
**Beyond the modules** — SLO-based (burn-rate) alerting, log-match alert conditions, multi-condition policies with AND/OR combiners, webhook/PagerDuty/Slack notification channel types (only `email` is created here), MQL/PromQL alert queries, and dashboard `Compare to past` workflows. Each is a 10-minute console exercise on top of the deployed lab. **⚠️ Exam trap** — Renotification (every 1800s here) controls reminders for a *still-open* incident; it does not re-evaluate or re-fire the condition. Confusing renotification with re-alerting leads to wrong answers about alert noise tuning. --- # PDE Certification Preparation Guide: Section 5 — Optimizing performance and cost (~12% of the exam) PDE Certification Preparation Guide: Section 5 — Optimizing performance and cost (~12% of the exam) > 📚 **Official exam guide:** [Professional Cloud DevOps Engineer certification](https://cloud.google.com/learn/certification/cloud-devops-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers exam Section 5 using the RAD foundation modules. Performance levers live in `App_CloudRun` (execution environment, CPU allocation, probes, resources) and `App_GKE` (resource requests, VPA, quotas); cost levers span both engines plus Artifact Registry cleanup and the GKE cluster's cost-allocation configuration. Deploy the **Cost-lean serverless** profile from the [Lab Map](PDE_Certification_Guide.md); the GKE exercises reuse the **GKE release engineer** profile. --- ## 5.1 Collecting performance information in Google Cloud > ⏱ ~60 min · 💰 low · ⚙️ Requires: Cost-lean serverless profile; GKE release engineer profile for Kubernetes metrics **Why the exam cares** — Performance questions test cause isolation: is latency in cold starts, CPU throttling, the container's resource ceiling, or a downstream dependency? You need to know which platform setting produces which performance signature and which metric proves it. **How RAD implements it** | Lever | Module setting | Performance effect | |---|---|---| | Execution environment | `execution_environment` (default `gen2`) | gen2 gives full Linux compatibility (required by the module's NFS and GCS Fuse mounts — validated at plan time) and different startup/CPU characteristics vs. gen1 | | Startup CPU boost | startup CPU boost is always on for the Cloud Run service | extra CPU during instance start shrinks cold-start latency | | CPU allocation | `cpu_always_allocated` (default `false`) controls whether CPU stays allocated when idle | always-on CPU keeps background work running between requests; request-only CPU throttles to near-zero when idle | | Warm floor | `min_instance_count` (default `0`) | ≥1 eliminates cold starts at a constant cost | | Resource ceiling | `container_resources` (`cpu_limit` `1000m`, `memory_limit` `512Mi`) | undersized limits show up as throttling/OOM kills | | Probe tuning | `startup_probe_config` / `health_check_config` | a slow `/healthz` or tight `failure_threshold` masquerades as deploy flakiness | | GKE metrics source | managed Prometheus + `SYSTEM_COMPONENTS` monitoring on every Services_GCP cluster | PromQL-queryable workload performance data | The performance evidence lives in Metrics Explorer: `run.googleapis.com/request_latencies`, `run.googleapis.com/container/startup_latencies`, `run.googleapis.com/container/cpu/utilizations`, and `kubernetes.io/container/cpu/limit_utilization` — the same metrics the module's dashboards and alerts are built from (see the [Section 4 guide](PDE_Section_4_Exploration_Guide.md#43-managing-metrics-dashboards-and-alerts)). **Try it** 1. Measure cold starts: with `min_instance_count = 0`, let the service idle ~15 minutes, then: ```bash for i in 1 2 3; do curl -s -o /dev/null -w "request $i: %{time_total}s\n" ; done ``` The first request carries the cold start; compare with **Metrics Explorer >** `run.googleapis.com/container/startup_latencies`. 2. Set `min_instance_count = 1` in the portal, apply, repeat the measurement after another idle period — the cold-start penalty disappears. 3. Flip `cpu_always_allocated = false`, apply, and check **Console > Cloud Run > (service) > Revisions > (latest)** shows "CPU is only allocated during request processing"; with background-thread workloads you'd now see idle-time throttling. 4. On GKE, compare requested vs. actual: `kubectl top pods -n ` against the `container_resources` values in the pod spec (`kubectl get pod -n -o jsonpath='{.spec.containers[0].resources}'`). 5. You know it worked when you can attribute the first-request latency delta to startup (not request processing) using the startup-latency metric, and you can state each pod's utilization-to-request ratio. **Check yourself**
Q1: A Cloud Run service shows fast p50 but terrible p99 latency, concentrated right after idle periods. Which two settings fix it and what do they cost? A: `min_instance_count = 1` (warm instance — eliminates cold starts, constant baseline cost) and startup CPU boost (already on for this service — faster starts when they do happen, billed only during startup). The p99-after-idle signature is the classic cold-start fingerprint.
Q2: After setting `cpu_always_allocated = false`, a service's response webhooks stop firing even though requests succeed. Why? A: With CPU allocated only during requests, background threads (work continuing after the response is sent) are throttled to near-zero between requests. Anything asynchronous must either finish before the response, move to a Cloud Run job/queue, or the service needs always-allocated CPU.
**Beyond the modules** — Cloud Trace (where in the request path latency accrues), Cloud Profiler (which function burns CPU — add the language agent and read flame graphs), and load testing methodology are not provisioned. In a scratch project, instrument a Cloud Run service with OpenTelemetry and inspect a trace waterfall — exam questions name these tools explicitly. **⚠️ Exam trap** — "CPU always allocated" and `min_instance_count` are independent axes: a min-instances=1 service with request-only CPU still throttles between requests, and a scale-to-zero service with always-allocated CPU still pays nothing when no instance exists. Don't conflate warm instances with active CPU. --- ## 5.2 Implementing FinOps practices for optimizing resource utilization and costs > ⏱ ~60 min · 💰 reduces cost · ⚙️ Requires: Cost-lean serverless profile; GKE release engineer profile **Why the exam cares** — FinOps questions test matching the saving mechanism to the waste pattern: idle capacity → scale-to-zero or rightsizing; over-requested resources → VPA/Recommender; stale artifacts → lifecycle/cleanup policies; predictable steady load → committed use discounts; attribution gaps → labels and billing export. **How RAD implements it** - **Pay-for-nothing idle**: `min_instance_count = 0` (Cloud Run default) plus `cpu_always_allocated = false` gives true scale-to-zero with request-granular billing. - **Spend ceilings**: `max_instance_count` (Cloud Run default `1`, GKE default `3`) caps the worst-case bill. - **Right-sizing**: GKE Autopilot bills by pod *requests*, so `container_resources` is directly a billing input; `enable_vertical_pod_autoscaling` (default `false`) lets VPA continuously fit requests to observed usage (`updateMode: Auto`, floor `10m` CPU / `32Mi`). - **Consumption guardrails**: `enable_resource_quota` (default `false`) caps a namespace at `quota_cpu_requests`/`quota_cpu_limits` (default `"4"`), `quota_memory_requests` (default `"4Gi"`) / `quota_memory_limits` (default `"8Gi"`) — binary unit suffixes are mandatory and validated (a bare `"4"` would be read by Kubernetes as 4 *bytes* and block all scheduling) — plus `quota_max_pods` (`"20"`), `quota_max_services` (`"10"`), `quota_max_pvcs` (`"5"`). - **Artifact storage hygiene**: the Artifact Registry cleanup trio — `max_images_to_retain` (default `7`, KEEP), `delete_untagged_images` (default `true`), `image_retention_days` (default `30`) — and Cloud Run revision pruning via `max_revisions_to_retain` (default `7`). - **Cost visibility hooks**: every resource carries cost-attribution labels (`tenant`, `application`, `deployment`); the GKE cluster enables cost allocation, so namespace/workload costs surface in billing reports. Note that the old BigQuery resource-usage export is *not* supported on Autopilot and was removed — billing export plus cost allocation is the supported path. **Try it** 1. Quantify scale-to-zero: with the Cost-lean profile, watch the instance count fall to zero after traffic stops — **Metrics Explorer >** `run.googleapis.com/container/instance_count` — then compare against a day with `min_instance_count = 1` in **Billing > Reports**, filtering by SKU group Cloud Run and grouping by service label. 2. Right-size with evidence on GKE: run load, read `kubectl top pods -n `, and if usage sits far below requests, either lower `container_resources` or set `enable_vertical_pod_autoscaling = true` and apply; verify the VPA's target with: ```bash kubectl get vpa -vpa -n \ -o jsonpath='{.status.recommendation.containerRecommendations[0].target}' ``` 3. Apply a namespace budget: set `enable_resource_quota = true` (defaults above) and verify enforcement: ```bash kubectl describe resourcequota -n ``` Then try raising `max_instance_count` beyond what the quota allows and watch pods stay Pending with a quota event. 4. Audit artifact spend: `gcloud artifacts repositories describe --location=us-central1 --format="yaml(cleanupPolicies)"` and **Artifact Registry > (repo)** size over time. 5. You know it worked when instance count hits zero between bursts, the VPA target is below your original request, and the ResourceQuota shows used vs. hard limits. **Check yourself**
Q1: A GKE Autopilot bill seems high although `kubectl top` shows pods using ~20% of their CPU requests. What's the cheapest structural fix? A: Lower the requests — Autopilot bills requested resources, not used ones. Either set `container_resources` from observed usage or enable VPA to do it continuously. Adding CUDs before right-sizing would lock in the waste.
Q2: Why does the module validate that `quota_memory_requests` carries a binary suffix like `"4Gi"`? A: Kubernetes parses a bare `"4"` as 4 bytes. A 4-byte namespace memory quota makes every pod's request exceed the quota, so nothing schedules — an outage caused by a unit typo. The plan-time validation turns a runtime mystery into an immediate, explainable failure.
Q3: Finance wants per-team cost reports for workloads sharing one GKE Autopilot cluster. Which two platform features make that possible here? A: GKE cost allocation (enabled on Services_GCP clusters), which attributes cluster costs to namespaces/labels in billing data, combined with the modules' consistent resource labels (`tenant`, `application`). Export billing to BigQuery and group by those labels for the report.
**Beyond the modules** — Billing export to BigQuery (the foundation of any FinOps practice — configure under **Billing > Billing export**), budgets and programmatic budget alerts via Pub/Sub, Active Assist/Recommender rightsizing and idle-resource recommendations, committed use discounts (GKE Autopilot CUDs commit to vCPU/GB amounts, not machine types), and Spot provisioning for fault-tolerant batch work. None are provisioned by the modules; all are inexpensive console exercises against the lab project. **⚠️ Exam trap** — Deleting old container *images* and old Cloud Run *revisions* are separate problems: a revision pins its image by digest, so an aggressive registry cleanup can break rollback to a retained revision whose image was deleted. The module's KEEP policy default (`max_images_to_retain = 7`) matches `max_revisions_to_retain` (7) — keep that coupling in mind when tuning either. --- # Professional Cloud Security Engineer (PSE) Certification Lab Map > 📚 **Official exam guide:** [Professional Cloud Security Engineer certification](https://cloud.google.com/learn/certification/cloud-security-engineer) — always confirm section weightings against the current Google Cloud exam guide. The PSE certification validates your ability to design and implement secure workloads and infrastructure on Google Cloud — identity and access management, perimeter and boundary protection, data protection, security operations, and regulatory compliance. The RAD platform's four foundation modules (`Services_GCP`, `App_CloudRun`, `App_GKE`, `App_Common`) form a live security lab: they implement least-privilege service accounts, Workload Identity, IAP, Secret Manager with automated zero-downtime rotation, CMEK with plan-time key recovery, Binary Authorization with a KMS signer and attestor, VPC Service Controls with access levels and dry-run mode, Cloud Armor WAF, Kubernetes NetworkPolicy micro-segmentation, audit log configuration, and Security Command Center enrollment — all driven by portal variables you can toggle and observe. ## How to use this guide - Deploy one of the profiles below from your deployment portal. - Work through the matching section guide (`PSE_Section__Exploration_Guide.md`) topic by topic. - Use the coverage legend to know which exam topics you must study outside the platform — the section guides give concrete study pointers for every 🟡 and 📘 topic. **Coverage legend** | Symbol | Meaning | |---|---| | ✅ | Fully demonstrated — deploy it, see it, modify it in the RAD platform | | 🟡 | Partially demonstrated — the modules touch the concept; supplement with docs | | 📘 | Concept-only — not implemented by the modules; study pointers provided | ## Deployment profiles ### Profile: secure-platform *Purpose:* a hardened shared platform exercising CMEK, audit logging, SCC, vulnerability scanning, and Binary Authorization. *Modules:* `Services_GCP`. | Variable | Value | |---|---| | `create_postgres` | `true` (default) | | `enable_cmek` | `true` | | `cmek_key_rotation_period` | `7776000s` (default, 90 days) | | `enable_audit_logging` | `true` | | `enable_security_command_center` | `true` | | `enable_scc_notifications` | `true` | | `enable_vulnerability_scanning` | `true` | | `enable_binary_authorization` | `true` | | `binauthz_evaluation_mode` | `REQUIRE_ATTESTATION` | *Estimated incremental cost:* low–moderate — KMS keys cost cents per month; the dominant drivers are the Cloud SQL instance baseline and increased Cloud Logging volume from DATA_READ/DATA_WRITE audit logs. ### Profile: guarded-edge *Purpose:* a Cloud Run service protected by IAP, a Cloud Armor WAF behind a global HTTPS load balancer, and automated secret rotation. *Modules:* `App_CloudRun` (optionally on top of secure-platform). | Variable | Value | |---|---| | `enable_iap` | `true` | | `iap_authorized_users` | `["user:you@example.com"]` | | `enable_cloud_armor` | `true` | | `application_domains` | `["app.example.com"]` (required when Cloud Armor is on) | | `enable_auto_password_rotation` | `true` | | `secret_rotation_period` | `2592000s` (default, 30 days) | | `enable_audit_logging` | `true` | *Estimated incremental cost:* moderate — the global external Application Load Balancer forwarding rules and the Cloud Armor policy are the dominant drivers; IAP and Secret Manager rotation are negligible. ### Profile: zero-trust-gke *Purpose:* a GKE Autopilot workload with Workload Identity, NetworkPolicy micro-segmentation, namespace quotas, and IAP at the Gateway. *Modules:* `Services_GCP` + `App_GKE`. | Variable | Value | |---|---| | `create_google_kubernetes_engine` (Services_GCP) | `true` | | `enable_network_segmentation` | `true` | | `enable_resource_quota` | `true` | | `enable_custom_domain` | `true` + `application_domains` | | `enable_cloud_armor` | `true` | | `enable_iap` | `true` + `iap_oauth_client_id`, `iap_oauth_client_secret`, `iap_support_email` | *Estimated incremental cost:* high — the GKE Autopilot cluster is the dominant cost driver; the Gateway load balancer and Cloud Armor add a moderate increment. ### Profile: perimeter-lab *Purpose:* a VPC Service Controls perimeter in dry-run mode around the project's APIs. Requires a project in a GCP organization and org-level Access Context Manager permission. *Modules:* any of `Services_GCP`, `App_CloudRun`, `App_GKE` (each can create its own perimeter). | Variable | Value | |---|---| | `enable_vpc_sc` | `true` | | `admin_ip_ranges` | `["/32"]` (required — perimeter is skipped without it) | | `vpc_sc_dry_run` | `true` (default — audit before enforcing) | | `organization_id` (App_CloudRun / App_GKE only) | set only if the project is nested under a folder | *Estimated incremental cost:* none — VPC-SC, access levels, and Access Context Manager are free. ## Section 1: Configuring access (~25% of the exam) Identity and authorization is where the modules are strongest on the "workload identity" side (dedicated service accounts, Workload Identity, per-resource IAM) and weakest on the "human identity" side (Cloud Identity, SSO, org policy), which you must study separately. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 1.1 Managing Cloud Identity | 📘 | identities consumed via `iap_authorized_users/groups`, `support_users` | [Section 1 guide](PSE_Section_1_Exploration_Guide.md#11-managing-cloud-identity) | | 1.2 Managing service accounts | ✅ | purpose-built service accounts with Workload Identity; WIF via `enable_workload_identity_federation` + `wif_provider_type` | [Section 1 guide](PSE_Section_1_Exploration_Guide.md#12-managing-service-accounts) | | 1.3 Managing authentication | 🟡 | `enable_iap` on Cloud Run and GKE | [Section 1 guide](PSE_Section_1_Exploration_Guide.md#13-managing-authentication) | | 1.4 Managing and implementing authorization controls | ✅ | the platform's resource-level IAM layer, per-secret/per-bucket IAM | [Section 1 guide](PSE_Section_1_Exploration_Guide.md#14-managing-and-implementing-authorization-controls) | | 1.5 Defining the resource hierarchy | 📘 | org/folder/standalone detection in the VPC Service Controls layer is the nearest adjacency | [Section 1 guide](PSE_Section_1_Exploration_Guide.md#15-defining-the-resource-hierarchy) | ## Section 2: Securing communications and establishing boundary protection (~22% of the exam) The modules implement three distinct boundary layers you can deploy and break on purpose: an edge WAF (Cloud Armor + global HTTPS LB), an API-level data-exfiltration perimeter (VPC Service Controls), and pod-level micro-segmentation (Kubernetes NetworkPolicy on Dataplane V2). | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 2.1 Designing and configuring perimeter security | ✅ | `enable_cloud_armor` on Cloud Run and GKE | [Section 2 guide](PSE_Section_2_Exploration_Guide.md#21-designing-and-configuring-perimeter-security) | | 2.2 Configuring boundary segmentation | ✅ | `enable_vpc_sc`, `enable_network_segmentation` (Kubernetes NetworkPolicy), private-IP Cloud SQL | [Section 2 guide](PSE_Section_2_Exploration_Guide.md#22-configuring-boundary-segmentation) | | 2.3 Establishing private connectivity | 🟡 | Direct VPC egress, Private Services Access, Cloud NAT | [Section 2 guide](PSE_Section_2_Exploration_Guide.md#23-establishing-private-connectivity) | ## Section 3: Ensuring data protection (~23% of the exam) Secret Manager with automated dual-version rotation and CMEK with plan-time key recovery are the standout hands-on labs here. Sensitive Data Protection (DLP) and AI workload security are concept-only. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 3.1 Protecting sensitive data and preventing data loss | 🟡 | Secret Manager rotation pipeline, `enable_auto_password_rotation`; DLP is 📘 | [Section 3 guide](PSE_Section_3_Exploration_Guide.md#31-protecting-sensitive-data-and-preventing-data-loss) | | 3.2 Managing encryption at rest, in transit, and in use | ✅ | `enable_cmek`, TLS at the LB; EKM/HSM/Confidential Computing are 📘 | [Section 3 guide](PSE_Section_3_Exploration_Guide.md#32-managing-encryption-at-rest-in-transit-and-in-use) | | 3.3 Securing AI workloads | 📘 | not implemented by the foundation modules | [Section 3 guide](PSE_Section_3_Exploration_Guide.md#33-securing-ai-workloads) | ## Section 4: Managing operations (~19% of the exam) The supply-chain story is fully wired: Cloud Build → Artifact Registry scanning → KMS-signed attestation → Binary Authorization admission enforcement. Detection is covered through audit log configuration and SCC findings routed to Pub/Sub. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 4.1 Automating infrastructure and application security | ✅ | `enable_binary_authorization`, `enable_vulnerability_scanning`, CI/CD attestation | [Section 4 guide](PSE_Section_4_Exploration_Guide.md#41-automating-infrastructure-and-application-security) | | 4.2 Configuring logging, monitoring, and detection | 🟡 | `enable_audit_logging`, `enable_security_command_center` + `enable_scc_notifications`; flow logs / sinks / IDS are 📘 | [Section 4 guide](PSE_Section_4_Exploration_Guide.md#42-configuring-logging-monitoring-and-detection) | ## Section 5: Supporting compliance requirements (~11% of the exam) The modules demonstrate the technical controls that compliance frameworks demand (CMEK, audit trails, least privilege, perimeters) and the shared-responsibility narrowing of GKE Autopilot — but framework mapping, Assured Workloads, and Access Transparency are study-only topics. | Exam topic | Coverage | Where in RAD | Guide | |---|---|---|---| | 5.1 Adhering to regulatory and industry standards requirements for the cloud | 🟡 | composed controls across all four modules; Assured Workloads / Access Transparency are 📘 | [Section 5 guide](PSE_Section_5_Exploration_Guide.md#51-adhering-to-regulatory-and-industry-standards-requirements-for-the-cloud) | --- # PSE Certification Preparation Guide: Section 1 — Configuring access (~25% of the exam) PSE Certification Preparation Guide: Section 1 — Configuring access (~25% of the exam) > 📚 **Official exam guide:** [Professional Cloud Security Engineer certification](https://cloud.google.com/learn/certification/cloud-security-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 1 of the Professional Cloud Security Engineer exam through the RAD platform's foundation modules. `Services_GCP` creates the purpose-built service accounts and their project-level role grants; `App_CloudRun` and `App_GKE` wire those identities into workloads (Workload Identity on GKE, dedicated runtime service accounts on Cloud Run, IAP for end-user authentication); `App_Common` applies resource-level least-privilege bindings. Deploy the **secure-platform** profile plus either **guarded-edge** (Cloud Run) or **zero-trust-gke** from the Lab Map before starting. --- ## 1.1 Managing Cloud Identity > ⏱ ~45 min (mostly reading) · 💰 no additional cost · ⚙️ Requires: default deployment **Why the exam cares** — The exam tests whether you can choose the right identity architecture: Google Cloud Directory Sync (GCDS) versus Workforce Identity Federation versus plain Cloud Identity, when SAML SSO makes Google the service provider versus the identity provider, and how to protect super-admin accounts. These are design decisions about *human* identity lifecycle, which sit above any single project. **How RAD implements it** — Not implemented by the foundation modules. The modules *consume* existing identities rather than manage them: `iap_authorized_users` and `iap_authorized_groups` (both default `[]`) accept `user:`, `group:`, `serviceAccount:`, and `domain:` principals (the platform normalizes the principal format), and `support_users` (default `[]`) feeds monitoring notification channels. This mirrors real life: the security engineer receives groups from the identity team and binds them to resources. **Try it** 1. In your deployment portal, add a Google Group to `iap_authorized_groups` (format `group:team@example.com`) on a deployment with `enable_iap = true`, and redeploy. 2. In **Console > IAM & Admin > IAM**, filter for the group and confirm it now holds `roles/iap.httpsResourceAccessor` at the project level. 3. CLI check: ```bash gcloud projects get-iam-policy $GOOGLE_PROJECT_ID \ --flatten="bindings[].members" \ --filter="bindings.role:roles/iap.httpsResourceAccessor" \ --format="table(bindings.members)" ``` 4. You know it worked when the group appears in the binding list — and removing it from the portal variable and redeploying removes the binding again. **Check yourself**
Q1: Your company has 5,000 users in on-premises Active Directory and wants them to access GCP with their existing corporate credentials, without creating passwords at Google. What do you configure? A: GCDS to synchronize users/groups one-way from AD into Cloud Identity, plus SAML SSO with the corporate IdP so Google acts as the service provider and never stores or verifies passwords. Alternatively, Workforce Identity Federation avoids synchronization entirely by issuing short-lived federated credentials — choose it when you don't want user objects in Cloud Identity at all.
Q2: What is the difference between Workforce Identity Federation and Workload Identity Federation? A: Workforce Identity Federation federates *human* users from an external IdP (OIDC/SAML) into Google Cloud without provisioning Cloud Identity accounts. Workload Identity Federation federates *non-human* workloads (GitHub Actions, AWS, etc.) so they can impersonate service accounts without exported JSON keys. The exam frequently swaps these terms in distractors.
**Beyond the modules** — Study: GCDS one-way sync architecture; SAML 2.0 SSO configuration in the Admin Console (**Security > Authentication > SSO with third-party IdP**); super-admin best practices (≥2 break-glass accounts, hardware security keys, no day-to-day use); the Admin SDK Directory API for lifecycle automation; and Workforce Identity Federation pools/providers (**IAM & Admin > Workforce Identity Federation**). Try in a scratch org: `gcloud iam workforce-pools list --location=global --organization=ORG_ID`. **⚠️ Exam trap** — GCDS synchronizes *from* on-premises *to* Cloud Identity, never the reverse, and it does not synchronize passwords by default — authentication still happens via SSO or Google passwords. --- ## 1.2 Managing service accounts > ⏱ ~1.5 h · 💰 no additional cost · ⚙️ Requires: secure-platform; zero-trust-gke for Workload Identity **Why the exam cares** — The exam tests the credential-risk hierarchy: exported service-account keys (worst) → key rotation → impersonation/short-lived tokens → Workload Identity / federation (best, keyless). You must know when to create dedicated service accounts instead of using defaults, and how GKE Workload Identity binds a Kubernetes ServiceAccount (KSA) to a Google service account (GSA). **How RAD implements it** — `Services_GCP` never relies on the default Compute Engine service account for workloads. It creates purpose-scoped accounts: | Service account | Purpose | Example roles | |---|---|---| | `cloudrun-sa-{prefix}` | Cloud Run runtime | `roles/run.admin`, `roles/secretmanager.secretAccessor`, `roles/cloudsql.client`, `roles/storage.objectAdmin`, `roles/compute.networkUser` | | `cloudbuild-sa-{prefix}` | CI/CD builds | 17 roles incl. `roles/cloudkms.signerVerifier`, `roles/binaryauthorization.attestorsViewer`, `roles/containeranalysis.admin` | | `clouddeploy-sa-{prefix}` | Progressive delivery | `roles/clouddeploy.jobRunner`, `roles/run.admin`, `roles/container.admin` | | `gke-sa-{prefix}` | GKE nodes + workloads | node roles (`roles/logging.logWriter`, `roles/monitoring.metricWriter`, `roles/artifactregistry.reader`) plus workload roles (`roles/cloudsql.client`, `roles/secretmanager.secretAccessor`) | | `nfs-sa-{prefix}` | NFS server VM | minimal compute/monitoring roles | On GKE, `App_GKE` annotates the namespace KSA with `iam.gke.io/gcp-service-account` and binds `roles/iam.workloadIdentityUser` to the member `serviceAccount:{project}.svc.id.goog[namespace/ksa]`. The cluster's workload pool is `{project}.svc.id.goog` (explicit for STANDARD mode, automatic on Autopilot). No JSON keys are created anywhere in the modules. Impersonation is also demonstrated: `cloudbuild-sa` is granted `roles/iam.serviceAccountUser` on `clouddeploy-sa`, and `roles/iam.serviceAccountTokenCreator` at project level. Workload Identity Federation is live in `Services_GCP`: `enable_workload_identity_federation` (default `false`) creates pool `wif-pool` with one OIDC provider chosen by `wif_provider_type` — `"github"` (default) → provider `github-actions` with issuer `https://token.actions.githubusercontent.com` and an optional attribute condition pinning the repository owner to `wif_github_org`; `"gitlab"` → provider `gitlab-ci` against `wif_gitlab_hostname`; `"generic"` → provider `oidc-provider` with `wif_oidc_issuer_uri` and `wif_allowed_audiences`. The pool's identities (`principalSet://.../*`) get `roles/iam.workloadIdentityUser` on the Cloud Build, Cloud Deploy, and Cloud Run service accounts, so external CI exchanges its OIDC token for short-lived GCP credentials — no exported keys. Note the wildcard principal set is deliberately broad; production setups scope to a specific repository attribute. **Try it** 1. Deploy zero-trust-gke. In **Console > IAM & Admin > Service Accounts**, locate `gke-sa-{prefix}` and open its **Permissions** tab — you'll see the `roles/iam.workloadIdentityUser` grant to the KSA principal. 2. Inspect the KSA annotation and prove keyless token exchange: ```bash gcloud container clusters get-credentials --region us-central1 kubectl get serviceaccount -n \ -o jsonpath='{.metadata.annotations.iam\.gke\.io/gcp-service-account}' # From inside an application pod — token comes from the GKE metadata server: kubectl exec -n deploy/ -- \ curl -s -H "Metadata-Flavor: Google" \ "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" ``` 3. Confirm zero user-managed keys exist: ```bash for SA in $(gcloud iam service-accounts list --format="value(email)"); do gcloud iam service-accounts keys list --iam-account=$SA \ --managed-by=user --format="value(name)" done ``` 4. You know it worked when the pod reports the GSA email (not a node default account) and the key listing returns nothing. **Check yourself**
Q1: A pod in namespace `app1` must read a Secret Manager secret without any mounted key file. What three pieces make this work? A: (1) the cluster's Workload Identity pool `{project}.svc.id.goog`; (2) the KSA annotated with `iam.gke.io/gcp-service-account: gsa@project.iam.gserviceaccount.com`; (3) an IAM binding granting `roles/iam.workloadIdentityUser` on the GSA to `serviceAccount:{project}.svc.id.goog[app1/ksa-name]`. The pod then receives short-lived GSA tokens from the GKE metadata server.
Q2: Scenario — an auditor finds developers downloading JSON keys for a CI pipeline that runs on GitHub Actions. What do you recommend? A: Workload Identity Federation: create a workload identity pool with a GitHub OIDC provider, restrict it with an attribute condition on the repository owner, and grant the federated principal `roles/iam.workloadIdentityUser` on the pipeline's service account. GitHub's own OIDC tokens are exchanged for short-lived Google credentials; no key is ever exported. Additionally enforce `constraints/iam.disableServiceAccountKeyCreation`.
Q3: Why does the platform create `cloudbuild-sa-{prefix}` instead of using the default Cloud Build service agent for everything? A: A dedicated SA gets exactly the roles the pipeline needs (sign attestations, push to AR, deploy) and is auditable per deployment; the legacy default `{project_number}@cloudbuild.gserviceaccount.com` is shared by every build in the project, widening blast radius and muddying audit trails.
**Beyond the modules** — Inspect the deployed WIF setup with `gcloud iam workload-identity-pools providers list --workload-identity-pool=wif-pool --location=global`, and know the manual equivalents: `gcloud iam workload-identity-pools create demo-pool --location=global` then `gcloud iam workload-identity-pools providers create-oidc github --workload-identity-pool=demo-pool --location=global --issuer-uri=https://token.actions.githubusercontent.com --attribute-mapping="google.subject=assertion.sub"`. Also study org policies `constraints/iam.disableServiceAccountKeyCreation` and `constraints/iam.automaticIamGrantsForDefaultServiceAccounts`, key-age auditing, and impersonation via `gcloud auth print-access-token --impersonate-service-account=SA`. **⚠️ Exam trap** — `roles/iam.serviceAccountUser` lets a principal *attach/run as* a service account (deploy-time), while `roles/iam.serviceAccountTokenCreator` lets it *mint tokens* for the SA (impersonation). Granting either at the project level effectively hands over every SA in the project — grant on the individual SA resource instead. --- ## 1.3 Managing authentication > ⏱ ~1 h · 💰 no additional cost (IAP itself is free) · ⚙️ Requires: guarded-edge or zero-trust-gke with `enable_iap = true` **Why the exam cares** — The exam tests context-aware, proxy-based authentication (IAP as a BeyondCorp building block) versus network-based access (VPN), the OAuth consent flow, session control, and 2-Step Verification enforcement levels. You should know what IAP authenticates (Google identity, via OAuth) and what it then authorizes (`roles/iap.httpsResourceAccessor`). **How RAD implements it** — Two different IAP integration patterns, both behind `enable_iap` (default `false`): | Aspect | App_CloudRun | App_GKE | |---|---|---| | Mechanism | Cloud Run v2 native IAP — the service is created with IAP enabled and runs in the BETA launch stage | `GCPBackendPolicy` on the Gateway backend referencing a Kubernetes Secret `{service}-iap-oauth` holding the OAuth client secret | | Required inputs | ≥1 of `iap_authorized_users` / `iap_authorized_groups` (plan-time validation) | same, plus `iap_oauth_client_id`, `iap_oauth_client_secret`, and `iap_support_email` (plan-time validations) | | Service agent | `roles/run.invoker` granted to `service-{project_number}@gcp-sa-iap.iam.gserviceaccount.com` | IAP configured on the LB backend service | | User authorization | `roles/iap.httpsResourceAccessor` at project level + `roles/run.invoker` on the service | `roles/iap.httpsResourceAccessor` granted per backend | | Lockout protection | the deploying identity is auto-appended to the authorized list (discovered from the caller's OpenID userinfo) | same auto-append logic | Note that on Cloud Run, native IAP protects the direct `*.run.app` URL too, so no ingress restriction is needed for IAP alone. **Try it** 1. Enable `enable_iap = true` with your email in `iap_authorized_users` and redeploy. 2. Open the service URL in an incognito window — you are redirected to Google sign-in; after authenticating with a *non-authorized* account you get the IAP "You don't have access" page. 3. In **Console > Security > Identity-Aware Proxy**, find the resource and review the principals. 4. CLI checks: ```bash # Cloud Run: IAP-enabled services run in the BETA launch stage gcloud run services describe --region us-central1 \ --format="value(launchStage)" # Who can pass IAP? gcloud projects get-iam-policy $GOOGLE_PROJECT_ID \ --flatten="bindings[].members" \ --filter="bindings.role:roles/iap.httpsResourceAccessor" \ --format="table(bindings.members)" ``` 5. You know it worked when an unauthorized Google account is blocked by IAP *before* the request reaches your container (no application log entry is produced). **Check yourself**
Q1: Scenario — a contractor's engagement ends. With IAP protecting the internal app, how is access revoked and how fast? A: Remove the user (or their group membership) from `iap_authorized_users`/`iap_authorized_groups` and redeploy — the `roles/iap.httpsResourceAccessor` binding is removed and IAP denies them at the Google edge within minutes. No VPN certificate revocation, firewall change, or application logout is needed; this is the BeyondCorp advantage the exam looks for.
Q2: Why does App_GKE require an OAuth client ID/secret while App_CloudRun does not? A: Cloud Run v2 exposes native IAP (`iap_enabled`), which uses a Google-managed OAuth configuration. The GKE Gateway path uses classic backend-service IAP, which still requires you to create an OAuth client in **APIs & Services > Credentials** and supply it via `iap_oauth_client_id`/`iap_oauth_client_secret`; the module stores the secret in a Kubernetes Secret referenced by the `GCPBackendPolicy`.
**Beyond the modules** — Not covered: 2SV enforcement (**Admin Console > Security > 2-step verification**; know that FIDO2 hardware keys are the only phishing-resistant method), IAP session length tuning, context-aware access (combining IAP with Access Context Manager access levels for device/IP conditions), SAML app integration, and IAP TCP forwarding for SSH/RDP — try `gcloud compute ssh VM --tunnel-through-iap` in a scratch project (the platform's `fw-allow-iap-ssh` firewall rule for `35.235.240.0/20` already permits this path). **⚠️ Exam trap** — IAP authenticates the user, but a Cloud Run service must *also* authorize the forwarded request: without `roles/run.invoker` for the user (or the IAP service agent), requests still fail after successful sign-in. The module grants both — remember the pair on the exam. --- ## 1.4 Managing and implementing authorization controls > ⏱ ~1 h · 💰 no additional cost · ⚙️ Requires: default deployment of any app module **Why the exam cares** — Scenario questions hinge on *where* a role is granted: project-level `roles/editor` is almost always a wrong answer; resource-level grants of predefined roles are the expected pattern. You should also know uniform bucket-level access, IAM Conditions, deny policies, and Policy Intelligence tooling. **How RAD implements it** — the platform's resource-level IAM layer is a working least-privilege catalog: | Binding | Scope | Role | |---|---|---| | DB password secret read | the individual secret | `roles/secretmanager.secretAccessor` | | Writable app secrets | the individual secret | `roles/secretmanager.secretVersionManager` | | App bucket data access | the individual bucket | `roles/storage.objectAdmin` | | App bucket metadata | the individual bucket | `roles/storage.legacyBucketReader` | | GitHub token | the individual secret | `roles/secretmanager.secretAccessor` (build SAs only) | | Cloud Build deploy | project | `var.deployment_role` + `roles/iam.serviceAccountUser` on the runtime SA | Buckets created by the platform's object-storage layer set uniform bucket-level access per bucket, and the backup bucket has uniform bucket-level access enabled with public access prevention enforced. Secrets are injected into workloads by reference only — `secret_environment_variables` (default `{}`) maps env-var names to Secret Manager secret IDs, resolved at runtime. **Try it** 1. In **Console > Security > Secret Manager**, open the DB password secret (named `secret-{instance}-{service}`) and check **Permissions** — only the workload and build SAs appear, not project-wide principals. 2. Compare resource-level vs project-level policy: ```bash gcloud secrets get-iam-policy secret-- \ --format="table(bindings.role, bindings.members)" gcloud storage buckets describe gs:// \ --format="value(uniform_bucket_level_access)" ``` 3. Negative test: create a new secret manually (`gcloud secrets create scratch-secret --replication-policy=automatic`), then exec into the workload and attempt to read it — access is denied because the SA's `secretAccessor` grant is per-secret, not project-level. (On GKE, `gke-sa` also holds a project-level `secretAccessor` grant from Services_GCP, so run this test against the Cloud Run deployment for a clean result.) 4. Add the secret to `secret_environment_variables` in the portal, redeploy, and re-test. You know it worked when the previously denied read now succeeds. **Check yourself**
Q1: Scenario — an app needs to read one bucket and one secret. A teammate proposes granting `roles/editor` "to keep it simple." What do you do and why? A: Grant `roles/storage.objectViewer` (or `objectAdmin` if it writes) on that bucket and `roles/secretmanager.secretAccessor` on that secret only. If the SA is compromised, the attacker reaches two resources instead of the whole project. The exam expects predefined roles at the narrowest resource scope; custom roles only when no predefined role fits.
Q2: A legacy object ACL grants `allUsers` read on one object in a bucket. How do you guarantee IAM is the single source of truth? A: Enable uniform bucket-level access on the bucket — object ACLs stop being evaluated entirely and bucket/project IAM governs all access. Pair with `public_access_prevention = enforced` to block any future `allUsers`/`allAuthenticatedUsers` grant, as the module's backup bucket does.
Q3: How would you block *everyone*, including project owners, from deleting audit log sinks? A: An IAM deny policy — deny rules are evaluated before allow bindings and override them. Attach a deny policy on the relevant permissions (e.g., `logging.sinks.delete`) with an exception principal set for the break-glass identity. This cannot be done with allow-policy hygiene alone.
**Beyond the modules** — Not implemented: IAM Conditions (time/resource-attribute-bound bindings — try `gcloud projects add-iam-policy-binding $GOOGLE_PROJECT_ID --member=user:x@example.com --role=roles/viewer --condition='expression=request.time < timestamp("2026-12-31T00:00:00Z"),title=temp'`), IAM deny policies (`gcloud iam policies create ... --kind=denypolicies`), Privileged Access Manager (JIT elevation), IAM Recommender, Policy Analyzer, and Policy Troubleshooter. **⚠️ Exam trap** — `roles/secretmanager.secretAccessor` allows reading secret *payloads*; `roles/secretmanager.viewer` only reads metadata. Distractors swap them. The module grants `secretAccessor` for reads and `secretVersionManager` (manage versions) for the rotation path. --- ## 1.5 Defining the resource hierarchy > ⏱ ~45 min · 💰 no additional cost · ⚙️ Requires: a project inside an organization to see hierarchy effects **Why the exam cares** — Organization → folder → project inheritance determines effective policy: IAM allow bindings inherit downward, deny overrides allow, and organization policy constraints set guardrails that no project admin can bypass. The exam tests folder design, custom org policy constraints (CEL), and the effective-policy evaluation order. **How RAD implements it** — The modules are project-scoped and do not manage folders or organization policies. The one place the hierarchy is visible is VPC Service Controls organization discovery: the platform reads the project's org ID and distinguishes three cases — project directly under the organization (org ID auto-discovered, VPC-SC proceeds), project nested under a folder (folder ID set but org ID empty → a warning instructs you to set `organization_id` explicitly), and standalone project (no org at all → VPC-SC permanently unavailable, skipped with a warning). This is a practical lesson in how project placement in the hierarchy changes which security features you can even use. **Try it** 1. Discover where your lab project sits: ```bash gcloud projects describe $GOOGLE_PROJECT_ID --format="value(parent.type, parent.id)" ``` 2. In **Console > IAM & Admin > Organization Policies**, review effective constraints on the project (e.g., `constraints/iam.disableServiceAccountKeyCreation`, `constraints/gcp.resourceLocations`) and note at which level each is set: ```bash gcloud org-policies list --project=$GOOGLE_PROJECT_ID ``` 3. If you hold org-policy admin in a sandbox, set a location constraint on a test folder and attempt to deploy a bucket outside the allowed region from the portal — the apply fails with a policy violation. 4. You know it worked when you can explain, for one constraint, which ancestor set it and why the project cannot override it. **Check yourself**
Q1: Scenario — `enable_vpc_sc = true` deploys cleanly but no perimeter appears, and the log says the organization ID could not be auto-discovered. The project lives under a folder. What is the fix? A: Set `organization_id` explicitly in the App_CloudRun/App_GKE portal variables. `data.google_project` only exposes `org_id` for projects parented *directly* by the organization; folder-nested projects return `folder_id` instead, so auto-discovery fails by design and the module skips VPC-SC with a warning rather than guessing.
Q2: An IAM role granted at the organization level conflicts with a deny policy at a folder. Who wins? A: The deny policy. Deny is evaluated before allow at every level; an inherited org-level allow cannot override a folder-level deny. Use Policy Troubleshooter to trace the effective decision.
**Beyond the modules** — Study: folder design patterns (by environment/business unit/compliance tier), org policy custom constraints in CEL (**IAM & Admin > Organization Policies > Custom constraints**), and project-factory automation. Useful scratch command: `gcloud org-policies describe constraints/iam.disableServiceAccountKeyCreation --effective --project=$GOOGLE_PROJECT_ID`. **⚠️ Exam trap** — Organization policy constraints restrict *resource configuration* (what can be created and how); IAM controls *who* can act. "Use an org policy to remove a user's access" is a classic wrong answer — and vice versa. --- # PSE Certification Preparation Guide: Section 2 — Securing communications and establishing boundary protection (~22% of the exam) PSE Certification Preparation Guide: Section 2 — Securing communications and establishing boundary protection (~22% of the exam) This guide covers Section 2 of the Professional Cloud Security Engineer exam. The relevant foundation modules: `Services_GCP` builds the VPC, firewall rules, Cloud NAT, and Private Services Access; `App_CloudRun` and `App_GKE` each implement a Cloud Armor WAF edge and a VPC Service Controls perimeter; `App_GKE` adds Kubernetes NetworkPolicy micro-segmentation on Dataplane V2. Deploy **guarded-edge** (or **zero-trust-gke**) and **perimeter-lab** from the Lab Map before starting. --- ## 2.1 Designing and configuring perimeter security > ⏱ ~2 h · 💰 moderate — global LB + Cloud Armor policy charges · ⚙️ Requires: `enable_cloud_armor = true` (`application_domains` optional on Cloud Run) **Why the exam cares** — You must pick the right edge control per threat: Cloud Armor preconfigured WAF rules for OWASP attacks, rate-based bans for brute force/scraping, Adaptive Protection for L7 DDoS, and IAP for identity — and know that Cloud Armor only protects traffic that actually flows through the load balancer it is attached to. **How RAD implements it** — `enable_cloud_armor` (default `false`) creates a Cloud Armor security policy with identical rule content in both modules: | Priority | Rule | Action | |---|---|---| | 100 | `admin_ip_ranges` allowlist | `allow` (bypasses WAF rules) | | 1000–1003 | `evaluatePreconfiguredExpr('sqli-v33-stable')`, `xss-v33-stable`, `lfi-v33-stable`, `rce-v33-stable` | `deny(403)` | | 2000 | rate limit 500 requests/min per IP | `rate_based_ban` → `deny(429)`, 300 s ban | | 2147483647 | default | `allow` | Adaptive Protection (Layer 7 DDoS defense) is on in both. The plumbing differs: - **App_CloudRun** builds the entire edge: serverless NEG → backend service (an external Application Load Balancer, with request logging at full sample rate) → URL map → HTTPS proxy → global static IP, with Certificate Manager Google-managed certificates per `application_domains` and a permanent HTTP→HTTPS redirect. Cloud Run ingress is force-overridden to internal-and-cloud-load-balancing whenever `enable_cloud_armor` or `enable_cdn` is true, so the direct `*.run.app` URL cannot bypass the WAF. A plan-time validation requires at least one entry in `application_domains` when `enable_cloud_armor = true`; a nip.io-derived Google-managed certificate fallback exists only for the LB-without-Cloud-Armor path (e.g., CDN-only). - **App_GKE** creates the policy as `{service}-waf-policy` and attaches it to the Gateway API backend via a `GCPBackendPolicy`; a plan-time validation requires `enable_custom_domain = true` or `service_type = "LoadBalancer"`. The priority-100 `admin_ip_ranges` allow rule is present in both policies; on App_CloudRun the same variable *also* feeds the VPC-SC access level, so trusted networks bypass the WAF and pass the perimeter with one setting. **Try it** 1. Deploy guarded-edge. In **Console > Network Security > Cloud Armor policies**, open `{service}-waf-policy` and review rule priorities and the Adaptive Protection tab. 2. Fire a simulated SQL injection at the LB and watch it bounce: ```bash LB_IP=$(gcloud compute addresses list --global \ --filter="name~lb-ip" --format="value(address)") curl -sk "https://app.example.com/?q=1%27%20OR%20%271%27=%271" -o /dev/null -w "%{http_code}\n" # Expect 403 from rule sqli-v33-stable gcloud compute security-policies describe -waf-policy \ --format="yaml(rules[].priority, rules[].action, rules[].description)" ``` 3. Verify the bypass is closed: `curl -s -o /dev/null -w "%{http_code}\n" https://-.run.app/` returns 404/403 because ingress is restricted to the load balancer. 4. You know it worked when WAF denials appear in **Logging > Logs Explorer** under the backend service's `requests` log with `jsonPayload.enforcedSecurityPolicy.outcome="DENY"`. **Check yourself**
Q1: Scenario — after enabling Cloud Armor on Cloud Run, a pen tester still reaches the app through its run.app URL. What was missed? A: Ingress was left at `all`. Cloud Armor only inspects traffic traversing the load balancer; the service must be restricted to `internal-and-cloud-load-balancing` so direct URLs are refused. The RAD module does this automatically — the exam expects you to know it must be done.
Q2: A credential-stuffing botnet sends 2,000 requests/min/IP. Which of the deployed rules responds, and how? A: The priority-2000 `rate_based_ban` rule: each source IP exceeding 500 requests/60 s gets `deny(429)` and a 300-second ban. Adaptive Protection complements this by detecting distributed L7 anomalies that per-IP limits miss and suggesting targeted rules.
Q3: When is IAP the right edge control instead of (or in addition to) Cloud Armor? A: Cloud Armor filters by request signature/source (no identity); IAP requires an authenticated, authorized Google identity. For an internal tool, IAP alone suffices. For a public app, Cloud Armor (WAF/DDoS/rate limiting). For a sensitive internal app exposed via LB, layer both: Armor scrubs attacks at the edge, IAP enforces identity.
**Beyond the modules** — Not implemented: Cloud NGFW network/hierarchical firewall policies (the platform uses classic VPC firewall rules with network tags), FQDN/geo/threat-intelligence rules, Cloud Armor bot management with reCAPTCHA, edge security policies, Certificate Authority Service for mTLS, and Secure Web Proxy for egress filtering. Scratch-project starter: `gcloud compute network-firewall-policies create demo-policy --global` and `gcloud compute security-policies update --enable-layer7-ddos-defense`. **⚠️ Exam trap** — Preconfigured WAF rules come in sensitivity levels and can false-positive on legitimate payloads (e.g., a CMS saving HTML). The exam answer is to run new rules in `preview` mode and tune with `evaluatePreconfiguredWaf(...,{'sensitivity': N})` or opt-out rule IDs — not to disable the WAF. --- ## 2.2 Configuring boundary segmentation > ⏱ ~2.5 h · 💰 none for VPC-SC/NetworkPolicy · ⚙️ Requires: perimeter-lab (org + ACM permission); zero-trust-gke for NetworkPolicy **Why the exam cares** — IAM answers *who*, network segmentation answers *from where*, and VPC Service Controls answers *where data may flow at the API layer*. Exam scenarios about stolen-but-valid credentials exfiltrating Cloud Storage or BigQuery data are VPC-SC questions; pod-lateral-movement scenarios are NetworkPolicy questions. **How RAD implements it** — *VPC Service Controls* (created in App_CloudRun/App_GKE when `enable_vpc_sc = true`, default `false`; `Services_GCP` has a standalone equivalent): - **Organization ID contract:** the org ID is auto-discovered from the project. In App_CloudRun/App_GKE, an explicit `organization_id` (default `""`) overrides discovery and is *required only when the project is nested under a folder*; standalone projects skip VPC-SC with a warning. `Services_GCP` relies purely on auto-discovery (it has no `organization_id` variable). - Creation is further gated by: non-empty `admin_ip_ranges` (lockout prevention) and a plan-time permission probe that runs `gcloud access-context-manager policies list --organization=...` and gracefully skips all VPC-SC resources with a warning if the caller lacks org-level ACM permission. - What gets built: an Access Context Manager policy (reused if the org already has one), four access levels — VPC subnet CIDRs (auto-discovered from the network when `vpc_cidr_ranges` is empty, falling back to `10.0.0.0/8`), `admin_ip_ranges`, the IAP service agent, and CI/CD service accounts — and a regular service perimeter restricting 15 services (Cloud Run, GKE, Cloud SQL Admin, Secret Manager, Storage, Artifact Registry, Cloud Build, KMS, Pub/Sub, Redis, Filestore, Firestore, Compute, Certificate Manager, IAP) with VPC-accessible-services restriction, ingress policies sourced from the four access levels, and scoped egress policies. - `vpc_sc_dry_run` (default `true`) writes the configuration to the perimeter's dry-run spec: violations are logged, not blocked. *Kubernetes NetworkPolicy* (`enable_network_segmentation` default `false`, requires Dataplane V2 — which `Services_GCP` clusters always use via the advanced datapath): a default-deny-by-omission policy selecting all pods in the namespace for both ingress and egress. Allowed ingress: same-namespace pods, Google LB/health-check ranges `130.211.0.0/22` and `35.191.0.0/16`, `35.235.240.0/20`, and — whenever `service_type` is `LoadBalancer` (the module default) or `NodePort` — `0.0.0.0/0` on the container port only, because an L4 Network Load Balancer preserves the original client IP so real traffic matches none of the Google ranges. Restricting callers is then Cloud Armor's job (`enable_cloud_armor` + `admin_ip_ranges`) or an internal `service_type`, not the NetworkPolicy. Allowed egress: DNS (53 TCP/UDP), HTTPS 443 (including the restricted/private googleapis VIPs `199.36.153.4/30` and `199.36.153.8/30`), same-namespace pods, Cloud SQL proxy loopback plus port 3307 to `10.0.0.0/8`, the metadata server `169.254.169.254/32:80` (Workload Identity token endpoint), and NFS 2049 when enabled. *Network-level isolation:* Cloud SQL is private-IP-only (no public IPv4, encrypted-only SSL mode), and firewall rules use network tags (`httpserver`, `nfsserver`, etc.) rather than broad CIDR allows. **Try it** 1. Deploy perimeter-lab. In **Console > Security > VPC Service Controls**, switch to the dry-run tab and open the perimeter; review restricted services and access levels. ```bash POLICY=$(gcloud access-context-manager policies list \ --organization=ORG_ID --format="value(name)") gcloud access-context-manager perimeters dry-run list --policy=$POLICY gcloud access-context-manager levels list --policy=$POLICY ``` 2. From a machine *outside* `admin_ip_ranges`, run `gcloud secrets versions access latest --secret=secret--` and then search **Logs Explorer** for `protoPayload.metadata.dryRun="true"` violations against `secretmanager.googleapis.com`. 3. For NetworkPolicy, deploy zero-trust-gke and probe segmentation: ```bash kubectl get networkpolicy -n kubectl describe networkpolicy -namespace-isolation -n # Negative test from a scratch namespace — should time out: kubectl run probe --image=busybox -n default --rm -it --restart=Never \ -- wget -T 5 -qO- http://..svc.cluster.local ``` 4. You know it worked when cross-namespace pod traffic times out while the app still serves LB health checks and reaches Cloud SQL. **Check yourself**
Q1: Scenario — an attacker steals a service account key with `roles/storage.admin` and runs `gsutil cp` from their home network. IAM allows it. What deployed control stops the copy, and why? A: The VPC-SC perimeter (once `vpc_sc_dry_run = false`). `storage.googleapis.com` is a restricted service, and the request originates from outside every access level (not the VPC CIDRs, not `admin_ip_ranges`, not the IAP/CI-CD identities), so the API call itself is rejected regardless of valid credentials. VPC-SC controls *where from*, IAM controls *who*.
Q2: Why does the module refuse to create the perimeter when `admin_ip_ranges` is empty? A: Lockout prevention. With enforcement on and no admin access level, operators and CI/CD outside the VPC would be unable to call any restricted API — including the calls needed to fix or remove the perimeter. The module emits a warning and skips creation instead.
Q3: Your GKE pods must reach Secret Manager under the NetworkPolicy. Which two egress rules make that possible? A: Egress 443 (covering the googleapis endpoints, including the restricted-VIP ranges `199.36.153.4/30`/`199.36.153.8/30` when Cloud DNS maps `*.googleapis.com` there) and egress to the metadata server `169.254.169.254:80`, which Workload Identity uses to exchange the KSA token for a GSA access token before the HTTPS call can authenticate.
**Beyond the modules** — Not implemented: Shared VPC host/service projects, VPC peering between customer VPCs, hierarchical firewall policies, Cloud NGFW L7 inspection, and per-pod (rather than per-namespace) policy granularity. Study the Shared VPC IAM model (`roles/compute.networkUser` on shared subnets) and try `gcloud compute shared-vpc enable HOST_PROJECT` in a scratch org. Also study VPC-SC ingress/egress rule semantics for cross-perimeter sharing and perimeter bridges. **⚠️ Exam trap** — Dry-run mode is the right rollout default but enforces *nothing*. An audit finding of "VPC-SC configured" is not "VPC-SC enforced" — check `vpc_sc_dry_run` (the module defaults it to `true`) and the perimeter's enforced vs dry-run spec before claiming exfiltration protection. --- ## 2.3 Establishing private connectivity > ⏱ ~1.5 h · 💰 low — Cloud NAT data processing; PSA/Direct VPC egress free · ⚙️ Requires: secure-platform + any app module (defaults suffice) **Why the exam cares** — The exam tests choosing among Private Google Access, Private Services Access (PSA), Private Service Connect, Direct VPC egress / serverless VPC access, and hybrid options (HA VPN, Interconnect) — and knowing which gives private reachability to *Google APIs* versus *managed services* versus *your own VPC*. **How RAD implements it** — - **Private Services Access**: a reserved `/16` internal range used for VPC peering plus a Service Networking connection with custom-route import/export — this is how Cloud SQL, AlloyDB, and Memorystore get private IPs inside the producer network peered to your VPC. - **Direct VPC egress on Cloud Run**: the service attaches a network interface in the subnet; `vpc_egress_setting` (default `PRIVATE_RANGES_ONLY`, or `ALL_TRAFFIC`) controls whether only RFC-1918-bound traffic or everything is routed through the VPC. No Serverless VPC Access connector is used. - **Cloud NAT**: one Cloud Router + NAT gateway per region (covering all subnetworks and IP ranges) gives instances and egressing workloads outbound internet without external IPs. - **Restricted/private Google API VIPs**: the GKE NetworkPolicy explicitly allows `199.36.153.4/30` (restricted.googleapis.com, the VPC-SC-compatible endpoint) and `199.36.153.8/30` (private.googleapis.com). - The Cloud SQL Auth Proxy sidecar on GKE dials the instance's *private* IP (`--private-ip`, port 3307), keeping database traffic entirely on the VPC. **Try it** 1. In **Console > VPC network > VPC network peering**, observe the `servicenetworking` peering created by PSA; in **SQL > instance > Connections**, confirm there is no public IP. ```bash gcloud services vpc-peerings list --network=vpc-network- gcloud sql instances describe \ --format="value(settings.ipConfiguration.ipv4Enabled, ipAddresses[].ipAddress)" gcloud compute routers nats list --router= --region=us-central1 ``` 2. Flip `vpc_egress_setting` to `ALL_TRAFFIC` in the portal and redeploy; in **Cloud Run > service > Networking**, the egress setting changes — outbound calls to public APIs now exit via Cloud NAT with the NAT IP (verify with `curl https://ifconfig.me` from the container). 3. You know it worked when the Cloud SQL instance shows only a 10.x address and the container's public egress IP equals the NAT address. **Check yourself**
Q1: Scenario — a payment gateway allowlists a single static IP. Your Cloud Run service must call it. How do you guarantee a stable source IP with the deployed architecture? A: Set `vpc_egress_setting = "ALL_TRAFFIC"` so all outbound traffic routes through the VPC, then ensure Cloud NAT uses a reserved static external address. The gateway then sees only the NAT IP. With `PRIVATE_RANGES_ONLY`, calls to public endpoints would leave directly from Google's serverless pool with unpredictable IPs.
Q2: What's the difference between Private Services Access (used here for Cloud SQL) and Private Service Connect? A: PSA creates a VPC peering to a Google-managed producer network and allocates an IP range from your space — connectivity is network-to-network and non-transitive. PSC exposes a service (Google APIs or a producer service) as an *endpoint IP inside your own subnet*, with no peering and finer control. The exam favors PSC for new designs needing per-service endpoints or overlapping-IP tolerance; PSA remains the mechanism Cloud SQL/Memorystore private IP classically uses.
**Beyond the modules** — Not implemented: Cloud VPN / HA VPN, Cloud Interconnect (Dedicated/Partner, MACsec), BGP custom routing beyond NAT, Network Connectivity Center, Private Google Access subnet flag demonstrations, proxy-only subnets, internal load balancers, and Cloud DNS private zones for `*.googleapis.com` → restricted VIP mapping (the NetworkPolicy allows those CIDRs, but the DNS zone itself is not created). Scratch commands: `gcloud compute networks subnets update SUBNET --region=R --enable-private-ip-google-access` and `gcloud compute vpn-gateways create ...` to study HA VPN topology. **⚠️ Exam trap** — `restricted.googleapis.com` only serves APIs that VPC-SC supports and is the endpoint to use *inside* a perimeter; `private.googleapis.com` serves nearly all APIs but provides no exfiltration protection. Pointing perimeter workloads at the private VIP instead of the restricted VIP is a classic mis-hardening the exam probes. --- # PSE Certification Preparation Guide: Section 3 — Ensuring data protection (~23% of the exam) PSE Certification Preparation Guide: Section 3 — Ensuring data protection (~23% of the exam) > 📚 **Official exam guide:** [Professional Cloud Security Engineer certification](https://cloud.google.com/learn/certification/cloud-security-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 3 of the Professional Cloud Security Engineer exam. The relevant foundation modules: `App_Common` (Secret Manager lifecycle and the zero-downtime rotation pipeline), `Services_GCP` and `App_Common` (customer-managed encryption keys with plan-time key recovery), and the TLS/storage controls spread across `App_CloudRun`, `App_GKE`, and the platform's object-storage layer. Deploy the **secure-platform** profile with `enable_cmek = true` plus **guarded-edge** with `enable_auto_password_rotation = true` before starting. --- ## 3.1 Protecting sensitive data and preventing data loss > ⏱ ~2 h · 💰 low — Secret Manager versions and one rotation job · ⚙️ Requires: guarded-edge (`enable_auto_password_rotation = true`); any database-backed deployment **Why the exam cares** — The exam tests secret hygiene end to end: never in code/env-var plaintext/Terraform state, least-privilege access per secret, automated rotation with zero downtime, and a defensible audit trail. It also tests Sensitive Data Protection (Cloud DLP) for PII discovery and de-identification — which the modules do not implement. **How RAD implements it** — the platform's Secret Manager layer: - The database password is generated randomly (`database_password_length`, default `32`, range 16–64) and stored as `secret-{instance}-{service}`. Workload access is per-secret `roles/secretmanager.secretAccessor`. Apps consume secrets by reference: `secret_environment_variables` on Cloud Run resolves at runtime via a secret key reference; App_GKE uses the Secret Manager add-on — a `SecretProviderClass` plus a `SecretSync` custom resource materializes Secret Manager values into the Kubernetes Secret the pods reference, with `secret_propagation_delay` (default `30` s) absorbing replication lag. - The GitHub CI/CD token is written with `gcloud secrets versions add` inside a provisioner specifically so the plaintext **never enters Terraform state** — a state-hygiene pattern worth quoting in exam answers about IaC secret handling. - `secret_rotation_period` (default `2592000s` = 30 days) creates the Pub/Sub topic `secret-{service}-rotation` and grants the Secret Manager service identity `roles/pubsub.publisher` so Secret Manager itself emits rotation notifications on schedule. - `enable_auto_password_rotation` (default `false`; plan-time validation requires a database) deploys the full handler: Eventarc trigger → `pw-rotator-dispatcher` Cloud Run service → `{prefix}-pw-rotator` Cloud Run job. The job's zero-downtime dual-version flow: record current ENABLED version → generate a new random password → `ALTER USER` on Cloud SQL (effective immediately) → add a new secret version (so `latest` serves the new value) → sleep `rotation_propagation_delay_sec` (default `90`) → disable (not destroy) the old version, keeping it for rollback and audit. Datastore-side protection: Cloud SQL is private-IP-only with encrypted-only SSL mode; Redis has AUTH enabled with the AUTH string stored in Secret Manager. **Try it** 1. Deploy with rotation enabled. In **Console > Security > Secret Manager**, open `secret-{instance}-{service}` → **Versions**: note the version history and the rotation settings (next rotation time, topic). 2. Trigger and observe a rotation without waiting 30 days: ```bash gcloud run jobs execute -pw-rotator --region us-central1 --wait gcloud secrets versions list secret-- \ --format="table(name, state, createTime)" ``` 3. You should see a new ENABLED version and the previous one DISABLED. Confirm the app still serves traffic during the swap (the dual-version window plus the propagation sleep covers in-flight connections). 4. Audit who touched the secret (requires `enable_audit_logging = true`): ```bash gcloud logging read 'protoPayload.serviceName="secretmanager.googleapis.com" AND protoPayload.methodName:"AccessSecretVersion"' \ --limit=10 --format="table(timestamp, protoPayload.authenticationInfo.principalEmail)" ``` 5. You know it worked when the version table shows the dual-version history and the data-access log names the workload SA as the only payload reader. **Check yourself**
Q1: Scenario — during password rotation, users must see zero failed logins. Order the steps correctly and explain the critical ordering decision. A: Generate new password → update the database user (`ALTER USER`) → add the new Secret Manager version → wait a propagation delay → disable the old version. The critical choice: the DB is updated *before* the secret version, and the old version is disabled only *after* propagation — during the window both old (cached) and new credentials authenticate, so no client fails. Disabling (not destroying) the old version preserves rollback.
Q2: Why does Secret Manager's rotation feature alone not rotate anything? A: `rotation` on a secret only publishes a Pub/Sub notification on schedule. Something must consume it and perform the change — here, Eventarc fires the dispatcher, which executes the rotator job. The exam loves this distinction: rotation schedule = notification; rotation handler = your code.
Q3: A teammate proposes declaring Secret Manager secret versions directly in Terraform for API tokens. What is the risk and the deployed alternative? A: That resource stores the plaintext payload in Terraform state — anyone with state access (or a committed state file) reads the secret. The module instead pushes the value with `gcloud secrets versions add` in a provisioner, so only a non-reversible hash is kept in state and workloads read the secret by ID at runtime.
**Beyond the modules** — Sensitive Data Protection (Cloud DLP) is not implemented: study infoType inspection of Cloud Storage/BigQuery, de-identification (redaction, `CryptoDeterministicConfig` tokenization, format-preserving encryption with `CryptoReplaceFfxFpeConfig`), and routing findings to SCC. Scratch command: `gcloud dlp inspect-templates create` or inspect a string via the API: `gcloud alpha dlp text inspect --content="My SSN is 123-45-6789" --info-types=US_SOCIAL_SECURITY_NUMBER` (or use the **Security > Sensitive Data Protection** console). Also study BigQuery column-level security and dynamic data masking — no BigQuery exists in the platform. **⚠️ Exam trap** — `DISABLED` secret versions can be re-enabled; `DESTROYED` versions are gone forever. Rotation handlers should disable, not destroy, the previous version until the new one is proven — exactly what the rotator job does. --- ## 3.2 Managing encryption at rest, in transit, and in use > ⏱ ~2 h · 💰 low — a few KMS key versions per month · ⚙️ Requires: secure-platform (`enable_cmek = true`) **Why the exam cares** — You must choose the right key-management tier (Google default → CMEK → Cloud HSM → Cloud EKM/Hold-Your-Own-Key) for a compliance requirement, understand rotation semantics (new versions encrypt; old versions still decrypt), key state transitions (`ENABLED`/`DISABLED`/`DESTROY_SCHEDULED`/`DESTROYED`), and crypto-shredding. **How RAD implements it** — - **At rest, CMEK** (`enable_cmek` default `false`): keyring `cmek-{prefix}` (created idempotently because keyrings are indestructible) with three symmetric-encryption keys — `cloudsql-{prefix}-key`, `artifactregistry-{prefix}-key`, `storage-{prefix}-key` — each rotating on `cmek_key_rotation_period` (default `7776000s`, 90 days). Each service identity (Cloud SQL, AlloyDB, Artifact Registry; the GCS service agent) is granted `roles/cloudkms.cryptoKeyEncrypterDecrypter` **on the individual key**, not the project. - **App-layer CMEK**: discovers the Services_GCP keyring or creates `{project_id}-cmek-keyring`, manages well-known keys `storage-key` and `artifact-registry-key`, and waits ~60 s for IAM propagation before encrypting buckets/repos. - **Key recovery at plan time** (a key-recovery script runs on every plan): if the named key version is scheduled for destruction it is restored, if disabled it is re-enabled, if missing it is created — returning a key status of `enabled|restored|created|skipped`. A companion check re-asserts the GCS service agent's KMS grant. The Binary Authorization signing key gets the same restore-and-enable treatment. - **In transit**: Certificate Manager Google-managed certificates terminate TLS at the global HTTPS LB with permanent HTTP→HTTPS redirects; Cloud SQL enforces encrypted-only SSL mode (PostgreSQL); the Cloud SQL Auth Proxy sidecar gives mTLS-wrapped database connections on GKE. - **In use**: not implemented (no Confidential VMs / Confidential GKE Nodes). **Try it** 1. In **Console > Security > Key Management**, open keyring `cmek-{prefix}`; check each key's rotation period and next rotation date, and the per-key IAM grants on the **Permissions** pane. ```bash gcloud kms keys list --keyring=cmek- --location=us-central1 \ --format="table(name, purpose, rotationPeriod, primary.state)" gcloud kms keys get-iam-policy cloudsql--key \ --keyring=cmek- --location=us-central1 gcloud sql instances describe --format="value(diskEncryptionConfiguration.kmsKeyName)" ``` 2. Exercise the recovery path: disable the storage key version (`gcloud kms keys versions disable 1 --key=storage-key --keyring= --location=us-central1`), then run a plan/redeploy from the portal — the platform's plan-time key-recovery check re-enables it before any bucket operation; check the plan log for a key status of `restored`/`enabled`. 3. You know it worked when the Cloud SQL instance reports your CMEK key name and the disabled key version is ENABLED again after the next plan. **Check yourself**
Q1: Scenario — a regulator requires that you can render all customer data unrecoverable on demand ("crypto-shredding"). How does the deployed CMEK design satisfy this, and what is the irreversible step? A: All Cloud SQL/GCS/AR data is encrypted under customer-managed keys. Disabling the key versions makes data immediately inaccessible but reversible; scheduling destruction (`gcloud kms keys versions destroy`) and letting the 24-hour-plus pending window elapse destroys the key material, making every byte encrypted under it permanently unrecoverable — including backups. Destruction of the key version is the irreversible step.
Q2: After automatic rotation creates key version 5, can data encrypted under version 2 still be read? A: Yes. Rotation changes the *primary* version used for new encryption; existing ciphertext stays decryptable by its original version as long as that version remains enabled. This is why disabling/destroying *old* versions, not rotation itself, is what cuts access to old data.
Q3: Cloud SQL instance creation fails with "Cloud KMS key is disabled, destroyed, or scheduled to be destroyed." What does the platform do about this class of failure, and what would you answer on the exam? A: The platform's plan-time `data "external"` probe restores `DESTROY_SCHEDULED` versions and re-enables `DISABLED` ones before dependent resources are touched. The exam answer: restore the key version (possible only during the scheduled-destruction window) or re-enable it, and verify the service agent still holds `roles/cloudkms.cryptoKeyEncrypterDecrypter` on the key.
**Beyond the modules** — Not implemented: Cloud HSM protection level (FIPS 140-2 Level 3 — `--protection-level=hsm` at key creation), Cloud EKM (keys held outside Google entirely), customer-supplied encryption keys (CSEK), key import, Confidential Computing (AMD SEV Confidential VMs / Confidential GKE Nodes), and client-side/application-layer encryption (e.g., Tink). Scratch commands: `gcloud kms keys create hsm-key --keyring=KR --location=L --purpose=encryption --protection-level=hsm` and `gcloud compute instances create cvm --confidential-compute --maintenance-policy=TERMINATE`. **⚠️ Exam trap** — CMEK does not mean Google "can't see" your data (the key still lives in Cloud KMS and Google infrastructure performs the cryptography); it means *you* control the key lifecycle and IAM. Only Cloud EKM (external key manager) keeps key material outside Google — pick EKM for "provider must never hold the key" scenarios. --- ## 3.3 Securing AI workloads > ⏱ ~30 min (reading) · 💰 n/a · ⚙️ Requires: nothing deployable — concept-only **Why the exam cares** — The current PSE exam includes securing AI/ML systems: threats unique to models (prompt injection, training-data poisoning, model inversion/extraction), guardrail services (Model Armor), de-identifying training data, and the IaaS-vs-PaaS responsibility split for training infrastructure. **How RAD implements it** — Not implemented by the foundation modules. The nearest adjacency: every generic control here also protects an AI workload's serving path — Artifact Registry vulnerability scanning for model-server images, Binary Authorization for attested inference containers, VPC-SC around storage/APIs holding training data, CMEK on the buckets that would hold model artifacts, and IAP/Cloud Armor in front of inference endpoints. **Try it** 1. There is no AI surface in the modules; instead, rehearse the perimeter pattern you would reuse: confirm `storage.googleapis.com` (training data) and `artifactregistry.googleapis.com` (model images) are in the deployed perimeter's restricted-services list. ```bash POLICY=$(gcloud access-context-manager policies list --organization=ORG_ID --format="value(name)") gcloud access-context-manager perimeters dry-run describe vpcsc__perimeter \ --policy=$POLICY --format="yaml(spec.restrictedServices)" ``` 2. You know you've internalized the mapping when you can state which deployed control would cover `aiplatform.googleapis.com` if it were added to a perimeter (the same access-level + restricted-service mechanism). **Check yourself**
Q1: Scenario — a fine-tuned internal LLM occasionally outputs employee phone numbers from its training data. Which two Google Cloud controls address this, at which stage? A: Sensitive Data Protection de-identification of the training corpus *before* fine-tuning (prevents memorization of PII), and Model Armor response filters on the serving path to detect and block PII leakage in outputs. IAM/VPC-SC don't help — the leak is via legitimate model responses.
Q2: What changes in your security responsibilities between training on Compute Engine GPUs (IaaS) versus Vertex AI Training (PaaS)? A: IaaS: you own OS hardening (Shielded VM), patching, inter-node lateral-movement controls (no external IPs, firewall rules), plus data/IAM controls. PaaS: Google runs the nodes; your scope narrows to Vertex AI IAM roles, CMEK on artifacts, VPC-SC on the Vertex AI API, and private endpoints. Same shared-responsibility narrowing logic as GKE Standard vs Autopilot.
**Beyond the modules** — Study: Model Armor templates (prompt-injection/jailbreak/PII filters for Gemini and Vertex endpoints), Vertex AI security controls (CMEK, VPC-SC support, Private Service Connect endpoints, granular roles such as `roles/aiplatform.user`), and the OWASP Top 10 for LLM Applications. Scratch commands: `gcloud ai models list --region=us-central1` and review **Vertex AI > Model Armor** in the console. **⚠️ Exam trap** — Prompt injection is an *input-validation* problem at the model boundary; WAFs like Cloud Armor cannot parse prompts semantically. Answers that bolt a WAF onto an LLM endpoint to stop jailbreaks are distractors — Model Armor (or equivalent guardrails) is the control. --- # PSE Certification Preparation Guide: Section 4 — Managing operations (~19% of the exam) PSE Certification Preparation Guide: Section 4 — Managing operations (~19% of the exam) > 📚 **Official exam guide:** [Professional Cloud Security Engineer certification](https://cloud.google.com/learn/certification/cloud-security-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 4 of the Professional Cloud Security Engineer exam. The relevant foundation modules: `Services_GCP` (Binary Authorization, Artifact Registry, audit logging, SCC, monitoring), `App_CloudRun`/`App_GKE` (CI/CD with attestation, audit logging, monitoring), and `App_Common` (image signing). Deploy the **secure-platform** profile with `binauthz_evaluation_mode = "REQUIRE_ATTESTATION"` plus one app module before starting. --- ## 4.1 Automating infrastructure and application security > ⏱ ~2.5 h · 💰 low — Cloud Build minutes + Container Analysis scans · ⚙️ Requires: secure-platform (`enable_binary_authorization`, `enable_vulnerability_scanning`); app module with `enable_cicd_trigger` for the full pipeline **Why the exam cares** — Software supply-chain security is heavily tested: scan-on-push vulnerability detection, gating deploys on scan results, cryptographic attestations (who signs, with what key, verified by whom), Binary Authorization evaluation/enforcement modes, and how IaC itself becomes a security automation layer. **How RAD implements it** — The full chain when `enable_binary_authorization = true` (default `false`) with `binauthz_evaluation_mode` (default `ALWAYS_ALLOW`; also `REQUIRE_ATTESTATION`, `ALWAYS_DENY`): 1. **Signing key** — a KMS asymmetric-signing key (RSA 2048, SHA-256). `Services_GCP` creates `binauthz-{prefix}-signer` in keyring `binauthz-{prefix}-keyring`; the app layer uses keyring `{project}-binauthz-keyring` with key `binauthz-signer`. Both creation paths are idempotent and restore/enable version 1 if it was scheduled for destruction. 2. **Attestor** — a Container Analysis note (`binauthz-{prefix}-note`) wrapped by attestor `binauthz-{prefix}-pipeline-attestor` carrying the KMS public key. 3. **Policy** — imported via `gcloud container binauthz policy import` in an *additive* way: the current policy is exported, the attestor appended to `requireAttestationsBy` only if missing (so multiple tenants coexist), evaluation mode `REQUIRE_ATTESTATION`, enforcement mode block-and-audit-log, and allowlist patterns for Google system images (`gke.gcr.io/*`, `gcr.io/cloudrun/*`, `gcr.io/cloud-sql-connectors/*`, ...). The policy survives destroy by design. 4. **Cluster/service enforcement** — GKE clusters enforce the project singleton Binary Authorization policy when enabled; Cloud Run honors the project policy. 5. **Pipeline signing** — the Cloud Build trigger (`enable_cicd_trigger` + `cicd_trigger_config`, branch pattern default `^main$`) builds with Kaniko, then runs `gcloud beta container binauthz attestations sign-and-create` against the KMS key; first-deploy images are signed by the app layer so the initial apply doesn't deadlock. Cloud Build SA IAM is correspondingly narrow: `roles/binaryauthorization.attestorsViewer`, `roles/cloudkms.signerVerifier`, `roles/containeranalysis.notes.attacher`. 6. **Vulnerability scanning** — `enable_vulnerability_scanning` (default `false`) enables the Container Analysis + On-Demand Scanning APIs and turns on scan-on-push for the Artifact Registry repo (inherited enablement, else disabled); the build SA gains `roles/containeranalysis.occurrences.viewer` to read findings. IaC-as-security-automation also shows up as drift correction: redeploying reverts out-of-band IAM changes, and plan-time probes (KMS key recovery, permission checks) self-heal the security baseline. GKE clusters additionally enable the security posture dashboard (basic posture + basic vulnerability scanning) and run on the `REGULAR` release channel with Shielded nodes (Secure Boot + integrity monitoring) on STANDARD node pools. **Try it** 1. In **Console > Security > Binary Authorization**, review the policy: default rule `REQUIRE_ATTESTATION`, your attestor listed, enforcement "Block and audit log." ```bash gcloud container binauthz policy export gcloud container binauthz attestors list gcloud artifacts docker images list \ us-central1-docker.pkg.dev/$GOOGLE_PROJECT_ID/shared-repo- \ --show-occurrences --occurrence-filter='kind="VULNERABILITY"' --limit=5 ``` 2. Negative test — deploy an unsigned public image and watch admission fail: ```bash gcloud run deploy binauthz-test --image=docker.io/library/nginx:latest \ --region=us-central1 --no-allow-unauthenticated # Expect: "Container image ... must be attested by attestor projects/.../attestors/..." ``` 3. In **Artifact Registry > repository > image > Vulnerabilities**, review CVE findings per digest after a push. 4. You know it worked when the unsigned deploy is rejected with a Binary Authorization violation while the pipeline-built, attested image deploys cleanly. **Check yourself**
Q1: Scenario — a developer bypasses CI and runs `gcloud run deploy` with an image built on their laptop. With the secure-platform profile, what happens and why? A: The deploy is rejected. The Binary Authorization policy requires an attestation from the pipeline attestor; only Cloud Build (holding `roles/cloudkms.signerVerifier` on the signing key) creates attestations, and only after building the image. A laptop image has no attestation, so `ENFORCED_BLOCK_AND_AUDIT_LOG` blocks it and writes an audit log entry.
Q2: What is the difference between `ALWAYS_ALLOW`, `REQUIRE_ATTESTATION`, and `ALWAYS_DENY`, and when would you use each? A: `ALWAYS_ALLOW` admits everything (rollout/bootstrap phase — the module's default so first deploys succeed); `REQUIRE_ATTESTATION` admits only images with a valid signature from the required attestors (steady-state production); `ALWAYS_DENY` blocks all new deployments (emergency freeze during an incident). Enforcement vs dry-run is a separate axis: dry-run logs would-be violations without blocking.
Q3: Why does the module import the Binary Authorization policy additively instead of declaring it as a plain Terraform resource? A: The policy is a project **singleton**. A plain resource owned by one tenant's state would overwrite every other tenant's attestor requirements on each apply. Export-merge-import appends this deployment's attestor only if absent, making multiple independent deployments safe — and the policy intentionally survives destroy so other tenants keep their protection.
**Beyond the modules** — Not implemented: failing the build on CVE severity (the scan results exist; a gate step querying Container Analysis and aborting on CRITICAL findings is left to you — try `gcloud artifacts docker images scan IMAGE --format="value(response.scan)"` then `gcloud artifacts docker images list-vulnerabilities SCAN_ID`), continuous validation (post-deploy revalidation of running pods), OS patch management for VM fleets (`gcloud compute os-config patch-jobs execute`), Shielded VM integrity-monitoring alerting, and policy-as-code with Policy Controller/OPA (the `configure_policy_controller` fleet feature exists in `Services_GCP` but constraint authoring is out of scope). **⚠️ Exam trap** — Vulnerability scanning *informs*, Binary Authorization *enforces* — scanning alone never blocks a deployment. Conversely, Binary Authorization checks signatures, not CVEs: an attested-but-vulnerable image deploys unless your pipeline refuses to attest it. The exam expects you to wire scan → conditional attestation → enforcement. --- ## 4.2 Configuring logging, monitoring, and detection > ⏱ ~2 h · 💰 moderate if DATA_READ logging is left on (log volume) · ⚙️ Requires: secure-platform (`enable_audit_logging`, `enable_security_command_center`, `enable_scc_notifications`) **Why the exam cares** — You must know the four Cloud Audit Logs types (Admin Activity always on and free; Data Access opt-in except BigQuery; System Event; Policy Denied), how to enable Data Access logs per service, how SCC findings are produced and routed, and how to design log access, retention, and export. **How RAD implements it** — - **Audit log configuration** — `enable_audit_logging` (default `false`, available identically in `Services_GCP`, `App_CloudRun`, and `App_GKE`): an IAM audit config for all services enabling `ADMIN_READ`, `DATA_READ`, and `DATA_WRITE` (ADMIN_WRITE is always on), plus explicit per-service configs for `secretmanager.googleapis.com` and `cloudkms.googleapis.com` (DATA_READ + DATA_WRITE) so secret reads and key usage are always evidenced. - **SCC enrollment** — `enable_security_command_center` (default `false`) enables the `securitycenter.googleapis.com` API and creates Pub/Sub topic `scc-{prefix}-findings`. `enable_scc_notifications` (default `false`) provisions the SCC notification service identity, grants it `roles/pubsub.publisher` on the topic, then — only if an org-level permission probe (`gcloud scc notifications list --organization=...`) succeeds — creates an SCC notification config filtered to `state="ACTIVE"` findings for this project. Lacking `roles/securitycenter.notificationConfigEditor` at org level, the config is skipped with a warning instead of failing the apply. - **Monitoring & alerting** — `Services_GCP` creates infrastructure alert policies driven by `alert_cpu_threshold` / `alert_memory_threshold` / `alert_disk_threshold` (all default `80`) with email channels from `configure_email_notification` + `notification_alert_emails`; app modules create channels from `support_users`, custom `alert_policies` (metric type, comparison, threshold, duration) scoped to the service, a dashboard, and — for publicly reachable endpoints — a synthetic uptime check plus failure alert from `uptime_check_config`. GKE clusters ship cluster logging for system components and workloads plus managed Prometheus. - **Edge/request logging** — the Cloud Armor-fronted backend service logs every request at full sample rate, giving you WAF verdict logs for detection work. **Try it** 1. Enable the audit/SCC flags and redeploy. In **Console > IAM & Admin > Audit Logs**, confirm "All services" shows Admin Read / Data Read / Data Write enabled, with Secret Manager and KMS individually configured. 2. Generate and find a data-access event: ```bash gcloud secrets versions access latest --secret=secret-- >/dev/null gcloud logging read \ 'logName:"cloudaudit.googleapis.com%2Fdata_access" AND protoPayload.serviceName="secretmanager.googleapis.com"' \ --limit=5 --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.methodName)" ``` 3. Wire findings to a consumer and watch them flow: ```bash gcloud pubsub subscriptions create scc-tap --topic=scc--findings gcloud pubsub subscriptions pull scc-tap --auto-ack --limit=5 ``` In **Security > Security Command Center > Findings**, filter to your project and compare with what arrives on the subscription (only ACTIVE findings pass the filter). 4. You know it worked when your own `AccessSecretVersion` call appears in Data Access logs and an SCC finding (e.g., from Security Health Analytics) lands in the Pub/Sub pull. **Check yourself**
Q1: Scenario — the SOC asks for evidence of every secret read in the last 30 days. Default project, nothing enabled. Can you produce it, and what does the platform change? A: No — `AccessSecretVersion` is a DATA_READ event, and Data Access audit logs are off by default (except BigQuery). Evidence only exists from the moment they're enabled. The platform's `enable_audit_logging` turns them on project-wide plus explicit Secret Manager/KMS configs; the exam lesson is to enable Data Access logs for sensitive services *before* the incident.
Q2: Why does the module route SCC findings to Pub/Sub rather than relying on the SCC dashboard? A: Pub/Sub makes findings machine-consumable in near-real-time — SIEM ingestion, ticket creation, automated remediation — and decouples producers from consumers. A dashboard requires a human to look. Note the org-level requirement: notification configs are organization resources, hence the permission probe and graceful skip.
Q3: An apply succeeds but no SCC notification config exists and the log shows a warning about org-level permission. Is this a bug? A: No — it is the documented degraded mode. Creating an SCC notification config requires `roles/securitycenter.notificationConfigEditor` at the organization; the module probes first and skips with a warning so a project-scoped service account can still deploy everything else. Grant the org role and redeploy to get the config.
**Beyond the modules** — Not implemented: VPC Flow Logs (`gcloud compute networks subnets update SUBNET --enable-flow-logs --logging-flow-sampling=1.0`), firewall rule logging, log sinks/aggregated sinks to BigQuery/GCS/Pub-Sub (`gcloud logging sinks create`), Bucket Lock/locked retention for WORM compliance, Log Analytics, Cloud IDS, Packet Mirroring, Event Threat Detection / Container Threat Detection specifics (SCC Premium), and Google SecOps (Chronicle) SIEM integration. Design study: log views + `roles/logging.viewAccessor` for least-privilege analyst access; 400-day retention via sinks for Admin Activity logs. **⚠️ Exam trap** — Admin Activity audit logs are always on, free, and cannot be disabled; Data Access logs are opt-in, billed as log volume, and can be expensive at scale (especially `storage.googleapis.com` DATA_READ). "Enable everything everywhere" is a cost trap; "rely on defaults for forensics" is an evidence trap. Scope Data Access logging deliberately — as the module's explicit Secret Manager/KMS overrides illustrate. --- # PSE Certification Preparation Guide: Section 5 — Supporting compliance requirements (~11% of the exam) PSE Certification Preparation Guide: Section 5 — Supporting compliance requirements (~11% of the exam) > 📚 **Official exam guide:** [Professional Cloud Security Engineer certification](https://cloud.google.com/learn/certification/cloud-security-engineer) — always confirm section weightings against the current Google Cloud exam guide. This guide covers Section 5 of the Professional Cloud Security Engineer exam. No single module owns compliance; instead, all four foundation modules contribute the technical controls auditors ask for — CMEK, immutable-by-default audit configuration, least-privilege IAM, perimeters, and managed-platform responsibility narrowing (GKE Autopilot, Cloud Run). Deploy the **secure-platform** profile (ideally with **perimeter-lab**) before starting, since most evidence-gathering exercises below depend on its flags. --- ## 5.1 Adhering to regulatory and industry standards requirements for the cloud > ⏱ ~2 h · 💰 no additional cost beyond the underlying profiles · ⚙️ Requires: secure-platform; SCC enabled for posture findings **Why the exam cares** — The exam tests three skills: (1) reasoning with the shared responsibility / shared fate model across service tiers (IaaS → GKE Standard → Autopilot → Cloud Run), (2) mapping a regulatory requirement (PCI-DSS, HIPAA, GDPR) to the specific Google Cloud control that satisfies it, and (3) scoping — knowing that compliance applies to the projects/services touching regulated data, not the whole organization. **How RAD implements it** — The modules are a compliance *control library* you can point an auditor at: | Compliance requirement (typical) | Deployed control | Where | |---|---|---| | Encryption at rest with customer-controlled keys | `enable_cmek` — per-service keys, 90-day rotation (`cmek_key_rotation_period` default `7776000s`) | the CMEK keyring and per-service keys | | Encryption in transit | TLS at the global LB (managed certs), HTTP→HTTPS redirect, Cloud SQL encrypted-only SSL mode | the load balancer edge and Cloud SQL instance | | Access-evidence / audit trail | `enable_audit_logging` — ADMIN_READ/DATA_READ/DATA_WRITE for all services + Secret Manager/KMS overrides | the project IAM audit config | | Least privilege | per-secret/per-bucket IAM, dedicated SAs, Workload Identity | the resource-level IAM layer and service accounts | | Credential rotation | `enable_auto_password_rotation` + `secret_rotation_period` (default `2592000s`) | the Secret Manager rotation pipeline | | Data exfiltration prevention | `enable_vpc_sc` perimeter with access levels, dry-run rollout | the VPC Service Controls perimeter | | Trusted software supply chain | `enable_binary_authorization` (`REQUIRE_ATTESTATION`) + `enable_vulnerability_scanning` | the Binary Authorization policy and Artifact Registry repo | | Continuous misconfiguration detection | `enable_security_command_center` + findings to Pub/Sub | the SCC enrollment and findings topic | | Public-exposure prevention | public access prevention enforced + uniform bucket-level access on the backup bucket | the backup bucket | | Backup/retention | Cloud SQL PITR (7-day txn logs, 7 daily backups), `backup_retention_days` bucket lifecycle | the Cloud SQL instance and backup bucket | | Data residency (regional pinning) | all resources placed in the selected `availability_regions` | the VPC and per-resource region settings | Shared responsibility is observable, not just theoretical: GKE Autopilot clusters (`gke_cluster_mode` default `AUTOPILOT`) hand node OS hardening, patching (auto-repair/auto-upgrade on the `REGULAR` release channel), and node configuration to Google, while STANDARD mode shows the line moving back to you — the module must then manage the node pool itself, with Shielded-node settings (Secure Boot and integrity monitoring) made explicit on the nodes. Cloud Run narrows your scope further: no nodes at all, just code, IAM, and network posture. **Try it** 1. Generate an evidence pack for a mock audit — every command below produces an artifact you could hand to an assessor: ```bash # Encryption: which CMEK key protects the database? gcloud sql instances describe \ --format="value(diskEncryptionConfiguration.kmsKeyName)" # Audit posture: which log types are enabled project-wide? gcloud projects get-iam-policy $GOOGLE_PROJECT_ID --format="yaml(auditConfigs)" # Least privilege: who can read the DB password? gcloud secrets get-iam-policy secret-- # Supply chain: what does the admission policy require? gcloud container binauthz policy export # Residency: where does everything actually live? gcloud sql instances describe --format="value(region)" gcloud storage buckets list --format="table(name, location)" ``` 2. In **Console > Security > Security Command Center > Findings**, filter by your project and treat each ACTIVE finding as an audit exception: identify the violated control and which portal variable remediates it. 3. In **Kubernetes Engine > Clusters**, open an Autopilot cluster's **Security** posture panel and list which controls show as Google-managed — that list *is* your responsibility-narrowing evidence. 4. You know it worked when you can present, for one framework requirement of your choice, the variable, the resource, and the CLI-verifiable evidence in one line each. **Check yourself**
Q1: Scenario — a HIPAA assessor asks who is responsible for OS patching of the Kubernetes nodes running PHI workloads. Your answer differs by one portal variable — which, and how? A: `gke_cluster_mode`. On `AUTOPILOT` (the default), Google manages node provisioning, OS hardening, and patching — it falls on Google's side of the shared responsibility line (covered by the BAA). On `STANDARD`, node management is configured by you (the module sets auto-upgrade/auto-repair and Shielded settings, but the responsibility — and the audit scope — is yours).
Q2: Map GDPR Article 17 (right to erasure) for backups to a deployed control. A: CMEK crypto-shredding: Cloud SQL data *and its backups* are encrypted under `cloudsql-{prefix}-key`. Destroying that key's versions renders all of it — including backups you cannot individually edit — permanently unreadable. Pair with the backup bucket's `backup_retention_days` lifecycle deletion for data minimization.
Q3: Scenario — only the payment service handles cardholder data, but the CISO wants PCI controls (VPC-SC enforcement, CMEK, Data Access logging) applied to all 40 projects in the org. What do you advise? A: Scope down. PCI-DSS applies to the cardholder data environment; applying maximum controls everywhere multiplies cost (Data Access log volume) and operational friction (VPC-SC breakage) without reducing CDE risk. Isolate in-scope workloads in dedicated projects/folders, apply the strict profile there (this platform's per-project perimeter model fits naturally), and document segmentation as the scope boundary.
**Beyond the modules** — Not implemented, study separately: - **Assured Workloads** — compliance-regime folders (FedRAMP, EU Sovereign Controls) that pre-enforce location and personnel constraints: `gcloud assured workloads list --organization=ORG_ID --location=us-central1` (**Console > Compliance > Assured Workloads**). - **Access Transparency & Access Approval** — logs of *Google staff* actions on your content, and an approval gate before such access; filter Logs Explorer on `cloudaudit.googleapis.com%2Faccess_transparency`. Transparency = passive record, Approval = active control; both require Premium/Enterprise support tiers. - **SCC compliance posture reporting** — mapping findings to CIS GCP Foundations, PCI-DSS, NIST 800-53, ISO 27001 in SCC Premium (**SCC > Compliance**). - **Compliance documentation** — Google's compliance reports portal (SOC 2, ISO certificates), the HIPAA BAA process and the eligible-services list, and data residency / data processing terms. The exam expects you to know that you inherit Google's certifications for infrastructure but must still certify your own configuration and processes. **⚠️ Exam trap** — "Google Cloud is PCI-DSS / HIPAA compliant, therefore my application is" is always wrong. Compliance is inherited only for the layers Google operates; your IAM, network, encryption, and logging configuration — exactly the variables this platform exposes — remain your responsibility, and a misconfigured bucket fails the audit no matter what certificates Google holds.