Multi-Tenancy Patterns and Which One Is Correct for You
published at 08-06-2026 by Dominik Heilbock
One core topic in Platform Engineering is multi tenancy. Natively Kubernetes supports this either by dedicated namespaces or dedicated clusters per tenant. Both come with advantages and disadvantages and need to be selected based on the use-case at hand. However they sit at two ends of tenant isolation. With this article we are trying to shed some light on mechanisms for tenant isolation, that sit between those opposites. All of these models is really a story about which layer of the stack people have chosen to bolt tenancy onto, and what they gave up to do it, as Kubernetes has no built in concepts of "tenancy". Therefore, the following 4 patterns will be discussed: Namespaces, separate clusters, vCluster and Capsule.
Namespaces
A Namespace is a a mechanism for grouping resources within a single cluster. You can add some isolation mechanisms and limits via RoleBindings that scope a role to a specific Namespace, Network policies and Quotas. They are very cheap and easy and as they are Kubernetes native, every controller already understands namespaces
Some of the core issues of using namespaces per tenant are the following:
- Cluster-scoped objects are shared. For example
CustomResourceDefinition,ClusterRoleorPersistentVolume, and admission webhook configs live outside any namespace. If Tenant A installs a CRD with the same group/version as one Tenant B needs at a different schema, you have a conflict with no boundary to save you. This is the single biggest structural limitation of namespace-based tenancy: There is no way to give two tenants different versions of the same CRD in one cluster. - The API server, etcd, scheduler, and kubelet are shared. A tenant that manages to trigger API server overload affects everyone. There's no per-tenant API server.
- Lack of network segmentation. Without a CNI that allows for Network Policies, there is no network boundary between namespaces and pods can communicate across them.
- Nodes are shared across namespaces. Unless using tains and tolerations to bound teams to nodes, there not only is a noisy neighbor risk, but if an attacker escapes to the underlying node, shared across namesapces, namespace level enforcements can theoretically be bypassed.
Because of that, namespaces are well used for internal teams with the same trust boundary, where gloabl resources dont need to be different by tenant. To have some level of security, it is advised to bind roles to namespaces, use networkpolicies if possible, quotas and also an external policy engine for further enforcements. Moreover creating a new namespace is a cluster operation and unless you want to give teams the permissions to create namespaces and adjust ResourceQuotas, this becomes a bottleneck quickly.
Cluster-per-tenant
On the other hand, one can give every tenant a fully separate Kubernetes cluster, with a separate API server, separate etcd, separate nodes. This eliminates most of the issues I defined for namespace isolation, however comes with drawbacks itself.
- Duplication of components. Control plane and core components like the monitoring-stack or CNI get duplicated. This leads to additional cost and gets more complx to manage at scale.
- No resource sharing across tenants. A quiet tenant's idle capacity can't be borrowed by a busy tenant's cluster. You either overprovision every cluster or accept poor utilization.
- Fleet management becomes its own discipline. Before you start creating clusters for tenants, you should build a system to manage them centrally and keep drift to a minimum.
VCluster: virtual control planes inside a host cluster
VCluster sits between namespaces and full clusters. The core idea: give each tenant a Kubernetes control plane with its own API server and run that control plane as a workload inside a namespace of a shared host cluster. A vCluster's control plane runs in a StatefulSet pod. From the host cluster's point of view, it's just a pod in a namespace. The so-called syncer watches objects created inside the virtual cluster and translates them into real objects in the host cluster's namespace so they can actually be scheduled and run. Critically, higher-level resources never leave the virtual cluster.A `Deployment`, `StatefulSet`, or a CRD you install inside the vCluster exists only in the virtual API server/datastore. The host cluster's API server never sees it. This is what gives tenants real CRD and admission-control autonomy without needing cluster-admin on the host. vCluster also has an answer to compute level isolation, as it comes with several node models. Shared nodes: Tenant workloads run as regular pods on the host cluster's shared node pool. With private nodes clusters can mapped to nodes in a 1:1 relationship, there always is just one tenant on one node. Moreover, each virtual cluster gets its own CoreDNS by default, so in-cluster service discovery works normally from the tenant's point of view.
- Compute isolation is only as strong as the underlying node model. In the default shared-nodes mode, a "tenant" is still just Linux cgroups/namespaces on a shared kernel, the same compute isolation strength as plain namespaces. To get materially stronger compute isolation you move to private nodes, which reduces the resource-sharing efficiency argument.
- Per-tenant control-plane overhead adds up. Each vCluster is its own API server + datastore process that needs CPU/memory headroom, needs upgrading, and can itself become unhealthy (etcd/SQLite corruption, API server OOMs) independent of the host. Running hundreds of vClusters means monitoring hundreds of small control planes.
- Not all resources/features virtualize cleanly. Certain operators that manage cluster-wide state (service meshes, some CNIs, cert-manager cluster issuers) don't virtualize well and often need to live only at the host level, which tenants then don't have control over.
- The host cluster is still a single point of compromise. If the host cluster's control plane is compromised, or if RBAC on the host cluster is misconfigured, an attacker can potentially reach into every vCluster's synced resources, as they are all stored in the hosts etcd. For instance, when a developer inside
vcluster-teamBcreates a Secret with a database password, vCluster's syncer mirrors it down into the host cluster as a real object in namespacehost-ns-teamB. If someone on the host cluster has a broad ClusterRole likeget/list secretsbound cluster-wide, they can now runkubectl get secret db-password -n host-ns-teamB -o yamland read team B's credentials directly from the host, even though they have zero access tovcluster-teamBitself and team B's vCluster RBAC would have denied them.
VCluster works well for Platforms that need to hand tenants real CRD/operator installation, or more generally isolation at the API-Server level. It also allows for running some shared components like observability inside the host cluster and reduce overhead that way. One also needs to keep in mind that all tenants still live within the same host cluster and there always exist potential escape vectors out of a vCluster.
Capsule
Tenant custom resource that groups namespaces and cascades policy onto them. You install the Capsule controller (a single Deployment, plus a set of CRDs: Tenant, CapsuleConfiguration, GlobalTenantResource, TenantResource, ProxySettings). Cluster admins create a Tenant object per team/customer and specify owners. Tenant Owners then self-service-create namespaces that automatically get attached to their tenant, without requiring intervention from a cluster admin. Everything defined at the Tenant level is automatically inherited by every namespace in that tenant. Via Admission rules one can enforce policies on tenant objects, like allowed image registries, forbidden security contexts or network policy templates.list / watch requests like kubectl get namespaces down to only the objects that belong to the requesting tenant. As Capsule is just one controller and a few CRDs it is very lightweight and doesnt introduce a new abstraction apart from tenants. However, Capsule is still namespace based isolation, just improved for scale. Capsule does not give tenants their own API server, so it inherits every constraint from section 1: no per-tenant CRD versions, no cluster-scoped resource isolation beyond what Capsule Proxy filters at read time, shared blast radius on the real API server/etcd.Comparison at a glance
| Dimension | Namespaces | Cluster-per-tenant | VCluster | Capsule |
| Isolation unit | Namespace | Whole cluster | Virtual control plane (pod) | Tenant (group of namespaces) |
| Per-tenant CRDs / API isolation | No | Yes | Yes | No |
| Per-tenant control plane | No | Yes | Yes (virtual) | No |
| Compute / kernel isolation | Shared | Full | Depends on node mode (shared / private) | Shared |
| Provisioning speed | Seconds | Tens of Minutes | Seconds to Minutes | Seconds |
| Operational overhead | Low | High | Medium | Low |
| Governance / maturity | Upstream Kubernetes | Upstream Kubernetes | Single-vendor OSS + commercial lay | CNCF Sandbox |
| Biggest structural gap | No CRD/cluster-scoped isolation | Cost & fleet sprawl | Syncer can't losslessly translate everything | Still shares one control-plane |