A detailed guide on deploying a web application on Google Cloud Run

Most of these steps are applicable to a web application written in other languages too and should help you to deploy your own app.

We love Google Cloud Run. We deployed the “Checklist” app on Google Cloud Run. Here’s a list of steps that we followed to deploy this Rails app on Google Cloud Run.

Assumptions

  • We assume that your application is ready to be deployed on the google cloud run.
  • We also assume that you have signed up for your Google Cloud account and have set up the Google cloud SDK

The GCP services used

Can you use other services too? But for the sake of this application below are the services used.

Let’s do the deployment now.

Get gcloud setup

$ PROJECT_ID=<<project_id>>
$ gcloud auth login <<account_name>>
$ gcloud config set project $PROJECT_ID
$ gcloud config set run/region us-central1

Enabled API’s

$ gcloud services enable run.googleapis.com # Cloud Run API
$ gcloud services enable sqladmin.googleapis.com # Cloud SQL API
$ gcloud services enable cloudkms.googleapis.com # Cloud KMS API

Rails Master Key (Rails specific, skip if you using other languages)

EDITOR="atom --wait" bin/rails credentials:edit

Set up SQL database (skip if you already have a database OR if you want to set it up using console.cloud.gcloud.com)

Get a small Cloud SQL instance

$ gcloud sql instances create cloudanix-checklist-production --tier=db-f1-micro --region=us-central1 --assign-ip

Protect database root account

$ gcloud sql users set-password root --host % --instance cloudanix-checklist-production --password your_root_db_password

Create a new database account for Rails

$ gcloud sql users create prod_db_user --instance cloudanix-checklist-production --host % --password your_prod_db_password

After the above is done, see if you got your instance all setup.

$ gcloud sql instances list

Update your connection details in your configuration file

Even if the syntax below is Rails specific, your app will also have some configuration files that you need to update. For Rails, we do this in the database.yml file.

Please note the following:

As you can notice, we are not storing the “database password” which you set above in the configuration file. It’s being read from the environment variable which we will control via KMS as we proceed.

production:
<<: *default
database: cloudanix_checklist_production
username: cloudanix_checklist_dbuser
password: <%= ENV[‘DATABASE_PASSWORD’] %>
socket: “/cloudsql/project_id:us-central1:cloudanix-web-pg”

Service account to run the application

This account will be used to run the CloudRun. Access will be given to other resources (e.g. if you are storing documents, then the bucket can be given access on this service account)

$ gcloud iam service-accounts create cloudanix-checklist-srvacc --display-name “Service Account for Cloudanix Checklist”

You will see an output like below

Created service account [cloudanix-checklist-srvacc].

Giving access to this service account on resources and getting the service account key

Get the name of the account in a variable for later use

$ SRV_ACCOUNT=cloudanix-checklist-srvacc@$PROJECT_ID.iam.gserviceaccount.com

Grant client role on CloudSql

$ gcloud projects add-iam-policy-binding $PROJECT_ID
--member serviceAccount:$SRV_ACCOUNT --role roles/cloudsql.client

As you can see we are giving appropriate access to this service account on the resources required. If you are using additional resources, you can extend the permissions accordingly.

The output should look something like this

Updated IAM policy for project [cloudanix-app].<br>bindings:<br><<snip>>

Create a key file for this service account

$ gcloud iam service-accounts keys create ./config/cloudanix_checklist_srvacc.key --iam-account cloudanix-checklist-srvacc@$PROJECT_ID.iam.gserviceaccount.com

Your output should be like below

created key [6bc27e420377c3124e3172cf2b76b89d4axxxxx]of type [json] as [./config/cloudanix_checklist_srvacc.key]for[cloudanix-checklist-srvacc@cloudanix-app.iam.gserviceaccount.com]

Let’s now store our secrets (the json file above, the master key, database password) more secret and securely! Time to KMS.

Store the keys and master them to KMS

Create key ring

$ gcloud kms keyrings create cloudanix_checklist_ring --location=us-central1

Encrypt the credentials of the service account

$ gcloud kms keys create cloudanix_checklist_srvacc_key --location us-central1
--keyring cloudanix_checklist_ring --purpose encryption

$ gcloud kms encrypt --location us-central1 --keyring cloudanix_checklist_ring --key cloudanix_checklist_srvacc_key --plaintext-file ./config/cloudanix_checklist_srvacc.key
--ciphertext-file ./config/cloudanix_checklist_srvacc.key.enc

We should also encrypt and store the Rails master key file. For other languages, this step could be optional, unless you too got a master key which you want to encrypt with KMS.

$ gcloud kms keys create cloudanix_checklist_web_key --location us-central1 --keyring cloudanix_checklist_ring --purpose encryption

$ gcloud kms encrypt --location us-central1 --keyring cloudanix_checklist_ring --key cloudanix_checklist_web_key --plaintext-file ./config/master.key --ciphertext-file ./config/master.key.enc

Database password setup for google cloud run

$ gcloud kms keys create db_password_key --location=us-central1 --keyring cloudanix_checklist_ring --purpose encryption

replace the password of your database inside the quotes. The output of this, make sure you copy and keep it in a textfile. We will need it later.

$ echo -n "<>" | gcloud kms encrypt --location us-central1 --keyring cloudanix_checklist_ring --key db_password_key --plaintext-file - --ciphertext-file -| base64

The 2nd line from above will give you a base64 encoded string. Copy it and you shall need it for your cloudbuild.yml file.

Using Google CloudBuild

We will use Google CloudBuild to get our master branch running into google cloud run.

# get the service account for CloudBuild which you can fine in IAMCB_SRV_ACCOUNT=xxx...xxx@cloudbuild.gserviceaccount.com# Grant Cloud Build the right to decrypt Rails master key$ gcloud kms keys add-iam-policy-binding cloudanix_checklist_web_key --location=us-central1 --keyring=cloudanix_checklist_ring --member=serviceAccount:$CB_SRV_ACCOUNT --role=roles/cloudkms.cryptoKeyDecrypter# Grant Cloud Build the right to decrypt Rails the production database password$ gcloud kms keys add-iam-policy-binding db_password_key --location=us-central1 --keyring=cloudanix_checklist_ring --member=serviceAccount:$CB_SRV_ACCOUNT --role=roles/cloudkms.cryptoKeyDecrypter# Grant Cloud Build the right to decrypt the cloud service account credentials$ gcloud kms keys add-iam-policy-binding cloudanix_checklist_srvacc_key --location=us-central1 --keyring=cloudanix_checklist_ring --member=serviceAccount:$CB_SRV_ACCOUNT --role=roles/cloudkms.cryptoKeyDecrypter

Creating cloudbuild.yaml file in your code (root folder)

steps: # Decrypt Rails Master key file - name: gcr.io/cloud-builders/gcloud args: ["kms", "decrypt", "--ciphertext-file=./config/master.key.enc", "--plaintext-file=./config/master.key", "--location=us-central1","--keyring=cloudanix_checklist_ring", "--key=cloudanix_checklist_web_key"] # Decrypt Cloudanix Checklist Service account credentials - name: gcr.io/cloud-builders/gcloud args: ["kms", "decrypt", "--ciphertext-file=./config/cloudanix_checklist_srvacc.key.enc", "--plaintext-file=./config/cloudanix_checklist_srvacc.key", "--location=us-central1","--keyring=cloudanix_checklist_ring", "--key=cloudanix_checklist_srvacc_key"] # Build image with tag 'latest' and pass decrypted Rails DB password as argument - name: 'gcr.io/cloud-builders/docker' args: ['build', '--tag', 'gcr.io/$PROJECT_ID/cloudanix_checklist:latest', '--build-arg', 'DB_PWD', '.'] secretEnv: ['DB_PWD'] # Push new image to Google Container Registry - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/cloudanix_checklist:latest'] # Deploy the new image to Cloud run instance - name: 'gcr.io/cloud-builders/gcloud' args: ['beta', 'run', 'deploy', 'cloudanix-checklist', '--image', 'gcr.io/cloudanix-app/cloudanix_checklist', '--region', 'us-central1','--set-cloudsql-instances','cloudanix-app:us-central1:cloudanix-web-pg','--platform','managed', '--allow-unauthenticated'] secrets: - kmsKeyName: projects/cloudanix-app/locations/us-central1/keyRings/cloudanix_checklist_ring/cryptoKeys/db_password_key secretEnv: DB_PWD: "<<your encrypted password from step 10>>" timeout: 1800s

Creating the Docker file (part of this file will vary based on your language and runtime requirements)

# Leverage the official Ruby image from Docker Hub# https://hub.docker.com/_/rubyFROM ruby:2.6# Install recent versions of nodejs (10.x) and yarn pkg manager# Needed to properly pre-compile Rails assetsRUN (curl -sL https://deb.nodesource.com/setup_10.x | bash -) && apt-get update && apt-get install -y nodejsRUN (curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -) && \    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \    apt-get update && apt-get install -y yarn# Install MySQL client (needed for the connection to Google CloudSQL instance)RUN apt-get install -y postgresql-client# Install production dependencies (Gems installation in# local vendor directory)WORKDIR /usr/src/appCOPY Gemfile Gemfile.lock ./ENV BUNDLE_FROZEN=trueRUN bundle install# Copy application code to the container image.# Note: files listed in .gitignore are not copied# (e.g.secret files)COPY . .# Pre-compile Rails assets (master key needed)RUN RAILS_ENV=production bundle exec rake assets:precompile# Set Google App Credentials environment variable with Service AccountENV GOOGLE_APPLICATION_CREDENTIALS=/usr/src/app/config/cloudanix_checklist_srvacc.key# Setup Rails DB password passed on docker command line (see Cloud Build file)ARG DB_PWDENV DATABASE_PASSWORD=${DB_PWD}# For now we don't have a Nginx/Apache frontend so tell# the Puma HTTP server to serve static content# (e.g. CSS and Javascript files)ENV RAILS_SERVE_STATIC_FILES=true# Redirect Rails log to STDOUT for Cloud Run to captureENV RAILS_LOG_TO_STDOUT=true# Designate the initial sript to run on container startupRUN chmod +x /usr/src/app/entrypoint.shENTRYPOINT ["/usr/src/app/entrypoint.sh"]

Creating the entrypoint.sh file for google cloud run

#!/usr/bin/env bashcd /usr/src/app# Create the Rails production DB on first runRAILS_ENV=production bundle exec rake db:create# Make sure we are using the most up to date# database schemaRAILS_ENV=production bundle exec rake db:migrate# Do some protective cleanup> log/production.logrm -f tmp/pids/server.pid# Run the web service on container startup# $PORT is provided as an environment variable by Cloud Runbundle exec rails server -e production -b 0.0.0.0 -p $PORT

Submit the build and deploy the application (or you can set up a trigger to start the build when a commit happens on a branch)

$ gcloud builds submit --config cloudbuild.yaml

If you go back to your cloudbuild.yaml file, you will notice this comment – # Deploy the new image to Cloud run an instance. Below this comment is the instructions that will deploy the newly created build onto the Cloud Run instance.

Done

Your application is now running on Google Cloud Run! Congratulations

Common Errors

1. Step #2: Your Ruby version is 2.6.5, but your Gemfile specified 2.6.3

Solution: Ensure that your Docker file and Gemfile do not conflict for Ruby versions

Know more about

Subscribe to Cloudanix Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe