A weekend, a Rails app, a Kubernetes and an Azure.
This weekend, I’ve been exploring the depths of Kubernetes and Azure, to see if we can host Ruby on Rails with them. Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications. And Azure is a cloud infrastructure provider by Microsoft.
My intent was to catch-up with what’s happening in app-deployment-and server-management-world, and to see if there are newer, better, easier ways of setting up and deploying Rails apps or our company and team. We’ve been working with Azure the past three weeks for a corporate client, and I’ve heard and read about Kubernetes the past few years. So this seemed a good moment to spend a few hours on during my little daughter’s bed time, and mine.
The scope of my explorations
At Firmhouse we’re really good at shipping new products quickly. Most of the apps we build are (market) experiments, prototypes, MVPs and early stages of successful (and less successful) apps to-be. Here’s what our apps and their requirements normally look like:
- App deployments set up in 5 minutes. It’s crucial that no extensive server knowledge is required to set up an app.
- Rails with PostgreSQL and Redis (for Sidekiq)
- An SSL certificate must be there on first the deploy
- Apps are 12factor and configured via ENV
- Most apps are early stage with short development cycle
- Very frequent deployments
- High availability is not crucial
- Don’t have to handle lots of traffic on day 1
- Does need very secure environment
- Conform to data protection and compliance of our enterprise customers
- Deployed on-premise or in public cloud
We can do all this because we’ve built an awesome in-house app deployment dashboard: Intercity.
Intercity is a web frontend for Dokku that you can run on-premise on your own servers or VMs in the cloud. Once you’ve Intercity installed (and this takes about 15 minutes), you can basically deploy any Rails apps within 2 minutes each, including database servers like PostgreSQL, MySQL, and Redis. I wanted to see if I could get the same kind of ease-of-use and fast setup experience with Azure and Kubernetes.
Before I set out, I read somewhere that Azure has a native integration and documentation to work with Kubernetes on their Azure Container Service. That sounded very promising. I was looking forward to some magical three-step process: create a file with app requirements, run a command, done!
Steps I followed, and resources I used
A few hours in, I realized this would not be as easy as “one, two, three” but I learned a great bunch on how to deploy Rails apps via Kubernetes (on Azure). Here’s what I did — so you can try and do it too.
- Add a free subscription to our Azure Firmhouse directory.
- Followed quick-start tutorial of Azure Container Service with Kubernetes: Deploy Kubernetes cluster for Linux containers. After reading this, I had a pretty good idea on how I could control Azure from my command line. And more importantly, I had their sample Python-based web app running on my own Azure IP. Yay!
- Started with Azure’s extended tutorial on creating, managing and deploying apps on Azure Container Service: Create container images to be used with Azure Container Service. Decided to not use their sample app, but apply to one of our own products: GoMonthly.
- Figured out I had to build Docker images for GoMonthly, and did it via Docker Compose. Followed the Ruby on Rails getting started guide on the Docker website: Quickstart: Compose and Rails. Surprised: worked really well. (Note: I’ve had already used Docker *a lot* before)
- Managed easily to create a cluster and run it on Azure’s services. I did run into a bunch of Rails deployment problems. (I’ll list them below) The Azure/Kubernetes tutorial is for a sample Pythong app, not Rails. This article from an EngineYard employee helped me along the way: Kubernetes Tutorial: Running a Rails App in Kubernetes. This one also helped, even if it’s for Google Container Engine: Deploying Rails on Kubernetes.
- Finally, I got my app live on an Azure IP-address. 🎉 Then this article helped me find out to run database migrations and other one-off tasks: Managing Rails tasks such as ‘db:migrate’ and ‘db:seed’ on Kubernetes while performing rolling deployments
These 6 steps only got me so far to deploy Rails apps as I’m normally used to, but it was a great way to learn the basic Kubernetes and Azure ecosystem. I hope to learn more soon. This is only less than 10% of what you need in a healthy app development and deployment environment.
To round off, here’s a short FAQ and some pointers on getting Kubernetes, Azure, and Ruby on Rails to play together:
Useful bits for Rails on Azure via Kubernetes
Define standard CMD in Dockerfile (AKA: “Switch to inspect mode.” in logs)
Initially, my application container would not successfully start in my Kubernetes cluster. I had no idea what was going on since I didn’t get any useful output in the logs when running
kubectl logs <web-pod-name>. The only thing it gave me was two lines:
Switch to inspect mode.
Turns out, this is the standard output from the Rails console if you don’t give it a valid terminal. This made me realize that my Rails app container was starting in Ruby/Console-mode, and not with the command I had defined in my
bundle exec rails s. After setting the container run
CMD in the Dockerfile it worked. Here’s the Dockerfile:
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp
CMD bundle exec rails s
Deploying a new version of your app
To deploy a new container image version of your app (after running
docker-compose build), you can do two things:
Or, you set the image for your Rails app deployment resource manually via command-line:
kubectl set image deployment paymentthingy-web myfirmhouseregistry.azurecr.io/paymentthingy_web:v3
Or, you modify the
image key of your app container configuration in your Kubernetes manifest file, and apply it against your cluster via:
kubectl apply -f <your-kubernetes.yml>
Configuring the database
I named my PostgreSQL database service in my Kubernetes cluster
paymentthingy-postgresql. If you want Rails to pick up the correct
DATABASE_URL to that PostgreSQL container, you need to set it as an ENV var for your web Deployment. Here’s the full version of my web Deployment config in my
- name: paymentthingy-web
- containerPort: 3000
- name: DATABASE_URL