GCP for DevOps — IaC Exercises

3 minute read

Infrastructure as Code — Practice Exercises

Test your knowledge of Terraform with the Google provider, remote state, modules, and CFT.


Exercise 1 — VPC with Terraform

Write Terraform to create a VPC with two subnets, a Cloud NAT, and a firewall rule allowing HTTP traffic.

Show Answer
# main.tf
provider "google" {
  project = var.project_id
  region  = "us-central1"
}

resource "google_compute_network" "vpc" {
  name                    = "exercise-vpc"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "public" {
  name          = "public-subnet"
  ip_cidr_range = "10.0.1.0/24"
  region        = "us-central1"
  network       = google_compute_network.vpc.id
}

resource "google_compute_subnetwork" "private" {
  name                     = "private-subnet"
  ip_cidr_range            = "10.0.2.0/24"
  region                   = "us-central1"
  network                  = google_compute_network.vpc.id
  private_ip_google_access = true
}

resource "google_compute_router" "router" {
  name    = "exercise-router"
  region  = "us-central1"
  network = google_compute_network.vpc.id
}

resource "google_compute_router_nat" "nat" {
  name                               = "exercise-nat"
  router                             = google_compute_router.router.name
  region                             = "us-central1"
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}

resource "google_compute_firewall" "allow_http" {
  name    = "exercise-allow-http"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["80", "443"]
  }

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["http-server"]
}
$ terraform init && terraform plan
$ terraform apply

Exercise 2 — Remote State in GCS

Set up a GCS bucket for Terraform state with versioning and configure the backend.

Show Answer
# Create state bucket
$ gcloud storage buckets create gs://exercise-tf-state-$(gcloud config get-value project) \
    --location=us-central1 \
    --uniform-bucket-level-access

$ gcloud storage buckets update gs://exercise-tf-state-$(gcloud config get-value project) \
    --versioning
# backend.tf
terraform {
  backend "gcs" {
    bucket = "exercise-tf-state-MY_PROJECT_ID"
    prefix = "exercises/vpc"
  }
}
# Migrate to remote backend
$ terraform init -migrate-state

# Verify state is remote
$ terraform state list

Exercise 3 — Reusable Module

Create a reusable Cloud Run module that accepts image, service name, region, and min/max instances as variables.

Show Answer
# modules/cloud-run/variables.tf
variable "project_id" { type = string }
variable "service_name" { type = string }
variable "region" { type = string; default = "us-central1" }
variable "image" { type = string }
variable "min_instances" { type = number; default = 0 }
variable "max_instances" { type = number; default = 10 }
variable "port" { type = number; default = 8080 }
variable "env_vars" { type = map(string); default = {} }

# modules/cloud-run/main.tf
resource "google_cloud_run_v2_service" "service" {
  name     = var.service_name
  location = var.region
  project  = var.project_id

  template {
    scaling {
      min_instance_count = var.min_instances
      max_instance_count = var.max_instances
    }

    containers {
      image = var.image
      ports {
        container_port = var.port
      }

      dynamic "env" {
        for_each = var.env_vars
        content {
          name  = env.key
          value = env.value
        }
      }
    }
  }
}

# modules/cloud-run/outputs.tf
output "url" {
  value = google_cloud_run_v2_service.service.uri
}

output "service_name" {
  value = google_cloud_run_v2_service.service.name
}

Usage:

module "api" {
  source       = "./modules/cloud-run"
  project_id   = var.project_id
  service_name = "my-api"
  image        = "us-central1-docker.pkg.dev/my-project/repo/api:v1.0.0"
  min_instances = 1
  max_instances = 50
  env_vars     = { ENV = "production", LOG_LEVEL = "info" }
}

Exercise 4 — Import Existing Resource

You have an existing Cloud Storage bucket created manually. Import it into Terraform state and manage it.

Show Answer
# Write the resource configuration first
resource "google_storage_bucket" "existing" {
  name          = "my-existing-bucket"
  location      = "US"
  force_destroy = false

  versioning {
    enabled = true
  }

  uniform_bucket_level_access = true
}
# Import the existing bucket into state
$ terraform import google_storage_bucket.existing my-existing-bucket

google_storage_bucket.existing: Importing from ID "my-existing-bucket"...
google_storage_bucket.existing: Import prepared!
google_storage_bucket.existing: Refreshing state...

Import successful!

# Verify — plan should show no changes if config matches
$ terraform plan

No changes. Your infrastructure matches the configuration.

Exercise 5 — CFT Network Module

Use the CFT terraform-google-modules/network/google module to create a VPC with a subnet and secondary ranges for GKE.

Show Answer
module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = var.project_id
  network_name = "exercise-cft-vpc"
  routing_mode = "GLOBAL"

  subnets = [
    {
      subnet_name           = "app-subnet"
      subnet_ip             = "10.0.0.0/20"
      subnet_region         = "us-central1"
      subnet_private_access = true
    },
  ]

  secondary_ranges = {
    "app-subnet" = [
      { range_name = "pods",     ip_cidr_range = "10.4.0.0/14" },
      { range_name = "services", ip_cidr_range = "10.8.0.0/20" },
    ]
  }
}

output "network_name" {
  value = module.vpc.network_name
}

output "subnets" {
  value = module.vpc.subnets
}
$ terraform init
$ terraform plan
$ terraform apply

Cleanup

$ terraform destroy -auto-approve
$ gcloud storage rm -r gs://exercise-tf-state-$(gcloud config get-value project)

Next Chapter → Serverless

Updated: