Define a Google Load Balancer and Cloud Storage bucket with Terraform

Here’s an example of using Terraform to define resources to host static content in a Google Cloud Storage bucket, fronted by a Cloud Load Balancer with a custom URL and SSL certificate. This example uses some other advanced features, such as Google Secrets and a map variable to define the SSL certificates. It’s pulled from a larger project, so this block of code isn’t guaranteed to run as-is. At a minimum, you’ll need to define the variables and set values.

# Use Google-provided module to define a bucket

module "cloud_storage_buckets" {
  source  = "terraform-google-modules/cloud-storage/google"
  version = "2.1.0"

  project_id       = var.project
  prefix           = var.bucket_prefix
  location         = var.bucket_location
  names            = var.bucket_names
  set_viewer_roles = true
  viewers = [
    "allUsers"
  ]
  website = {
    main_page_suffix = "index.html"
    not_found_page   = "index.html"
  }
}

# External IP Address for load balancer
resource "google_compute_global_address" "static" {
  name        = "static"
  description = "Static external IP address for hosting"
}

# SSL Certificates
data "google_secret_manager_secret_version" "cert" {
  for_each = var.ssl_map
  secret   = each.value.tls_cert
}

data "google_secret_manager_secret_version" "key" {
  for_each = var.ssl_map
  secret   = each.value.tls_key
}

resource "google_compute_ssl_certificate" "default" {
  for_each    = var.ssl_map
  name_prefix = each.key
  description = each.value.description
  private_key = data.google_secret_manager_secret_version.key[each.key].secret_data
  certificate = data.google_secret_manager_secret_version.cert[each.key].secret_data

  lifecycle {
    create_before_destroy = true
  }
}

# SSL Policies
resource "google_compute_ssl_policy" "tls12_modern" {
  name            = "${var.environment}-static-ssl-policy"
  profile         = "MODERN"
  min_tls_version = "TLS_1_2"
}

# HTTPS load balancer for backend bucket
resource "google_compute_backend_bucket" "static" {
  name        = "${var.environment}-us-static-backend"
  description = "Backend storage bucket for statick static files"
  bucket_name = "${var.environment}-us-static"
  # bucket_name = module.cloud_storage_buckets.google_storage_bucket.buckets["static"].name
  enable_cdn = false
}

resource "google_compute_url_map" "static" {
  name            = "${var.environment}-static-load-balancer"
  default_service = google_compute_backend_bucket.static.id
}

resource "google_compute_target_https_proxy" "static" {
  for_each         = var.ssl_map
  name             = "${var.environment}-static-${each.key}"
  url_map          = google_compute_url_map.static.id
  ssl_certificates = [google_compute_ssl_certificate.default[each.key].id]
  ssl_policy       = google_compute_ssl_policy.tls12_modern.id
}

resource "google_compute_global_forwarding_rule" "static" {
  for_each   = var.ssl_map
  name       = "${var.environment}-static-${each.key}"
  target     = google_compute_target_https_proxy.static[each.key].id
  port_range = "443"
  ip_address = google_compute_global_address.static.id
}

# Partial HTTP load balancer redirects to HTTPS
resource "google_compute_url_map" "static_http" {
  name = "${var.environment}-static-http-redirect"
  default_url_redirect {
    https_redirect = true
    strip_query    = false
  }
}

resource "google_compute_target_http_proxy" "static" {
  name    = "${var.environment}-static-http-proxy"
  url_map = google_compute_url_map.static_http.id
}

resource "google_compute_global_forwarding_rule" "static_http" {
  name       = "${var.environment}-static-forwarding-rule-http"
  target     = google_compute_target_http_proxy.static.id
  port_range = "80"
  ip_address = google_compute_global_address.static.id
}

for_each and the SSL map

You may be confused by the for_each construct if you’re new to Terraform. This parameter refers to a map variable, which is defined like this:

ssl_map = {
  my_cert_2022 = {
    tls_cert    = "my-tls-cert",
    tls_key     = "my-tls-key",
    description = "mysite.com wildcard issued 2022-08 expires 2023-08"
  }
}

The for_each construct causes Terraform to iterate over the keys in the map and define multiple copies of the resource (there is only one key in this case). Other parameters in the resource can use either the key itself or the values. I have mixed feelings about for_each. It’s a great shortcut, but also makes the code harder to read.

Next Steps

This code block could easily be made into a module, and that’s what you should do if you plan to use it more than once. The module should accept an array of SSL certs. The module would also hide the confusing aspects of the for_each.

Ideally, I’d like Google’s load balancer module to support a static backend configuration (probably as a submodule).

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.