Connecting GKE to Cloud SQL and Cloud Storage with Terraform

Connecting GKE to Cloud SQL and Cloud Storage with Terraform

Introduction

Part 1 covered VPC + GKE with Terraform modules and Terragrunt live config: remote state, subnet secondary ranges, and a small node pool. This post adds the data plane most small platforms need next: Cloud SQL (PostgreSQL or MySQL) and Cloud Storage (GCS) buckets for object data, uploads, or static assets.

Read the foundation first: Provisioning a Small GKE Cluster with Terraform and Terragrunt.

The GKE storage docs explain when to use Persistent Disks, Filestore, or object storage in Kubernetes. Here we focus on provisioning SQL and buckets in GCP and hooking them up to workloads running on GKE.

Stack Layout: DB and Bucket Alongside GKE

In the same repo style as part 1, you typically have Terragrunt children:

  • environments/<env>/vpc/
  • environments/<env>/gke/
  • environments/<env>/db/
  • environments/<env>/bucket/

Cloud SQL must attach to a VPC for private IP access, so db/terragrunt.hcl almost always dependencys the VPC stack and passes network_id and the VPC self link into the module.

A GCS application bucket may not need VPC outputs if the module only creates the bucket and IAM; still, keep it in the same environment tree so inputs (project, region, naming suffix) stay consistent.

Cloud SQL: Private IP and Service Networking

Why private IP

Exposing Cloud SQL on a public IPv4 address simplifies demos but is a security liability for production. A common Terraform pattern sets:

ip_configuration {
  ipv4_enabled    = false
  private_network = var.vpc_self_link
}

That forces traffic over private networking inside your VPC (clients still need a path from GKE nodes or a proxy).

Service networking connection

Private Cloud SQL on GCP uses VPC peering to the Google-managed service producer network. Terraform usually creates:

  1. A google_compute_global_address with purpose = "VPC_PEERING" and an internal prefix (e.g. /24)
  2. A google_service_networking_connection to servicenetworking.googleapis.com using that reserved range

Without this, private IP instances fail to allocate addresses correctly.

Instance and databases

The module may define:

  • google_sql_database_instance per environment key (or a single instance)
  • backup_configuration, optional PITR, disk size, tier, availability_type (zonal vs regional)
  • Primary and extra databases/users via for_each maps

Passwords are often generated with random_password and lifecycle { ignore_changes = [password] } after the first apply so Terraform does not rotate secrets on every run. Store the value in Secret Manager or your secret store; avoid committing secrets to Git.

Connecting from GKE

From pods, typical options:

  1. Cloud SQL Auth Proxy or Cloud SQL connector libraries (recommended for IAM DB auth and TLS)
  2. Direct private IP if network routing and firewall rules allow node → SQL traffic

Hardening topics (Workload Identity to IAM DB auth, least-privilege service accounts) belong with GKE security — implement them explicitly; a minimal Terraform repo may only create the instance and network, not the full auth story.

GCS: Buckets and IAM for Workloads

Bucket resource

A small application bucket module might:

  • Name buckets with environment + suffix (globally unique)
  • Set location (e.g. multi-region or EU)
  • Enable uniform bucket-level access
  • Turn on versioning when you need rollback or auditability

Service account and binding

One pattern creates a dedicated storage service account and grants it roles/storage.objectViewer (read-only) on that bucket via google_storage_bucket_iam_binding. Write-heavy apps need objectCreator / objectAdmin or fine-grained IAM Conditions — adjust to least privilege.

Getting credentials into the cluster

At this layer Terraform often stops at GCP IAM. Inside Kubernetes you still choose how the pod authenticates:

  • Workload Identity (bind a Kubernetes SA to a Google SA) — preferred on GKE; see GKE security
  • Mounted JSON key — works but harder to rotate and easier to leak

If your Terraform repo only creates the Google SA and bucket IAM, plan a follow-up (Helm/Kustomize, Config Connector, or a small manifest repo) to wire Workload Identity or Workload Identity Federation patterns.

Terragrunt: DB Depends on VPC

The db stack mirrors gke: include parent config, depend on VPC, map outputs to module variables:

terraform {
  source = "../../../modules/db"
}

include {
  path = find_in_parent_folders()
}

dependency "vpc" {
  config_path = "../vpc"
  mock_outputs = {
    network_id    = "network-00000001"
    vpc_self_link = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/my-vpc"
  }
}

inputs = {
  network_id    = dependency.vpc.outputs.network_id
  vpc_self_link = dependency.vpc.outputs.vpc_self_link
}

Shared inputs (project, region, db_envs maps, etc.) flow from the environment root the same way as for GKE.

When to Use Cloud SQL / GCS vs Kubernetes Volumes

NeedTypical choice
Relational transactional dataCloud SQL (or AlloyDB for larger Postgres)
Large objects, static assets, user uploadsGCS
Block storage for a database inside the clusterPersistent Disk + StatefulSet — see GKE storage
Shared file semantics (NFS-like)Filestore or a CSI driver

Avoid running production Postgres only on a single PD unless you accept operational risk; managed SQL trades cost for backups, HA, and patching.

Apply Order

Within one environment:

  1. VPC (subnets + secondary ranges)
  2. Cloud SQL module (depends on VPC for private IP + peering)
  3. GKE (depends on VPC)
  4. Bucket (can run in parallel with GKE once project inputs exist)

Use terragrunt run-all apply from the env folder so dependency order is respected.

Cross-Linking for SEO and Readers

Summary

  • Cloud SQL in this pattern uses private IP + service networking; the db Terragrunt unit depends on vpc.
  • GCS modules provision buckets and IAM; pod auth should move toward Workload Identity, not long-lived keys.
  • Keep Terraform focused on GCP resources; Kubernetes manifests and GitOps layers sit above this base.

Together with part 1, you get a small but coherent GCP stack: network, cluster, SQL, and object storage—ready to layer applications and platform tooling on top.