Tenant Layer¶
A tenant is an organisation, a team or a customer that consumes the platform without operating it.
They get real Kubernetes clusters — cluster-admin in each one — on hardware they share with other tenants and can never observe.
This page is about where the boundary between them sits, and what holds it.
What a tenant owns¶
Three objects, in that order:
| Tenant | Who they are. A Capsule Tenant names the identity that owns everything below. |
| Environment | Where their things live. A namespace on the under cluster, owned by the Tenant, with a VPC and subnets of its own. A Tenant collects as many as it needs — per stage, per region, per workload. |
| Cluster | What they run. A Cluster API Cluster in an Environment, with a hosted control plane and worker VMs. |
Inside their clusters, tenants are administrators in the full sense: they install what they like, define their own RBAC, run their own operators. The platform sets the frame around that — which Kubernetes versions, how much they may consume, which add-ons are mandatory — and does not reach inside it.
See Create First Tenant and Create First Environment for the walkthrough.
Hard multi-tenancy¶
kMetal does not rely on namespace isolation to keep tenants apart. Two tenants on the same hardware share the under cluster, but their workloads do not share a kernel, do not share a network domain, and do not share a control plane.
Four boundaries hold at once, and each is a different kind of thing — which is what makes the combination worth calling hard multi-tenancy. A weakness in one does not open the others.
Compute — a kernel per tenant, via KVM¶
Every tenant worker node is a KubeVirt virtual machine on the under cluster, running on KVM.
A tenant's containers therefore run inside the tenant's own kernel, inside the tenant's own VM. A container escape — the failure that ends a shared-kernel multi-tenancy story — buys an attacker the inside of that tenant's VM, and nothing else: not the host, not the under cluster, not another tenant.
This is the boundary that namespace-based multi-tenancy cannot offer at all, and it is the reason kMetal runs tenant nodes as machines rather than as namespaces.
Network — a VPC per tenant, via Kube-OVN¶
Each tenant gets a dedicated VPC — an OVN logical router with its own routing table — and one or more subnets inside it.
Cross-VPC traffic is dropped by default. A tenant cannot reach another tenant's pods, services or worker nodes, even knowing their addresses, because no route exists to be taken. A policy on every tenant VPC also blocks traffic toward the platform's own addresses, so a tenant cannot reach the under cluster's API server or platform services from a workload.
Tenant traffic runs on a Geneve overlay between under-cluster nodes, so isolation needs no per-tenant switch configuration and adding a tenant is a software operation. Where a tenant needs its egress separated on the wire as well, it can be bound to a provider network of its own.
See Networking for the VPC and subnet model in depth.
Control plane — a cluster of their own¶
Every tenant cluster has its own control plane: api-server, controller-manager, scheduler and konnectivity-server as pods on the under cluster, with its own etcd behind them.
One tenant's etcd is not visible to another, and authentication is independent — a tenant's kubeconfig is meaningless against any other tenant's cluster, because the certificate authority that signed it exists only for that cluster.
The tenant's own control plane is also, from their side, unreachable at the host level: there are no control plane nodes in a tenant cluster to log into or to attack. See Hosted Control Planes for what that changes about security posture.
Storage — consumed without ever holding a credential¶
The usual way to give a cluster storage is to install a CSI driver in it and hand it a Secret: array credentials, a Ceph keyring, a cloud API key.
On a multi-tenant platform that is a bad trade.
Every cluster becomes a copy of a credential to shared infrastructure, and every tenant's cluster-admin — legitimately theirs — effectively holds it.
kMetal does not do that.
The CSI controller runs on the under cluster, where the storage and its credentials already are.
What lands inside a tenant cluster is only the node-side half: the driver's node component, a StorageClass, and a VolumeSnapshotClass.
The tenant gets the full experience and none of the access.
They create a PersistentVolumeClaim, they take a VolumeSnapshot, and volumes are provisioned on the platform's storage and hot-plugged into their own worker VMs — while no credential for the storage backend ever exists inside their cluster.
That is what the boundary is worth. A tenant cluster compromised to the root — cluster-admin taken, a node escaped onto — yields no key to the array behind it, and therefore no path to another tenant's volumes or to the storage system itself. The blast radius stops at the tenant's own data, which is where a shared platform needs it to stop.
See Storage for how the split works.
Consuming, without being able to over-consume¶
The awkward part of multi-tenancy is not isolation — it is shared, finite resources. Storage capacity, routable IPv4, subnets and address space are all pools that every tenant draws from, and any one of them can be drained by a tenant acting entirely within their rights.
kMetal handles this by making everything a tenant asks for an ordinary namespaced object in one of their Environments: a Cluster, a VpcClaim, a SubnetClaim, an EipClaim, a PersistentVolumeClaim.
That has two consequences, and both matter.
They can be counted. A namespaced object is something a quota can bound, so a Tenant can be capped on external addresses, subnets, clusters or storage the same way it is capped on CPU — no bespoke accounting, and nothing cluster-scoped that slips past the count.
They can be refused with an explanation. A request over quota is rejected when it is made, with a message that says why. The alternative is what makes shared platforms miserable: an accepted request that silently never completes, and a tenant who cannot tell an exhausted quota from a broken platform without opening a ticket.
That is a deliberate design position — the tenant, not the platform team, should be the first to know they have hit their limit.
Reaching a tenant cluster¶
The control plane is published as a LoadBalancer Service with an address from the pool reserved for tenant control planes.
Worker nodes and users authenticate to it with certificates issued for that cluster alone.
Workloads are exposed with addresses the tenant claims themselves: an EipClaim in the Environment, referenced from a LoadBalancer Service in the cluster.
Traffic reaches the address on the provider network and is translated straight to the workload — the under cluster is not a hop in the data path.
Everything else inside the cluster is the tenant's business. Ingress controllers, service meshes, private connectivity back to their own network: they run them in their own cluster, the way they would anywhere else.
What is shared, honestly¶
The hardware is shared — that is the point of the platform, and the reason a cluster is cheap.
What that means in practice: tenants compete for the capacity of the under cluster's worker nodes, and for the storage behind the class they were given. Isolation keeps them out of each other's data and traffic; quota, not dedicated hardware, is what keeps them out of each other's capacity. Sizing those quotas is a platform-team decision, and an important one.
See Multi-Tenancy for how the boundaries are configured and enforced.