Coding, How to

K8s Operator with dynamic CRDs using controller runtime (no structs)

Warning: This is a bit of a brain dump.

I’m working on a project at the moment which dynamically creates a set of CRDs in Kubernetes and an operator to manage them based off a schema which is provided by the user at runtime.

When the code is being built it doesn’t know the schema/shape of the CRDs. This means the standard approach used in Kubebuilder with controller-gen isn’t going to work.

Now, for those that haven’t played with Kubebuilder it’s gives you a few super useful things to build a K8s operator in Go:

  1. Controller-gen creates all the structs, templated controllers and keeps all those type files in sync for you. So you change a CRDs Struct and the CRD Yaml spec is updated etc. These are all build time tools so we can’t use em.
  2. A nice abstraction around how to interact with K8s as a controller – The controller-runtime. As the name suggests we can use this one at runtime.

So while we can’t use the build time controller-gen we can still use all the goodness of the controller-runtime. In theory.

This is where the fun came in, there aren’t any docs on interacting with a dynamic/unstructured object type using the controller runtime so I did a bit of playing around.

(Code samples for illustration – if you want end2end running example skip to the bottom).

To get started on this journey we need a helping hand. Kuberentes has an API for working which objects which don’t have a Golang struct defined. This is how we can start: Lets check out the go docs for unstructured..

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

Ok so this gives us some nice ways to work with a CRD which doesn’t have a struct defined.

To use this meaningfully we’re going to have to tell it the type it represents – In K8s this means telling it it’s Group, Version and Kind. These are all wrapped up nicely in the schema.GroupVersionKind struct. Lets look at the docs:

"k8s.io/apimachinery/pkg/runtime/schema"

Great so hooking these two up together we can create an Unstructured instance that represents a CRD, like so!

Cool, so what can we do from here? Well the controller runtime uses the runtime.object interface for all it’s interactions and guess what we have now? Yup a runtime.Object.. wrapper method to make things obvious

Well now we can create an instance of the controller for our unstructured CRD.

Notice that I’m passing the GroupVersionKind into the controller struct – this will be useful when we come to make changes to a CRD we’re handling.

In the same way that you can use the r.Client on the controller in Kubebuilder you can now use it with the unstructured resource. We use the gvk again here to set the type so that the client knows how to work with it.

Now you might be thinking – wow isn’t it going to be painful working without the strongly typed CRD structs?

Yes it’s harder but there are some helper methods in the unstructured api which make things much easier. For example, the following let you easily retrieve or set a string which is nested inside it.

unstructured.NestedString(resource.Object, "status", "_tfoperator", "tfState")

unstructured.SetNestedField(resource.Object, string(serializedState), "status", "_tfoperator", "tfState")

Here is the end result hooking up the controller runtime to a set of dynamically created and managed CRDS. It’s very much a work in progress and I’d love feedback if there are easier ways to tackle this or things that I’ve got wrong.

Standard
Coding, How to, kubernetes

How to build Kubernetes from source and test in Kind with VSCode & devcontainers

I found myself last week looking at a bit of code in K8s which I thought I could make better, so I set about trying to understand how to clone, change and test it.

Luckily K8s has some good docs, trust these over me as they’re a great guide. This blog is more of a brain dump of how I got on trying with Devcontainers and VSCode. This is my first try at this so I’ve likely got lots of things wrong.

Roughly I knew what I needed as I’d heard about:

  1. Bazel for the Kubernetes build
  2. Kind to run a cluster locally
  3. Kubetest for running end2end tests

As the K8s build and testing cycle can use up quite a bit of machine power I didn’t want be doing all this on my laptop and ideally I wanted to capture all the setup in a nice repeatable way.

Enter DevContainers for VSCode, I’ve got them setup on my laptop to actually run on a meaty server for me and I can use them to capture all the setup requirements for building K8s.

Continue reading
Standard
Coding, How to, vscode

VSCode Container dev a Remote Docker Daemon [Brain dump]

Note: This is more a stream of consciousness than a blog post. It’s not detailed and mainly for my own memory on how to set this up. Be cautious.

So I’ve recently built a new home server/lab and wanted to use some of it’s power to run my daily dev stuff.

I’ve been playing a bit with VSCode Remote for Containers which lets you define you dev environment as a container and commit it along with your code… it’s super nice.

So how about hooking it up to a remote docker daemon so that I can use the power of the new server from my laptop when I’m at home but switch back to my local docker deamon when I’m traveling… turns out the team have thought of that!

There are some great docs here on how to do this in detail: https://code.visualstudio.com/docs/remote/containers-advanced#_developing-inside-a-container-on-a-remote-docker-host

What will follow is only useful if your like me, running Ubuntu on your laptop and also Ubuntu/Linux on the remote Daemon machine.

First up one of the key problems with this setup is with the file system access as a remote docker daemon on the server can’t see files on the Laptop. To get around this I’ve setup two alias which use rsync to push and pull data to and from the server from my laptop.

alias docker-remote-push="rsync -rlptv --progress \${PWD} \"lawrence@ubuntudev:\${PWD}/../\""

alias docker-remote-pull="rsync -rlptv --progress \"lawrence@ubuntudev:\${PWD}\" \${PWD}/../"

Then I can use docker context to setup a context for local and my ubuntudev remote box to easy switch between them.

Have a look here on how to create a context. https://docs.docker.com/engine/reference/commandline/context_create/

The last alias are to foward the docker for the remote deamon to my local box and switch to the context I’ve created for it.

alias docker-remote-start="ssh -fNL localhost:23750:/var/run/docker.sock lawrence@ubuntudev && docker context use remote"

alias docker-remote-stop="docker context use local"

Then all you need to do is add

 "docker.host":"tcp://localhost:23750"

To your settings.json in VSCode and now you are set to go.

Open up a folder in VSCode with a `.devcontainer` then run

docker-remote-push
docker-remote-start

Then use the `Reopen in container` option in VSCode and it’s done.

Standard