0

I want to host my website on Cloud Run. The website is developed in React, and packaged using golang's "embed" package, which enables the React build assets to be served by the Go "wrapper" REST API.

I want to automatically create a DNS Zone and add the DNS records in there, then map the domain name to my Cloud Run based API/website. All in all, I want everything automated by Terraform.

I have written my main.tf file like this:


# loal ariables
locals {
  project_id                         = var.project_id
  project_number                     = var.project_number
  region                             = var.region
  service_name                       = var.service_name
  concurrency                        = var.concurrency
  service_account                    = var.service_account
  timeout                            = var.timeout
  domain_name                        = var.domain_name
  image_url                          = var.image_url
  load_balancer_address_name         = var.load_balancer_address_name
  load_balancer_backend_name         = var.load_balancer_backend_name
  load_balancer_url_map_name         = var.load_balancer_url_map_name
  load_balancer_proxy_name           = var.load_balancer_proxy_name
  load_balancer_forwarding_rule_name = var.load_balancer_forwarding_rule_name
  google_api_services = [
    "cloudbilling.googleapis.com",
    "serviceusage.googleapis.com",
    "run.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "containerregistry.googleapis.com",
    "iam.googleapis.com",
    "dns.googleapis.com",
    "compute.googleapis.com"
  ]
  required_roles = [
    "roles/run.admin",
    "roles/run.invoker",
    "roles/iam.serviceAccountUser",
    "roles/logging.logWriter",
    "roles/storage.admin",
    "roles/storage.objectViewer",
    "roles/owner",
    "roles/resourcemanager.projectIamAdmin",
    "roles/domains.admin"
  ]
}

# google provider
provider "google" {
  project = local.project_id
  region  = local.region
}

# Enable Google Cloud APIs
resource "google_project_service" "enabled" {
  for_each           = toset(local.google_api_services)
  service            = each.key
  disable_on_destroy = false
}

# Assign required roles/rights to the default service account
resource "google_project_iam_member" "default_service_account_roles" {
  project  = local.project_id
  for_each = toset(local.required_roles)
  role     = each.key
  member   = "serviceAccount:${local.project_number}-compute@developer.gserviceaccount.com"
  lifecycle {
    prevent_destroy = true
  }
}

# Create or Update Cloud Run service
resource "google_cloud_run_service" "mw_cloud_run_svc" {
  name     = local.service_name
  location = local.region
  template {
    spec {
      containers {
        image = local.image_url
      }
    }
  }
  metadata {
    annotations = {
      "run.googleapis.com/ingress" = "all"
    }
  }
  traffic {
    percent         = 100
    latest_revision = true
  }
}

# allow everyone on internet to access this cloud run service 
resource "google_cloud_run_service_iam_member" "cloud_run_svc_invoker_policy_1" {
  project  = local.project_id
  service  = google_cloud_run_service.mw_cloud_run_svc.name
  location = google_cloud_run_service.mw_cloud_run_svc.location
  role     = "roles/run.invoker"
  member   = "allUsers"
}

# DNS Managed Zone
resource "google_dns_managed_zone" "my_dns_zone" {
  name        = "my-dns-zone"
  dns_name    = "${local.domain_name}."
  description = "Managed zone for ${local.domain_name}."
  labels = {
    environment = "production"
  }
  depends_on = [google_project_service.enabled]
}

# DNS Record Sets
resource "google_dns_record_set" "root_subdomain" {
  name         = "${local.domain_name}."
  type         = "A"
  ttl          = 300
  managed_zone = google_dns_managed_zone.my_dns_zone.name

  rrdatas = [
    google_cloud_run_service.mw_cloud_run_svc.status[0].url
  ]
}

resource "google_dns_record_set" "www_subdomain" {
  name         = "www.${local.domain_name}."
  type         = "CNAME"
  ttl          = 300
  managed_zone = google_dns_managed_zone.my_dns_zone.name

  rrdatas = [
    google_cloud_run_service.mw_cloud_run_svc.status[0].url
  ]
}

# use the authenticated (existing) project -> "my-website"
data "google_project" "my_website" {}

# map cloud run service to a custom domain name
resource "google_cloud_run_domain_mapping" "myweb" {
  name     = local.domain_name
  location = google_cloud_run_service.mw_cloud_run_svc.location
  metadata {
    namespace = data.google_project.my_website.project_id
  }
  spec {
    route_name = google_cloud_run_service.mw_cloud_run_svc.name
  }
  depends_on = [
    google_dns_managed_zone.mahani_website_dns_zone,
    google_dns_record_set.root_subdomain,
    google_dns_record_set.www_subdomain
  ]
}
#===============================
output "url" {
  value = google_cloud_run_service.mw_cloud_run_svc.status[0].url
}

output "domain_mapping" {
  value = google_cloud_run_domain_mapping.myweb
}

I get this Error:

Error: Error creating DNS RecordSet: googleapi: Error 400: Invalid value for 'entity.change.additions[mywebxxx.com.][A].rrdata[0]': 'https://***-x4kxyob2wq-ue.a.run.app', invalid
│ 
│   with google_dns_record_set.root_subdomain,
│   on main.tf line 107, in resource "google_dns_record_set" "root_subdomain":
│  107: resource "google_dns_record_set" "root_subdomain" ***
│ 
╵
╷
│ Error: Error creating DNS RecordSet: googleapi: Error 400: Invalid value for 'entity.change.additions[www.mywebxxx.com.][CNAME].rrdata[0]': 'https://***-x4kxyob2wq-ue.a.run.app', invalid
│ 
│   with google_dns_record_set.www_subdomain,
│   on main.tf line 118, in resource "google_dns_record_set" "www_subdomain":
│  118: resource "google_dns_record_set" "www_subdomain" ***

NOTE: I had to first manually add some permissions to the service account that authenticates Terraform, by running the following commands:

gcloud projects add-iam-policy-binding <PROJECT_ID> --member='serviceAccount:<PROJECT_NUMBER>-compute@developer.gserviceaccount.com'  --role='roles/iam.serviceAccountUser'  

gcloud projects add-iam-policy-binding <PROJECT_ID> --member='serviceAccount:<PROJECT_NUMBER>-compute@developer.gserviceaccount.com' --role='roles/run.admin'

gcloud projects add-iam-policy-binding <PROJECT_ID> --member='serviceAccount:<PROJECT_NUMBER>-compute@developer.gserviceaccount.com' --role='roles/resourcemanager.projectIamAdmin'
  • I am curious why this question has been downvoted and received a close vote. The close vote is for not being a programming question. Terraform is a programming language, so this post is on topic. This post includes the code and error messages, which meet minimum requirements. I am interested in better understanding the cause and solution. Cloud Run and DNS resource record setup are common tasks that need to be performed. Using Terraform is a good solution. – John Hanley Jun 19 '23 at 17:19
  • Thank you @John Hanley! For the "cause", let me give a background. I want to host my website on Cloud Run. The website is developed in React, and packaged using golang's "embed" package, which enables the React build assets to be served by the Go "wrapper" REST API. This API should be (and has successfully been) deployed to Google Cloud Run using terraform. Though Cloud Run provides a way to **Add a custom domain**, The limitation is to have to add DNS records at the domain provider's side. My domain provider is NameCheap, but I don't want to use their DNS. I prefer the Google Cloud DNS. – Rodgers Ategyeka Jun 20 '23 at 04:32
  • I want to automatically create a DNS Zone and add the DNS records in there, then map the domain name to my Cloud Run based API/website. All in all, I want everything automated by Terraform. The code above has all that I have tried so far. The Terraform script is **partly** doing the job, but failing at the point of creating the DNS RecordSets. The part of creating a Cloud Run service and deploying the API there succeeds well. – Rodgers Ategyeka Jun 20 '23 at 04:37
  • The website is accessible but only if I use the Cloud Run auto-generated link, not my custom domain! This is where I am stuck! – Rodgers Ategyeka Jun 20 '23 at 04:37
  • If nobody provides a solution, I plan to reproduce your issue tomorrow. Are you using an already verified domain name? https://cloud.google.com/run/docs/mapping-custom-domains#add-verified Read the note about `When a user verifies a domain, that domain is only verified to that user's account`. Are you using a service account or user credentials to authorize Terraform? Is Google Cloud DNS correctly set up with your domain's registrar? – John Hanley Jun 20 '23 at 04:56
  • To make sure that Google Cloud DNS is correctly set up, go through the steps to manually create a custom domain mapping using this documentation: https://cloud.google.com/run/docs/mapping-custom-domains – John Hanley Jun 20 '23 at 05:00
  • Yes, I did register the Google Nameservers within NameCheap. To authenticate Terraform, I am using a service account but I gave it the "Owner" rights, so it has full permission to "administer" the domain. The domain is verified successfully, I followed the instructions given by Google to add the TXT record and complete the verification. – Rodgers Ategyeka Jun 20 '23 at 05:42
  • Having `owner` does not mean that the service account has a verified domain. Please reread the links I provided. Did you also follow the link to create a custom domain mapping and verified that the URL works? Please read my comments in detail and do not skip or assume anything. Being accurate is very important to debug this problem. – John Hanley Jun 20 '23 at 05:49
  • I did follow the step, trying to create the domain mapping manually. The challenge is, at some point they say the mapping will take effect after DNS records are configured at the domain registrar. Yet, I don't want to register my records within NameCheap (my domain registrar). Instead, I want to use the (should be) auto-created DNS zone and records within Google Cloud DNS. – Rodgers Ategyeka Jun 20 '23 at 05:50
  • The reason I'm confident that the verification is not an issue for the service account, I had some issue that was throwing an error about the same, which I fixed by adding the service account to the list of "Admins" of the verified domain name. – Rodgers Ategyeka Jun 20 '23 at 06:08
  • Thanks for the intention to reproduce, the more information I would like to give is that you need to first add some permissions manually to your service account by running the following commands: – Rodgers Ategyeka Jun 20 '23 at 06:17
  • Please put details like that in your post and not as comments. – John Hanley Jun 20 '23 at 06:27
  • I do not understand your comments about DNS. Did you follow the steps to manually set up the custom domain mapping? If that does not work, there is no magic that anyone can do with Terraform to make that work. The registrar and DNS server settings must be correct first. The verified domain settings must be correct as well. – John Hanley Jun 20 '23 at 15:57
  • Post the output of this command in your question: `gcloud domains list-user-verified --impersonate-service-account=REPLACE_WITH_SERVICE_ACCOUNT_EMAIL_ADDRESS`. – John Hanley Jun 20 '23 at 18:04

0 Answers0