14 Mar 2021 | 6 min read | 20.5k views

Deploying to Google Cloud Run with Terraform

⚠️ This post has been published a while ago. Some of the information might be outdated.

Cloud Run is a serverless platform from Google Cloud to deploy and run containers. It’s fully managed, autoscallable, and has a generous free tier. Cloud Run can be used to serve Restful web APIs, WebSocket applications, or microservices connected by gRPC. It also integrates well with other Google Cloud solutions such as Cloud Tasks, Cloud Scheduler, and Pub/Sub.

Terraform is a popular open-source tool for running infrastructure as code. Terraform lets you manage and deploy infrastructure from multiple providers, one of them being Google Cloud. If this is your first time reading about Terraform, you might wanna check this introduction first.

In this article, you will see how to deploy a Cloud Run service to Google Cloud using Terraform. All the infrastructure will be written using HCL, the native syntax for Terraform’s language. By the end of the tutorial, you should have a service up and running on Cloud Run and a URL to access it.

Prerequisites

To follow this tutorial you will need:

  • Terraform CLI. I recommend using the latest version, currently v0.14. Instructions to download and install Terraform can be found here.
  • Google Cloud SDK. The most recent version should also work well for this tutorial. Installation instructions here.
  • A Google Cloud account. If you don’t have one, create it here.

Initial setup

Start by authenticating the SDK to Google Cloud:

# Authenticate to Google Cloud
gcloud auth application-default login

Follow the web flow to obtain the access credentials. By the end of this step, you will be able to execute commands on the SDK similar to a service account.

Create a new project where your Cloud Run service will be deployed. Replace PROJECT_ID and PROJECT_NAME with the desired values:

gcloud projects create "PROJECT_ID" --name="PROJECT_NAME"

Create a folder with a HCL file inside:

mkdir my-service && cd my-service
 
touch main.tf

For simplicity, all the Terraform code in the next steps will be added to main.tf.

Creating your first service

Begin by adding the requirements for Terraform and the Google provider on main.tf:

# main.tf
 
terraform {
  required_version = ">= 0.14"
 
  required_providers {
    # Cloud Run support was added on 3.3.0
    google = ">= 3.3"
  }
}

This will require the Terraform version to be the latest and the Google provider to be at least on version 3.3 - when Cloud Run support was added.

Now add the Google provider configuration. Replace PROJECT_ID with the value from the previous step:

# main.tf
 
provider "google" {
  # Replace `PROJECT_ID` with your project
  project = "PROJECT_ID"
}

The Cloud Run API doesn’t come enabled on projects by default. Add the following resource to enable it:

# main.tf
 
# Enables the Cloud Run API
resource "google_project_service" "run_api" {
  service = "run.googleapis.com"
 
  disable_on_destroy = true
}

Now create the Cloud Run service in the us-central1 region:

# main.tf
 
# Create the Cloud Run service
resource "google_cloud_run_service" "run_service" {
  name = "app"
  location = "us-central1"
 
  template {
    spec {
      containers {
        image = "gcr.io/google-samples/hello-app:1.0"
      }
    }
  }
 
  traffic {
    percent         = 100
    latest_revision = true
  }
 
  # Waits for the Cloud Run API to be enabled
  depends_on = [google_project_service.run_api]
}

Let’s stop for a while and check what the code above is doing:

  • name: the name of your service. It will be displayed in the public URL.
  • location: the region where your service will run. See all the options here.
  • image: The Docker image that will be used to create the container. Cloud Run has direct support for images from the Container Registry and Artifact Registry.
  • traffic: controls the traffic for this revision. The percent property indicates how much traffic will be redirected to this revision. latest_revision specifies that this traffic configuration needs to be used for the latest revision.
  • depends_on: waits for a resource to be ready, in this case, the Cloud Run API.

Invoking the service

By default, Cloud Run services are private and secured by IAM. To access them, you would need valid credentials with at least the Cloud Run Invoker permission set.

Let’s change that and make the service publicly available through an HTTP endpoint.

There are other ways than HTTP requests to trigger a service. For example, they can be accessed by gRPC requests, WebSockets, and other Google Cloud products like Cloud Scheduler.

Add the following code on main.tf to expose your service:

# main.tf
 
# Allow unauthenticated users to invoke the service
resource "google_cloud_run_service_iam_member" "run_all_users" {
  service  = google_cloud_run_service.run_service.name
  location = google_cloud_run_service.run_service.location
  role     = "roles/run.invoker"
  member   = "allUsers"
}

The resource above is adding the permission to invoke the service to anyone on the internet. The allUsers identifier is a special value that represents authenticated and unauthenticated users.

To display the service URL in the Terraform command output, add this output to the configuration:

# main.tf
 
# Display the service URL
output "service_url" {
  value = google_cloud_run_service.run_service.status[0].url
}

Deploying the infrastructure

At this point, you have all it takes to deploy the infrastructure to Google Cloud using Terraform.

Start by initializing the configuration. Run the following command in your terminal:

# Initialize Terraform configuration
terraform init

Run terraform plan to verify the changes that will be applied:

# Plan the changes
terraform plan

If everything is correct, you will see that 3 resources will be created and the service URL will be displayed.

Plan: 3 to add, 0 to change, 0 to destroy.
 
Changes to Outputs:
  + service_url = (known after apply)

Run terraform apply to apply all the changes:

# Apply all the changes
terraform apply

If everything goes well, you will see this at the end of the output:

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
 
Outputs:
 
service_url = https://app-cvmew7554a-uc.a.run.app

You can check if the service is running using curl:

curl https://app-cvmew7554a-uc.a.run.app
 
Hello, world!
Version: 1.0.0
Hostname: localhost

Updating the service

Terraform can be used not just to push your initial infrastructure to Cloud Run, but also to update it.

Cloud Run works with revisions. When a configuration is changed or a new image is added, a new revision is created as a result. You can then redirect all the traffic to the new revision and start serving your updated application.

To update your service, simply change the value in the image property and pass it a new image:

# main.tf
 
resource "google_cloud_run_service" "run_service" {
  name = "app"
 
  # ...
 
  template {
    spec {
      containers {
        # Change `hello-app:1.0` to `hello-app:2.0` 👇
        image = "gcr.io/google-samples/hello-app:2.0"
      }
    }
  }
 
  # ...
}

With this, Terraform will create a new revision on Cloud Run. The previous revision is preserved, but because of the traffic options defined previously, it won’t recieve any traffic.

Run terraform apply to deploy the changes:

# Apply all the changes
terraform apply

You can check that the new image is live using curl:

curl https://app-cvmew7554a-uc.a.run.app
 
Hello, world!
Version: 2.0.0 # Now serving 2.0.0
Hostname: localhost

Cleaning up

To delete all resources created with Terraform, run the following command and confirm the prompt:

# Destroy all the infrastructure created by Terraform
terraform destroy

This will disable the Cloud Run API, delete the Cloud Run service and its permissions.

The project was created using the gcloud CLI tool, so you will need to delete it manually. For that, you can run:

# Remove the Google Cloud project
gcloud projects delete PROJECT_ID

—

Thanks for reading this far!