When you work with Kubernetes and want to learn more about its internal workings and how to use the API, you will sooner or later reach the point at which the documentation can no longer answer all your questions and you need to consult the one and only source of truth – the source code of Kubernetes and a plethora of examples. Of course all of this is not written in my beloved Python (nor in C or Java) but in Go. So I decided to come up with a short series of posts that documents my own efforts to learn Go, using the Kubernetes source code as an example.
Note that this is not a stand-alone introduction into Go – there are many other sites doing this, like the Tour of Go on the official Golang home page, the free Golang book or many many blogs like Yourbasic. Rather, it is meant as an illustration of Go concepts for programmers with a background in a language like C (preferred), Java or Python which will help you to read and understand the Kubernetes server and client code.
What is Go(lang)?
Go – sometimes called Golang – is a programming language that was developed by Google engineers in an effort to create an easy to learn programming language well suited for programming fast, multithreaded servers and web applications. Some of its syntax and ideas actually remind me of C – there are pointers and structs – other of things I have seen in Python like slices. If you know any of these languages, you will find your way through the Go syntax quickly.
Go compiles code into native statically linked executables, which makes them easy to deploy. And Go comes with built-in support for multithreading, which makes it comparatively easy to build server-like applications. This blog lists some of the features of the Go language and compares them to concepts known from other languages.
Installation
The first thing, of course, is to install the Go environment. As Go is evolving rapidly, the packages offered by your distribution will most likely be outdated. To install the (fairly recent) version 1.10 on my Linux system, I have therefore downloaded the binary distribution using
wget https://dl.google.com/go/go1.10.8.linux-amd64.tar.gz gzip -d go1.10.8.linux-amd64.tar.gz tar xvf go1.10.8.linux-amd64.tar
in the directory where I wanted Go to be installed (I have used a subdirectory $HOME/Local for that purpose, but you might want to use /usr/local for a system-wide installation).
To resolve dependencies at compile time, Go uses a couple of standard directories – more on this below. These directories are stored in environment variables. To set these variables, add the following to your shell configuration script (.bashrc or .profile, depending on your system). Here GOROOT needs to point to the sub-directory go which the commands above will generate.
export GOROOT=directory-in-which-you-did-install-go/go export GOPATH=$HOME/go export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
The most important executable in $GOROOT/bin is the go utility itself. This program can operate in various modes – go build
will build a program, go get
will install a package and so forth. You can run go help
for a full list of available commands.
Packages and imports
A Go program is built from a collection of packages. Each source code file is part of a package, defined via the package declaration at the start of the file. Packages can be imported by other packages, which is the basis for reusable libraries. To resolve package names, Go has two different mechanisms – the GOPATH mode and the newer module mode available since Go 1.11. Here we will only discuss and use the old GOPATH mode.
In this mode, the essential idea is that your entire Go code is organized in a workspace, i.e. in one top-level directory, typically located in your home directory. In my case, my workspace is $HOME/go. To tell the Go build system about your workspace, you have to export the GOPATH environment variable.
export GOPATH=$HOME/go
The workspace directory follows a standard layout and typically contains the following subdirectories.
- src – this is where all the source files live
- pkg – here compiled versions of packages will be installed
- bin – this is were executables resulting out of a build process will be moved
Let us try this out. Run the following commands to download the Kubernetes Go client package and some standard libraries.
go get k8s.io/client-go/... go get golang.org/x/tools/...
When this has completed, let us take a look at our GOPATH directory to see what has happened.
$ ls $GOPATH/src github.com golang.org gopkg.in k8s.io
So the Go utility has actually put the source code of the downloaded package into our workspace. As GOPATH points to this workspace, the Go build process can resolve package names and map them into this workspace. If, for instance, we refer to the package k8s.io/client-go/kubernetes in our source code (as we will do it in the example later on), the Go compiler will look for this package in
GOPATH/src/k8s.io/client-go/kubernetes
To get information on a package, we can use the list command of the Go utility. Let us try this out.
$ go list -json k8s.io/client-go/kubernetes { "Dir": "[REDACTED - this is $GOPATH]/src/k8s.io/client-go/kubernetes", "ImportPath": "k8s.io/client-go/kubernetes", "ImportComment": "k8s.io/client-go/kubernetes", [REDACTED - SOME MORE LINES] "Root": "[REDACTED - THIS SHOULD BE $GOROOT]", "GoFiles": [ "clientset.go", "doc.go", "import.go" ], [... REDACTED - MORE OUTPUT ...]
Here I have removed a few lines and redacted the output a bit. We see that the Dir field is the directory in which the compiler will look for the code constituting the package. The top-level directory is the directory to which GOPATH points. The import path is the path that an import statement in a program using this package would be. The list GoFiles is a list of all files that are parts of this package. If you inspect these files, you will in fact find that the first statement they contain is
package kubernetes
indicating that they belong to the Kubernetes package. You will see that the package name (defined in the source code) equals the last part of the full import path (part of the filesystem structure) by convention (which, as far as I understand, is not enforced technically).
I recommend to spend some time reading more on typical layouts of Go packages here ore here.
We have reached the end of our first post in this series. In the next post, we will write our first example program which will list nodes in a Kubernetes cluster.
1 Comment