GCP for DevOps — Project: Production VPC with Terraform

4 minute read

Project 1: Production VPC with Terraform

Build a production-ready VPC network using Terraform, complete with multiple subnets, firewall rules, Cloud NAT, and Cloud Router. This project demonstrates Infrastructure as Code best practices with GCP networking.

Architecture

Production VPC Architecture:
┌─────────────────────────────────────────────────────────┐
│  VPC: production-vpc                                     │
│                                                          │
│  ┌────────────────────┐   ┌────────────────────┐       │
│  │  Public Subnet      │   │  Private Subnet     │       │
│  │  10.0.1.0/24       │   │  10.0.2.0/24       │       │
│  │  us-central1       │   │  us-central1       │       │
│  │                    │   │                    │       │
│  │  ┌──────────┐     │   │  ┌──────────┐     │       │
│  │  │ Bastion  │     │   │  │ App VMs  │     │       │
│  │  │ Host     │     │   │  │ (no ext  │     │       │
│  │  └──────────┘     │   │  │  IP)     │     │       │
│  └────────────────────┘   │  └──────────┘     │       │
│                           └─────────┬──────────┘       │
│                                     │                   │
│                           ┌─────────▼──────────┐       │
│                           │  Cloud NAT          │       │
│                           │  (via Cloud Router) │       │
│                           └─────────┬──────────┘       │
│                                     │                   │
│                                  Internet               │
└─────────────────────────────────────────────────────────┘

Step 1: Project Structure

gcp-production-vpc/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
├── backend.tf
└── modules/
    └── vpc/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Step 2: Configure Remote State Backend

# backend.tf
terraform {
  required_version = ">= 1.5"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }

  backend "gcs" {
    bucket = "my-project-terraform-state"
    prefix = "vpc/production"
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

Create the state bucket first:

$ gcloud storage buckets create gs://my-project-terraform-state \
    --location=us-central1 \
    --uniform-bucket-level-access
Creating gs://my-project-terraform-state/...

$ gcloud storage buckets update gs://my-project-terraform-state \
    --versioning
Updating gs://my-project-terraform-state/...

Step 3: VPC Module

# modules/vpc/variables.tf
variable "project_id" {
  description = "GCP project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
}

variable "vpc_name" {
  description = "Name of the VPC"
  type        = string
}

variable "public_subnet_cidr" {
  description = "CIDR for the public subnet"
  type        = string
}

variable "private_subnet_cidr" {
  description = "CIDR for the private subnet"
  type        = string
}
# modules/vpc/main.tf
resource "google_compute_network" "vpc" {
  name                    = var.vpc_name
  auto_create_subnetworks = false
  project                 = var.project_id
}

resource "google_compute_subnetwork" "public" {
  name          = "${var.vpc_name}-public"
  ip_cidr_range = var.public_subnet_cidr
  region        = var.region
  network       = google_compute_network.vpc.id

  log_config {
    aggregation_interval = "INTERVAL_5_SEC"
    flow_sampling        = 0.5
    metadata             = "INCLUDE_ALL_METADATA"
  }
}

resource "google_compute_subnetwork" "private" {
  name                     = "${var.vpc_name}-private"
  ip_cidr_range            = var.private_subnet_cidr
  region                   = var.region
  network                  = google_compute_network.vpc.id
  private_ip_google_access = true

  log_config {
    aggregation_interval = "INTERVAL_5_SEC"
    flow_sampling        = 0.5
    metadata             = "INCLUDE_ALL_METADATA"
  }
}

# Cloud Router for NAT
resource "google_compute_router" "router" {
  name    = "${var.vpc_name}-router"
  region  = var.region
  network = google_compute_network.vpc.id
}

# Cloud NAT for private subnet outbound access
resource "google_compute_router_nat" "nat" {
  name                               = "${var.vpc_name}-nat"
  router                             = google_compute_router.router.name
  region                             = var.region
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

  subnetwork {
    name                    = google_compute_subnetwork.private.id
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }

  log_config {
    enable = true
    filter = "ERRORS_ONLY"
  }
}

# Firewall: Allow SSH to bastion only
resource "google_compute_firewall" "allow_ssh_bastion" {
  name    = "${var.vpc_name}-allow-ssh-bastion"
  network = google_compute_network.vpc.id

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["bastion"]
}

# Firewall: Allow internal communication
resource "google_compute_firewall" "allow_internal" {
  name    = "${var.vpc_name}-allow-internal"
  network = google_compute_network.vpc.id

  allow {
    protocol = "tcp"
    ports    = ["0-65535"]
  }

  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }

  allow {
    protocol = "icmp"
  }

  source_ranges = [var.public_subnet_cidr, var.private_subnet_cidr]
}

# Firewall: Deny all ingress (low priority)
resource "google_compute_firewall" "deny_all_ingress" {
  name     = "${var.vpc_name}-deny-all"
  network  = google_compute_network.vpc.id
  priority = 65534

  deny {
    protocol = "all"
  }

  source_ranges = ["0.0.0.0/0"]
}
# modules/vpc/outputs.tf
output "vpc_id" {
  value = google_compute_network.vpc.id
}

output "vpc_name" {
  value = google_compute_network.vpc.name
}

output "public_subnet_id" {
  value = google_compute_subnetwork.public.id
}

output "private_subnet_id" {
  value = google_compute_subnetwork.private.id
}

output "router_name" {
  value = google_compute_router.router.name
}

Step 4: Root Module

# variables.tf
variable "project_id" {
  description = "GCP project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
  default     = "us-central1"
}
# main.tf
module "vpc" {
  source = "./modules/vpc"

  project_id          = var.project_id
  region              = var.region
  vpc_name            = "production-vpc"
  public_subnet_cidr  = "10.0.1.0/24"
  private_subnet_cidr = "10.0.2.0/24"
}

# Bastion host in public subnet
resource "google_compute_instance" "bastion" {
  name         = "bastion-host"
  machine_type = "e2-micro"
  zone         = "${var.region}-a"
  tags         = ["bastion"]

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-12"
    }
  }

  network_interface {
    subnetwork = module.vpc.public_subnet_id
    access_config {}  # Gives external IP
  }

  labels = {
    env  = "prod"
    role = "bastion"
  }
}
# outputs.tf
output "vpc_name" {
  value = module.vpc.vpc_name
}

output "bastion_external_ip" {
  value = google_compute_instance.bastion.network_interface[0].access_config[0].nat_ip
}

Step 5: Deploy

# Initialize Terraform
$ terraform init
Initializing modules...
Initializing the backend...
Terraform has been successfully initialized!

# Plan
$ terraform plan -var="project_id=my-project"
Plan: 8 to add, 0 to change, 0 to destroy.

# Apply
$ terraform apply -var="project_id=my-project" -auto-approve
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

Outputs:
  bastion_external_ip = "35.192.0.100"
  vpc_name            = "production-vpc"

# Verify
$ gcloud compute networks subnets list --network=production-vpc
NAME                    REGION        RANGE
production-vpc-public   us-central1   10.0.1.0/24
production-vpc-private  us-central1   10.0.2.0/24

Step 6: Verify and Clean Up

# Test SSH to bastion
$ gcloud compute ssh bastion-host --zone=us-central1-a

# Destroy all resources
$ terraform destroy -var="project_id=my-project" -auto-approve
Destroy complete! Resources: 8 destroyed.

Key Learnings

  • Terraform modules for reusable VPC configurations
  • GCS backend for remote state with versioning
  • Cloud NAT for private subnet outbound access
  • Layered firewall rules with priority-based deny-all
  • Flow logs for network monitoring

Next → GKE Autopilot CI/CD Pipeline

Updated: