Build it with Terraform
Provision Google Cloud infrastructure using Hashicorp Terraform. Spin up instances of CloudSQL, Redis, Kubernetes and more.

This post covers how to build Google Cloud Platform infrastructure using Terraform.
The files you need include:
- main.tf
- variables.tf
- backend.tf (Optional)
The configurations are referenced from inside modules in this implementation, but you could have everything defined in one main.tf.
Note: Google Cloud Platform requires APIs to be enabled, and you will need account credentials with sufficient permission to build the following resources. The project will need to be linked to a billing account.
GCP Setup via CLI
Here's what you'll need to do to get set up via the CLI:
$ export PROJECT_ID=my_gcp_project
$ export ACCOUNT_ID=$(gcloud beta billing accounts list | grep True | cut -d ' ' -f1)
$ gcloud auth login
$ gcloud projects create $PROJECT_ID
$ gcloud config set compute/region us-east1
$ gcloud config set project $PROJECT_ID
$ gcloud beta billing projects link $PROJECT_ID --billing-account=$ACCOUNT_ID
# enable apis
$ gcloud services enable \
cloudapis.googleapis.com \
cloudresourcemanager.googleapis.com \
container.googleapis.com \
containerregistry.googleapis.com \
iam.googleapis.com \
redis.googleapis.com \
servicenetworking.googleapis.com \
sqladmin.googleapis.com
# If you want to use gcs for remote storage
$ gsutil mb -c standard -l us-east1 gs://$PROJECT_ID
# Create a service account for terraform
$ gcloud iam service-accounts create terraform \
--description="Terraform Service Account" \
--display-name="Terraform"
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member serviceAccount:terraform@$PROJECT_ID.iam.gserviceaccount.com \
--role roles/owner
$ gcloud iam service-accounts keys create CREDENTIALS_FILE.json --iam-account=terraform@$PROJECT_ID.iam.gserviceaccount.com --project $PROJECT_ID
$ mv CREDENTIALS_FILE.json terraform/
Setting up Terraform
Once all the variables are set, run:
$ terraform init
This will download the necessary files to ./terraform/
.
Next we'll create a dry-run execution plan to see what infrastructure will be built, and decide if any changes are necessary:
$ terraform plan
The following will create the infrastructure as it's been defined:
$ terraform apply
Configurations for GCP Infrastructure
Let's get into what our actual Terraform configurations look like. We start with some general configuration in main.tf:
locals {
database_version = "" # "POSTGRES_11"
network = "" # Network name
region = "" # us-east1
project_id = "" # GCP Project ID
subnetwork = "" # Subnetwork name
}
// Configure the Google Cloud provider
provider "google" {
credentials = file("CREDENTIALS_FILE.json")
project = local.project_id
region = local.region
}
provider "google-beta" {
credentials = file("CREDENTIALS_FILE.json")
project = local.project_id
region = local.region
}
module "cloudsql" {
source = "./modules/cloudsql"
network = local.network
private_ip_name = "" # Private IP Name
project = local.project_id
region = local.region
}
module "gke" {
source = "./modules/gke"
cluster = "" # Cluster Name
network = local.network
project = local.project_id
region = local.region
subnetwork = local.subnetwork
zones = "" # ["us-east1-b", "us-east1-c", "us-east1-d"]
}
module "memorystore" {
source = "./modules/memorystore"
display_name = "" # Display Name
ip_range = "" #
location = "" # Zone
name = "" # Instance name
network = local.network
project = local.project_id
redis_version = "" # 5.0
region = local.region
size = "" # 1
tier = "" # STANDARD
}
module "vpc" {
source = "./modules/vpc"
project = local.project_id
network = local.network
region = local.region
subnetwork = local.subnetwork
}
Next we need to make Terraform aware of our credentials file in backend.tf:
data "terraform_remote_state" "backend" {
backend = "gcs"
config = {
bucket = ""
prefix = "terraform"
credentials = file("CREDENTIALS_FILE.json")
}
}
# GCP variables
CloudSQL Configuration
locals {
network = join("/", ["projects", var.project, "global", "networks", var.network])
}
resource "google_compute_global_address" "private_ip_address" {
provider = google-beta
name = var.private_ip_name
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = local.network
depends_on = [local.network]
}
resource "google_service_networking_connection" "private_vpc_connection" {
provider = google-beta
network = local.network
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
depends_on = [local.network]
}
resource "random_id" "db_name_suffix" {
byte_length = 4
}
resource "google_sql_database_instance" "instance" {
provider = google-beta
name = "private-instance-${random_id.db_name_suffix.hex}"
database_version = var.database_version
region = var.region
depends_on = [google_service_networking_connection.private_vpc_connection]
settings {
tier = "db-custom-1-3840"
ip_configuration {
ipv4_enabled = false
private_network = local.network
}
}
}
resource "google_sql_user" "users" {
name = var.user_name
instance = google_sql_database_instance.instance.name
password = var.user_password
}
variable "database_version" {
description = "The database version"
}
variable "network" {
description = "The name of the network being created"
}
variable "private_ip_name" {
description = "The name of the private ip address being created"
}
variable "project" {
description = "Project ID"
}
variable "region" {
description = "Region"
}
variable "user_name" {
default = "DB_USER"
}
variable "user_password" {
default = "DB_PASSWORD"
}
Kubernetes Configuration
module "gke" {
source = "terraform-google-modules/kubernetes-engine/google"
project_id = var.project
name = var.cluster
region = var.region
zones = var.zones
network = var.network
subnetwork = var.subnetwork
ip_range_pods = join("-",[var.subnetwork,"pods"])
ip_range_services = join("-",[var.subnetwork,"services"])
http_load_balancing = "true"
horizontal_pod_autoscaling = "true"
network_policy = "true"
maintenance_start_time = "05:00"
remove_default_node_pool = "true"
node_pools = [
{
name = "pool-1"
machine_type = "n1-standard-2"
min_count = 1
max_count = 10
local_ssd_count = 0
disk_size_gb = 100
disk_type = "pd-standard"
image_type = "COS"
auto_repair = "true"
auto_upgrade = "true"
preemptible = "true"
initial_node_count = 1
}
]
node_pools_oauth_scopes = {
all = [
"https://www.googleapis.com/auth/cloud-platform",
]
}
node_pools_labels = {
all = {}
}
node_pools_metadata = {
all = {}
}
node_pools_tags = {
all = []
}
}
variable "cluster" {
description = "Cluster name"
}
variable "kubernetes_version" {
description = "The Kubernetes version of the masters. If set to 'latest' it will pull latest available version in the selected region."
type = string
default = "latest"
}
variable "network" {
description = "The name of the network being created"
}
variable "project" {
description = "Project"
}
variable "region" {
description = "Region of resources"
}
variable "subnetwork" {
description = "The name of the subnetwork being created"
}
variable "zones" {
description = "Zones"
}
Redis Configuration
resource "google_redis_instance" "cache" {
authorized_network = var.network
display_name = var.display_name
name = var.name
memory_size_gb = var.size
location_id = var.location
project = var.project
redis_version = var.redis_version
region = var.region
reserved_ip_range = var.ip_range
tier = var.tier
}
variable "display_name" {
description = "Instance Name"
}
variable "ip_range" {
description = "IP Range"
}
variable "location" {
description = "Zone"
}
variable "name" {
description = "Instance Name"
}
variable "network" {
description = "Authorized Network"
}
variable "project" {
description = "Project ID"
}
variable "redis_version" {
description = "Redis Version"
}
variable "region" {
description = "Region"
}
variable "size" {
description = "Memory Size in GB"
}
variable "tier" {
description = "Service Tier"
}
VPC Configuration
module "vpc" {
source = "terraform-google-modules/network/google"
version = "~> 2.3"
project_id = var.project
network_name = var.network
routing_mode = "GLOBAL"
subnets = [
{
subnet_name = var.subnetwork
subnet_ip = "10.183.0.0/20"
subnet_region = var.region
subnet_private_access = "true"
subnet_flow_logs = "true"
description = var.subnet_description
}
]
secondary_ranges = {
(var.subnetwork) = [
{
range_name = join("-", [var.subnetwork, "pods"])
ip_cidr_range = "10.184.0.0/14"
},
{
range_name = join("-", [var.subnetwork, "services"])
ip_cidr_range = "10.188.0.0/20"
},
]
}
}
variable "auto_create_subnetworks" {
type = bool
description = "When set to true, the network is created in 'auto subnet mode' and it will create a subnet for each region automatically across the 10.128.0.0/9 address range. When set to false, the network is created in 'custom subnet mode' so the user can explicitly connect subnetwork resources."
default = false
}
variable "description" {
type = string
description = "An optional description of this resource. The resource must be recreated to modify this field."
default = "Toptal VPC network"
}
variable "network" {
description = "The name of the network being created"
}
variable "project" {
description = "Toptal Project"
}
variable "region" {
description = "Region of resources"
}
variable "routing_mode" {
type = string
default = "GLOBAL"
description = "The network routing mode (default 'GLOBAL')"
}
variable "subnet_description" {
type = string
description = "An optional description of this resource. The resource must be recreated to modify this field."
default = "Toptal VPC subnetwork"
}
variable "shared_vpc_host" {
type = bool
description = "Makes this project a Shared VPC host if 'true' (default 'false')"
default = false
}
variable "subnetwork" {
description = "The name of the subnetwork being created"
}