Deploy Google Cloud Endpoints for Cloud Run with Terraform

September 16, 2020
google cloud platform terraform endpoints cloud run

Introduction

Today I will demonstrate how to deploy Google Cloud Endpoints for Cloud Run using Terraform. This is a “getting started” style tutorial. We are going to bypasses Google’s recommendation of building an ESP image on every Endpoints configuration change and break the circular dependency between Endpoints configuration resource and ESP Cloud Run resource.

Problems

ESP image build

Google recommends to build a new ESP image on every Endpoints configuration change.

In other words if you want to manage your OpenAPI configuration and ESP deployment from the same terraform project it is not possible. You need to split configuration management from ESP image build. You can find some good suggestions here.

Solution

For purposes of a prototype or a small project we can work around that using the unofficial ENDPOINTS_SERVICE_NAME environment variable.

From the ESPv2 source code:

Note this deployment mode is not officially supported.

But it is used by the project internally e.g. for the end to end tests.

Circular dependency

We get circular dependency between Endpoints configuration and Cloud Run resource because CLOUD_RUN_HOSTNAME. It represents the hostname portion of the URL that Cloud Run creates during deployment. I am using the same name from the official tutorial.

google_endpoints_service requires CLOUD_RUN_HOSTNAME to be used as host in openapi.json before creating a configuration. CLOUD_RUN_HOSTNAME is known only after google_cloud_run_service deployment. google_cloud_run_service requires google_endpoints_service configuration.

Solution

To solve that circular dependency we can use the cloud.goog domain instead CLOUD_RUN_HOSTNAME. It is not mentioned in the main Endpoints tutorial but explained here.

Working example

Now we can use both google_endpoints_service and google_cloud_run_service in one terraform file and create and destroy both resources on demand e.g. in your continuous integration and deployment pipeline.

Just simple terraform apply.

Full terraform configuration below. You can test it with:

$ terraform plan
$ terraform apply

Prerequisites:

variables.tf

variable "project_id" {
  default = "<your-project-id>"
}
variable "project_number" {
  default = "<your-project-number>"
}
variable "suffix" {
  default = "<your-random-suffix>"
}
variable "region" {
  default = "us-central1"
}

openapi.json

{
  "swagger": "2.0",
  "info": {
    "title": "Service API",
    "version": "0.0.1",
    "description": "Service API"
  },
  "basePath": "/v1",
  "host": "${endpoints_host}",
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "schemes": [
    "https"
  ],
  "paths": {
  }
}

endpoints.tf

##############################################################################
#  Cloud Endpoints
##############################################################################

locals {
  endpoints_host = "api-${var.suffix}.endpoints.${var.project_id}.cloud.goog"
}

# Create/update endpoints configuration based on OpenAPI
resource "google_endpoints_service" "api" {
  project = var.project_id
  service_name = local.endpoints_host
  openapi_config = templatefile("openapi.json", {
    project_id = var.project_id
    endpoints_host = local.endpoints_host
  })
}

# Enable service
resource "google_project_service" "api-project-service" {
  service = google_endpoints_service.api.service_name
  project = var.project_id
  depends_on = [google_endpoints_service.api]
}


# Create/update Cloud Run service
resource "google_cloud_run_service" "endpoints-cloud-run" {
  name     = "endpoints-${var.suffix}"
  location = var.region
  project = var.project_id
  template {
    spec {
      containers {
        image = "gcr.io/endpoints-release/endpoints-runtime-serverless:2"
        env {
          name  = "ENDPOINTS_SERVICE_NAME"
          value = local.endpoints_host
        }
      }
    }
  }
  depends_on = [google_endpoints_service.api]
}

# Create public access
data "google_iam_policy" "noauth" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers",
    ]
  }
}

# Enable public access on endpoints service
resource "google_cloud_run_service_iam_policy" "noauth-endpoints" {
  location    = google_cloud_run_service.endpoints-cloud-run.location
  project     = google_cloud_run_service.endpoints-cloud-run.project
  service     = google_cloud_run_service.endpoints-cloud-run.name
  policy_data = data.google_iam_policy.noauth.policy_data
}

outputs.tf

output "endpoints-url" {
  value = google_cloud_run_service.endpoints-cloud-run.status[0].url
  description = "API endpoint URL"
}

Comments

Text search in Firebase Firestore - Part 1

March 16, 2021
google cloud platform cloud endpoints cloud run algolia cloud functions python terraform docker-compose