Skip to main content

GCP Virtual Private Cloud (VPC) and Public Subnet Scheme, Part 2: Multi Region, Terraform Configuration, Mermaid Flowchart

1611 words·
GCP Google Cloud SDK Terraform Virtual Private Cloud (VPC) Compite Engine Instance Mermaid Flowchart
Table of Contents
GCP-VPC-Terraform - This article is part of a series.
Part 2: This Article
GitHub Repository Available

Flowchart
#

graph TD %% VPC subgraph VPC["VPC Global"] %% Region1 subgraph Region1["GCP Region us-central1"] %% Subnet 1 AZ1 subgraph AZ1["AZ us-central1-a"] subgraph PublicSubnet1a["Public Subnet 1"] VM1["VM1"] end end %% Subnet 1 AZ2 subgraph AZ2["AZ us-central1-b"] PublicSubnet1b["Public Subnet 1"] end %% Subnet 1 AZ3 subgraph AZ3["AZ us-central1-c"] PublicSubnet1c["Public Subnet 1"] end end %% Subnet Connections PublicSubnet1a <-.-> PublicSubnet1b PublicSubnet1b <-.-> PublicSubnet1c %% Region2 subgraph Region2["GCP Region us-east1"] %% Subnet 2 AZ1 subgraph S2AZ1["AZ us-east1-a"] PublicSubnet2a["Public Subnet 2"] end %% Subnet 2 AZ2 subgraph S2AZ2["AZ us-east1-b"] subgraph PublicSubnet2b["Public Subnet 2"] VM2["VM2"] end end %% Subnet 2 AZ3 subgraph S2AZ3["AZ us-east1-c"] PublicSubnet2c["Public Subnet 2"] end end %% Subnet Connections PublicSubnet2a <-.-> PublicSubnet2b PublicSubnet2b <-.-> PublicSubnet2c %% VM Connections VM1 <--> VM2 end %% Dashed Border classDef BoarderDash stroke-dasharray:5 5; class Region1,AZ1,AZ2,AZ3 BoarderDash classDef BoarderDash2 stroke-dasharray:5 5; class Region2,S2AZ1,S2AZ2,S2AZ3 BoarderDash2 %% Node Colors style PublicSubnet1a fill:#d1f7d6,stroke:#333,stroke-width:2px style PublicSubnet1b fill:#d1f7d6,stroke:#333,stroke-width:2px style PublicSubnet1c fill:#d1f7d6,stroke:#333,stroke-width:2px style PublicSubnet2a fill:#f7d6d6,stroke:#333,stroke-width:2px style PublicSubnet2b fill:#f7d6d6,stroke:#333,stroke-width:2px style PublicSubnet2c fill:#f7d6d6,stroke:#333,stroke-width:2px



Prerequisites
#

Google Cloud SDK
#

Installation
#

The Google Cloud SDK (Software Development Kit) includes the gcloud command-line interface (CLI).

# Install dependencies
sudo apt install curl apt-transport-https ca-certificates gnupg -y

# Add package source
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list

# Import the Google Cloud public key
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg

# Update package index
sudo apt update

# Install the Google Cloud SDK
sudo apt install google-cloud-sdk -y
# Verify installation / list version
gcloud version

# Shell output:
Google Cloud SDK 506.0.0
alpha 2025.01.10
beta 2025.01.10
bq 2.1.11
bundled-python3-unix 3.11.9
core 2025.01.10
gcloud-crc32c 1.0.0
gsutil 5.33

Login to GCP / Verify Login
#

Open the link from the shell in a browser, login to GCP and paste the verification code in the shell.

# Alternative use the following command to login without a browser
gcloud auth login --no-launch-browser

Verify Login:

# List Google Cloud accounts that are currently authenticated
gcloud auth list

Set Default Project
#

# Set the default project: Syntax
gcloud config set project <PROJECT_ID>


# Set the default project: Example
gcloud config set project my-example-project-448310

# Shell output:
Updated property [core/project].

Enable Project APIs
#

# Enable the necessary APIs, that are used in the project
gcloud services enable compute.googleapis.com

# Shell output:
Operation "operations/acf.p2-249181329052-8b52d534-2fc8-4221-88b8-7f17b4df9d95" finished successfully.

Service Account for Terraform
#

# Create a Service Account named "terraform"
gcloud iam service-accounts create terraform \
    --description="Terraform service account" \
    --display-name="Terraform"

# Shell output:
Created service account [terraform].
# Assign IAM roles: Define project ID
gcloud projects add-iam-policy-binding my-example-project-448310 \
    --member="serviceAccount:terraform@my-example-project-448310.iam.gserviceaccount.com" \
    --role="roles/editor"

# Shell output:
Updated IAM policy for project [my-example-project-448310].
bindings:
- members:
  - serviceAccount:terraform@my-example-project-448310.iam.gserviceaccount.com
  role: roles/editor
- members:
  - user:juergen@jklug.work
  role: roles/owner
etag: BwYsDPrXlRw=
version: 1
# Generate a JSON Key (used by Terraform for authentication): Define project ID
gcloud iam service-accounts keys create ~/terraform-key.json \
    --iam-account terraform@my-example-project-448310.iam.gserviceaccount.com

# Shell output:
created key [76d8418c1229bec494f7c0b1a5c1d9e600b4b65e] of type [json] as [/home/ubuntu/terraform-key.json] for [terraform@my-example-project-448310.iam.gserviceaccount.com]



Terraform Installation
#

# Install the HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# Verify the GPG key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint

# Add the official HashiCorp repository 
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
sudo apt update && sudo apt-get install terraform
# Verify installation / check version
terraform version



Terraform Network Stack
#

File and Folder Structure
#

The file and folder structure of the Terraform project looks like this:

gcp-vpc-stack
├── compute.tf
├── outputs.tf
├── terraform.tf
├── variables.tf
└── vpc.tf

Create SSH Key Pair
#

# Create a new SSH key pair for the VM access
ssh-keygen -t rsa -b 4096 -f ~/.ssh/terraform

Terraform Configuration Files
#

Project Folder & Terraform Provider
#

# Create project folder
TF_PROJECT_NAME=gcp-vpc-stack
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME
  • terraform.tf
# Terraform Provider
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

# Provider GCP Region (Fallback)
provider "google" {
  credentials = file(var.gcp_credentials_file)
  project     = var.gcp_project_id
  region      = var.gcp_region
}

Variables
#

  • variables.tf
## GCP Configuration

# GCP Credentials File
variable "gcp_credentials_file" {
  description = "Path to the GCP credentials JSON file"
  type        = string
  default     = "~/terraform-key.json"
}

# GCP Project ID
variable "gcp_project_id" {
  description = "GCP project ID"
  type        = string
  default     = "my-example-project-448310"
}

# GCP Region
variable "gcp_region" {
  description = "GCP region"
  type        = string
  default     = "us-central1"
}


## VPC & Subnets

# VPC Name
variable "vpc_name" {
  description = "The name of the VPC"
  type        = string
  default     = "example-vpc"
}

# Subnets Regions: Define different regions
variable "subnets_regions" {
  description = "List of regions for the subnets"
  type        = list(string)
  default     = ["us-central1", "us-east1"]
}

# Subnets CIDR Blocks
variable "subnets_cidr_blocks" {
  description = "List of CIDR blocks for subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

# Subnets Names
variable "subnets_names" {
  description = "List of names for the subnets"
  type        = list(string)
  default     = ["subnet-public1", "subnet-public2"]
}


## Compuite Engine

# Zones for VirtualMachines: Define different region AZ
variable "gcp_zones" {
  description = "List of GCP zones for the subnets"
  type        = list(string)
  default     = ["us-central1-a", "us-east1-b"] 
}

# Image ID
variable "image_id" {
  description = "GCP image to use for the instances"
  type        = string
  default     = "projects/debian-cloud/global/images/family/debian-12"
}

# Machine Type
variable "machine_type" {
  description = "VM Type"
  type        = string
  default     = "f1-micro"
}


## SSH Key

# Define local public SSH Key
variable "ssh_public_key_file" {
  description = "Path to the public SSH key file"
  type        = string
  default     = "~/.ssh/terraform.pub"
}

VPC & Subnets
#

  • vpc.tf
# VPC
resource "google_compute_network" "vpc_network" {
  name                    = var.vpc_name
  auto_create_subnetworks = false
}

# Subnets Public
resource "google_compute_subnetwork" "subnets_public" {
  count          = length(var.subnets_cidr_blocks)
  name           = var.subnets_names[count.index]
  ip_cidr_range  = var.subnets_cidr_blocks[count.index]
  region         = var.subnets_regions[count.index]
  network        = google_compute_network.vpc_network.id
}

Computing & Firewall
#

  • compute.tf
## Compute

# Static IPs for VMs
resource "google_compute_address" "static_ip" {
  count   = length(var.gcp_zones)
  name    = "debian-vm-${count.index + 1}"
  region  = substr(var.gcp_zones[count.index], 0, length(var.gcp_zones[count.index]) - 2) # Extract the region from the zone
}

# Compute Engine Instances
resource "google_compute_instance" "debian_vm" {
  count        = length(var.gcp_zones)
  name         = "debian-${count.index + 1}"
  machine_type = var.machine_type
  zone         = element(var.gcp_zones, count.index)
  tags         = ["ingress","egress"]  # Add both ingress and egress tags

  # Metadata for SSH Access
  metadata = {
    ssh-keys = "debian:${file(var.ssh_public_key_file)}"
  }

  boot_disk {
    initialize_params {
      image = var.image_id
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.subnets_public[count.index].id

    access_config {
      nat_ip = google_compute_address.static_ip[count.index].address
    }
  }
}


## Firewall

# Firewall: Ingress Rules
resource "google_compute_firewall" "ingress" {
  name          = "ingress"
  network       = google_compute_network.vpc_network.name
  target_tags   = ["ingress"]

  allow {
    protocol = "tcp"
    ports    = ["22"]  # Allow SSH
  }
  allow {
    protocol = "icmp"  # Allow Ping
  }
  source_ranges = ["0.0.0.0/0"]  # Allow traffic from anywhere
}

# Firewall: Egress Rules
resource "google_compute_firewall" "egress" {
  name    = "egress"
  network = google_compute_network.vpc_network.name
  target_tags   = ["egress"]

  allow {
    protocol = "all"
  }
  direction          = "EGRESS"
  destination_ranges = ["0.0.0.0/0"]
}

Outputs
#

  • outputs.tf
# Public IPs of the VMs
output "vm_public_ips" {
  description = "Public IP addresses of the VMs"
  value       = [for ip in google_compute_address.static_ip : ip.address]
}

# Private IPs of the VMs
output "vm_private_ips" {
  description = "Private IP addresses of the VMs"
  value       = [for vm in google_compute_instance.debian_vm : vm.network_interface[0].network_ip]
}



Apply Terraform Configuration
#

Initialize Terraform Project
#

This will download and install the AWS Terraform provider defined in the terraform.tf file with “hashicorp/aws”, as well as setting up the configuration files in the project directory.

# Initialize the Terraform project
terraform init

Validate Configuration Files
#

# Validates the syntax and structure of Terraform configuration files
terraform validate

# Shell output:
Success! The configuration is valid.

Plan the Deployment
#

# Dry run / preview changes before applying them
terraform plan

Apply the Configuration
#

# Create network stack
terraform apply -auto-approve

# Shell output:
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

vm_private_ips = [
  "10.0.1.2",
  "10.0.2.2",
]
vm_public_ips = [
  "34.31.72.75",
  "35.237.167.84",
]

Verify Deployment State
#

# Lists all resources tracked in the Terraform state file
terraform state list

# Shell output:
google_compute_address.static_ip[0]
google_compute_address.static_ip[1]
google_compute_firewall.egress
google_compute_firewall.ingress
google_compute_instance.debian_vm[0]
google_compute_instance.debian_vm[1]
google_compute_network.vpc_network
google_compute_subnetwork.subnets_public[0]
google_compute_subnetwork.subnets_public[1]



Verify Network Connectivity
#

SSH Into Compute Instances
#

# SSH into EC2 instance 1
ssh -i ~/.ssh/terraform debian@34.31.72.75

# SSH into EC2 instance 2
ssh -i ~/.ssh/terraform debian@35.237.167.84

Note: If GCP assigns the same public IP to the VMs (in case you destroy and reapply them), it’s neccessary to remove the host keys.

# Remove host keys
ssh-keygen -R 34.31.72.75
ssh-keygen -R 34.68.253.237

Ping VM in other Subnet
#

# Ping instance 2 from instance 1: Use private IP
debian@debian-1:~$ ping 10.0.2.2

# Shell output:
PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data.
64 bytes from 10.0.2.2: icmp_seq=1 ttl=64 time=40.3 ms
64 bytes from 10.0.2.2: icmp_seq=2 ttl=64 time=38.8 ms
# Ping instance 1 from instance 2: Use private IP
debian@debian-2:~$ ping 10.0.1.2

# Shell output:
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=40.9 ms
64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=38.8 ms



Verify GCP Resources
#

List VPCs
#

# List VPC networks
gcloud compute networks list

# Shell output:
NAME         SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4
default      AUTO         REGIONAL
example-vpc  CUSTOM       REGIONAL

List Subnets in VPC
#

# List subnets in VPC
gcloud compute networks subnets list --filter="network:example-vpc"

# Shell output:
NAME            REGION       NETWORK      RANGE        STACK_TYPE  IPV6_ACCESS_TYPE  INTERNAL_IPV6_PREFIX  EXTERNAL_IPV6_PREFIX
subnet-public1  us-central1  example-vpc  10.0.1.0/24  IPV4_ONLY
subnet-public2  us-east1     example-vpc  10.0.2.0/24  IPV4_ONLY

List Compute Instances
#

# List virtual machines
gcloud compute instances list

# Shell output:
AME      ZONE           MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS
debian-1  us-central1-a  f1-micro                   10.0.1.2     34.31.72.75    RUNNING
debian-2  us-east1-b     f1-micro                   10.0.2.2     35.237.167.84  RUNNING



Links #

# GitHub Repository
https://github.com/jueklu/terraform-gcp-vpc-subnet
GCP-VPC-Terraform - This article is part of a series.
Part 2: This Article