Skaffold

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.

note
These posts are explorations of each solution, not comparisons or a pros and cons analysis. I think everyone’s development platform and experience is sufficiently different that they should explore solutions and identify which works for them. Additionally, in many cases, these solutions might not fit at all and something like Docker Compose might address your needs.

The posts:

Skaffold ๐Ÿ”—︎

Skaffold is Google’s contribution to Kubernetes’ local development. In their words:

Skaffold handles the workflow for building, pushing and deploying your application. So you can focus more on application development.

At its core, Skaffold provides fast, local development on a local or remote Kubernetes cluster. It automatically detects changes in your source code and rebuilds and redeploys your application upon change. It handles local logging, showing you the output from your application in a local development server. Skaffold supports multiple environments, each configurable with different requirements for different scenarios. Skaffold also integrates into your CI/CD pipeline, allowing you to run local builds and tests, before pushing changes into the next stage of the pipeline or sharing them with your peers. Interestingly, unlike some other tools in this space, it’s client-side only. Your Kubernetes cluster doesn’t require any configuration or a server component.

Let’s take a look at installing and running Skaffold now.

Installing Skaffold ๐Ÿ”—︎

We’re going to install on macOS but the documentation has installation instructions for Linux and Windows too. There are several prerequisites for Skaffold:

  1. Docker and a local Kubernetes cluster (you can also point Skaffold at an external cluster).
  2. 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 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 version
Client 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 Skaffold ๐Ÿ”—︎

Next, we need to install Skaffold 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 skaffold

We can then test it is working like so:

$ skaffold version
v0.36.0

Configuring applications for Skaffold ๐Ÿ”—︎

Let’s add Skaffold support to an application. We’re going to use a Skaffold 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/skaffold-example.git
$ cd skaffold-example
$ ls
Dockerfile    Gemfile       Gemfile.lock  README.md     k8s  mate          mate.rb       skaffold.yaml

Here we’ve checked out the project, changed into the resulting directory, and listed its contents. We have a README for the project and the files for an application called mate, including a Dockerfile that builds the application. The mate application is a Sinatra-based Ruby application.

We also have our Kubernetes configuration in a directory called k8s, with a single file, k8s-pod.yaml. This file defines the basics of a pod to run our mate application. Let’s look at it first.

apiVersion: v1
kind: Pod
metadata:
  name: mate
spec:
  containers:
  - name: mate
    image: jamtur01/skaffold-mate

We’ve created a skeleton Kubernetes Pod configuration containing a single container, using the image jamtur01/skaffold-mate. Skaffold will build this image if it doesn’t exist.

To enable Skaffold for our application we need to initialize it in our repository. We do this using the skaffold init command.

apiVersion: skaffold/v1beta13
kind: Config
build:
  artifacts:
  - image: jamtur01/skaffold-mate
    context: mate
deploy:
  kubectl:
    manifests:
    - k8s-pod.yaml

Do you want to write this configuration to skaffold.yaml? [y/n]: y
Configuration skaffold.yaml was written
You can now run [skaffold build] to build the artifacts
or [skaffold run] to build and deploy
or [skaffold dev] to enter development mode, with auto-redeploy

The init command writes a Skaffold configuration based on the Kubernetes’ Pod configuration we just saw. We type y to write that configuration out to the file skaffold.yaml.

Let’s look at the skaffold.yaml file we’ve just generated.

apiVersion: skaffold/v1beta13
kind: Config
build:
  artifacts:
  - image: jamtur01/skaffold-mate
deploy:
  kubectl:
    manifests:
    - k8s/k8s-pod.yaml

We can see a Skaffold Config object. It has two elements: build and deploy. The build element describes what Skaffold should build, in our case an image called skaffold-mate. The deploy element tell Skaffold to use kubectl to deploy the Kubernetes Pod configuration contained in the k8s-pod.yaml file.

We need to add some more configuration to the file to support port forwarding, so Skaffold knows which ports to forward for our application.

warning
Skaffold’s documentation suggests you can automatically port forward requests but a flurry of bugs suggest this functionality is largely broken and you need to manually specify how ports are forwarded.

To do this we add another block, portForward, to our skaffold.yaml file.

portForward:
- resourceType: pod
  resourceName: mate
  port: 3000
  localPort: 3001

Our new block tells Skaffold to forward port 3000 exposed on our mate Pod to local port 3001 (if you specify a local port it’ll try to map the same port number or choose a random port).

If you have different configuration, for example you want to forward ports in a deployment or a service, you can also do that using this approach.

note
You can find the skaffold.yaml file’s full definition in the Skaffold documentation.

Deploying applications with Skaffold ๐Ÿ”—︎

Now Skaffold is configured for our mate application we can use it to deploy the application in testing. Skaffold has two modes:

  • Development mode - in which Skaffold monitors your source code for changes and then rebuilds and redeploys your application upon change.
  • Deployment mode - in which Skaffold runs once, builds the relevant artifacts and then exits. This is useful to use in a CI/CD or code promotion context.

We’re going to use the development mode to test Skaffold right now. We initiate that via the skaffold dev command.

$ scaffold dev --port-forward
Generating tags...
 - jamtur01/skaffold-mate -> jamtur01/skaffold-mate:82f5818-dirty
Tags generated in 32.260486ms
Checking cache...
 - jamtur01/skaffold-mate: Not found. Building
Cache check complete in 5.863024ms
Starting build...
Found [docker-desktop] context, using local docker daemon.
Building [jamtur01/skaffold-mate]...
Sending build context to Docker daemon   55.3kB
Step 1/9 : FROM ruby
 ---> 8fe6e1f7b421

. . .

Step 9/9 : CMD ["ruby", "mate.rb"]
 ---> Running in 33a58470fc8f
 ---> e9e4f52c3111
Successfully built e9e4f52c3111
Successfully tagged jamtur01/skaffold-mate:82f5818-dirty
Build complete in 1.047712534s
Starting test...
Test complete in 10.852ยตs
Starting deploy...
kubectl client version: 1.15
pod/mate created
Port forwarding pod/mate in namespace default, remote port 3000 -> local port 3001
Watching for changes...
[mate] [2019-08-17 20:03:53] INFO  WEBrick 1.4.2
[mate] [2019-08-17 20:03:53] 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 20:03:53] INFO  WEBrick::HTTPServer#start: pid=1 port=3000

We’ve run skaffold dev with a single flag, --port-forward, that enables port forwarding (which is otherwise off by default). We can see that Skaffold has built our Docker image and deployed a pod to Kubernetes. It’s mapped port 3000 on our pod to port 3001 on our local host. The final stage of our process, Skaffold has echoed the output of the application’s Sinatra server. It’s now watching our code for changes.

We can curl the application’s endpoint on the mapped port to test it’s all working.

$ curl localhost:3001/mate
Mate!

Developing with Skaffold ๐Ÿ”—︎

Now we have the application deployed, let’s add another endpoint to the mate application in the mate.rb file.

get '/folks' do
  "Folks!"
end

After we save the file, we’ll see Skaffold kick off the rebuild process in the window running our skaffold dev process.

[mate] <Container was Terminated>
[mate] <Container was Terminated>
INFO[17732] files modified: [mate.rb]
INFO[17732] files deleted: [.mate.rb.swp]
Generating tags...
 - jamtur01/skaffold-mate -> jamtur01/skaffold-mate:82f5818-dirty
Tags generated in 25.1029ms
Checking cache...
 - jamtur01/skaffold-mate: Not found. Building
Cache check complete in 2.869852ms
Starting build...

. . .

Starting test...
Test complete in 5.022ยตs
Starting deploy...
kubectl client version: 1.15
pod/mate configured

And our application will be redeployed and we can curl our new endpoint.

$ curl localhost:3001/folks
Folks!

We’ve now got a development life cycle running inside Skaffold!

Summary ๐Ÿ”—︎

I hope this was useful as a walk through. This just scratches the surface of Skaffold’s capabilities. It’s capable of integrating with a large number of potential Builders for your Docker images. You can also integrate local tests into your rebuild process, using container structure testing, to ensure the consistency of your build. Additionally, Skaffold allows you to build these features with profiles, to allow different builders, test approaches, or deployment processes for different environments. You can explore features and other uses cases in the documentation.

comments powered by Disqus