Note: at the time of writing, these instructions are using 0.4.2 Cluster API and 1.21.1 kubernetes
special thanks to Alex Gradouski and Abhijit Gaikwad for helping to debug these instructions
I think there has been some really cool development happening in the Cluster API community around testing. I know that might not sound like the most exciting thing, but from the perspective of someone who spends large amounts of time developing, running, and debugging things like the Kubernetes cluster autoscaler, having a method for building lightweight clusters for testing is invaluable. I’ll explain this idea more deeply, but first a little background.
Earlier this year, the Cluster API community had started developing a Kubemark provider. It had started as a project in its own repository, then was brought into a pull request on the main repository. Recently though, the community has decided to push forward with the separate repistory and we have added a few more maintainers to the project.
So, the community is fully behind the Cluster API Kubemark Provider and we are ramping up development there and working towards having some automated image builds and updates to the latest Cluster API version.
Now, back to the original thought, why is using Kubemark with Cluster API important?
Let’s start with what Kubemark is, and what it can do. Kubemark is an application that allows you to create “hollow” Kubernetes nodes. What this means is that the nodes do not actually run containers or attach storage, but they do behave like they did, with updates to etcd and all the trimmings.
This allows a user to create a cluster of kubemark nodes and then exercise their applications that manage nodes and other infrastructure pieces, and these components can be tested in isolation from the actual infrastructure provider. For testing an application like the cluster autoscaler, this is perfect as it allows us to exercise the main mechanisms of pending pods and node utilization without worrying about the underlying infrastructure. It can also be very helpful for testing the core Cluster API controllers, as well as other controllers like the Machine Health Checker.
As I have been doing a lot of work around the autoscaler and Cluster API communities in the last year, I am very excited to see what we do next with these tools. To that end I have needed to setup a reliable environment for working on Cluster API and kubemark.
Configuring a host for Cluster API
In the past, I have used Fedora (almost religiously) to create my development environments. But I have been challenged recently as I hit some blockers involving Docker, SELinux, and a wierd file system error that lead me to try Ubuntu for my environment this time around. So, the first thing I did was create a new virtual machine in Boxes on my Fedora workstation ;)
Starting with an Ubuntu 20.10 server, I install the following tools:
- Install Go language.
snap install go --classic
- Install build tools.
apt install make gcc
- Install docker.
apt install docker.io
- Add
$USER
to the docker group, this helps so that I can docker commands from the main user.usermod -a -G docker $USER
- Install kind, we will use
kind
for running all the clusters.go get sigs.k8s.io/kind
- Install kubectl.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
- Install kustomize.
go get sigs.k8s.io/kustomize/kustomize/v3
- Install kubebuilder.
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
The machine is now setup to build Cluster API and the kubemark provider, also anything else I might need.
Install Cluster API
No reason to duplicate the excellent guidance given by the project itself, I usually follow
the clusterctl for Developers
documentation. I build the clusterctl
binary, and also the controller images for Cluster API
and the Docker provider. If you are following these instructions, don’t forget to checkout the
v0.4.2
tag before building otherwise you will most likely run into strange API errors when
deploying your manifests.
Install Cluster API Kubemark provider
Installing the kubemark provider is relatively straightforward, clone the repository and then build the images locally.
-
git clone https://github.com/kubernetes-sigs/cluster-api-provider-kubemark
-
make docker-build REGISTRY=localhost:5000
. Note theREGISTRY
variable here, I am using this with the local registry feature in kind.
After building the code there are a few bits to setup in the local Cluster API configuration folder.
I add kubemark to the $HOME/.cluster-api/dev-repository/config.yaml
file,
the kubemark entry looks like this:
- name: "kubemark"
type: "InfrastructureProvider"
url: "$HOME/.cluster-api/dev-repository/infrastructure-kubemark/v1.0.99/infrastructure-components.yaml"
Note: you must replace $HOME
in that path
I also need to setup .HOME/.cluster-api/dev-repository/infrastructure-kubemark/v1.0.99/
, this directory
will contain all the configuration and template files for the kubemark provider. I copy the contents
of the templates
directory, the metadata.yaml
file, and the output of the make release-manifests
command which should be the
infrastructure-components.yaml
file.
Check the infrastructure-components.yaml
file to ensure that the images it is using are
avaible on your system (they should be if you used the build command above). Also
you might want to modify how your kubemark pods start to specify a different kubemark
image than the default. For example, I am using this:
spec:
containers:
- args:
- --metrics-addr=127.0.0.1:8080
- --enable-leader-election
- --kubemark-image=quay.io/elmiko/kubemark
command:
- /manager
image: localhost:5000/cluster-api-kubemark-controller-amd64:dev
imagePullPolicy: Always
name: manager
resources:
limits:
cpu: 100m
memory: 30Mi
requests:
cpu: 100m
memory: 20Mi
Running the Kubemark provider
If you have followed these instructions you should now have all the necessary images built in your local docker registry. The next steps involve setting up the kind cluster, installing the docker provider, and finally running a kubemark cluster.
Make sure to setup the local registry for kind, then start a kind cluster and install Cluster API similar to the description in the clusterctl for Developers documentation. My initialization command looks something like this:
clusterctl init \
--core cluster-api:v0.4.2 \
--bootstrap kubeadm:v0.4.2 \
--control-plane kubeadm:v0.4.2 \
--infrastructure kubemark:v1.0.99,docker:v0.4.2 \
--config ~/.cluster-api/dev-repository/config.yaml
Once the Cluster API controllers are up and running you can start deploying the control plane cluster for kubemark. I am currently using this definition:
apiVersion: cluster.x-k8s.io/v1alpha4
kind: Cluster
metadata:
name: km-cp
namespace: default
spec:
clusterNetwork:
pods:
cidrBlocks:
- 172.17.0.0/16
serviceDomain: cluster.local
services:
cidrBlocks:
- 192.168.122.0/24
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha4
kind: KubeadmControlPlane
name: km-cp-control-plane
namespace: default
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
kind: DockerCluster
name: km-cp
namespace: default
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
kind: DockerCluster
metadata:
name: km-cp
namespace: default
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
kind: DockerMachineTemplate
metadata:
name: km-cp-control-plane
namespace: default
spec:
template:
spec:
extraMounts:
- containerPath: /var/run/docker.sock
hostPath: /var/run/docker.sock
---
apiVersion: controlplane.cluster.x-k8s.io/v1alpha4
kind: KubeadmControlPlane
metadata:
name: km-cp-control-plane
namespace: default
spec:
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
certSANs:
- localhost
- 127.0.0.1
controllerManager:
extraArgs:
enable-hostpath-provisioner: "true"
initConfiguration:
nodeRegistration:
criSocket: /var/run/containerd/containerd.sock
kubeletExtraArgs:
cgroup-driver: cgroupfs
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
joinConfiguration:
nodeRegistration:
criSocket: /var/run/containerd/containerd.sock
kubeletExtraArgs:
cgroup-driver: cgroupfs
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
kind: DockerMachineTemplate
name: km-cp-control-plane
namespace: default
replicas: 1
version: v1.21.1
Once that is running and I have installed a container network interface (CNI) into
that cluster, the last step for my purposes is to create a MachineDeployment
that will contain more compute plane nodes for the cluster. I use this manifest
and then scale it using either kubectl
or the cluster autoscaler:
apiVersion: cluster.x-k8s.io/v1alpha4
kind: MachineDeployment
metadata:
annotations:
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "5"
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "1"
name: km-wl-kubemark-md-0
namespace: default
spec:
clusterName: km-cp
replicas: 4
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: km-cp
cluster.x-k8s.io/deployment-name: km-wl-kubemark-md-0
template:
metadata:
labels:
cluster.x-k8s.io/cluster-name: km-cp
cluster.x-k8s.io/deployment-name: km-wl-kubemark-md-0
spec:
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha4
kind: KubeadmConfigTemplate
name: km-wl-kubemark-md-0
clusterName: km-cp
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
kind: KubemarkMachineTemplate
name: km-wl-kubemark-md-0
version: 1.21.1
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
kind: KubemarkMachineTemplate
metadata:
labels:
cluster.x-k8s.io/cluster-name: km-cp
name: km-wl-kubemark-md-0
namespace: default
spec:
template:
spec: {}
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha4
kind: KubeadmConfigTemplate
metadata:
name: km-wl-kubemark-md-0
namespace: default
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
name: ''
Cluster API / Docker / Kubemark architecture
With these two manifests you can piece together how a docker/kubemark cluster is put together. You could even replace the docker cluster with a cluster on another infrastructure provider. To help simplify the discussion a little, let’s use this diagram:
On the host’s docker, we use kind
to create the CAPI Management Cluster which
is a container running the necessary kubernetes pieces. Next, we use the
clusterctl
tool to create a new “docker provider” cluster for the Kubemark
Control Plane Cluster, which is another container. Lastly, we want to create
new nodes for the Kubemark Control Plane Cluster, kubemark requires that we create
these hollow nodes as pods running on a cluster that can join the control plane.
The Cluster API kubemark provider then creates pods within the CAPI Management
Cluster which then join the Kubemark Control Plane Cluster as nodes.
It’s worthing noting that the kubemark nodes are also containers running on the
host docker, but that previous paragraph was getting quite convoluted!
You can use kind
to get kubeconfig files for both clusters and inspect how they
look. Notice where the machines and nodes are, and what pods are running
on each cluster.
I hope this information helps or inspires you to try out Cluster API and play around with this fun technology. Until next time, happy hacking =)