Creating a Go Kubernetes client with client-go
Tags: kubernetes, go
Published: 2021-01-17 17:35:50

I mostly use [client-go](https://github.com/kubernetes/client-go) to talk to the Kubernetes API in my [go](https://golang.org/) applications.

Whenever I'm starting a new project I inevitably have to figure out the setup steps. I thought I'll just document these for myself here.

### A `client-go` "Hello, World!"

Creating a client that implements `kubernetes.Interface` is just `kubernetes.NewForConfig()` 

```go
import "k8s.io/client-go/kubernetes"

client, err := kubernetes.NewForConfig(kubeconfig)
if err != nil {
	return nil, fmt.Errorf("unable to create a client: %v", err)
}
```

### Creating Client Config

Creating the config defers for go clients within and outside of the clients. I think this is mostly because [access tokens](https://www.oauth.com/oauth2-servers/access-tokens/) need to be read to from a mounted file rather than the [kubeconfig file](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/).

### Creating out of cluster config

```go
import "k8s.io/client-go/tools/clientcmd"

config, err := clientcmd.BuildConfigFromFlags(
    "", kubeconfigPath,
)
if err != nil {
	return nil, fmt.Errorf(
	    "unable to load kubeconfig from %s: %v", 
	    kubeconfigPath, err,
	    )
}
```

### Creating in cluster config

```go
import "k8s.io/client-go/rest"

config, err := rest.InClusterConfig()
if err != nil {
	return nil, fmt.Errorf("unable to load in-cluster config: %v", err)
}
```

### Putting it all together

```go
import (
	"fmt"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

func createClient(kubeconfigPath string) (kubernetes.Interface, error) {
	var kubeconfig *rest.Config

	if kubeconfigPath != "" {
		config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
		if err != nil {
			return nil, fmt.Errorf("unable to load kubeconfig from %s: %v", kubeconfigPath, err)
		}
		kubeconfig = config
	} else {
		config, err := rest.InClusterConfig()
		if err != nil {
			return nil, fmt.Errorf("unable to load in-cluster config: %v", err)
		}
		kubeconfig = config
	}

	client, err := kubernetes.NewForConfig(kubeconfig)
	if err != nil {
		return nil, fmt.Errorf("unable to create a client: %v", err)
	}

	return client, nil
}
```

### Testing with Fake Client

When testing `client-go` code it's useful to use the built-in [fake client](https://github.com/kubernetes/client-go/tree/master/examples/fake-client). Here's a short example:

```go
import (
	"context"
	"testing"

	"k8s.io/client-go/kubernetes/fake"
	appsv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"github.com/stretchr/testify/require"
)

func TestKubeClient(t *testing.T) {
	fc := fake.NewSimpleClientset(&exampleDeploy)

	deploy, err := fc.AppsV1().Deployments("ns").Get(
		context.Background(),
		"deployment",
		metav1.GetOptions{},
	)

	require.NoError(t, err)
```

### Managing package dependencies

`client-go` version needs to match that of the cluster we're using. I have sometimes had trouble with importing the right `k8s.io/api` and `k8s.io/apimachinery` dependencies so here's a example for that as well.


**`go.mod`**

```go

module github.com/muaazsaleem/example

go 1.13

require (
// we only really need client-go here the right `api` and 
// `apimachinery` versions "should" get picked up
	k8s.io/client-go v0.19.0
	k8s.io/api v0.19.0
	k8s.io/apimachinery v0.19.0
// stuff I use in pretty much all my projects
	github.com/sirupsen/logrus v1.7.0
	github.com/stretchr/testify v1.6.1
)
```

### Further Resources

If you'd like to learn more about building things with the Kubernetes API, Vladimir Vivien has a [whole series](https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-part-4-using-go-b1d0e3c1c899) on it.

The `client-go` [docs](https://github.com/kubernetes/client-go/) have also improved. There are [examples](https://github.com/kubernetes/client-go/tree/master/examples) now!