Last reviewed: May 2026
Build the AWS services on the ACE exam with plain Terraform — one block at a time, each tied back to an exam domain. The same code works on OpenTofu.
By the end of this lab you'll have provisioned, with plain Terraform, the smallest realistic ACE-flavored GCP admin substrate — a custom-mode VPC with one regional subnet, two firewall rules (allow internal + IAP SSH), a Compute Engine VM running a service account with least-privilege IAM, and a Cloud Logging alert policy that pages on quota errors. This is the day-one IaaS admin loadout.
Drop the snippets into a single main.tf, run terraform init, then terraform apply step-by-step.
>= 1.5 or OpenTofu >= 1.6.gcloud auth application-default login.your-project-id in the snippet below with your actual project ID.gcloud compute ssh certlabpro-ace-vm --tunnel-through-iap.e2-micro: ~$6.50/month in us-central1 (1 always-free tier per account if eligible).~$6.50/month while the VM is running. Stop or destroy the VM when not actively using it — running VMs are the #1 lab-cost surprise on GCP.
Enable Compute Engine, IAM, and Cloud Logging APIs. ACE exam tests this enable-API-per-resource pattern repeatedly.
terraform {
required_version = ">= 1.5"
required_providers {
google = { source = "hashicorp/google", version = "~> 6.0" }
}
}
provider "google" {
project = "your-project-id" # REPLACE
region = "us-central1"
zone = "us-central1-a"
}
locals {
labels = {
project = "certlabpro-ace"
managed_by = "terraform"
}
}
resource "google_project_service" "compute" {
service = "compute.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "iam" {
service = "iam.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "logging" {
service = "logging.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "monitoring" {
service = "monitoring.googleapis.com"
disable_on_destroy = false
}GCP networks are global — a single VPC spans all regions, and subnets are regional primitives carved out of it. This is a recurring ACE exam-question shape: AWS has regional VPCs; Azure has regional VNets; GCP has a global VPC with regional subnets.
We disable auto_create_subnetworks (custom-mode VPC, the ACE-recommended default) and carve a single /24 subnet in us-central1 with Private Google Access enabled so VMs without external IPs can still reach GCP APIs.
resource "google_compute_network" "main" {
name = "certlabpro-ace-vpc"
auto_create_subnetworks = false
routing_mode = "REGIONAL"
depends_on = [google_project_service.compute]
}
resource "google_compute_subnetwork" "main" {
name = "certlabpro-ace-subnet"
ip_cidr_range = "10.10.1.0/24"
region = "us-central1"
network = google_compute_network.main.id
private_ip_google_access = true
}GCP firewall rules are VPC-scoped (not per-subnet) and have explicit direction: ingress (default) or egress. Default ingress is deny-all; default egress is allow-all. ACE exam tests this direction + default-deny invariant relentlessly.
We add:
10.10.1.0/24 subnet.35.235.240.0/20) so you can SSH without exposing the VM to the internet.The IAP-SSH rule is the ACE-recommended pattern — it requires roles/iap.tunnelResourceAccessor on the VM (binding handled in Step 4).
resource "google_compute_firewall" "allow_internal" {
name = "certlabpro-ace-allow-internal"
network = google_compute_network.main.name
direction = "INGRESS"
source_ranges = ["10.10.1.0/24"]
allow {
protocol = "tcp"
}
allow {
protocol = "udp"
}
allow {
protocol = "icmp"
}
}
resource "google_compute_firewall" "allow_iap_ssh" {
name = "certlabpro-ace-allow-iap-ssh"
network = google_compute_network.main.name
direction = "INGRESS"
source_ranges = ["35.235.240.0/20"] # IAP gateway range
allow {
protocol = "tcp"
ports = ["22"]
}
}Compute Engine VMs run with an attached service account — the GCP equivalent of an AWS EC2 instance profile or an Azure VM managed identity. ACE-recommended pattern: never use the default Compute service account (over-privileged); always create a per-workload service account with only the IAM roles the workload needs.
We create a service account, grant it roles/logging.logWriter + roles/monitoring.metricWriter (the minimum for the Ops Agent to send logs / metrics), and attach it to an e2-micro Debian VM with no external IP. The IAP-SSH firewall rule from Step 3 lets you reach it via tunnel.
resource "google_service_account" "vm" {
account_id = "certlabpro-ace-vm-sa"
display_name = "ACE lab VM service account"
}
resource "google_project_iam_member" "vm_log_writer" {
project = data.google_project.current.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${google_service_account.vm.email}"
}
resource "google_project_iam_member" "vm_metric_writer" {
project = data.google_project.current.project_id
role = "roles/monitoring.metricWriter"
member = "serviceAccount:${google_service_account.vm.email}"
}
data "google_project" "current" {}
resource "google_compute_instance" "vm" {
name = "certlabpro-ace-vm"
machine_type = "e2-micro"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
subnetwork = google_compute_subnetwork.main.id
# No access_config block = no external IP. SSH via IAP only.
}
service_account {
email = google_service_account.vm.email
scopes = ["cloud-platform"] # API scope; IAM does the real gating
}
labels = local.labels
}ACE-recommended observability shape: a log-based metric counts matches of a Cloud Logging filter, then a Cloud Monitoring alert policy fires when the metric crosses a threshold. We watch for severity >= ERROR log lines from any GCE VM in this project, count them per minute, and alert when more than 5 hit in a 5-minute window.
The notification channel is left implicit — alert policies need a notification_channels array, but the channel itself (email, Slack, PagerDuty) is typically configured once per project in the console. ACE exam tests this log-metric → alert policy → notification channel triangle as the standard observability primitive.
resource "google_logging_metric" "vm_errors" {
name = "certlabpro_ace_vm_errors"
filter = "resource.type=\"gce_instance\" AND severity >= ERROR"
metric_descriptor {
metric_kind = "DELTA"
value_type = "INT64"
}
depends_on = [google_project_service.logging]
}
resource "google_monitoring_alert_policy" "vm_error_burst" {
display_name = "ACE lab — VM error burst"
combiner = "OR"
conditions {
display_name = "More than 5 ERROR lines per 5 minutes"
condition_threshold {
filter = "metric.type=\"logging.googleapis.com/user/${google_logging_metric.vm_errors.name}\" AND resource.type=\"gce_instance\""
duration = "300s"
comparison = "COMPARISON_GT"
threshold_value = 5
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_DELTA"
}
}
}
# notification_channels = [] # add channels via the console or as a separate TF resource
depends_on = [google_project_service.monitoring]
}terraform destroy tears down everything. The VM stops billing immediately on destroy (the disk goes with it). The service account destroys; IAM bindings detach. The VPC + subnet + firewalls destroy clean. Log-based metric + alert policy detach cleanly. Project services stay enabled (free to leave on).
ACE covers many GCP admin surfaces this lab can't fit — Cloud Load Balancing (global HTTP(S) LB, regional internal LB, network LB), Managed Instance Groups + autoscaling, Cloud Run, GKE clusters, Cloud Storage (covered in [[gcp-cdl]]), Cloud SQL, Cloud Spanner, Cloud Bigtable, Pub/Sub, Cloud Functions, Cloud Scheduler, Cloud Tasks, Cloud KMS, VPC Peering / Shared VPC ([[gcp-pcne]]), Cloud NAT, Cloud VPN / Interconnect, Cloud DNS, Cloud Armor, IAM custom roles, Resource Manager hierarchy (folders + orgs), Cloud Asset Inventory, Cloud Deployment Manager (deprecated in favor of Terraform).
We stick to the VPC + GCE + IAM + Logging primitives because they're the foundation every more-advanced GCP admin pattern composes on top of. MIGs scale GCE instances; LBs front MIGs; GKE / Cloud Run run on top of compute; everything writes to Cloud Logging.
For the service-by-service conceptual coverage, see the Browse, Playbook, and Editorial sections of this cert page.