Skip to main content

Terraform

1. Overview

All GCP infrastructure is provisioned from infra/tf/gcp. The configuration is module-based, and Terrakube supplies the environment-specific variables and remote state for the gateway production workspace and gateway-staging staging workspace.


2. Directory Structure

infra/tf/gcp/
├── main.tf # Providers, backend, root resources, module wiring
├── variables.tf # Shared inputs and safe defaults
├── outputs.tf # Root outputs
├── backend-prod.hcl # Terrakube workspace: gateway
├── backend-staging.hcl # Terrakube workspace: gateway-staging
├── terrakube-imports.tf # Imported Terrakube WIF resources
├── removed-secret-versions.tf
└── modules/
├── apphub/
├── artifact-registry/
├── cloud-run/
├── device-runtime/
├── firebase/
├── iam/
├── identity-platform/
├── kms/
├── load-balancer/
├── monitoring/
├── mqtt-broker/
├── networking/
├── pubsub/
├── secrets/
├── spanner/
└── storage/

3. Backend Configuration

The root configuration deliberately leaves the workspace out of the remote backend block. Select it with the checked-in backend file when running local read-only commands:

terraform {
required_version = ">= 1.7.0"

required_providers {
google = {
source = "hashicorp/google"
version = "~> 7.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 7.0"
}
}

backend "remote" {
hostname = "iac-api.peaksuite.dev"
organization = "peakgateway"
}
}

backend-prod.hcl selects gateway; backend-staging.hcl selects gateway-staging. Terrakube VCS/API runs already know their workspace and do not use the local selector.


4. Module Specifications

4.1 Project Module

The project module is intentionally empty today. API enablement and core project configuration live in the root main.tf file so Terraform applies them before dependent modules.

# main.tf (API enablement)
resource "google_project_service" "apis" {
for_each = toset([
"run.googleapis.com",
"spanner.googleapis.com",
"pubsub.googleapis.com",
"secretmanager.googleapis.com",
"artifactregistry.googleapis.com",
"compute.googleapis.com",
"vpcaccess.googleapis.com",
"cloudresourcemanager.googleapis.com",
"firebase.googleapis.com",
"identitytoolkit.googleapis.com",
"monitoring.googleapis.com",
"logging.googleapis.com",
"cloudtrace.googleapis.com",
"cloudkms.googleapis.com",
"iam.googleapis.com",
"apphub.googleapis.com",
"dns.googleapis.com",
"storage.googleapis.com",
])

project = var.project_id
service = each.value
disable_on_destroy = false
}

4.2 Networking Module

Implements the VPC, subnets, serverless VPC access connector, and firewall rules. It also configures Private Google Access via Cloud DNS.

# modules/networking/main.tf
resource "google_compute_network" "vpc" {
name = "gateway-vpc"
auto_create_subnetworks = false
project = var.project_id
}

resource "google_compute_subnetwork" "subnet" {
name = "gateway-subnet"
ip_cidr_range = "10.0.0.0/24"
region = var.region
network = google_compute_network.vpc.id
project = var.project_id
}

resource "google_vpc_access_connector" "connector" {
name = "gateway-vpc-connector"
region = var.region
network = google_compute_network.vpc.name
ip_cidr_range = "10.8.0.0/28"
project = var.project_id
}

# Private Google Access DNS and routes are also configured here

4.3 Cloud Run Module

Deploys microservices using for_each. Services are internal-only by default (accessible via Load Balancer or internal VPC). Includes probes and IAM bindings.

# modules/cloud-run/main.tf
resource "google_cloud_run_v2_service" "service" {
for_each = var.services

name = "gateway-${each.key}"
location = var.region
project = var.project_id
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"

template {
service_account = var.service_accounts[each.key]

vpc_access {
network_interfaces {
network = var.vpc_network_id
subnetwork = var.vpc_subnet_id
tags = ["cloud-run"]
}
egress = "PRIVATE_RANGES_ONLY"
}

containers {
image = each.value.image

startup_probe {
http_get { path = "/health" }
}

liveness_probe {
http_get { path = "/health" }
}

# Environment variables and secrets mapped dynamically
}
}
}

4.4 Spanner Module

Configures the Cloud Spanner instance and database with CMEK encryption and a daily backup schedule.

# modules/spanner/main.tf
resource "google_spanner_instance" "instance" {
name = "gateway-spanner"
config = "regional-${var.region}"
processing_units = var.spanner_processing_units
project = var.project_id
}

resource "google_spanner_database" "database" {
instance = google_spanner_instance.instance.name
name = "gateway-db"
project = var.project_id
database_dialect = "POSTGRESQL"

encryption_config {
kms_key_name = var.kms_key_id
}
}

resource "google_spanner_backup_schedule" "daily" {
instance = google_spanner_instance.instance.name
database = google_spanner_database.database.name
name = "daily-backup"
spec {
cron_spec { text = "0 2 * * *" }
}
}

5. Environment Configuration

Terrakube workspace variables are the source of truth for live environment values. Both environments use the same unsuffixed resource names; the GCP project separates them.

SettingStagingProduction
Workspacegateway-staginggateway
GCP projectpeakgateway-stagingpinpoint-gateway
Cloud Run regionus-east1us-east1
API hoststaging-api.peakgateway.coapi.peakgateway.co
Payment hoststaging-pay.peakgateway.copay.peakgateway.co
Artifact RegistryPulls cross-project from productionHosted in pinpoint-gateway

Checked-in variable defaults are fallbacks, not a record of live workspace configuration. Never infer a deployed value from default alone; inspect the target Terrakube workspace and the active GCP resource before an operational change.


6. Local Read-Only Commands

cd infra/tf/gcp

# Staging
terraform init -reconfigure -backend-config=backend-staging.hcl
terraform plan

# Production: reinitialize before changing backend
terraform init -reconfigure -backend-config=backend-prod.hcl
terraform plan

Apply through the authorized Terrakube workspace. Local production applies are not part of the supported workflow.