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.
- Cloud Build: Cloud Build Builds container image of your application
- Container Registry: Container Registry Stores your application’s container image
- Cloud SQL: Cloud SQL, Your databases go here
- Cloud Key Management Service: Cloud key management service is for your application secrets and credentials
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
This step should create two files config/credentials.yml.enc and config/master.key.
- 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].
bindings:
<<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 [c[email protected]]
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 "<<your database password>>" | 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 IAM [email protected] # 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/_/ruby FROM ruby:2.6 # Install recent versions of nodejs (10.x) and yarn pkg manager # Needed to properly pre-compile Rails assets RUN (curl -sL https://deb.nodesource.com/setup_10.x | bash -) && apt-get update && apt-get install -y nodejs RUN (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/app COPY Gemfile Gemfile.lock ./ ENV BUNDLE_FROZEN=true RUN 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 Account ENV 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_PWD ENV 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 capture ENV RAILS_LOG_TO_STDOUT=true # Designate the initial sript to run on container startup RUN chmod +x /usr/src/app/entrypoint.sh ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
- Creating the entrypoint.sh file for google cloud run
#!/usr/bin/env bash cd /usr/src/app # Create the Rails production DB on first run RAILS_ENV=production bundle exec rake db:create # Make sure we are using the most up to date # database schema RAILS_ENV=production bundle exec rake db:migrate # Do some protective cleanup > log/production.log rm -f tmp/pids/server.pid # Run the web service on container startup # $PORT is provided as an environment variable by Cloud Run bundle 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
- Implementing IAM in Google Cloud Platform (GCP)
- CVE-2022–0185: What is it, how to identify, and it’s impact on Kubernetes Workloads
- List of Security and Operational Questions to Ask A SaaS Provider Before Signing Up