Pablo Alejandro Costesich's Blog

Reducing blog hosting costs

Posted by @pcostesi on 2021-07-19Time to read: 94 minutes

In the last blog post, we set up Terraform to automatically provision our infrastructure so we could host this blog. Now, I'm going to focus on a pain point I found after the first month of use.

A word of caution

Always set up billing alerts and budgets, or you will find out that something as easy as hosting a blog can end up being expensive if you are not careful.

If you are using Terraform, you might be interested in checking out Infracost.

The Problem

GCP is rather pricy when it comes to serving https traffic. That's because the Load Balancer we need for TLS termination has a fixed price per hour for each forwarding rule.

However, HTTP hosting is essentially free, so now we have to find out a way to replace the Load Balancer with something else.

The Solution

Alex Olivier came up with a solution I think is great for this use-case. We don't need end-to-end encryption for a blog, but we do need encryption for .dev domains.

We are going to use Terraform to do it, though.

Enter CloudFlare

We first have to set-up a CloudFlare account and to get the "Global API Key" under the Api Tokens page.

We then have to add that key to our secrets.auto.tfvars file, together with the email we used to set-up the account. My secrets.auto.tfvars looks something like this:

email_address = "[email protected]"
cf_api        = "c0ffee123123123"

The main.tf file now has a new cloudflare provider:

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

This allows us to use the CloudFlare API from Terraform.

Networking

Since we're moving our public layer to another provider, some things are going to change in the networking section. Most notably, we now have to point our domain to CloudFlare's own load balancers instead of Google's, and to fetch the resources from the bucket for us.

This means we need to remove the old configuration and replace it with an equivalent version using CloudFlare. However, we might want to switch back to the older config! Although we've got this under version control, going back and forth between commits to conditionally deploy resources is not a good practice. In order to avoid this, we're going to use a little hack: leverage the count field in the terraform settings to decide if we want this resource to be deployed or not.

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

Terraform allows for conditonal expressions and arbitrary values through the use of ternary if operators. This enables us to check a variable (such as use_cloudflare) to deploy just the resources we want.

That variable is located in the updated variables.tf file and can be overriden by the CLI:

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

Routing

It's not always just DNS. We need to disable the Load Balancer and all of the expensive routing components (this is, in fact, the most important part). We simply use the count trick to disable everything here.

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

The Rest

For the sake of completeness, I'm going to post the rest of the files.

The build process stays the same:

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

The monitor still points to the dev site (although we could add cloudflare_healthchecks to do the same)

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

Finally, the bucket is still there. We could also host this using AWS, but I bet you have seen a thousand blogs do the same.

resource "google_cloudbuild_trigger" "site_trigger" {
provider = google-beta
name = "site-trigger"
project = var.project
github {
push {
branch = "master"
}
owner = "pcostesi"
name = "pcostesi.dev"
}
filename = "cloudbuild.yaml"
}
view raw build.tf hosted with ❤ by GitHub
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`)
resource "google_compute_global_address" "static_sites" {
provider = google
name = "static-sites"
count = var.use_cloudflare ? 0 : 1
}
# Managed DNS zone
data "google_dns_managed_zone" "main_site" {
provider = google
name = var.project
count = var.use_cloudflare ? 0 : 1
}
# Bind IP to the DNS
resource "google_dns_record_set" "static_site" {
provider = google
name = "${var.static_site}."
type = "A"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = [google_compute_global_address.static_sites[0].address]
count = var.use_cloudflare ? 0 : 1
}
# Site verification
resource "google_dns_record_set" "static_site_txt" {
provider = google
name = "${var.static_site}."
type = "TXT"
ttl = 300
managed_zone = data.google_dns_managed_zone.main_site[0].name
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"]
count = var.use_cloudflare ? 0 : 1
}
resource "cloudflare_zone" "main_site" {
zone = var.static_site
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_zone_settings_override" "main_site" {
zone_id = cloudflare_zone.main_site[0].id
settings {
ssl = "full"
always_use_https = "on"
automatic_https_rewrites = "on"
}
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
value = "c.storage.googleapis.com"
type = "CNAME"
ttl = 1
proxied = true
count = var.use_cloudflare ? 1 : 0
}
resource "cloudflare_record" "static_site_txt" {
zone_id = cloudflare_zone.main_site[0].id
name = "${var.static_site}."
type = "TXT"
ttl = 300
value = "google-site-verification=${var.google_site_verification}"
count = var.use_cloudflare ? 1 : 0
}
view raw dns.tf hosted with ❤ by GitHub
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
google-beta = {
source = "hashicorp/google-beta"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 2.0"
}
}
backend "gcs"{
bucket = "terraform-static-state"
prefix = "blog"
credentials = "../credentials.json"
}
}
provider "google" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
provider "cloudflare" {
email = var.email_address
api_key = var.cf_api
}
view raw main.tf hosted with ❤ by GitHub
resource "google_monitoring_notification_channel" "basic" {
display_name = "Main Notification Channel"
type = "email"
project = var.project
provider = google-beta
labels = {
email_address = var.email_address
}
}
resource "google_monitoring_uptime_check_config" "static_site_https" {
provider = google-beta
display_name = "static-site-https-uptime-check"
timeout = "60s"
project = var.project
http_check {
path = "/"
port = "443"
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
project_id = var.project
host = var.static_site
}
}
}
resource "google_monitoring_alert_policy" "static_site_alert_policy" {
display_name = "Static Site Alert Policy"
combiner = "OR"
conditions {
display_name = "SSL certificate expiring soon"
condition_threshold {
filter = "metric.type=\"monitoring.googleapis.com/uptime_check/time_until_ssl_cert_expires\" AND resource.type=\"uptime_url\""
duration = "600s"
comparison = "COMPARISON_LT"
threshold_value = 15
trigger {
count = 1
}
aggregations {
alignment_period = "1200s"
per_series_aligner = "ALIGN_NEXT_OLDER"
cross_series_reducer = "REDUCE_MEAN"
group_by_fields = ["resource.label.*"]
}
}
}
user_labels = {
"uptime" = "ssl_cert_expiration"
"version" = "1"
}
notification_channels = [ google_monitoring_notification_channel.basic.name ]
}
view raw monitoring.tf hosted with ❤ by GitHub
# Add the bucket as a CDN backend
resource "google_compute_backend_bucket" "static_site" {
provider = google
name = "static-site-backend"
description = "Contains files needed by the website"
bucket_name = google_storage_bucket.static_site.name
enable_cdn = true
depends_on = [google_dns_record_set.static_site_txt]
count = var.use_cloudflare ? 0 : 1
}
# HTTPS certificate
resource "google_compute_managed_ssl_certificate" "static_site" {
provider = google-beta
name = "static-site-cert"
managed {
domains = [google_dns_record_set.static_site[0].name]
}
count = var.use_cloudflare ? 0 : 1
}
# GCP URL MAP (frontend config)
resource "google_compute_url_map" "static_site" {
provider = google
name = "static-site-url-map"
default_service = google_compute_backend_bucket.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
# GCP proxy (load balancer)
resource "google_compute_target_https_proxy" "static_site" {
provider = google
name = "static-site-target-proxy"
url_map = google_compute_url_map.static_site[0].self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_site[0].self_link]
count = var.use_cloudflare ? 0 : 1
}
# GCP forwarding rule (IP <-> Load Balancer mapping)
resource "google_compute_global_forwarding_rule" "default" {
provider = google
name = "static-site-forwarding-rule"
load_balancing_scheme = "EXTERNAL"
ip_address = google_compute_global_address.static_sites[0].address
ip_protocol = "TCP"
port_range = "443"
target = google_compute_target_https_proxy.static_site[0].self_link
count = var.use_cloudflare ? 0 : 1
}
view raw routing.tf hosted with ❤ by GitHub
resource "google_storage_bucket" "static_site" {
name = var.static_site
force_destroy = true
uniform_bucket_level_access = false
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["https://${var.static_site}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}
resource "google_storage_default_object_acl" "static_site_read" {
bucket = google_storage_bucket.static_site.name
role_entity = ["READER:allUsers"]
}
view raw storage.tf hosted with ❤ by GitHub
variable "project" {
default = "pcostesi-dev"
}
variable "credentials" {
default = "../credentials.json"
}
variable "region" {
default = "us-central1"
}
variable "zone" {
default = "us-central1-c"
}
variable "name" {
default = "main"
}
variable "static_site" {
default = "pcostesi.dev"
}
variable "google_site_verification" {
default = "KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"
}
variable "use_cloudflare" {
default = true
}
variable "email_address" {}
variable "cf_api" {}
view raw variables.tf hosted with ❤ by GitHub

Thoughts

My expenses have gone down quite significantly, as the load balancer was responsible for the largest amount by far.

But I think the most interesting trick here was to use count and to do all this using Terraform.