A Complete Guide to Deploying a Containerized Application Using Managed Instance Groups (MIGs) in Google Cloud (GCP) with Continuous Integration (CICD) — Part 3

Image for post
Image for post
Photo by Mike Gieson from FreeImages

In the third part of this guide, we will walk through setting up a CICD solution for Docker containerized application such as a Ruby on Rails backend.

Scenario

We want to deploy a web application consisting of one or more frontends using a client side framework such as ReactJS which connects to one (or more) backends that have been containerized with Docker. Our backend will connect to a CloudSQL instance for storage and all environment variables will be encrypted with GCP’s Key Management Service (KMS). GIT commits to a specific branch on GitHub will trigger a build and deploy of the application. A load balancer will direct traffic and serve as a proxy for HTTPS using a managed certificate.

Pre-requisites

Setup the environment proposed in part 1 of this guide or equivalent. Basic understanding of Docker and CloudBuild triggers as shown in part 2.

Step 1: Dockerize the App

Below, we have a Dockerfile (placed in the root of the repository) that creates docker images containing our application’s source code. Note that this is a multi-stage build with 2 distinct end images. We will use 1 image for testing and the other for deployment since each case has different dependencies (this may not be true for your application).

Note: We define an ENTRYPOINT for our production image which performs a database migration prior to starting our web server. This is one of many ways to perform migrations and strategies for rolling back need to be considered when choosing how to migrate.

Step 2: Create Docker-Compose for Testing

Docker Compose is a useful tool for running multiple containers and allowing them to interface with each other. We use this capability to run our integration tests (in the example below we only connect to a DB but options are limitless).

The Compose below spins up a MySQL database container called db with a default database my-db and no root password (please don’t do this with a user facing DB!). It then creates a second container called app running a test version of our application. Note the ./scripts/wait-for-mysql.sh (shown after the docker-compose file) preceding the test command. This is required (as discussed here) in order to ensure that the database is ready to accept connections prior to connecting to it.

The wait-for-mysql.sh script is shown below and is based on the Compose documentation script.

Step 3: Create CloudBuild File for Testing

Note: For more information on CloudBuild see part 2 of the guide.

The deployment pipeline described in this part of the guide will require two build files. One to create and deploy a production grade image and the other to create a test image and run tests on it. The latter is shown in this step.

We create a file named cloudbuild-tests.yml(you can rename it as desired) as shown below. The build it describes consists of two steps:

  1. Build the Docker image using Kaniko. We use Kaniko in order to cache the Docker build steps which speeds up future runs of the build. The image is automatically pushed to the specified destination when build is complete along with all cache artifacts.
  2. We run a docker-compose which is responsible for running our tests. The exit code from our docker-compose is used to indicate if tests passed or failed.

Step 4: Create CloudBuild Trigger for Testing

Whenever a developer pushes to a branch we want our tests to automatically run. To this end, we create a trigger such as the one below (for more information on creating a trigger see part 2):

The trigger above will run whenever there is a push to any branch except staging or production. We will have a separate script (or multiple separate scripts) that will run for those branches.

Step 5: Create CloudBuild File for Deployment

The CloudBuild script below consists of 5 steps:

  1. We build the production image and push it to our container registry.
  2. We decrypt our secrets.
  3. We create a new instance template using our previously built image and decrypted secrets. Note: This manner of secret management may not be appropriate for your organization. Adjust accordingly.
  4. If your organization does deploys often, you may need to ensure that the cluster is stable (the last update has already been applied) before attempting to perform a new update. Else, the new update will fail. This step waits for the cluster to be stable.
  5. We update our managed instance group with our new instance template. Note the --max-unavailable flag. This is recommended if you have a small clusters (less servers than number of availability zones in your region) in order to not have disruptions during the update.

Note: We increased the timeout (from the default 10 minutes) to accommodate the time required to build our image if cache has expired. Adjust accordingly for your application.

Step 6: Create CloudBuild Trigger for Deployment

We want to configure a trigger to automatically deploy our application to the staging environment whenever code is merged into the staging branch. The trigger is shown below:

Note: A similar trigger can be created for production. However, I would recommend placing production on a separate project entirely (The $PROJECT_ID in our scripts will handle that as long as the trigger is created on the separate project as well). With separate projects better separation between staging and production is possible. It will also simplify the permission management for developers that have access to either system.

Conclusion

At this point, we have all the infrastructure setup (part 1) and are able to deploy frontend code automatically to our Cloud Storage bucket(s) (part 2). Now we are also able to perform testing and deployment of our backends.

Written by

A curious minded engineer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store