Several teams, one cluster, without anyone standing on anyone
A namespace is a naming boundary, not a security boundary. Quotas, default-deny network policy, workload identity and a clear answer on node isolation are what make a shared cluster work.
The two ways this goes wrong are opposites. In the first, everything lives in one cluster with a namespace per team, no quotas, no network policy, and cluster-admin handed out because someone needed to debug once. A single team's runaway job starves the node pool, and any pod can reach any other pod and the cloud metadata endpoint. In the second, the platform team says multi-tenancy is unsafe, and eighteen months later there are twenty-three clusters, each with its own control plane fee, its own NAT gateway, its own upgrade backlog and its own drift.
Both are expensive. The middle is achievable, and it rests on being precise about what a namespace actually gives you.
What a namespace is and is not
A namespace gives you a scope for names, a target for role-based access control, a place to attach resource quotas, and a selector for network policy. That is a genuinely useful set.
A namespace does not give you isolation of the kernel, the node, or the network by default. Two pods in different namespaces on the same node share a kernel. Without network policy, they can talk freely. Without quotas, they compete for the same CPU and memory. A container escape reaches the node, and from the node it reaches every workload on it.
So the honest framing is: namespaces are the unit of management, and isolation is a set of controls you add on top. How many of those controls you need depends on one question, which you should answer explicitly before designing anything.
Soft or hard multi-tenancy
Soft multi-tenancy means the tenants are teams inside your organisation. They are not adversaries. The controls exist to prevent accidents, contain blast radius and make costs attributable. This covers the overwhelming majority of shared clusters, and everything below is sufficient for it.
Hard multi-tenancy means a tenant may be hostile: customer-supplied code, untrusted workloads, or a regulatory boundary that treats another tenant as an outside party. Here namespace-level controls are not enough. You need node-level separation, a sandboxed runtime, or separate clusters, and the honest answer is usually separate clusters.
Most teams asking for multi-tenancy have the soft case and are being sold the hard one.
Quotas and limit ranges, or one team takes the cluster
A ResourceQuota per namespace caps the total CPU, memory, storage and object counts a team can consume. Without it, a single deployment with a large replica count and no ceiling can consume the entire node pool, and the autoscaler will helpfully buy the nodes to let it.
Set quotas on requests and on limits, and include object counts, because the exhaustion is not always CPU. Ten thousand ConfigMaps or a runaway loop creating Jobs will hurt etcd long before it hurts the node pool.
Pair the quota with a LimitRange that sets default requests and limits for pods that declare none. Without it, a pod with no requests is scheduled as best-effort, gets evicted first under pressure, and contributes nothing to the quota accounting, which quietly defeats the whole mechanism.
Quotas also make cost attribution possible, which is the argument in splitting a shared cluster's bill. A team with a quota knows what it has claimed, and a claim is something you can invoice against.
Default-deny network policy is the highest-value control
By default, every pod in a Kubernetes cluster can open a connection to every other pod, in every namespace. Most engineers are surprised by this, and it is the finding that makes lateral movement trivial after any single compromised workload.
The fix is a default-deny ingress policy in every namespace, plus explicit allow rules for the traffic that should exist. Do it namespace by namespace in audit mode first if your CNI supports it, because the first enforcement always breaks something nobody documented, usually DNS.
Three specifics worth stating: allow DNS to the cluster DNS service explicitly or everything breaks; control egress, not just ingress, because egress is what an attacker uses; and block access to the cloud instance metadata endpoint from pods, because reaching it is the classic route from a compromised container to cloud credentials.
Your CNI has to support NetworkPolicy for any of this to be real. Some do not, and a cluster where policies are accepted by the API server and silently ignored by the data plane is the worst possible state.
Identity: from pod to cloud without a static key
Each team's workloads need their own cloud permissions, and the wrong way to do it is a service account key in a Secret.
Every managed Kubernetes offering now supports federating a Kubernetes service account to a cloud identity, so a pod receives a short-lived token with a scoped role and no static credential exists anywhere. Bind one Kubernetes service account to one cloud role per workload, not one per namespace, so the blast radius of a compromise is one workload.
Inside the cluster, roles should be namespace-scoped Role and RoleBinding, never ClusterRole, and the cluster-scoped resources a team can see should be an explicit, small list. The permission to create pods in a namespace is effectively the permission to run anything as that namespace's service accounts, so treat namespace edit rights as significant.
The same instinct applies as in least privilege in the cloud: grant to a workload, not to a team, and grant the verbs actually used.
Node isolation, when it is genuinely needed
If a workload processes data that must not share a kernel with other tenants, separate the nodes. Taints and tolerations plus node selectors give you dedicated node pools per sensitive tenant. It costs you bin-packing efficiency, which is the trade.
Sandboxed runtimes such as gVisor or Kata Containers put a stronger boundary around the container at a performance cost, and are the right answer for genuinely untrusted code. Pod Security Admission at the restricted level should be the baseline everywhere regardless, and policy engines fill the gaps, which is the subject of Kyverno or Gatekeeper.
The shared things that nobody owns
The failures in a shared cluster rarely come from the isolated parts. They come from the shared ones:
- Cluster DNS. One team's misconfigured loop can saturate CoreDNS for everyone. Set per-namespace query limits if your DNS supports it, and monitor it as a tenant-facing service.
- The ingress or gateway. One hostname claim conflicting with another, one team's TLS change affecting a shared listener. This is precisely the separation that Gateway API exists to provide.
- Custom resource definitions. Cluster-scoped and shared. One team installing an operator changes the cluster for everyone.
- Upgrades. One shared maintenance window for all tenants, which means the platform team owns the coordination, as covered in cluster upgrades.
- Admission webhooks. A failing webhook blocks everyone's deployments, including the deployment that would fix it.
When to split the cluster instead
Split when tenants have different compliance boundaries, different upgrade cadences you cannot reconcile, genuinely untrusted code, or different regions. Do not split because teams want autonomy over things that should be standard anyway.
The honest cost comparison is a control plane fee plus a NAT gateway plus baseline system workloads plus the operational time per cluster, against the platform engineering time to run the controls above once. For a handful of internal teams, one well-governed cluster wins comfortably. We size this explicitly during the platform phase of a cloud engagement.
What to do this week
Pick two namespaces that should have nothing to do with each other, exec into a pod in one, and try to open a connection to a service in the other. Then try to reach the cloud metadata endpoint. If both succeed, you have found the highest-value afternoon of work available in your cluster, and a default-deny policy with explicit DNS allow is where it starts.