Shaving yak with K8S and Draft Part 1 . Local draft with minikube.

TLDR: use my mac to deploy ruby and node services to minikube with draft.

Kubernetes seems to be everywhere today. Google pioneered it, Azure has AKS, Amazon has it. Cloud Foundry now has it as default for BOSH. In other words, if you have been under a rock and never heard of it, it is time. All the cool kids using it.

K8S ( short for Kubernetes) is great. It allows you have great CI/CD story, will schedule resources for you, have the amazing tooling and the list can go on. It has solid documentation and if you haven’t, you most certainly should go through k8s-basics.

But even that is still a lot of friction and ceremony. If you are like me, you probably still remember times of heroku create and git push heroku beeing pretty much only deployment toolchain that you needed. And wondering why those time should have go… Well, wonder no more, since there are two things that came to rescue this year, namely Helm and Draft. That is why I decided to give it a shot write a series on using them.

In this post, which is part 1 of the series, I am going to get local K8S (with minikube) up and running; install draft along with some other addons; and deploy two tiny services to it, one written in Ruby using Sinatra and another one in node using Zeit’s micro. (I am choosing node and ruby as platforms since draft’s getting started shows how to deploy python.) Now when I am done with links to all the things I am going to use let’s shave that yak!

I am going to use my MBP running macOS Seirra, but you can follow along with almost exact steps on Linux (of course) or Windows with Linux Subsystem installed. Since you (and me) are on the mac, I assume you already have homebrew installed along with ruby, bundle and node with npm so I am not going to describe how to install those. Also I am going to show commands i type in console with > sign like so:

# see files in folder
> ls
index.js package.json

First, we need to get our minikube installed. Minikube is just tiny K8S that you can run on your laptop. If we look at installation instructions at we have one-liner:

> brew cask install minikube

However, that wouldn’t work because we needed to scroll down and see requirements for running minikube which states one of the following on mac:

| xhyve driver, VirtualBox, or VMware Fusion

I will go with virtualbox. So here are first steps I am doing:

# install virtualbox
> brew cask install virtualbox
# install minikube
> brew cask install minikube
# init minikube
> minikube start
# now let's confirm that it is installed correctly
> kubectl cluster-info
Kubernetes master is running at

Your address in the output of kubectl might be different, write it downs as we are going to make use of it.

Let’s enable additional services we will use with our little K8S cluster.

# as command says it enables registry addon
> minikube addons enable registry
# add ingress
> minikube addons enable ingress

We could’ve done it after minikube started, but lets check out nice K8S dashboard right now:

> minikube dashboard

You will see something similar to the screenshot below:

Image for post
Image for post

Now when we get this out of the way lets install draft and helm:

# install helm
> brew install kubernetes-helm
# configure helm on your minikube
> helm init
# install draft
> brew tap azure/draft
> brew install draft
# init draft with ingress support
> draft init --auto-accept --ingress-enabled

OK, now when we have all our devops toys prepared let’s to get our applications running.

You can find this source code in this gist.

Create folder ruby and create app.r and Gemfile files as below:

Important things to note here are

  • We are getting port from environment variable PORT
  • We are binding service to so it is going be accessible from anywhere.

We can test that our tiny service is working by

> bundle install
> env PORT=3000 ruby app.rb
# and in another console
> curl localhost:3000
> { "message": "Sinatra on K8S Draft!" }

Now that it runs locally I want to get it onto the K8S cluster. Thanks to draft, that we installed before, it is quite simple:

> draft create --app sinatra
--> Draft detected the primary language as Ruby with 100.000000% certainty.
--> Ready to sail

Almost exactly like build pack detection on heroku! Doing this added quite a few things to our working folder

> ls
Dockerfile Gemfile Gemfile.lock app.rb chart draft.toml

So just by running draft create command we got container definition (Dockerfile ) our draft service definition (draft.toml) and helm chart (whole chart folder). I am going to skip detailed look at them for know and just look the container definition (Dockerfile):

FROM ruby:onbuild
CMD ["ruby", "app.rb"]

Pretty straightforward, we got standard base container image for ruby, set environment variable PORT=3000 asking container runtime to expose this port and telling to run our app.rb after container started.

Following along draft documentation will tell us that in order to deploy service to K8S we need just one command:

> draft up
Draft Up Started: 'sinatra'
sinatra: Building Docker Image: SUCCESS ⚓ (0.9869s)
sinatra: Pushing Docker Image: SUCCESS ⚓ (16.0748s)
sinatra: Releasing Application: SUCCESS ⚓ (1.0566s)

(of course, your build ID and times are going to be different)

Cool, it says it built the image, pushed it to some registry and released our application. Draft conveniently provides a way to locally test deployment:

> draft connect
Connecting to your app...SUCCESS...Connect to your app on localhost:53704
Starting log streaming...
[2017-10-30 05:49:25] INFO WEBrick 1.3.1
[2017-10-30 05:49:25] INFO ruby 2.4.2 (2017-09-14) [x86_64-linux]
== Sinatra (v2.0.0) has taken the stage on 3000 for development with backup from WEBrick
[2017-10-30 05:49:25] INFO WEBrick::HTTPServer#start: pid=1 port=3000

Nice! Application started in K8S and is listening on port 3000 ( just as Dockerfile said), and I can connect to it using local port 53704 ( of course you are going to have your own port ). Lets open another console and check it:

> curl localhost:53704
{"message":"Sinatra on K8S Draft!"}

Yay! Ruby application is indeed running. Now let’s see what have actually happened behind the scene.(Close new console, go back to the main one, press Ctrl-C and continue in there.)

If you go to the K8S dashboard (reopen it with minikube dashboard, if needed) you now see this:

Image for post
Image for post

Well, if you are not impressed with this, I most definitely am. By running couple of short command, I’ve got two pods for my Ruby service, a deployment, a replica set, an ingress binding and a service! To make it clear how much work it really is, click on triple dot icon next to some of our resources and click view/edit YAML.

Good, now to the second service, that will run under node.js using micro framework.

You can find source code for second service in this gist.

Lets quickly check it is running:

> npm install
> npm start
node@1.0.0 start .../k8s-draft/node3
node .
Listening on https://localhost:8080

I created a little bit more involved service, it requires valid JSON to be posted in order to get a result. Let me test it works:

> curl -H "Content-Type: application/json" -X POST -d '{"test":"hi"}'  http://localhost:8080
{"test":"hi","response":"Hi from MICROservice on K8S draft"}

Perfect, now the Draft part:

> draft create --app micro
--> Draft detected the primary language as JSON with 87.096774% certainty.
Error: Could not find a pack Q_Q

Oh no! Yak shaving session again. Even though it is pretty obvious that package.json file (as last resort since it can be anything else like go/ruby/python really) cries that it is node.js app, draft’s way to detect language failed here. Let me find out what I can do to correct it:

> draft create --help
This command transforms the local directory to be deployable via 'draft up'.
draft create [path] [flags]
-a, --app string name of the Helm release. By default, this is a randomly generated name
-p, --pack string the named Draft starter pack to scaffold the app with
Global Flags:
--debug enable verbose output
--draft-namespace string namespace where Draftd is running. This is used when Draftd was installed in a different namespace than kube-system. Overrides $DRAFT_NAMESPACE (default "kube-system")
--home string location of your Draft config. Overrides $DRAFT_HOME (default "/usr/local/etc/draft")
--host string address of Draftd. This is used when the port forwarding feature by Kubernetes is unavailable. Overrides $DRAFT_HOST
--kube-context string name of the kubeconfig context to use

Great, I can explicitly provide pack to use:

> draft create --app micro --pack=javascript
Error: could not load /usr/local/etc/draft/packs/JavaScript:
lstat /usr/local/etc/draft/packs/JavaScript/chart/:
no such file or directory

That looks interesting, but what do I have in /usr/local/etc/draft/packs/:

> ls /usr/local/etc/draft/packs/

If I keep cd’ing into that folder, it seems that packs are actually where they are in the draft github repo, so using full path to the pack:

> draft create --app micro
--> Ready to sail

Nice, time to deploy it to K8S cluster again:

> draft up
Draft Up Started: 'micro'
micro: Building Docker Image: SUCCESS ⚓ (1.9985s)
micro: Pushing Docker Image: SUCCESS ⚓ (17.0501s)
micro: Releasing Application: SUCCESS ⚓ (2.4598s)
micro: Build ID: 01BXNX6XX15H7Q8JKCXWMC8VAT

And check that it is indeed working:

> draft connect
Connecting to your app...Error: could not find a ready pod

Oh no! So why our app working perfectly fine locally did not started in K8S? Lets start from ground up, Dockerfile:

> cat Dockerfile
FROM node:6-onbuild
RUN npm install
CMD ["npm", "start"]

This is probably it! We are telling docker to use node v6 as base image while using fancy async/await statements in our source code.

I am updating FROM part of the file to use v8 of the node and now it looks like this:

FROM node:8-onbuild
RUN npm install
CMD ["npm", "start"]

And deploy again:

> draft up
Draft Up Started: 'micro'
micro: Building Docker Image: SUCCESS ⚓ (21.3716s)
micro: Pushing Docker Image: SUCCESS ⚓ (17.0075s)
micro: Releasing Application: SUCCESS ⚓ (3.9480s)
micro: Build ID: 01BXP1CK0NJ1MGHPSNW58BQ47E
> draft connect
Connecting to your app...SUCCESS...Connect to your app on localhost:51463
Starting log streaming...
> node@1.0.0 start /usr/src/app
> node .
Listening on https://localhost:8080

Yay! I still want to confirm it returns what I expect:

> curl -H "Content-Type: application/json" -X POST -d '{"test":"hi"}'  http://localhost:51463
{"test":"hi","response":"Hi from MICROservice on K8S draft"}

Hooray! Both of my services are up and running. Now I can explore a bit more.

So I mentioned ingress in the beginning. It’s website says: Ingress can provide load balancing, SSL and named based virtual hosting. So what’s it state of affairs with draft?

> kubectl get ing
micro-javascript micro.k8s.local 80 14m
sinatra-ruby sinatra.k8s.local 80 1h

Ah sweet! Draft, again, did all the heavy lifting and set things up for me. Although it did not setup DNS that’s why if I try connect to my Ruby service, for instance:

> curl sinatra.k8s.local

It just timeouts. What I need is to use address (same as I saw in the beginning) and set proper hostheader:

> curl --header Host:sinatra.k8s.local
{"message":"Sinatra on K8S Draft!"}

Perfect! What about our javascript service?

> curl -H "Host:sinatra.k8s.local" -H "Content-Type: application/json" -X POST -d '{"test":"hi"}'  http://localhost:51463
{"test":"hi","response":"Hi from MICROservice on K8S draft"}

Sweet! Both of them up and running!

Before we wrap it up lets TLDR what we done in order to get our services running in a K8S cluster. It is pretty much just:

> mkdir ruby
> cd ruby
> git clone .
> bundle install
> draft create --app sinatra
> draft up

That’s it! pretty impressive if you ask me!

In the next post I am going to dive deeper into some advanced features of Draft and deploy our simple apps to managed K8S cluster.

If you have any corrections or additions to this post, you can reach me by email or on twitter

Written by

Senior Software Engineer at Instacart

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store