hey,

as someone who’s been working with Kubernetes for a while, having had explored kubectl’s source code a few times, I figured it’d be a good time to share my preferred way of installing it in a machine.

yes, this is very biased!

precompiled

if all that you care is just grabbing the binary and go about interacting with a cluster, this is for you.

just follow what’s in the website, and you’re good to go :)

my preferences here are:

and really, you’re probably good with that.

but, maybe you want to explore kubectl and play around with the source code to learn more about it.

from source

sure, kubernetes/kubernetes is quite big, but, at the end of the day, it’s not that hard to build and install the components that make up Kubernetes, especially kubectl.

naturally, being all Go-based, the first thing you need to do is install Go.

installing go

on macos, homebrew is my way to go:

# install the latest version of go
#
brew install go

and on linux, de-archive the tarball in the right location and that’s it (if you need to update you can use go get to do so later on, or just download and de-archive in the same location).

# download and uncompress into `/usr/local` at the same time
#
curl -SL https://golang.org/dl/go1.15.5.linux-amd64.tar.gz | tar xvzf - -C /usr/local


# point PATH to `$GOBIN` so that binaries installed via `go install` will be
# easily found by bash.
#
echo "
export GOPATH=$HOME/go
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
" >> ~/.bashrc

building kubectl

differently from other components (like kube-apiserver), for kubectl there’s no code generation needed - it’s a straightforward Go binary install:

  1. clone the repository
# clone the whole repo (or, add a `--depth 1` to do a shallow clone if you don't
# care about the history - i.e., if you're going to build from `master` anyway)
#
git clone https://github.com/kubernetes/kubernetes
cd kubernetes
  1. go install it
# jump into the directory where we can find `kubectl`'s main package
#
cd ./cmd/kubectl


# install it. this will make the binary available under `$GOBIN/kubectl` (which
# should be under your `$PATH`.
#
go install -v

OH NO, what’s up with versions?

with a build like that, you’ll miss version information, some other little tweaks to the final binary, but that’s mostly it.

for instance, kubectl version will look kinda odd:

$ kubectl version
Client Version: version.Info{
        Major:"", Minor:"", 
        GitVersion:"v0.0.0-master+$Format:%h$", 
        GitCommit:"$Format:%H$", 
        GitTreeState:"", 
        BuildDate:"1970-01-01T00:00:00Z", 
        GoVersion:"go1.15.1", 
        Compiler:"gc", 
        Platform:"linux/amd64",
}

that’s because the version command gets that information from variables that are only initialized at build time.

for instance, looking at the version command source code, we can notice it retrieving version information from component-base’s version package:

import "k8s.io/component-base/version"

func (o *Options) Run() error {

	clientVersion := version.Get()
	versionInfo.ClientVersion = &clientVersion

	// ...
}
func Get() apimachineryversion.Info {
        // These variables typically come from -ldflags settings and in
        // their absence fallback to the settings in ./base.go
        return apimachineryversion.Info{
                Major:        gitMajor,
                Minor:        gitMinor,
                GitVersion:   gitVersion,
                GitCommit:    gitCommit,
                GitTreeState: gitTreeState,
                BuildDate:    buildDate,
                GoVersion:    runtime.Version(),
                Compiler:     runtime.Compiler,
                Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
        }
}

(^ see component-base/version/version.go)

package version

// Base version information.
//
// ...
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
// ...
var (
        // ...
	gitVersion   = "v0.0.0-master+$Format:%h$"
	gitCommit    = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState = ""            // state of git tree, either "clean" or "dirty"
	buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)

(^ see component-base/version/base.go)

to have those fields set then, we can instead of going with our straight go install, switch to using the targets from the repostory’s makefile:

$ make WHAT=cmd/kubectl
+++ [1214 15:58:45] Building go targets for linux/amd64:
    cmd/kubectl

with it, the final go build command that runs will end up including all of the variables that are expected to be filled during build time

-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.buildDate=2020-12-14T21:07:57Z'
-X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.buildDate=2020-12-14T21:07:57Z'
-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitCommit=87984d84d18c7816835e5efedebb17ed13aa2509'
-X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitCommit=87984d84d18c7816835e5efedebb17ed13aa2509'
-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitTreeState=dirty'
-X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitTreeState=dirty'
-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitVersion=v1.20.0-beta.0.1281+87984d84d18c78-dirty'
-X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitVersion=v1.20.0-beta.0.1281+87984d84d18c78-dirty'
-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMajor=1'
-X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMajor=1'
-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMinor=20+'
-X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMinor=20+'

the only “cost” of using make CMD=what is that you’ll have to have rsync so that some automatically generated code can be synchronized before the build (things that kubectl itself doesn’t need but as the target is super generic, you get to pay that little cost).

ps.: if you’re curious about how that information is gathered, check out hack/lib/version.sh, which is used during the build under hack/lib/golang.sh which is called by the default target under build/root/Makefile.