This is the second part in my series on building and deploying a Rails app using Docker containers and Kubernetes. Find the first part here.
Sharing the App with the World
In part one I showed how to create Docker containers for a Rails app and run those containers locally. Running locally is fun but my end goal is to share my brilliant ToDo app with the world. To do this I need to deploy to a real server. Because I used Docker to containerize my app I’m going to use Kubernetes to deploy it. Kubernetes will take care of some of the low level monitoring and management tasks so that I can focus more on application development.
Kubernetes is an open source orchestration system for Docker containers. You write a few simple files describing what you want your system to look like and provide a few VMs for running your app. After that Kubernetes takes over monitoring the system and maintaining the desired state.
The most basic unit of Kubernetes cluster is a pod. A pod is a collection of containers that “travel together” and are deployed to the same host. For the ToDo app I have two pods: one for the Rails server and one for the DB. I could put these both together in the same pod but I want the ability to scale each component separately so they’ll be different pods.
In order for my pods to talk to each other I need to set up a service. This is basically a named load balancer that coordinates traffic to a collection of pods. For the ToDo app I need a DB service that allows the Rails servers to talk to the DB. I also need a service so the Rails servers can talk to the outside world.
Finally, I want to have more than one Rails server. Initially, I’ll set up two but I’d like the ability to scale that number up or down depending on predicted load. With Kubernetes you do this with a replication controller.
Deploying with Kubernetes on Google Container Engine
I’m using Google Container Engine to deploy my Rails application. You don’t have to use Container Engine to use Kubernetes. Kubernetes can be used on your own hardware or on VMs but for a quick demo it is easiest to use the hosted Kubernetes provided by Google Container Engine.
Project Set Up
First, I need to create a project on http://console.developers.google.com. Once the project is created I also have to enable the Container Engine API, Compute Engine API, and Cloud Storage API.
To work with Container Engine I need to have the gcloud utility installed on my dev machine. (Instructions for installing gcloud are here). As usual I need to set the project and compute zone in gcloud.
us-central1-f because it is close to where I am. If you are in Europe use
europe-west1-[bcd]; in Asia
Finally, I need to create a container cluster with gcloud. I could do this from the UI as well but I prefer the command line.
get-credentials command writes authentication information to a file so that the Kubernetes command line tool can access it.<
Building and Sharing the Container Image
To deploy my ToDo app to a remote server I need to build my container image and share it in some place I can access from my server. I could use Docker Hub but for projects where you need to restrict access to your image I prefer to use Google Container Registry. To build the Docker image do this:
Once the image is built I upload it to Container Registry by doing the following:
Important note: If a project id has dashes in it you need to replace them with underscores for these two commands.
Deploying the Database
Just like with the Docker version of the ToDo app I start by setting up the database. Here’s the configuration for the database pod:
This yaml file has almost the exact same information that I supplied at the command line when running Docker locally (passwords and port mappings). To deploy the database pod I use
kubectl, which is installed along with
kubectl create means ‘from file’. Once this runs I check the status of my pods by doing:
Once this says “Running” for the db pod my database is up and running.
I want my Rails front end to be able to talk to the db pod so I need to set up a service. Setting up a service is really simple. Here’s the configuration file for the database service:
Deploying it is just like deploying the pod:
At this point my database is up and running and other pods in the Kubernetes cluster can talk to it. The next step is to set up the Rails front ends.
To deploy the Rails front end I need another pod. In this case I want to have two replicas so I use a replication controller to create and manage them.
This file contains a pod specification (under the nested spec key). It also contains information about how many replicas to run, and what labels should be applied to the controller and the pods. If one of the replicas fails Kubernetes will start another within seconds.
In the pod specification I’m passing the credentials for the Postgres database via the env key. Using environment variables isn’t a good idea since I have to put the secrets in source. In a future post I’ll show how to use Kubernetes’ secrets functionality instead. For now I start up the replication controller and pods using
kubectl create -f again
kubectl get to see the status of both the controller and the pods:
Since I have two replicas running my Rails code I need a Kubernetes service to load balance between them and to allow access from the outside world. Here’s the service manifest:
type: LoadBalancer line is important here. With some managed Kubernetes providers this will automatically create an external load balancer for your application. Google Container Engine supports this feature. To create the service I use the same commands I used to create the database service.
This should show that the web service is up and running.
Accessing The Running Site
At this point all the servers are up and running on Google Cloud Platform but I can’t access them because I haven’t opened up the firewall yet. To open up the firewall I need to figure out the name pattern for my Kubernetes nodes. The easiest way to do this is to ask
This should return something that looks like this:
Copy everything in the name before the last dash. For the data above (my nodes) I need to copy gke-todo-5ccd2616-node. To create the firewall rule that allows traffic into the cluster I use a
gcloud compute command.
There’s a lot going on in that one line.
gcloud compute firewall-rules create says I’m creating a firewall rule for my project.
--allow=tcp:3000 allows tcp traffic over port 3000 (the default WEBrick port).
--target-tags=gke-node-XXXX-node says which vms should be included in the firewall rule. In this case it is the vms for our Kubernetes cluster. Finally the
todo on the end is a name for this firewall rule in case I want to reference it later.
To get the address for my app I need to run:
That produces output that looks like this:
When I navigate my browser to http://104.197.XXX.XXX:3000/tasks I get my ToDo list application.