loading...
Cover image for Continuous deployment Ruby application to Minikube with Google's Skaffold
Amplifr.com

Continuous deployment Ruby application to Minikube with Google's Skaffold

dsalahutdinov profile image Salahutdinov Dmitry ・4 min read

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.

Discussion

pic
Editor guide
Collapse
dsalahutdinov profile image
Salahutdinov Dmitry Author

Hi! No, I did not meet the same trouble
I used minikube's docker engine to push images there. Some details you could find here stackoverflow.com/questions/523105...