Safely and easily deploying new version of your web app
Canary Release is the technique that we use to “softly” deploy a new version of an application into Production. It consists of letting only a part of the audience get access to the new version of the app, while the rest still access the “old” version one. This is very useful when we want to be sure about stability in case of any changes which may be breaking, and have big side effects.
The point is: canary release has never been easy to be put into practice. Depending on the environment we have, it can take so long to be put in place that we often prefer to leave this away.
However, with Docker containers and Kubernetes orchestration it is quite friendly to do that. Here I am going to show how to do a small canary deployment of a new version of a dummy website using this tool.
Those are the versions of our website:
On the left, musicstore v1; on the right, the new version v2
In order to use Kubernetes locally, we are going to install minikube and kubectl. We are going also need to install Docker.
With everything installed, we launch minikube in order to have Kubernetes up and running:
minikube start
As a response, the terminal will display:
Starting local Kubernetes cluster...Starting VM...SSH-ing files into VM...Setting up certs...Starting cluster components...Connecting to cluster...Setting up kubeconfig...Kubectl is now configured to use the cluster.
Second step: call this command in order to run Docker inside Minikube:
eval $(minikube docker-env)
For this app, the backend side will be an API developped in Go, querying against a Mongo DB. We are not going into details about this part, as it is not the goal here to show how to develop a REST API in Go.
To deploy the backend, clone the repository and build the docker image with the command below:
docker build -t api-musicstore .
Enter the docker container with the following command:
docker exec -t -i <mongo_container> /bin/bash
where <mongo_container> is the id of the container running against the mongo image. You can discover it after running
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3304ee6ab94e api-musicstore:v1 "/go/bin/musicstore" 3 minutes ago Up 4 minutes k8s_backend.72a7365d_api-musicstore-931708012-g4grg_musicstore_442ad9d4-123c-11e7-ae23-0800275d1cac_9f1e978d**75b2eca8cf1f mongo ** "docker-entrypoint..." 4 minutes ago Up 4 minutes k8s_db.b9d32dcd_api-musicstore-931708012-g4grg_musicstore_442ad9d4-123c-11e7-ae23-0800275d1cac_67653438
In this case, <mongo_container> is 75b2eca8cf1f. So we run:
docker exec -t -i 75b2eca8cf1f /bin/bash
Inside the container, we open mongo command-line tool with the following command:
mongo
Once we are inside mongo, copy-paste the following contents and hit Enter:
That’s it! Now we have some seed data in our DB, let’s now deploy our REST API that will communicate with the website!
Normally, I like to organize my deployments and services inside namespaces. Here, I create a namespace musicstore to deploy my Music Store dummy application.
To create this namespace, copy this file locally and run the following command:
kubectl create -f namespace.yaml
After, copy these files and run the following command in order to create the API/DB deployment in Kubernetes:
kubectl create -f deployment.json
Note: configuration files for Kubernetes can be created in both structures JSON and YAML.
In order to create replica controllers, run this command:
kubectl create -f rc.yaml
Finally, we can create the service, and our backend is ready to run:
kubectl create -f service.json
We can see that our API is well deployed consulting the URL and opening it in a web browser:
minikube service api-musicstore --url --namespace=musicstorehttp://<url>:32495
Don’t forget to specify the namespace musicstore, otherwise kubectl is going to search for the service in the default namespace and it is not going to find it!
Now we are going to build the image for the version 1 of the website. In order to do this, you are going to need to clone this repository and run the following command:
docker build -t musicstore:v1 .
With our container built, we can proceed with the deployment. First, we create our service:
kubectl -f service.yaml
This will create the musicstore-website service. We are going to create the deployments, so that we can deploy our code to be served by this service. Let’s get its URL and keep it for later testing:
minikube service musicstore-website --url --namespace=musicstorehttp://<url>:32000
Let’s take a look at the deployment config file: we are going to ask Kubernetes to create 3 replicas of this website in order to handle traffic and load balancing. All of the replicas will be, in a first moment, pointing to the image corresponding to the image of version v1 of the musicstore-website:
Let’s deploy it:
kubectl create -f deployment-without-canary.json
Now, if you open the website you are going to see its version 1:
Website after first deployment
First step in order to deploy version 2 is to create a docker image for it, just like we did for version 1.
So, copy the contents from here and run the following command in a terminal:
docker build -t musicstore:v2 .
As seen before, we created 3 replicas of our front-end service. As explained before, the idea of canary deployment is getting a part of the traffic and redirect it to a new version.
We are going to achieve this pointing one of the replicas of our service to the new version of the Music Store. Like this, most part of the traffic will still be redirected to version 1.
To achieve this, we run the following commands:
kubectl apply deployment.jsonkubectl apply deployment-canary.json
With this command, we tell Kubernetes to apply this deployment to our environment. If we take a look in the file, we see that the only thing it changes regarding the deployment of version 1 is the label “canary” (the other version is considered “stable”), and the number of replicas (1 to the new version, and 2 serving the old one). And of course, it points to the Docker image musicstore:v2:
And here is how the deployment of version 1 looks like, with 2 replicas pointing to the stable version:
Now, we can see that some of the traffic are already seeing the version 2 of our Music Store dummy app:
Musicstore version 2
Now we feel safe and sound to go for the new version. With the canary delpoyment, this is kind of easy: we just need to set all of the replicas to point to a stable version running the image of the version 2 of the Music Store:
kubectl apply deployment-new-version.json
This is how our deployment-new-version.json will look like:
Very good! Now, everybody will be able to see the new version of the Music Store web site!
All the files used in this tutorial can be found here.
A great example of Canary Release can be found here as well.