VCluster Introduction
Does a Kubernetes analog to Docker-in-Docker (DinD) exist?
Well it turns out that there is such a technology. However, it is not called KinK (for better or worse depending on your view). I am talking about vCluster. vCluster allows you to run a virtual Kubernetes cluster within your already existing host cluster. In fact, you can run a vCluster within another vCluster. What makes vCluster an amazing tool is that in order to create a virtual cluster you do not need elevated privileges within the cluster. In fact if you are able to deploy a simple web app in a namespace you are more than likely able to create a vCluster.
Use Cases
vCluster allows us to run a brand new cluster within our host cluster in a fashion that requires very minimal additional resource usage. Through using a virtual cluster we can more easily accomplish tasks that would sometimes require the creation and management of another host k8s cluster.
- Multi tenancy
- Virtual admin access within a controlled environment
- Test out new k8s versions
Although multi tenancy could be accomplished to some extent through the use of namespaces, the other two example use cases can not. However, if you use namespace isolation you can’t make a CRD available to one tenant but not another. You could restrict access to the CRD through k8s RBAC, but in some sense both tenants will “know” of the CRD since it is a cluster level resource. But with a vCluster, the CRDs defined in one cluster aren’t known to/shared with another vCluster.
Architecture
A vCluster consists of two main components, the control plane and syncer. The control plane is responsible for vCluster’s API server, controller manager, and the connection used to talk to its datastore. By default the used datastore is a sqlite database, but this can be swapped out with minimal impact on the architecture. While the syncer works with the host cluster’s scheduler to place pods on nodes and sharing this information with the vCluster. It is syncs other low level objects that involve networking, such as services, with the host cluster.
The control plane lives within a namespace of the host cluster, while the syncer lives within
the host cluster’s kube-system
namespace. A high level visual representation of the interaction
between the virtual and host cluster is depicted below, in an image copied from the vCluster
documentation.
Nodes and scheduling
Since a vCluster offloads the task of scheduling pods to the host cluster, it also makes sense
that it does not contain any of its own nodes. Instead the nodes that are returned by running
kubectl get nodes
from within the vCluster are fake nodes. One for each spec.nodeName
that
is encountered. In some cases, for instance running daemon sets, it is beneficial to copy the
real node’s data with the fake node used by vCluster. This is accomplished through modifying the
.sync.nodes.enabled
, .sync.nodes.syncAllNodes
, and .sync.nodes.nodeSelector
helm chart values.
Additionally, the scheduling of pods is delegated to the host cluster’s scheduler by default. However, this limits in the types of pod distributions we are able to enforce. This is due to the fact that some node data, labels for instance, are may not always be kept in sync with the node data of the vCluster. This is the case when using the default node syncing behavior. To overcome this you can do one of the following:
- Have the vCluster use its own scheduler instead of the host cluster’s scheduler.
- Provide helm values that allow the syncer to use an updated RBAC role to allow for more “powerful” use of the host scheduler.
- Enable node data syncing. This way the vCluster scheduler will adhere to rules caused by the node topology.
More information about vCluster node data syncing and pod scheduling can be found in the documentation (pod scheduling, node data).
Installation
In order to install vCluster you will first need to have helm installed. Once installed, the next step is to install the vCluster CLI. This will mostly be used to allow us to easily switch between the host cluster and any vCluster that we may create. Alternatively, we could change which KUBECONFIG file that is being used. But I find it easier to just use the CLI to perform this task.
The process used to install the CLI will vary depending on what OS you are running. Instructions
for Linux, Mac, and Windows can be found here. Once installed, we will need to add the loft-sh
helm repository.
helm repo add loft-sh https://charts.loft.sh
helm repo update
Once the loft-sh
repo is added you can use helm search repo loft-sh
to see which Kubernetes
variants can be used out of the box to create a virtual cluster. By default k3s will be used, but
you could also use k0s or even k8s. For this post I will use the default k3s variant and install
it within the k3d cluster that I have created (see previous post in this series).
To create a virtual cluster you can either use the vCluster CLI or use kubectl. I personally prefer to use the kubectl approach since I can save the cluster configuration. Which can later be reused or updated as needed.
kubectl create namespace my-vcluster-ns
helm template my-vcluster loft-sh/vcluster -n my-vcluster-ns > my_vcluster.yaml
kubectl apply -f my_vcluster.yaml
Or if you want, with the vCluster CLI (which will create the vcluster-my_vcluster
namespace
automatically).
vcluster create my_vcluster
After running one of the above commands you should check that the cluster is available either via
vcluster list
and see that the cluster is running or kubectl -n my-vcluster-ns get sts
and
see that all of the StatefulSet pods are running.
Expose Servers
With applications being added to the vCluster we would like the ability to interact with those applications from outside of our cluster. Be that outside of the vCluster, with host cluster apps sending it requests, or even from outside of the host cluster. Since a vCluster will sync its Pod and Services resources with the host cluster, host cluster to vCluster communication is possible by using the cluster’s DNS. Or if we want via port-forwarding or an Ingress within the host cluster.
However, it would be preferable to instead specify the Ingress resource within the vCluster
and not the host-cluster. This way we don’t have to look up the Service’s modified name within
the host cluster before creating the Ingress routing rule. Unfortunately, this capability isn’t
available out of the box by default since Ingress resources are stored within the vCluster and
not synced with the host cluster. But enabling the sharing of Ingress resources is possible with a
simple modification. We add the following to a values.yaml
file
sync:
ingresses:
enabled: true
Then run vcluster create my-vcluster --update -f values.yaml
. Or if you are creating a cluster
for the first time helm template my-vcluster loft-sh/vcluster -n my-vcluster-ns -f values.yaml
.