Create GKE Cluster with Terraform/Opentofu 0x02

Part 2 of this post

Table of Contents

GKE Module

This module is where everything comes together—network, IAM, and node pools—to create a working GKE cluster.

Standard vs Autopilot

Feature Standard Autopilot
Node control Full None
Scaling Manual + autoscaler Fully managed
Operations You manage Google manages
Flexibility High Limited

Standard

Gives you full control over nodes and scaling.

  • removes default node pool - you manage it separately
  • uses VPC-native networking (pods/services ranges)
  • private cluster - nodes have no public IP
  • uses Workload Identity for secure access

modules/gke/standard/main.tf

 1resource "google_container_cluster" "gke" {
 2  name                     = var.cluster_name
 3  location                 = var.region
 4  remove_default_node_pool = true
 5  initial_node_count       = var.initial_node_count
 6  network                  = var.network
 7  subnetwork               = var.subnetwork
 8  networking_mode          = "VPC_NATIVE"
 9
10  deletion_protection = false
11
12  # Optional, if you want multi-zonal cluster
13  # node_locations = ["us-central1-b"]
14
15  node_config {
16    disk_size_gb = 15
17    machine_type = "e2-micro"
18    disk_type    = "pd-balanced"
19  }
20
21  addons_config {
22    http_load_balancing {
23      disabled = true
24    }
25    horizontal_pod_autoscaling {
26      disabled = false
27    }
28  }
29
30  release_channel {
31    channel = "REGULAR"
32  }
33
34  workload_identity_config {
35    workload_pool = "${var.project_id}.svc.id.goog"
36  }
37
38  ip_allocation_policy {
39    cluster_secondary_range_name  = "k8s-pods"
40    services_secondary_range_name = "k8s-services"
41  }
42
43  private_cluster_config {
44    enable_private_nodes    = true
45    enable_private_endpoint = false
46    master_ipv4_cidr_block  = "192.168.0.0/28"
47  }
48}

modules/gke/standard/outputs.tf

 1output "cluster_id" {
 2  value = google_container_cluster.gke.id
 3}
 4
 5output "endpoint" {
 6  value = google_container_cluster.gke.endpoint
 7}
 8
 9output "ca_certificate" {
10  value = google_container_cluster.gke.master_auth[0].cluster_ca_certificate
11}

Autopilot

Fully managed—no node pool management required.

  • no node pools to manage
  • google handles scaling, upgrades, and operations
  • best for simplicity and faster setup

modules/gke/standard/main.tf

 1resource "google_container_cluster" "gke" {
 2  name                     = var.cluster_name
 3  location                 = var.region
 4
 5  enable_autopilot = true
 6
 7  network                  = var.network
 8  subnetwork               = var.subnetwork
 9  networking_mode          = "VPC_NATIVE"
10
11  deletion_protection = false
12
13  addons_config {
14    http_load_balancing {
15      disabled = false
16    }
17    horizontal_pod_autoscaling {
18      disabled = false
19    }
20  }
21
22  release_channel {
23    channel = "REGULAR"
24  }
25
26  workload_identity_config {
27    workload_pool = "${var.project_id}.svc.id.goog"
28  }
29
30  ip_allocation_policy {
31    cluster_secondary_range_name  = "k8s-pods"
32    services_secondary_range_name = "k8s-services"
33  }
34
35  private_cluster_config {
36    enable_private_nodes    = true
37    enable_private_endpoint = false
38    master_ipv4_cidr_block  = "192.168.0.0/28"
39  }
40}

modules/gke/autopilot/outputs.tf

 1output "cluster_id" {
 2  value = google_container_cluster.gke.id
 3}
 4
 5output "endpoint" {
 6  value = google_container_cluster.gke.endpoint
 7}
 8
 9output "ca_certificate" {
10  value = google_container_cluster.gke.master_auth[0].cluster_ca_certificate
11}

Storage Module

In GKE, a default storage class is already created for you. This means you can provision persistent volumes out of the box without defining anything.

This module only matters if you want to customize storage behavior—such as disk type, reclaim policy, or default class.

We define two storage classes:

  • balance (default) - general-purpose workloads
  • SSD - high-performance workloads

module/storage/main.tf

 1resource "kubernetes_storage_class" "balanced" {
 2  metadata {
 3    name = var.balanced_sc_name
 4  }
 5
 6  storage_provisioner = "pd.csi.storage.gke.io"
 7
 8  parameters = {
 9    type = "pd-balanced"
10  }
11
12  reclaim_policy      = "Retain"
13  volume_binding_mode = "WaitForFirstConsumer"
14
15  allow_volume_expansion = true
16}
17
18resource "kubernetes_storage_class" "ssd" {
19  metadata {
20    name = var.ssd_sc_name
21
22  }
23
24  storage_provisioner = "pd.csi.storage.gke.io"
25
26  parameters = {
27    type = "pd-ssd"
28  }
29
30  reclaim_policy      = "Retain"
31  volume_binding_mode = "WaitForFirstConsumer"
32
33  allow_volume_expansion = true
34}
35
36resource "kubernetes_annotations" "default_storageclass" {
37  api_version = "storage.k8s.io/v1"
38  kind        = "StorageClass"
39  metadata {
40    name = var.balanced_sc_name
41  }
42
43  annotations = {
44    "storageclass.kubernetes.io/is-default-class" = "true"
45  }
46
47  force = true
48}

Addons Module

These are not required for GKE to function, but they are critical for real-world applications.

In this module, we install two key components using Helm:

  • Ingress NGINX - exposes services to the internet
  • cert-manager - manages TLS certificates automatically

Ingress-Nginx

Ingress is how external traffic reaches services inside your cluster.

modules/addons/ingress-nginx/main.tf

 1resource "helm_release" "nginx_ingress" {
 2  name       = "ingress-nginx"
 3  repository = "https://kubernetes.github.io/ingress-nginx"
 4  chart      = "ingress-nginx"
 5  namespace  = "ingress-nginx"
 6
 7  create_namespace = true
 8
 9  set {
10    name  = "controller.publishService.enabled"
11    value = "true"
12  }
13}

cert-manager

Automates the creation and renewal of SSL/TLS certificates inside Kubernetes.

modules/addons/cert-manager/main.tf

 1resource "helm_release" "cert_manager" {
 2  name       = "cert-manager"
 3  namespace  = "cert-manager"
 4  repository = "https://charts.jetstack.io"
 5  chart      = "cert-manager"
 6  version    = "v1.20.1"
 7
 8  create_namespace = true
 9
10  values = [<<EOF
11crds:
12  enabled: true
13
14global:
15  leaderElection:
16    namespace: cert-manager
17EOF
18  ]
19}