ko: fast Kubernetes microservice development in Go

Matthew Moore
4 min readDec 18, 2018

2019–06–19 Update: This post has been moved to knative.dev/blog

I originally wrote ko to help Knative developers. I was prompted to write this introductory post by the positive feedback from the community, including an IBM booth talk on ko during recent Kubecon Seattle 2018. I hope you enjoy using ko as much as we do, and I look forward to your feedback on slack.knative.dev.

Over the past few years, there has been a lot of hype about containers. Docker, Kubernetes and related technology have taken the public cloud by storm (pun intended). At the same time, it seems, as software projects grow increasingly more complex, so too does the development process.

What starts as:

Quickly becomes:

Tools such as skaffold can wrap this process for arbitrary languages and Dockerfiles to make it easier to manage (and faster), but you still need to write artisanal hand-crafted Dockerfiles, and typically need to write more yaml (or other) to tell the tooling how to orchestrate this (e.g. what gets pushed where?):

ko takes a different approach that leans into Go idioms to eliminate configuration.

One such Go idiom is that binaries are referenced by “import paths”; a typical way of installing a Go binary would be:

# e.g. installing ko itself
go get github.com/google/ko/cmd/ko

Getting started with ko does not take any additional configuration files, you simply replace references to container images with import paths:

# This example is based on:
# https://github.com/google/ko/blob/master/cmd/ko/test/test.yaml
apiVersion: v1
kind: Pod
name: kodata
- name: test
# ko builds and publishes this Go binary, and replaces this
# with an image name.
image: github.com/google/ko/cmd/ko/test
restartPolicy: Never

That’s it.

How do I consume this with ko?

ko also needs to know where the user wants to publish their images. This is defined outside of the yaml manifest as generally each developer on your team will use their own.

For example, developing on Knative, I use this in my .bashrc file:

export KO_DOCKER_REPO=gcr.io/mattmoor-private/ko

NOTE: for DockerHub users (and possibly others), this should be: docker.io/username as DockerHub does not support multi-level repository names.

After that, the command-line interface is modeled after kubectl:

ko apply -f directory/ -f file.yaml

This will have the same net effect as kubectl apply, but it will also build, containerize, and publish the Go microservices referenced from the yamls as well, with significantly less configuration:

You only write Kubernetes yamls and code. No Dockerfiles, no Makefiles. You run one command and your latest code is running.

Following the above example (trimmed for width):

$ ko apply -f cmd/ko/test/test.yaml
Using base .. for github.com/google/ko/cmd/ko/test
Publishing gcr.io/mattmoor-public/test-01234abcd:latest
mounted blob: sha256:deadbeef
mounted blob: sha256:baadf00d
pushed blob sha256:deadf00d
pushed blob sha256:baadbeef
pushed blob sha256:beeff00d
gcr.io/mattmoor-public/test-01234abcd:latest: digest: ... size: 915
Published gcr.io/mattmoor-public/test-01234abcd@...
pod/kodata created
~/go/src/github.com/google/ko$ kubectl get pods
kodata 0/1 Completed 0 1

Just the image

The simplest trick that ko supports is to simply containerize and publish an image. One neat thing about this is that it works with most Go binaries without any knowledge of ko.

For example (trimmed for width):

$ ko publish ./cmd/goimports/
Using base .. for golang.org/x/tools/cmd/goimports
Publishing gcr.io/mattmoor-public/goimports-01234:latest
mounted blob: sha256:deadbeef
mounted blob: sha256:baadf00d
mounted blob: sha256:deadf00d
pushed blob sha256:baadbeef
pushed blob sha256:beeff00d
gcr.io/mattmoor-public/goimports-01234:latest: digest: ... size: 914
Published gcr.io/mattmoor-public/goimports-01234@...

ko is for releases too!

You can also use ko to publish things for redistribution via:

# This does everything `apply` does except it pipes to
# stdout instead of kubectl
ko resolve -f config/ > release.yaml
# Later...
kubectl apply -f release.yaml

For example, we use this to release all of the Knative components.

Try it out, and tell us what you think.

This just scratches the surface of what you can do with ko, and what ko does for you. For more information check out the README.md. If you have questions: #ko on slack.knative.dev, or reach me on Twitter @mattomata.

Some Common Pitfalls

A couple of things to be careful of with ko because of how heavily it relies on convention:

  1. You need to be on ${GOPATH} or it will not know what package you are in.
  2. Typos are the worst. Because ko is insensitive to schemas, it will ignore any string that is not the import path of a “main” package, so if you have a simple typo in your import path then it will be left as is and you will likely see your Pod ErrImagePull.