πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Security advanced ⏱ 50 minutes K8s 1.28+

Kubernetes Multi-Tenancy for Enterprise Teams

Implement secure multi-tenancy with namespace isolation, ResourceQuotas, NetworkPolicies, hierarchical namespaces, and vCluster for strong isolation.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: Implement multi-tenancy with defense-in-depth: namespace isolation + RBAC + ResourceQuotas + LimitRanges + NetworkPolicies + Pod Security Standards. For strong isolation between untrusted tenants, use vCluster or Kamaji for virtual control planes.

The Problem

Enterprise platforms serve multiple teams, projects, or customers on shared Kubernetes clusters. Without proper isolation, one tenant can consume all resources, access another tenant’s secrets, or intercept network traffic. You need layered controls that enforce boundaries without requiring separate clusters per team.

flowchart TB
    subgraph Cluster["Shared Kubernetes Cluster"]
        subgraph TenantA["Tenant A (team-frontend)"]
            NSA["Namespace"] --> QA["ResourceQuota<br/>CPU: 16, Mem: 64Gi"]
            NSA --> NPA["NetworkPolicy<br/>Deny all ingress"]
            NSA --> RBACA["RBAC<br/>Group: frontend-devs"]
        end
        subgraph TenantB["Tenant B (team-backend)"]
            NSB["Namespace"] --> QB["ResourceQuota<br/>CPU: 32, Mem: 128Gi"]
            NSB --> NPB["NetworkPolicy<br/>Deny all ingress"]
            NSB --> RBACB["RBAC<br/>Group: backend-devs"]
        end
        subgraph TenantC["Tenant C (team-ml)"]
            NSC["Namespace"] --> QC["ResourceQuota<br/>GPU: 8, Mem: 256Gi"]
            NSC --> NPC["NetworkPolicy<br/>Deny all ingress"]
            NSC --> RBACC["RBAC<br/>Group: ml-engineers"]
        end
    end

The Solution

Layer 1: Namespace Isolation with RBAC

# Tenant namespace with labels
apiVersion: v1
kind: Namespace
metadata:
  name: team-frontend
  labels:
    tenant: frontend
    cost-center: cc-1234
    environment: production
---
# RBAC: team gets edit access to their namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenant-admin
  namespace: team-frontend
subjects:
  - kind: Group
    name: "oidc:frontend-devs"
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: edit
  apiGroup: rbac.authorization.k8s.io

Layer 2: Resource Quotas and LimitRanges

apiVersion: v1
kind: ResourceQuota
metadata:
  name: tenant-quota
  namespace: team-frontend
spec:
  hard:
    requests.cpu: "16"
    requests.memory: 64Gi
    limits.cpu: "32"
    limits.memory: 128Gi
    persistentvolumeclaims: "10"
    services.loadbalancers: "2"
    pods: "50"
    secrets: "20"
    configmaps: "20"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: tenant-limits
  namespace: team-frontend
spec:
  limits:
    - type: Container
      default:
        cpu: 500m
        memory: 512Mi
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      max:
        cpu: "4"
        memory: 8Gi
    - type: PersistentVolumeClaim
      max:
        storage: 100Gi

Layer 3: Network Isolation

# Default deny all ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: team-frontend
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
# Allow intra-namespace communication
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
  namespace: team-frontend
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector: {}
  egress:
    - to:
        - podSelector: {}
    # Allow DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
---
# Allow ingress from ingress controller only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-controller
  namespace: team-frontend
spec:
  podSelector:
    matchLabels:
      app: web
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx

Layer 4: Pod Security Standards

# Enforce restricted pod security on tenant namespace
apiVersion: v1
kind: Namespace
metadata:
  name: team-frontend
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Layer 5: Hierarchical Namespaces (HNC)

For organizations with many teams under departments:

# Install Hierarchical Namespace Controller
kubectl apply -f https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/latest/download/default.yaml

# Create parent namespace
kubectl create namespace department-engineering

# Create child namespaces that inherit policies
kubectl hns create team-frontend -n department-engineering
kubectl hns create team-backend -n department-engineering
kubectl hns create team-platform -n department-engineering

# Policies propagate from parent to children:
# - RBAC RoleBindings
# - NetworkPolicies
# - ResourceQuotas (via Kyverno/OPA)

Layer 6: vCluster for Strong Isolation

When namespace-level isolation isn’t sufficient (untrusted tenants, CRD conflicts):

# Install vCluster CLI
curl -L -o vcluster "https://github.com/loft-sh/vcluster/releases/latest/download/vcluster-linux-amd64"
chmod +x vcluster && sudo mv vcluster /usr/local/bin/

# Create a virtual cluster for tenant
vcluster create team-ml \
  --namespace vcluster-team-ml \
  --set vcluster.resources.limits.cpu=8 \
  --set vcluster.resources.limits.memory=16Gi \
  --set sync.persistentvolumes.enabled=true

# Connect to virtual cluster
vcluster connect team-ml --namespace vcluster-team-ml

# Tenant gets full cluster-admin inside their vCluster
# - Own CRDs, operators, RBAC
# - Isolated API server
# - Resources mapped to host namespace

Tenant Onboarding Automation

# Kyverno policy: auto-create tenant resources on namespace creation
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: tenant-onboarding
spec:
  rules:
    - name: generate-quota
      match:
        resources:
          kinds:
            - Namespace
          selector:
            matchLabels:
              tenant: "*"
      generate:
        kind: ResourceQuota
        name: tenant-quota
        namespace: "{{request.object.metadata.name}}"
        data:
          spec:
            hard:
              requests.cpu: "16"
              requests.memory: 64Gi
              pods: "50"
    - name: generate-network-policy
      match:
        resources:
          kinds:
            - Namespace
          selector:
            matchLabels:
              tenant: "*"
      generate:
        kind: NetworkPolicy
        name: default-deny
        namespace: "{{request.object.metadata.name}}"
        data:
          spec:
            podSelector: {}
            policyTypes:
              - Ingress
              - Egress

Common Issues

IssueCauseFix
Tenant can list other namespacesDefault K8s allows namespace listingUse OPA/Kyverno to filter namespace list or use vCluster
ResourceQuota not enforcedMissing LimitRange defaultsPods without requests/limits bypass quotas β€” add LimitRange
DNS resolution across tenantsDefault CoreDNS allows cross-namespaceRestrict with NetworkPolicy egress rules
CRD conflicts between tenantsCRDs are cluster-scopedUse vCluster or Kamaji for CRD isolation
Noisy neighbor (CPU throttling)One tenant consuming all CPUSet limits.cpu in ResourceQuota and enforce with LimitRange

Best Practices

  • Defense in depth β€” never rely on a single isolation mechanism; layer RBAC + NetworkPolicy + Quotas + PSS
  • Automate onboarding β€” use Kyverno/OPA to auto-generate isolation resources on namespace creation
  • Label everything β€” consistent tenant, cost-center, environment labels for policy enforcement and chargeback
  • Audit cross-tenant access β€” enable API server audit logging filtered by namespace
  • Right-size quotas β€” review usage quarterly and adjust; over-provisioning wastes capacity, under-provisioning blocks teams
  • Use vCluster for untrusted tenants β€” namespace isolation has inherent limits (CRDs, node access, cluster-scoped resources)

Key Takeaways

  • Enterprise multi-tenancy requires 5-6 layers: namespaces, RBAC, quotas, network policies, pod security, and optionally virtual clusters
  • Automate tenant onboarding with policy engines to ensure consistent isolation
  • vCluster provides the strongest isolation without the cost of separate physical clusters
  • Hierarchical namespaces simplify management for large organizations with department structures
#multi-tenancy #namespace-isolation #resource-quotas #network-policies #vcluster
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens