DEV Community

Miles Bardon
Miles Bardon

Posted on

Google Cloud Run and Functions with Terraform

What is Google Cloud Run?

Google Cloud Run, much like Google Cloud Functions, is a serverless solution for running code only when you need it, and being charged likewise. However, it has one key difference that makes it very flexible for developers: Containers

Cloud run allows you to build your Docker container, test it locally to your hearts content and push it to a private container repository in Google Cloud Storage. Once the Cloud Run service is created, you will be able to make HTTPS requests which invoke your container, running whatever you've deployed into it.

I chose this service as I needed to implement a Web Scraper for the YuGiOh Bot 3000. I had tried AWS Lambda and Google Functions, but neither had the ability to use Chrome and Chromedriver properly. Running this scraper in a Docker container, however, allowed me to test everything worked locally, and be confident that it would work anywhere else. It wouldn't cost the world either, as Cloud Run allows 180,000 vCPU-seconds and 360,000 GB-seconds for free (at the time of writing, this may have changed when you read this. Hello future person! See here for the up-to-date values).

The (TF) Plan

Whenever I deploy a cloud service, I want to be assured that everything can be automated. I've used Terraform a lot in the past, so decided to give it a go in deploying both my Cloud Function (which generates some text) and the Cloud Run service. As it's still in Beta, some of the work I've done may become outdated once Cloud Run and its associated Terraform provider reach standard availability.

Pushing the Docker container

The first step to deploying a Cloud Run service is to build, tag and push your Docker container. This can be achieved on the command line:

docker build -t gcr.io/${PROJECT}/${SERVICE_NAME} .
docker push gcr.io/${PROJECT}/${SERVICE_NAME}
export DIGEST=$(docker image ls --digests gcr.io/${PROJECT}/${SERVICE_NAME} --format "{{.Digest}}")

We'll use the DIGEST later to ensure new containers are deployed.

Note: If you are performing these commands in a CI/CD pipeline (such as Travis-CI) you'll need to authenticate Docker to allow it to push to your repo.

gcloud auth configure-docker

Setting up Terraform

To use Cloud Run in Terraform, you need to use the google-beta provider, as well as the standard google provider.

provider "google" {
  project = "project_name"
  region  = "us-east1"
  zone    = "us-east1-a"
}

provider "google-beta" {
  project = "project_name"
  region  = "us-east1"
  zone    = "us-east1-a"
}

It's also a good idea to store the Image repo and digest in variables for Terraform to use.

variable "image" {
  description = "Name of the docker image to deploy."
  default     = "gcr.io/project_name/service_name"
}

variable "digest" {
  description = "The docker image digest to deploy."
  default     = "latest"
}

When running Terraform from the command line, set the digest to the one you exported earlier. We will then concatenate the image and the digest, as the provider doesn't actually check if the image has changed. This may be fixed in a future update but for now this will do.

echo "Planning Terraform."
terraform plan \
    -var="digest=$DIGEST" \
    -out=output.tfplan

Creating the Cloud Run Service

At the very minimum, you need to provide the name, location, metadata and image location for the service.

resource "google_cloud_run_service" "service" {
  name     = var.name
  location = var.location
  provider = "google-beta"

  metadata {
    namespace = var.project
  }

  spec {
    containers {
      image = "${var.image}@${var.digest}"
    }
  }
}

I found it useful to also set the memory limit, as my container uses Chromium and needs a fair bit of RAM to run effectively:

...
spec {
    containers {
      image = "${var.image}@${var.digest}"
      resources {
        limits = {
          cpu    = "1000m"
          memory = "1024Mi"
        }
      }
    }
  }
...

And thats it; Your Cloud Run service is deployed!

Invoking the Cloud Run Service securely

Security is very important in Cloud computing. You don't want everyone and their dog calling your private function, costing you money and possibly having undesired effects. By default, Cloud Run Services are private and can only be invoked by authorised users.

If you want to invoke this manually, you'll need to create an IAM user and give them the Cloud Run Invoker Role. However, if you want to have this invoked by another Cloud Function (as I did), you'll already have a default service account created, usually with the name [ACCOUNT_NUMBER]-compute@developer.gserviceaccount.com. Giving this the same role will allow any of your Cloud Functions to authorise itself to call your HTTPS service.

Authorising a Python App

I used python for my Cloud Functions, so here is the code needed to authorise it:

import requests

receiving_service_url = 'https://some-app-name.run.app'

# Set up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
metadata_server_token_url = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='

token_request_url = metadata_server_token_url + receiving_service_url
token_request_headers = {'Metadata-Flavor': 'Google'}

# Fetch the token
token_response = requests.get(token_request_url, headers=token_request_headers)
jwt = token_response.content.decode("utf-8")

# Provide the token in the request to the receiving service
receiving_service_headers = {'Authorization': f'bearer {jwt}'}
service_response = requests.get(receiving_service_url, headers=receiving_service_headers)

The final line is a standard requests GET request, which you can add params to if you so desire. More information about service-to-service requests can be found here.

Conclusion

This has been a whistlestop tour of deploying Cloud Run through automation. There wasn't much out there about this at the time of publication, so I decided to share my newfound knowledge. Please comment below if these steps don't work for you - there may be updates to the service as it evolves. Thanks for reading!

Discussion (1)

Collapse
royletron profile image
Darren

Hey Miles. Found this handy, but there is a little bug. If your image path falls back to the default 'latest' it will fail. Annoyingly 'tags' like 'latest' have to be separated with : not with @ which is used for sha's