Kamaji and externalClusterReference usage
This document explains how to use Kamaji's externalClusterReference together with Cluster API (CAPI) to run Kubernetes control planes on an external cluster, while managing worker nodes from a management cluster.
It assumes the use of the KubeVirt infrastructure provider for ease of deployment and local testing.
High-level Architecture
The following setup operates on two Kubernetes clusters:
- Management cluster – runs Cluster API controllers and the Kamaji control-plane provider, and manages cluster lifecycle and topology.
- External cluster - runs Kamaji, hosts the Kubernetes control plane components and receives control plane workloads via Kamaji
Prerequisites
dockerkindkubectlclusterctlhelm
Step 1: Create the KIND clusters
Create the management cluster:
kind create cluster --name management
Create the external cluster that will host control planes:
kind create cluster --name external
Verify contexts:
kubectl config get-contexts
Step 2: Initialize Cluster API controllers
Switch to the management cluster:
kubectl config use-context kind-management
Enable ClusterClass support and initialize Cluster API with Kamaji and KubeVirt:
export CLUSTER_TOPOLOGY=true
clusterctl init \
--core cluster-api \
--bootstrap kubeadm \
--infrastructure kubevirt \
--control-plane kamaji
Step 3: Enable Kamaji external cluster feature gates
Patch the Kamaji controller to enable externalClusterReference:
kubectl -n kamaji-system patch deployment capi-kamaji-controller-manager \
--type='json' \
-p='[
{
"op": "replace",
"path": "/spec/template/spec/containers/0/args/1",
"value": "--feature-gates=ExternalClusterReference=true,ExternalClusterReferenceCrossNamespace=true"
}
]'
Step 4: Install KubeVirt
Fetch the latest stable KubeVirt version and install:
export VERSION=$(curl -s "https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt")
kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-operator.yaml"
kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-cr.yaml"
Enable emulation (optional, if virtualization is not supported):
kubectl -n kubevirt patch kubevirt kubevirt \
--type=merge \
--patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}'
Step 5: Prepare kubeconfig for the external cluster
Retrieve the external cluster control-plane address:
EXT_CP_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "external-control-plane")
Export and rewrite the kubeconfig:
kubectl --context kind-external config view --raw --minify --flatten > kind-external.kubeconfig
Replace the API endpoint with the cluster IP, required for cross-cluster access from the management cluster:
bash -c "sed -i -E 's#https://[^:]+:[0-9]+#https://$EXT_CP_IP:6443#g' kind-external.kubeconfig"
Create the kubeconfig secret in the management cluster:
kubectl -n default create secret generic kind-external-kubeconfig \
--from-file=kubeconfig=kind-external.kubeconfig
Step 6: Install Kamaji and dependencies on the external cluster
Switch context:
kubectl config use-context kind-external
Install cert-manager:
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
Install Kamaji:
helm upgrade --install kamaji clastix/kamaji \
--namespace kamaji-system \
--create-namespace \
--set 'resources=null' \
--version 0.0.0+latest
Install MetalLB:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml
Configure MetalLB IP address pool:
SUBNET=$(docker network inspect kind | jq -r '.[0].IPAM.Config[] | select(.Subnet | test(":") | not) | .Subnet' | head -n1)
NET_PREFIX=$(echo "$SUBNET" | cut -d/ -f1 | awk -F. '{print $1"."$2}')
kubectl apply -f - <<EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- ${NET_PREFIX}.255.200-${NET_PREFIX}.255.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
EOF
Create tenant namespace:
kubectl create namespace kamaji-tenants
Step 7: Definition of KamajiControlPlaneTemplate
The KamajiControlPlaneTemplate is defined in the following manifest and can be applied directly.
This template configures how Kamaji deploys and manages the tenant control plane on an external Kubernetes cluster using Cluster API.
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
metadata:
name: kamaji-controlplane-external
namespace: default
spec:
template:
spec:
addons:
coreDNS: {}
konnectivity: {}
kubeProxy: {}
dataStoreName: "default" # reference to DataStore present on external cluster
deployment:
externalClusterReference:
deploymentNamespace: kamaji-tenants
kubeconfigSecretName: kind-external-kubeconfig
kubeconfigSecretKey: kubeconfig
network:
serviceType: LoadBalancer
kubelet:
cgroupfs: systemd
preferredAddressTypes:
- InternalIP
registry: "registry.k8s.io"
The .spec.template.spec.deployment.externalClusterReference section defines how Kamaji connects to and deploys control plane components into the external cluster:
deploymentNamespace- The namespace on the external cluster whereTenantControlPlaneresources and control plane components are created.kubeconfigSecretName- The name of the Kubernetes Secret containing a kubeconfig that allows Kamaji to authenticate to the external cluster.kubeconfigSecretKey- The key inside the secret that holds the kubeconfig data.
The referenced secret must exist in the Kamaji management cluster and provide sufficient permissions to create and manage resources in the target external cluster.
Step 8: Create the Cluster
Switch context back to the management cluster:
kubectl config use-context kind-management
Apply the Cluster manifest:
kubectl apply -f "https://raw.githubusercontent.com/clastix/kamaji/master/config/capi/clusterclass-kubevirt-kamaji-external.yaml"
Validation
Check tenant control plane pods running in the external cluster:
kubectl --context kind-external -n kamaji-tenants get pods
Check cluster status in the management cluster:
kubectl --context kind-management get clusters
kubectl --context kind-management get kamajicontrolplanes
Get cluster kubeconfig and confirm it is working:
kubectl config use-context kind-management
clusterctl get kubeconfig demo-external > demo-external.kubeconfig
KUBECONFIG=./demo-external.kubeconfig kubectl get nodes
Clean up
Delete Kind clusters:
kind delete cluster --name management
kind delete cluster --name external
Summary
Using externalClusterReference with Kamaji and Cluster API enables:
- Hosted Kubernetes control planes on remote clusters
- Strong separation of concerns
- Multi-cluster management patterns
- Clean integration with ClusterClass