Sinatra on Google Container Engine

This is a basic tutorial on containerizing a simple Sinatra app and deploying it with Google Container Engine. The code for this post is on GitHub at http://www.github.com/thagomizer/examples.

If you want to follow along you’ll need Ruby (version ~> 2.2) with bundler installed. I use Oh my gems to manage gemsets and rbenv to manage different versions of Ruby. You’ll also need the gcloud utility installed.

Creating A Sinatra App

This is going to be a very bare bones Sinatra application. No database. No views. It will have a single HTTP endpoint. Start with a basic Gemfile and then use bundler to install the gems.

#Gemfile
source "https://rubygems.org"
gem 'sinatra'
bundle install

Now for the absolute bare minimum Sinatra application:

# web.rb
require 'sinatra'

set :bind, '0.0.0.0'

get '/' do
  "hello world"
end

Test that everything is working by running web.rb and then accessing it from a browser.

ruby web.rb

Google Container Engine Set Up

To run the application on Google Container Engine there is some basic setup necessary.

Create a project and Enable APIs

First, create a project on http://cloud.google.com/console. Once the project is created enable the Container Engine API, the Compute Engine API, and the Cloud Storage API. Some of these APIs may already be enabled.

Initialize your development environment

The next step is to initialize your gcloud environment. Use gcloud init to log in, set your project, and your compute zone. Since I switch projects frequently I find it easier to create a custom configuration in gcloud for each project.

Once gcloud is initialized it is time to create the container cluster.

gcloud container clusters create sinatra \
    --num-nodes 1 \
    --machine-type g1-small

This might take a few minutes. Once that is done you can push your application to Google Container Engine.

Containerize the Application

Dockerfile

To deploy this tiny little app on Google Container Engine it needs to be packaged as a Docker container. I found a basic Dockerfile for a Sinatra application here: https://rubyplus.com/articles/2461.

FROM ruby:2.2.1
MAINTAINER Aja Hammerly <thagomizer@google.com>

RUN apt-get update && \
    apt-get install -y net-tools

# Install gems
ENV APP_HOME /app
ENV HOME /root
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
COPY Gemfile* $APP_HOME/
RUN bundle install

# Upload source
COPY . $APP_HOME

# Start server
ENV PORT 8080
EXPOSE 8080
CMD ["ruby", "web.rb"]

This container extends the officially maintained Ruby container so the only additional work to be done is specific to this application: installing gems, copying the application source, and then starting the server. I started my server on port 3000 since that is common for Ruby web applications. Once the application is deployed to Google Cloud mapping to a more conventional port (like 80) is straightforward.

Building and Testing Locally

Now to build the Docker image and test the app locally. First build the image:

docker build -t username/sinatra .

Then run the app:

docker run --rm -p 8080:8080 username/sinatra

To see the app running in the browser you can get the IP address from Docker Machine.

docker-machine ip default

Deploying

Build the Container and Push it to Google Container Registry

In order to deploy the container to Container Engine the container image needs to be in an accessible place. Google Container Registry puts the container image in a Google Cloud Storage in the project. This allows you to limit access to the container image. The following commands build the docker image, tag it, and then push the tagged image to Google Container Registry.

docker build -t gcr.io/your-project-id/sinatra:v1 .
gcloud docker push gcr.io/your-project-id/sinatra:v1

If you are on a Mac or Windows machine and run into trouble with “gcloud docker push” try restarting Docker Machine. I ran into this issue since I had left Docker Machine running for several days. A restart fixed the problem.

Deploying with Kubernetes

A simple Kubernetes deployment is straight forward. In the series on deploying Rails via Kubernetes, I used a very simple pod, controller, and service. That isn’t the simplest way to deploy an application via Kubernetes though. For the Sinatra app we’ll make it even simpler.

kubectl run sinatra --image=gcr.io/your-project-id/sinatra --port=8080
kubectl expose deployment sinatra --type="LoadBalancer"

The first command starts up the Sinatra container in the cluster created earlier. (It actually creates a replication controller with a single replica.) The second command creates a load balancer that allows access to the Sinatra container. (The load balancer is actually a service in Kubernetes parlance.) To get the IP address where the application is running use kubectl.

kubectl get services sinatra

That command will list an external IP that you can use to access the Sinatra application on port 8080. That’s it. From nothing to a Sinatra app running in the cloud shouldn’t take more than an hour.