Gemini Generated Image 152pr0152pr0152p

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, ClusterRole or PersistentVolume, 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.
     
Having dedicated clusters per tenant, makes sense if tenants require strict isolation. In these cases you need to have a solid fleet management architecture in place first, e.g via a Hub and Spoke model with ClusterAPI, which takes significant time to build up. Also keep in mind that if network traffic between clusters is possible, tenants are never truly isolated.

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.
 
However vCluster also comes with some disadvantages:
 
  • 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-teamB creates a Secret with a database password, vCluster's syncer mirrors it down into the host cluster as a real object in namespace host-ns-teamB. If someone on the host cluster has a broad ClusterRole like get/list secrets bound cluster-wide, they can now run kubectl get secret db-password -n host-ns-teamB -o yaml and read team B's credentials directly from the host, even though they have zero access to vcluster-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
 

Capsule takes a different direction than vCluster. It does not give tenants their own control plane within a host cluster, but makes namespace boundaries better suited for multi tennacy, by adding a 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.
To prevent namespace leakage, Capsule implemets the Capsule Proxy. A reverse proxy tenants authenticate through instead of hitting the API server directly. It filters cluster-scoped 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.
 
It works well for platform teams that are happy with namespace-level isolation strength but need self-service namespace provisioning, tenant-scoped policy inheritance, and a "my namespaces only" view.

Comparison at a glance
 
DimensionNamespacesCluster-per-tenantVClusterCapsule
Isolation unitNamespaceWhole clusterVirtual control plane (pod)Tenant (group of namespaces)
Per-tenant CRDs / API isolationNoYesYesNo
Per-tenant control planeNoYesYes (virtual)No
Compute / kernel isolationSharedFullDepends on node mode (shared / private)Shared
Provisioning speedSecondsTens of MinutesSeconds to MinutesSeconds
Operational overheadLow HighMediumLow
Governance / maturityUpstream KubernetesUpstream KubernetesSingle-vendor OSS + commercial layCNCF Sandbox
Biggest structural gapNo CRD/cluster-scoped isolationCost & fleet sprawlSyncer can't losslessly translate everythingStill shares one control-plane
 

How to choose

Based on experience, these are a few decision heuristics that hold up in practice:
 
If there is a small set of "tenants" that are internal teams who trust each other and only need environment/app separation: Namespaces plus RBAC, ResourceQuota, NetworkPolicy, and PSA could be enough.
 
If tenants need to install their own operators/CRDs but you don't want to give them cluster-admin, and you're okay with shared-kernel compute isolation: vCluster is the most direct answer, especially for ephemeral environments (PR previews, CI, sandboxes) where fast teardown matters more than hardened isolation.
 
If you like namespace-level isolation but are tired of manually wiring RBAC/quotas/policy per namespace and want tenants to self-serve namespace creation within guardrails: Capsule is the lowest-overhead way to get there, and it also prevents namespace leakage.
 
If your tenants are external customers, regulated workloads, or need strong seperation, seperate clusters per tenant is the most proven way paired with centralized, automated cluster management to tame the operational overhead.
 

Summary

This Blogpost compared multiple multi tenancy options and tried to outline how they differentiate form each other and subsequently where they make sense. Navigating these trade-offs in your own environment isn't always straightforward, the right choice depends on your team structure, security requirements, and operational maturity. If you're evaluating multi-tenancy strategies or building out a Kubernetes platform for your organization, we specializes in platform engineering and Kubernetes multi-tenancy. Get in touch if you'd like help finding the right approach for your setup.