Continuous deployment Ruby application to Minikube with Google's Skaffold
Dmitry Salahutdinov
Posted on January 21, 2020
Kubernetes has made it very easy to deploy and scale applications to the cloud than ever. Still, the development process has not evolved at the same speed.
Today, most developers try to either run parts of the infrastructure locally with docker or test these integrations directly in the cluster via CI jobs or the "docker build
, docker push
, kubectl apply
" cycle. Manual deployment is painful and incredibly slow.
In this article, we will see how to set up continuous deployment of simple Ruby application to Minikube to reduce the development cycle time using the Google's Skaffold tool.
Skaffold handles the workflow for building/pushing Docker images and deploying your application. It helps to reuse your actual application production configuration (Kubernetes manifests, Helm chart, Docker Compose config) for the local continuous development cycle. Skaffold also supports file sync to prevent the full container rebuild/redeploy.
Preparation
First of all, let's install the required tooling.
Install Minikube
Here are the commands to run for Mac, more details could be found here:
$ brew install minikube
$ minikube version
minikube version: v1.5.2
commit: 792dbf92a1de583fcee76f8791cff12e0c9440ad-dirty
$ minikube start
$ eval $(minikube docker-env)
$ kubectl config use-context minikube
The line eval $(minikube docker-env)
set the docker client to use Minikube's Docker engine. It allows us to avoid docker image pushing and simplifies the full redeploy cycle.
We've also changed to actual kubectl
context to minikube.
Install Skaffold
Here are the documentation details of Skaffold installing, for running on Mac just use:
$ brew install skaffold
Ruby/Rack application
We will use the simplest Rack-application from the Skaffold's github:
$ git clone git@github.com:GoogleContainerTools/skaffold.git
$ cd examples/ruby
Skaffold dev
Just run skaffold dev
and you will see similar output:
$ skaffold dev
Listing files to watch...
- ruby-example
Generating tags...
- ruby-example -> ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
- ruby-example: Not found. Building
Found [minikube] context, using local docker daemon.
Building [ruby-example]...
Sending build context to Docker daemon 6.144kB
Step 1/6 : FROM ruby:2.7
---> fb53c5f433da
Step 2/6 : WORKDIR /app
---> Using cache
---> 0c2d04fabca0
Step 3/6 : ADD Gemfile* ./
---> Using cache
---> 06d6623561fc
Step 4/6 : RUN bundle install
---> Using cache
---> 9bbcf218dda5
Step 5/6 : ADD . ./
---> Using cache
---> 478cca6b9c4f
Step 6/6 : CMD ["bundle","exec","puma"]
---> Running in 434b1923c060
---> 988d9e9e7bf4
Successfully built 988d9e9e7bf4
Successfully tagged ruby-example:v1.1.0-118-g92f37b200-dirty
Tags used in deployment:
- ruby-example -> ruby-example:988d9e9e7bf4b44beb62a8aaa7104cdf407a8523db0bdc7fe331600d374ba274
local images can't be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
- service/ruby created
- deployment.apps/ruby created
Watching for changes...
[ruby-75c5b89895-rd2lb ruby] Puma starting in single mode...
[ruby-75c5b89895-rd2lb ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-75c5b89895-rd2lb ruby] * Min threads: 0, max threads: 16
[ruby-75c5b89895-rd2lb ruby] * Environment: development
[ruby-75c5b89895-rd2lb ruby] * Listening on tcp://0.0.0.0:9292
[ruby-75c5b89895-rd2lb ruby] Use Ctrl-C to stop
At that time Skaffold tool has built the Docker image, tagged it with a specific tag, and deployed application to Minikube cluster. Then it started to wait for the source file changes.
The result could be seen here:
$ curl -s $(minikube service ruby --url)
Hello Skaffold!
Hot reload
As configured at the Skaffold config - it watches for *.rb
files and reloads all the changes rights into the container sources. The sample Rack application also supports for reloading with help of the rack-unreloader
ruby-gem.
If we change the app.rb
file to return other string, like
class App
def self.call(env)
[ 200, {"Content-Type" => "text/html"}, ["Hello Skaffold!!!"]]
end
end
We will see skaffold sync files into the container:
Syncing 1 files for ruby-example:988d9e9e7bf4b44beb62a8aaa7104cdf407a8523db0bdc7fe331600d374ba274
Watching for changes...
As the Rack application supports file reloading, it will now produce the different output:
$ curl -s $(minikube service ruby --url)
Hello Skaffold!!!
Automatic redeploy
If we change others files, such as backend/Dockerfile
, Skaffold will autmatically rebuild the Docker image, and redeploy the application:
echo "gem 'oj'" >> backend/Gemfile
We'll see full redeploy:
Generating tags...
- ruby-example -> ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
- ruby-example: Not found. Building
Found [minikube] context, using local docker daemon.
Building [ruby-example]...
Sending build context to Docker daemon 6.144kB
Step 1/6 : FROM ruby:2.7
---> fb53c5f433da
Step 2/6 : WORKDIR /app
---> Using cache
---> 0c2d04fabca0
Step 3/6 : ADD Gemfile* ./
---> Using cache
---> 7b4671e7469d
Step 4/6 : RUN bundle install
---> Using cache
---> 0331311c04f3
Step 5/6 : ADD . ./
---> 67e2cf138ed3
Step 6/6 : CMD ["bundle","exec","puma"]
---> Running in fa04b480dd8f
---> 31f2e1f54283
Successfully built 31f2e1f54283
Successfully tagged ruby-example:v1.1.0-118-g92f37b200-dirty
Tags used in deployment:
- ruby-example -> ruby-example:31f2e1f5428397d5d4aa1f2d2328ff859e01d970f304f2f9dbb9f4ba8d0c8968
local images can't be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
- deployment.apps/ruby configured
Watching for changes...
[ruby-75c5b89895-rd2lb ruby] - Gracefully stopping, waiting for requests to finish
[ruby-75c5b89895-rd2lb ruby] === puma shutdown: 2020-01-20 11:49:02 +0000 ===
[ruby-75c5b89895-rd2lb ruby] - Goodbye!
[ruby-5b74868996-ckg24 ruby] Puma starting in single mode...
[ruby-5b74868996-ckg24 ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-5b74868996-ckg24 ruby] * Min threads: 0, max threads: 16
[ruby-5b74868996-ckg24 ruby] * Environment: development
[ruby-5b74868996-ckg24 ruby] * Listening on tcp://0.0.0.0:9292
[ruby-5b74868996-ckg24 ruby] Use Ctrl-C to stop
Change the app.rb
then to return json string:
require 'oj'
class App
def self.call(env)
json = { "message" => "hello" }
[ 200, {"Content-Type" => "text/html"}, [Oj.dump(json)]]
end
end
After the app.rb
file synchronization, the app would return:
$ curl -s $(minikube service ruby --url)
{"message":"hello"}
Features
File sync and automatic rebuild/redeploy
are the core.
But, Skaffold has many essential features for Kubernetes-native development:
- policy-based image tagging
- resource port-forwarding
- supports multiple profiles
- supports Helm, Kustomize, Docker Compose manifests
Conclusion
Skaffold is simple and lightweight tool, easy to get up and running for local development! It automates deployments, but for comfortable development I like it to have any other features, like backward file sync.
Posted on January 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.