Skip to main content

GCP Virtual Private Cloud (VPC) Part 1: Public Subnet Scheme Single Region, GCP Network Overview, Terraform Configuration, Mermaid Flowchart

1722 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 1: This Article
GitHub Repository Available

Overview
#

VPCs
#

  • In GCP, the VPCs are global and do not have a CIDR range.

Subnets
#

  • A single public subnet can serve all zones within that region.

  • It is not necessary to create separate public subnets for each zone as it would be in AWS.

  • A single subnet can host VM instances in multiple zones within the same region.

  • This makes it easier to create high availability architectures without needing to create separate subnets for each zone.


This Setup & Flowchart
#

This is a configuration example for two public subnets in one region. Technically it is not necessary to deploy two public subnets in the same region, since a single public subnet can serve all zones within that region.

For the a configuration that deploys two public subnets in two different regions, take a look at the next blog post in this series.


graph TD %% VPC subgraph VPC["VPC Global"] %% Region1 subgraph Region1["GCP Region us-central1"] %% Subnet 1 AZ1 subgraph SN1["Public Subnet 1"] SN1AZ1["AZ us-central1-a"] SN1AZ2["AZ us-central1-b"] SN1AZ3["AZ us-central1-c"] end %% Subnet 1 AZ1 subgraph SN2["Public Subnet 2"] SN2AZ1["AZ us-central1-a"] SN2AZ2["AZ us-central1-b"] SN2AZ3["AZ us-central1-c"] end end %% AZ Connections SN1AZ1 <-.-> SN1AZ2 SN1AZ2 <-.-> SN1AZ3 %% Subnet Connections SN2AZ1 <-.-> SN2AZ2 SN2AZ2 <-.-> SN2AZ3 end %% Connectivity SN1 <--> Internet[Internet] SN2 <--> Internet %% Dashed Border classDef BoarderDash1 stroke-dasharray:5 5; class Region1,SN1AZ1,SN1AZ2,SN1AZ3 BoarderDash1 classDef BoarderDash1 stroke-dasharray:5 5; class SN2AZ1,SN2AZ2,SN2AZ3 BoarderDash1 %% Node Colors style SN1 fill:#d1f7d6,stroke:#333,stroke-width:2px style SN2 fill:#d1f7d6,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-one-region
├── 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 (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"  # Define JSON Key created with SDK
}

# 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 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 Subnets: Define different AZs
variable "gcp_zones" {
  description = "List of GCP zones for the subnets"
  type        = list(string)
  default     = ["us-central1-a", "us-central1-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.gcp_region
  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}"
}

# 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 GCP Terraform provider defined in the configuration.

# 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",
  "34.68.253.237",
]

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 VM 1
ssh -i ~/.ssh/terraform debian@34.31.72.75

# SSH into VM 2
ssh -i ~/.ssh/terraform debian@34.68.253.237

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
debian@debian-1:~$ ping debian-2

# Shell output:
PING debian-2.c.my-example-project-448310.internal (10.0.2.2) 56(84) bytes of data.
64 bytes from debian-2.us-central1-b.c.my-example-project-448310.internal (10.0.2.2): icmp_seq=1 ttl=64 time=1.85 ms
64 bytes from debian-2.us-central1-b.c.my-example-project-448310.internal (10.0.2.2): icmp_seq=2 ttl=64 time=0.373 ms


# 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=2.24 ms
64 bytes from 10.0.2.2: icmp_seq=2 ttl=64 time=0.402 m
# Ping Instance 1 from instance 2
debian@debian-2:~$ ping debian-1

# Shell output:
PING debian-1.c.my-example-project-448310.internal (10.0.1.2) 56(84) bytes of data.
64 bytes from debian-1.us-central1-a.c.my-example-project-448310.internal (10.0.1.2): icmp_seq=1 ttl=64 time=2.19 ms
64 bytes from debian-1.us-central1-a.c.my-example-project-448310.internal (10.0.1.2): icmp_seq=2 ttl=64 time=0.399 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=0.024 ms
64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.037 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-central1  example-vpc  10.0.2.0/24  IPV4_ONLY

List Compute Instances
#

# List virtual machines
gcloud compute instances list

# Shell output:
NAME      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-central1-b  f1-micro                   10.0.2.2     34.68.253.237  RUNNING



Links #

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