Skaffold is a recent project from Google recently covered in an article by CBR. It is meant to be a simple pipeline tool for building container images. It dates back to January 3 2018 by Matt Rickard who used to work at Google on their containers team. Skaffold was later announced on google’s container blog March 2018. Google has since open-sourced it (and now has over 5200 commits by over 150 developers and just GA’ed it a week ago (Nov 7 2019).
It declares itself 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”. In short, a fast and light k8s-based CI/CD tool for things like golang.
Getting Started (with Getting Started)
Get skaffold from https://skaffold.dev/
$ brew update && brew install skaffold
Updated 2 taps (homebrew/core and homebrew/bundle).
==> Updated Formulae
aws-cdk exploitdb jfrog-cli-go now-cli yadm
bullet goreleaser lazygit suil
dvc hcloud mdbook ungit
==> Downloading https://homebrew.bintray.com/bottles/skaffold-1.0.0.mojave.bottl
==> Downloading from https://akamai.bintray.com/d9/d9591089d76a7cb833cf3f09a87ef
######################################################################## 100.0%
==> Pouring skaffold-1.0.0.mojave.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
/usr/local/etc/bash_completion.d
zsh completions have been installed to:
/usr/local/share/zsh/site-functions
==> Summary
🍺 /usr/local/Cellar/skaffold/1.0.0: 8 files, 40.5MB
Let’s start with their Hello World sample app:
git clone https://github.com/GoogleContainerTools/skaffold
We see there are just a few files skaffold needs to build and test a go lang app:
$ cd examples/getting-started/
$ ls
Dockerfile README.adoc k8s-pod.yaml main.go skaffold.yaml
It’s actually quite tight. First, the Dockerfile itself is really the build agent. It handles the copy and build of the golang app:
$ cat k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: getting-started
spec:
containers:
- name: getting-started
image: gcr.io/k8s-skaffold/skaffold-example
$ cat Dockerfile
FROM golang:1.12.9-alpine3.10 as builder
COPY main.go .
RUN go build -o /app main.go
FROM alpine:3.10
CMD ["./app"]
COPY --from=builder /app .
The skaffold.yaml tells skaffold which pods should be launched:
$ cat skaffold.yaml
apiVersion: skaffold/v1
kind: Config
build:
artifacts:
- image: gcr.io/k8s-skaffold/skaffold-example
deploy:
kubectl:
manifests:
- k8s-*
Such as:
$ cat k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: getting-started
spec:
containers:
- name: getting-started
image: gcr.io/k8s-skaffold/skaffold-example
My first build failed since it seems it needs my mac’s local docker to auth to gcloud..
$ skaffold dev
WARN[0000] port 50051 for gRPC server already in use: using 50052 instead
WARN[0000] port 50052 for gRPC HTTP server already in use: using 50053 instead
Listing files to watch...
- gcr.io/k8s-skaffold/skaffold-example
Generating tags...
- gcr.io/k8s-skaffold/skaffold-example -> gcr.io/k8s-skaffold/skaffold-example:v1.0.0-21-g4dd6b59e
Checking cache...
- gcr.io/k8s-skaffold/skaffold-example: Not found. Building
Building [gcr.io/k8s-skaffold/skaffold-example]...
Sending build context to Docker daemon 3.072kB
Step 1/6 : FROM golang:1.12.9-alpine3.10 as builder
---> e0d646523991
Step 2/6 : COPY main.go .
---> Using cache
---> 04f78252d617
Step 3/6 : RUN go build -o /app main.go
---> Using cache
---> e58e2f45ea3b
Step 4/6 : FROM alpine:3.10
---> 965ea09ff2eb
Step 5/6 : CMD ["./app"]
---> Using cache
---> c8d90b8a4f75
Step 6/6 : COPY --from=builder /app .
---> Using cache
---> b85e9ef75d32
Successfully built b85e9ef75d32
Successfully tagged gcr.io/k8s-skaffold/skaffold-example:v1.0.0-21-g4dd6b59e
The push refers to repository [gcr.io/k8s-skaffold/skaffold-example]
7aecf8524e6c: Preparing
77cae8ab23bf: Preparing
FATA[0001] exiting dev mode because first build failed: build failed: build failed: building [gcr.io/k8s-skaffold/skaffold-example]: build artifact: unauthorized: You don't have the needed permissions to perform this operation, and you may have invalid credentials. To authenticate your request, follow the steps in: https://cloud.google.com/container-registry/docs/advanced-authentication
I had to pause and install gcloud cli: https://cloud.google.com/sdk/docs/downloads-interactive
Once done, i could add gcloud as a docker auth provider:
$ gcloud auth configure-docker
The following settings will be added to your Docker config file
located at [/Users/Isaac/.docker/config.json]:
{
"credHelpers": {
"gcr.io": "gcloud",
"us.gcr.io": "gcloud",
"eu.gcr.io": "gcloud",
"asia.gcr.io": "gcloud",
"staging-k8s.gcr.io": "gcloud",
"marketplace.gcr.io": "gcloud"
}
}
That just brought me to another error…
Successfully built b85e9ef75d32
Successfully tagged gcr.io/k8s-skaffold/skaffold-example:v1.0.0-21-g4dd6b59e
The push refers to repository [gcr.io/k8s-skaffold/skaffold-example]
7aecf8524e6c: Preparing
77cae8ab23bf: Preparing
FATA[0003] exiting dev mode because first build failed: build failed: build failed: building [gcr.io/k8s-skaffold/skaffold-example]: build artifact: denied: Token exchange failed for project 'k8s-skaffold'. Caller does not have permission 'storage.buckets.get'. To configure permissions, follow instructions at: https://cloud.google.com/container-registry/docs/access-control
It wasn’t obvious to me at first… but the “k8s-skaffold” in the yaml files was supposed to be your project.
So if we circle back to the output from my gcloud login:
...
Pick cloud project to use:
[1] api-project-799042244963
[2] careful-compass-241122
[3] myexpressapp
[4] pebbletaskapp
[5] Create a new project
Please enter numeric choice or text value (must exactly match list
item): 1
Your current project has been set to: [api-project-799042244963].
...
We see i authed into my old api-project (api-project-799042244963). So i changed the files to refer to my project:
$ cat skaffold.yaml
apiVersion: skaffold/v1
kind: Config
build:
artifacts:
- image: gcr.io/api-project-799042244963/skaffold-example
deploy:
kubectl:
manifests:
- k8s-*
$ cat k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: getting-started
spec:
containers:
- name: getting-started
image: gcr.io/api-project-799042244963/skaffold-example
Then it worked:
$ skaffold dev
WARN[0000] port 50051 for gRPC server already in use: using 50052 instead
WARN[0000] port 50052 for gRPC HTTP server already in use: using 50053 instead
Listing files to watch...
- gcr.io/api-project-799042244963/skaffold-example
Generating tags...
- gcr.io/api-project-799042244963/skaffold-example -> gcr.io/api-project-799042244963/skaffold-example:v1.0.0-21-g4dd6b59e-dirty
Checking cache...
- gcr.io/api-project-799042244963/skaffold-example: Not found. Building
Building [gcr.io/api-project-799042244963/skaffold-example]...
Sending build context to Docker daemon 3.072kB
Step 1/6 : FROM golang:1.12.9-alpine3.10 as builder
---> e0d646523991
Step 2/6 : COPY main.go .
---> Using cache
---> 04f78252d617
Step 3/6 : RUN go build -o /app main.go
---> Using cache
---> e58e2f45ea3b
Step 4/6 : FROM alpine:3.10
---> 965ea09ff2eb
Step 5/6 : CMD ["./app"]
---> Using cache
---> c8d90b8a4f75
Step 6/6 : COPY --from=builder /app .
---> Using cache
---> b85e9ef75d32
Successfully built b85e9ef75d32
Successfully tagged gcr.io/api-project-799042244963/skaffold-example:v1.0.0-21-g4dd6b59e-dirty
The push refers to repository [gcr.io/api-project-799042244963/skaffold-example]
7aecf8524e6c: Preparing
77cae8ab23bf: Preparing
77cae8ab23bf: Layer already exists
7aecf8524e6c: Pushed
v1.0.0-21-g4dd6b59e-dirty: digest: sha256:055534d214782d521595db84a2ef80bbb18f58adbf20388b3df6248611d0cbd6 size: 739
Tags used in deployment:
- gcr.io/api-project-799042244963/skaffold-example -> gcr.io/api-project-799042244963/skaffold-example:v1.0.0-21-g4dd6b59e-dirty@sha256:055534d214782d521595db84a2ef80bbb18f58adbf20388b3df6248611d0cbd6
Starting deploy...
- pod/getting-started created
Watching for changes...
#And ctrl-c to delete:
^CCleaning up...
- pod "getting-started" deleted
However, you will see the container is left in your cloud storage (so you will want to delete it so as not to incur charges):
Note: leaving it a day showed just 1 cent so it wont be a large cost:
Using Azure
One nice thing is it assumes kubectl commands and will use whatever registry you set. So I changed to a current AKS and ACR and had only to log into ACR:
JOHNSI10-M1:getting-started johnsi10$ cat k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: getting-started
spec:
containers:
- name: getting-started
image: usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example
JOHNSI10-M1:getting-started johnsi10$ cat skaffold.yaml
apiVersion: skaffold/v1
kind: Config
build:
artifacts:
- image: usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example
deploy:
kubectl:
manifests:
- k8s-*
JOHNSI10-M1:getting-started johnsi10$ az acr login --name usxxxxxxxxcr
Login Succeeded
We can see (when i was running it), that the container is there in the default namespace:
JOHNSI10-M1:terraform johnsi10$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
default getting-started 1/1 Running 0 19s
kube-system coredns-7fc597cc45-crvll 1/1 Running 0 36m
kube-system coredns-7fc597cc45-hr4hc 1/1 Running 0 42m
running:
JOHNSI10-M1:getting-started johnsi10$ skaffold dev
WARN[0000] port 50051 for gRPC server already in use: using 50052 instead
WARN[0000] port 50052 for gRPC HTTP server already in use: using 50053 instead
Listing files to watch...
- usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example
Generating tags...
- usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example -> usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example:v1.0.0-21-g4dd6b59e-dirty
Checking cache...
- usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example: Found. Pushing
The push refers to repository [usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example]
7aecf8524e6c: Preparing
77cae8ab23bf: Preparing
7aecf8524e6c: Pushed
77cae8ab23bf: Pushed
v1.0.0-21-g4dd6b59e-dirty: digest: sha256:055534d214782d521595db84a2ef80bbb18f58adbf20388b3df6248611d0cbd6 size: 739
Tags used in deployment:
- usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example -> usxxxxxxxxcr.azurecr.io/skaffold-project/skaffold-example:v1.0.0-21-g4dd6b59e-dirty@sha256:055534d214782d521595db84a2ef80bbb18f58adbf20388b3df6248611d0cbd6
Starting deploy...
- pod/getting-started created
Watching for changes...
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
...
Summary
Skaffold holds a lot of promise for quick kubernetes development based system. As it uses native kubectl and docker push/pull it is easy to migrate to any container registry or kubernetes cluster of one’s choosing.
I would like to see how well this might work with k3s and pi3 based clusters. It will be worth following up with a more deep dive as it shows a lot of promise.