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