What’s the story?

This is a second part on my ‘How do I set up my local Kubernetes environment today?’ series. On the first part I covered how I am utilizing LinuxKit Kubernetes on my local development environment. If you haven’t read it yet, I strongly recommend to do it now. — It gives you the background to this story.

Code, Build, Push, Deploy, Test, Repeat

Code, build, push, deploy, test and repeat. Do you recognize the pattern? How many times you repeat it while building an image? — Some may say too many.

What if I tell you that there is a tooling available that helps on this. Tooling that is not as heavy as full blown CI/CD pipeline but something that covers the most crucial functionalities of it and is lightweight enough to run on our local development environment. — Got interested?

Skaffold

Skaffold is relatively new open source project from Google Could Platform team and this how they describe their project on their GitHub page:

Skaffold is a command line tool that facilitates continuous development for Kubernetes applications. You can iterate on your application source code locally then deploy to local or remote Kubernetes clusters. Skaffold handles the workflow for building, pushing and deploying your application. It can also be used in an automated context such as a CI/CD pipeline to leverage the same workflow and tooling when moving applications to production.

Sound like we have something in our hands that answers to our demand to automate parts of our development workflow. So, let’s see how it works in practice.

Installation

At the moment there is two easy ways to install Skaffold on Linux; download the latest binary release or build from sources.

Build from source

As the project is relatively young I like to keep up with the latest changes by building the skaffold from sources. — This requires installation of go (1.10 minimum) , git and make . So, let’s build the Skaffold!

Start by downloading the latest sources:

[nikovirtala@thinky ~ ]$ go get -u -d github.com/GoogleCloudPlatform/skaffold

package github.com/GoogleCloudPlatform/skaffold: no Go files in /home/nikovirtala/go/src/github.com/GoogleCloudPlatform/

skaffold

enter into the source folder:

[nikovirtala@thinky ~ ]$ cd $GOPATH/src/github.com/GoogleCloudPlatform/skaffold

build it:

[nikovirtala@thinky skaffold ]$ make install

mkdir -p ./out

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go install -ldflags " -X github.com/GoogleContainerTools/skaffold/pkg/skaffold/ver

sion.version=v0.4.0 -X github.com/GoogleContainerTools/skaffold/pkg/skaffold/version.buildDate=2018-04-18T15:36:12Z -X g

ithub.com/GoogleContainerTools/skaffold/pkg/skaffold/version.gitCommit=7a89445cc34ee6f243fdf9e039c7d9264fd7cd6a -X githu

b.com/GoogleContainerTools/skaffold/pkg/skaffold/version.gitTreeState=clean " -tags "kqueue container_image_ostree_stub

containers_image_openpgp" github.com/GoogleContainerTools/skaffold/cmd/skaffold

and test the installation:

[nikovirtala@thinky skaffold ]$ which skaffold

/home/nikovirtala/go/bin/skaffold [nikovirtala@thinky skaffold ]$ skaffold version

v0.4.0

OK, now we have skaffold installed and we are ready to configure our first project!

Binary release

An alternative for building the skaffold from sources is to download the latest binary release. At the point of writing the latest binary release version is v0.4.0 and the change log is available here.

Installing binary release can be done simply by running a following one liner. — If you already build one from sources, you can skip this step.



&& chmod +x skaffold && sudo mv skaffold /usr/local/bin curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin

Set up your first Skaffold project

Skaffold configuration is defined in skaffold.yaml . This YAML file defines how your container image is built, where it is stored and which resources should be deployed in to Kubernetes cluster.

To be able to demonstrate how the build, push and deploy automation works with Skaffold we need a little sample application, its Dockerfile , repository on some image registry where to push the image, Kubernetes resource definitions and a Kubernetes cluster. — I am going to use the same whalesay application that I used on first part to demonstrate the service deployment on Kubernetes, my repository is a nikovirtala/skaffold-demo on Docker Hub and my Kubernetes cluster is LinuxKit Kubernetes Project running on my laptop.

You may consider following definitions as a bare minimum required to run to continuous deployment with Skaffold. So, this is what I have on my project folder:

[nikovirtala@thinky whalesay ]$ ls -1

Dockerfile

kubernetes-whalesay.yml

skaffold.yaml

whalesay.go

whalesay.go — a little go application to print Moby ASCII and container ID.

[nikovirtala@thinky whalesay ]$ cat whalesay.go

package main



import (

"fmt"

"log"

"net/http"

"os"

)



func handler(w http.ResponseWriter, r *http.Request) {

var name, _ = os.Hostname()



fmt.Fprintf(w, `<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>nikovirtala/whalesay</title>

</head>

<body>

<pre>

## .

## ## ## ==

## ## ## ## ===

/""""""""""""""""\___/ ===

~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~

\______ o __/

\ \ __/

\____\______/



| |

__ | __ __ | _ __ _

/ \| / \ / |/ / _\ |

\__/| \__/ \__ |\_ \__ |



This request was processed by: %s

</pre>

</body>

</html>

`, name)

log.Print("Served request ",r,"

")

}



func main() {

log.SetOutput(os.Stderr)

log.Println("Starting server ...")

http.HandleFunc("/", handler)

err := http.ListenAndServe(":80",nil)

if err != nil {

log.Fatal("ListenAndServer: ", err)

}

}

Dockerfile — This defines the container image to be built.

[nikovirtala@thinky whalesay ]$ cat Dockerfile

FROM golang:alpine as build



WORKDIR /whalesay

ADD . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o whalesay .



FROM alpine:edge

LABEL maintainer "Niko Virtala <niko@nikovirtala.io>"



WORKDIR /

COPY --from=build /whalesay .

EXPOSE 80

ENTRYPOINT ["/whalesay"]

kubernetes-whalesay.yml — Definition for Kubernetes service deployment and exposure.

[nikovirtala@thinky whalesay ]$ cat kubernetes-whalesay.yml

apiVersion: apps/v1

kind: Deployment

metadata:

name: whalesay

labels:

app: whalesay

spec:

replicas: 3

selector:

matchLabels:

app: whalesay

template:

metadata:

labels:

app: whalesay

spec:

containers:

- name: whalesay

image: docker.io/nikovirtala/skaffold-demo

ports:

- containerPort: 80



---

apiVersion: v1

kind: Service

metadata:

name: whalesay

labels:

app: whalesay

spec:

type: NodePort

ports:

- port: 80

nodePort: 30001

protocol: TCP

selector:

app: whalesay

skaffold.yaml — Skaffold build, push and deploy instructions.

[nikovirtala@thinky whalesay ]$ cat skaffold.yaml

apiVersion: skaffold/v1alpha2

kind: Config

build:

artifacts:

- imageName: docker.io/nikovirtala/skaffold-demo

workspace: ./

docker: {}

local: {}

deploy:

kubectl:

manifests:

- ./kubernetes-whalesay.yml

Now we have all necessary configuration defined, so let’s get the party started!

Start continuous deployment

Just to make this demo clear, let’s make sure that our Kubernetes cluster does not have any related resources running before we begin:

[nikovirtala@thinky whalesay ]$ kubectl config current-context

kubernetes-admin@kubernetes

[nikovirtala@thinky whalesay ]$ kubectl get pods

No resources found.

Seems we have nothing running, so we are good to begin.

To start continuous deployment run: skaffold dev like this:

[nikovirtala@thinky whalesay ]$ skaffold dev

Starting build...

Sending build context to Docker daemon 108.5kB

Step 1/10 : FROM golang:alpine as build

---> 52d894fca6d4

Step 2/10 : WORKDIR /whalesay

---> Using cache

---> e247af3d14a2

Step 3/10 : ADD . .

---> Using cache

---> 26f6a12145a9

Step 4/10 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o whalesay .

---> Using cache

---> 117e35c8159a

Step 5/10 : FROM alpine:edge

---> 5c4fa780951b

Step 6/10 : LABEL maintainer "Niko Virtala <niko@nikovirtala.io>"

---> Using cache

---> 6abf21436c87

Step 7/10 : WORKDIR /

---> Using cache

---> e41edc48d94b

Step 8/10 : COPY --from=build /whalesay .

---> Using cache

---> 60169baaf4c7

Step 9/10 : EXPOSE 80

---> Using cache

---> 6ffacee2d0ce

Step 10/10 : ENTRYPOINT ["/whalesay"]

---> Using cache

---> 7c553383951b

Successfully built 7c553383951b

Successfully tagged bcc16f193f0c7bfc2ebe52e66d34892f:latest

Digest: bcc16f193f0c7bfc2ebe52e66d34892f:latest

Successfully tagged docker.io/nikovirtala/skaffold-demo:7c553383951b0e099e2ea1e81b86bee59e32c249ffb3279701f31dc799f08e0a

The push refers to repository [docker.io/nikovirtala/skaffold-demo]

d18415a0fc1a: Preparing

c9e8b5c053a2: Preparing

d18415a0fc1a: Layer already exists

c9e8b5c053a2: Layer already exists

7c553383951b0e099e2ea1e81b86bee59e32c249ffb3279701f31dc799f08e0a: digest: sha256:e64b03e193ef96d061b9db5f1db5c7b60d55af32d7db5300f2d

a29252913057a size: 739

Build complete in 6.041379946s

Starting deploy...

Deploying ./kubernetes-whalesay.yml...

Deploy complete in 444.734517ms

Watching for changes...

[whalesay-c47584b84-nzzhp whalesay] 2018/04/19 14:48:06 Starting server ...

[whalesay-c47584b84-np4cs whalesay] 2018/04/19 14:48:06 Starting server ...

[whalesay-c47584b84-zk8nn whalesay] 2018/04/19 14:48:07 Starting server ...

Quite amazing indeed! So, what really happened here?

Container image was built based on Dockerfile The image was tagged and pushed to repository defined in skaffold.yaml Resources defined in kubernetes-whalesay.yml was deployed in to Kubernetes cluster Skaffold is waiting for changes … Log stream to deployed pods was opened

Can we verify it all somehow? — Yes, we can!

First the container image, we should be able to pull it from registry:

[nikovirtala@thinky whalesay ]$ docker pull docker.io/nikovirtala/skaffold-demo:7c553383951b0e099e2ea1e81b86bee59e32c249ffb3279701f3

1dc799f08e0a

7c553383951b0e099e2ea1e81b86bee59e32c249ffb3279701f31dc799f08e0a: Pulling from nikovirtala/skaffold-demo

Digest: sha256:e64b03e193ef96d061b9db5f1db5c7b60d55af32d7db5300f2da29252913057a

Status: Downloaded newer image for nikovirtala/skaffold-demo:7c553383951b0e099e2ea1e81b86bee59e32c249ffb3279701f31dc799f08e0a

It seems that we can. How about the Kubernetes resources, we should be able to see those too?

[nikovirtala@thinky whalesay ]$ kubectl get deployments

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE

whalesay 3 3 3 3 10m

[nikovirtala@thinky whalesay ]$ kubectl get services

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2h

whalesay NodePort 10.105.164.190 <none> 80:30001/TCP 10m

[nikovirtala@thinky whalesay ]$ kubectl get pods

NAME READY STATUS RESTARTS AGE

whalesay-c47584b84-np4cs 1/1 Running 0 10m

whalesay-c47584b84-nzzhp 1/1 Running 0 10m

whalesay-c47584b84-zk8nn 1/1 Running 0 10m

As expected the resources we defined are deployed in to our Kubernetes cluster. So, no magic, just a little automation that makes developer happy.

The voice back of your head may say now: Hey, that is not continuous! —Yes, it is not until we make some change to our code … let me show you a little video: