A management cluster runs the control planes of other clusters as pods, and separate workers talk only to their own API through a published endpoint. It has the same shape as OpenShift HCP (ROSA HCP), built from upstream pieces so you can break it and understand it.
clusters-<name> namespace on the management cluster, with its own etcd. The workers are not management cluster nodes: they join the tenant's API through the LoadBalancer IP. The konnectivity-agent opens the tunnel from the worker, and kubectl logs, exec and port-forward travel back down that same tunnel. From your machine, each tenant's API is published on a fixed NodePort.| OpenShift HCP / HyperShift | In this lab |
|---|---|
| Management cluster | kind cluster hcp-mgmt |
Nodes for control planes (label hypershift.openshift.io/control-plane) | hcp-mgmt-worker and hcp-mgmt-worker2, with the same label |
| HyperShift operator | Kamaji, in kamaji-system |
HostedCluster + HostedControlPlane | TenantControlPlane (kubectl get tcp -A) |
Namespace clusters-<name> | Namespace clusters-<name> |
| etcd por hosted cluster | dedicated etcd por tenant (chart kamaji-etcd) |
| API publishing (NLB / Route) | LoadBalancer Service via MetalLB |
| Konnectivity | konnectivity-server in the control plane pod, agent as a DaemonSet in the tenant |
NodePool | 04-nodepool.sh: kindest/node containers |
| Ignition / node bootstrap | kubeadm join with a 15-minute token |
Control plane upgrade (spec.release) | spec.kubernetes.version on the TCP |
NodePool upgrade (upgradeType: Replace) | Rolling replacement, one node at a time |
| OVN-Kubernetes | kindnet |
Same as HCP: first you bump the control plane version, which only restarts pods on the management cluster. The workers stay on the previous version until you upgrade their NodePool.
Runs on macOS (podman machine) and on Fedora, CentOS Stream or RHEL hosts with 6 CPUs and 10–12 GB of RAM. The scripts download kind, helm and kubectl into the repo and use their own kubeconfigs, so they never touch ~/.kube/config.
git clone https://github.com/mcipamo/hcp-kind-lab.git && cd hcp-kind-lab # Linux (Fedora / CentOS Stream / RHEL), once: ./scripts/00-linux-host-prep.sh # macOS, once, if the podman VM is too small: podman machine stop && podman machine set --cpus 6 --memory 12288 && podman machine start ./scripts/01-mgmt-cluster.sh # management cluster ./scripts/02-addons.sh # cert-manager, MetalLB, Kamaji ./scripts/03-hosted-cluster.sh tenant-a 1 # hosted cluster in slot 1 → 127.0.0.1:31441 ./scripts/04-nodepool.sh tenant-a 1 # one worker in its data plane ./scripts/status.sh # overview source env.sh kubectl get tcp -A # control planes, seen from the management cluster hkubectl tenant-a get nodes # inside the hosted cluster ./scripts/stop.sh # pause for the day ./scripts/start.sh # resume, ~3 minutes
hkubectl tenant-a get pods -n kube-system shows no apiserver and no etcd.konnectivity-agent DaemonSet and try logs: No agent available. Kamaji brings it back in about 10 seconds.crictl ps on the worker.replicas to 2 and watch them spread across both management nodes.04-nodepool.sh tenant-a 3 and back to 1, drain included.podman stop hcp-mgmt-worker2: which control plane goes down? What about its etcd on a local PVC?kind network with the management cluster.nf_conntrack_max: permission deniedkube-proxy rewrites the value whenever it is not exactly 32768 × CPUs, and inside a container it is not allowed to. 01-mgmt-cluster.sh sets that exact value on the container host. Re-run it after restarting the VM or changing its CPUs.
ContainerCreating with flannelflannel delegates to the bridge CNI plugin, and kindest/node images do not ship it. That is why the lab uses kindnet, which comes inside the image itself.
logs and exec: No agent availableThe konnectivity-agent is not running, almost always because pod networking is broken. Since kubectl logs does not work either, debug from the node: podman exec <worker> crictl logs ….