Draft
I’ve been playing with a lot of development work flow tools over the last few weeks. I was an early adopter of Docker and have always been passionate about reducing friction in the development process, especially locally. Quick performance wins for individual developers add up to substantial productivity improvements across a whole team. I’ve been interested in how these tools are evolving, as we expand beyond the basic tools Docker provided with containers. As an outcome of this exploration I’m going to write a few posts on these tools.
The posts:
Draft
Draft is developed by Microsoft and:
makes it easy to build applications that run on Kubernetes. Draft targets the “inner loop” of a developer’s workflow: as they hack on code, but before code is committed to version control.
Draft is designed to handle local development, allowing a developer to make changes, test, and resolve issues, before committing your code and letting your CI/CD pipeline take over for deployment. To help reduce the friction of this deployment, Draft produces Helm-compatible artifacts.
Draft has a couple of neat features, the first is automatically detecting and bootstrapping your application. IT can peek at your code and help you generate the right configuration and Helm charts files to run your application in Kubernetes. This includes generating Dockerfile
’s and Kubernetes configuration files.
The second feature is at the heart of several of these products, the ability to do quickly re-deploy your applications when they change. Draft doesn’t quite automatically deploy changes. Rather, the approach is to rely on your IDE, there’s an integration for VS Code and KUbernetes that provides this capability.
You can read more about Draft’s architecture and what it is, and is not, in their design documentation.
So let’s get started by installing Draft locally and running it.
Installing Draft
We’re going to install on macOS but the documentation has installation instructions for Linux and Windows too. There are several prerequisites for Draft:
- Docker and a local Kubernetes cluster (you can also point Draft at an external cluster).
- A working Helm installation on the cluster.
- A local
kubectl
binary.
Let’s get these sorted first.
Installing Kubernetes
First, we will need to install Docker and a local Kubernetes environment. One of the easiest ways to do this locally is to use Docker for Mac, which installs both. You could also install something like Minikube. Install one or the other and initiate a local Kubernetes cluster, either enabling it in Docker for Mac, or initiating and building a Minikube cluster.
Installing Helm
We then need to install Helm, a package manager for Kubernetes, that is used to install packages, called charts, on Kubernetes clusters. We first install a local helm
binary. We’re going to use Homebrew for this but installation is also available for other platforms.
$ brew install kubernetes-helm
Then we need to initiate Helm on our cluster.
$ helm init
This will install Tiller, Helm’s server component, onto our Kubernetes cluster.
Installing kubectl
We’ll also need the kubectl
binary, which is a CLI for Kubernetes. There’s documentation to install and configure it on several platforms but we’ll use the Homebrew recipe.
$ brew install kubernetes-cli
We can then test kubectl
is installed.
$ kubectl versionClient Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.1", GitCommit:"4485c6f18cee9a5d3c3b4e523bd27972b1b53892", GitTreeState:"clean", BuildDate:"2019-07-18T14:25:20Z", GoVersion:"go1.12.7", Compiler:"gc", Platform:"darwin/amd64"}Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.3", GitCommit:"5e53fd6bc17c0dec8434817e69b04a25d8ae0ff0", GitTreeState:"clean", BuildDate:"2019-06-06T01:36:19Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}
If you’ve set up Minikube or Docker for Mac already then it’ll have configured kubectl
to connect to your local cluster, otherwise you’ll need to configure it.
Installing Draft
Next, we need to install Draft itself. We can either download a binary for our preferred environment, or use a tool like Homebrew or Chocolatey. As we’re on MacOS, we’re going to again use Homebrew.
$ brew install azure/draft/draft
We can then test it is working like so:
$ draft version&version.Version{SemVer:"v0.16.0", GitCommit:"5433afea1421810ae9d828631d8651de913b347a", GitTreeState:"dirty"}
Configure Draft
After we’ve installed Draft, we need to configure it. We do this with the draft init
command.
$ draft initInstalling default plugins...Downloading https://azuredraft.blob.core.windows.net/draft/pack-repo-v0.4.2-darwin-amd64.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 3270k 100 3270k 0 0 5973k 0 --:--:-- --:--:-- --:--:-- 5967kPreparing to install into /Users/james/.draft/plugins/draft-pack-repodraft-pack-repo installed into /Users/james/.draft/plugins/draft-pack-repo/draft-pack-repoInstalled plugin: pack-repoInstallation of default plugins completeInstalling default pack repositories...Installing pack repo from https://github.com/Azure/draftInstalled pack repository github.com/Azure/draftInstallation of default pack repositories complete$DRAFT_HOME has been configured at /Users/james/.draft.Happy Sailing!
We can see we’ve downloaded some packs. Packs are what informs Draft about the configuration of applications written in various programming languages. Using packs, Draft knows how to build Dockerfile
’s, Helm charts, including Kubernetes manifests, to build and deploy applications written in languages like Ruby, Python, Java, and Go, etc. Packs are extensible too, so if you have a language that isn’t supported, or a tweak or adjustment specific to your environment or implementation you can modify packs to suit.
We can also see Draft has configured its home directory in ~/.draft
.
We’re now ready to deploy an application with Draft.
Deploying applications with Draft
Let’s test how well Draft can recognize an application. We’re going to use a Draft example we’ve pre-built to take you through the process. You can find it on Github.
Let’s checkout our project.
$ git clone git@github.com:jamtur01/draft-example.git$ cd draft-example$ ls -laREADME.md mate
Here we’ve checked out the project, changed into the resulting directory, and listed its contents. We have a README
for the project, and a directory called mate
, that contains a service we’re going to test with Draft. The mate
service is a Sinatra-based Ruby application.
Configuring the mate service
Let’s see if Draft can recognize and deploy the mate
service. We change into the mate
directory which contains our Ruby application.
$ cd mate$ lsGemfile Gemfile.lock mate.rb
And then run draft create
to see if Draft can identify and build a scaffold for our application.
$ draft create--> Draft detected Ruby (78.378378%)--> Ready to sail
Woot! Looks like it correctly identified our application as Ruby-based. Let’s see what it did.
$ lsDockerfile charts draft.toml Gemfile mate.rb
We can see it created a Dockerfile
, a directory called charts
, and a file called draft.toml
. The Dockerfile
will help us build our Ruby-based image, the charts
directory contains a Helm chart that Draft has created for our application, and the draft.toml
contains Draft’s defintion of our application. Let’s start by looking at the Dockerfile
.
FROM rubyENV PORT 3000EXPOSE 3000RUN bundle config --global frozen 1
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock ./RUN bundle install
COPY . .CMD ["ruby", "app.rb"]
Hmmmm… Looks pretty good. Almost correct, our application is called mate.rb
and not app.rb
but we can edit that to reflect our environment. Let’s look into the draft.toml
file.
[environments] [environments.development] name = "mate" namespace = "default" wait = true watch = false watch-delay = 2 auto-connect = false dockerfile = "Dockerfile" chart = ""
The drafts.toml
file contains our application’s definition. It uses TOML and contains blocks of environments. Environments allow you to specify different running conditions for different purposes, for example how you configure your development environment might differ from how you create a test or staging environment. You can specify what environment Draft should deploy at runtime.
We can see we’ve only got one environment defined: environments.development
. It defines the name of our application, mate
, and the Kubernetes namespace into which it should be deployed, in our case the ever-popular default
.
We have also set the wait
option to true
, which ensures Draft will wait for all resources to be ready before it installs your application. The watch
option controls whether Draft should monitor for local file changes and automatically deploy them when they occur. The default is false
, and as mentioned above this functionality doesn’t currently work. Instead we’d enable monitor for changes in our IDE and re-deploy based on that.
The auto-connect
option controls whether Draft should automatically connect to the application after the deployment is successful.
The dockerfile
option controls the name of the Dockerfile
that will be used to build the image. The chart
option controls the path of the chart, relative to the location of the draft.toml
file, which will deploy the application. If nothing is specified then it’ll use the first directory it finds in the charts/
directory.
You can find the full specification for the draft.toml
file in the Draft documentation.
Let’s take a quick look at the charts
directory tree before we launch our application. Inside the charts
directory we have a directory called mate
, containing the generated Helm chart for our application.
$ lsChart.yaml charts templates values.yaml
Helm charts are templated. The easiest way to see what Helm will do when installing and configuring the mate
application is to look at the template values in the values.yaml
file.
replicaCount: 1image: pullPolicy: IfNotPresentservice: name: ruby type: ClusterIP externalPort: 80 internalPort: 3000resources: limits: cpu: 100m memory: 128Mi requests: cpu: 100m memory: 128Miingress: enabled: false
We can see some basic network configuration, mapping port 3000
inside the application, to port 80
externally via a ClusterIP. And we’ve got some basic memory and CPU resources controls.
Now let’s see if we can deploy the mate
service.
Deploying the mate service
Draft deploys applications using the draft up
command.
$ draft upWARNING: no registry has been set, therefore Draft will not push to a container registry. This can be fixed by running `draft config set registry docker.io/myusername`Hint: this warning can be disabled by running `draft config set disable-push-warning 1`Draft Up Started: 'mate': 01DJBX8D54VNF9C07TQHHVZBTVmate: Building Docker Image: SUCCESS ⚓ (1.0005s)mate: Releasing Application: SUCCESS ⚓ (2.5179s)Inspect the logs with `draft logs 01DJBX8D54VNF9C07TQHHVZBTV`
Draft has used the Dockerfile
it generated to build an image and then deployed it to our local Kubernetes cluster via Helm. The log output will show the output of that build.
We can see our running application in the cluster using kubectl
.
$ kubectl get podsNAME READY STATUS RESTARTS AGEmate-mate-c85b5ff84-lkltn 1/1 Running 0 65m
Currently we have no way of reaching this service and testing it but Draft allows us to create local proxy tunnels that map ports and stream the services logs using the draft connect
command. Let’s do that now.
$ draft connectConnect to mate:3000 on localhost:58037[mate]: [2019-08-16 15:08:33] INFO WEBrick 1.4.2[mate]: [2019-08-16 15:08:33] INFO ruby 2.6.3 (2019-04-16) [x86_64-linux][mate]: == Sinatra (v2.0.5) has taken the stage on 3000 for development with backup from WEBrick[mate]: [2019-08-16 15:08:33] INFO WEBrick::HTTPServer#start: pid=1 port=3000
We can see Draft has mapped port 3000
on our service to port 58037
on localhost
. If we curl
the API endpoint on this service we can see it in action.
$ curl localhost:58037/mateMate!
We can also indicate a specific port for Draft to use.
$ draft connect -p 3001:3000
Would set remote port 3000
to local port 3001
. If you don’t want to do this every time you run draft connect
, or you want to automatically connect, then you can use the override-ports
option in the draft.toml
file.
override-ports = ["3001:3000"]
Developing with Draft
So we’ve got our service running, now let’s make a change to it. We’re going to add a new endpoint to our service.
get '/folks' do "Folks!"end
We then run draft up
again to deploy our new application.
$ draft upDraft Up Started: 'mate': 01DJES911H79M41TPXS2JCCWC4mate: Building Docker Image: SUCCESS ⚓ (1.0011s)mate: Releasing Application: SUCCESS ⚓ (2.1385s)Inspect the logs with `draft logs 01DJES911H79M41TPXS2JCCWC4`
If we now connect to the application we should be able to curl
our new endpoint.
$ draft connect -p 3001:3000Connect to mate:3000 on localhost:3001[mate]: [2019-08-17 03:17:36] INFO WEBrick 1.4.2[mate]: [2019-08-17 03:17:36] INFO ruby 2.6.3 (2019-04-16) [x86_64-linux][mate]: == Sinatra (v2.0.5) has taken the stage on 3000 for development with backup from WEBrick[mate]: [2019-08-17 03:17:36] INFO WEBrick::HTTPServer#start: pid=1 port=3000
And then check the new endpoint:
$ curl localhost:3001/folksFolks!
Woot! Now we can continue our development cycle by making changes and re-deploying.
Finally, when we’re done, we can shut down our Draft applications using:
$ draft delete
Summary
I hope this was useful as a walk through. This just scratches the surface of Draft’s capabilities and you can explore features and other uses cases in the Draft documentation.