Veröffentlicht am

Kubernetes Multi-Tenancy für SaaS: Namespace vs. vCluster

Teilen:
Authors

Kubernetes Multi-Tenancy für SaaS: Namespace- oder vCluster-Isolation?

TL;DR

  • Ein Cluster pro Kunde skaliert wirtschaftlich nicht — ab etwa 10 Mandanten zahlen Sie pro Kunde Control-Plane-Kosten (bei EKS ca. 73 USD/Monat), Upgrade-Aufwand und Monitoring-Overhead, ohne dass die Kunden davon etwas merken.
  • Namespace-Isolation (RBAC + NetworkPolicy + ResourceQuota + Pod Security Admission) reicht für die meisten B2B-SaaS-Anbieter, deren Mandanten keinen eigenen Cluster-Zugriff bekommen. Aufwand: gering. Blast Radius: geteilte Control Plane.
  • vCluster lohnt sich, sobald Mandanten eigene CRDs, Operatoren oder kubectl-Zugriff brauchen — jeder Mandant bekommt eine eigene virtuelle Control Plane mit eigenem API-Server, die Workloads laufen aber auf gemeinsamen Nodes.
  • Eigener Cluster pro Kunde bleibt die richtige Wahl für regulierte Kunden (KRITIS, Banken, Gesundheitswesen) mit vertraglich geforderter physischer Trennung — als Ausnahme, nicht als Default.
  • Unten finden Sie die Vergleichstabelle und produktionsreife YAML-Manifeste für beide Modelle.

Das Problem: Mandantentrennung ohne Cluster-Wildwuchs

Der typische Verlauf bei SaaS-Anbietern mit 10–100 Mitarbeitern sieht so aus: Die ersten drei Enterprise-Kunden fordern "dedizierte Infrastruktur", das Team provisioniert pro Kunde einen eigenen Cluster. Nach 18 Monaten betreiben zwei Platform-Engineers fünfzehn Cluster — jeder mit eigener Kubernetes-Version, eigenem Ingress-Controller, eigenem cert-manager, eigener Monitoring-Pipeline. Ein CVE im Ingress-Controller bedeutet fünfzehn Rollouts. Ein Kubernetes-Minor-Upgrade blockiert ein Quartal.

Die Gegenrichtung ist genauso riskant: alle Mandanten in einen Cluster werfen, Trennung nur über Deployment-Labels, keine NetworkPolicies. Dann liest im Zweifel der Pod von Kunde A die Datenbank-Credentials von Kunde B — ein Vorfall, den Sie nach Art. 33 DSGVO binnen 72 Stunden der Aufsichtsbehörde melden dürfen.

Die eigentliche Entscheidung ist also nicht "Multi-Tenancy ja/nein", sondern: Welches Isolations-Modell passt zu welcher Vertrauens- und Compliance-Stufe Ihrer Kunden?

Grundlagen: Soft vs. Hard Multi-Tenancy

Die Kubernetes-SIG-Multitenancy unterscheidet zwei Grundmodelle:

Soft Multi-Tenancy: Die Mandanten sind grundsätzlich vertrauenswürdig — typischerweise interne Teams oder SaaS-Kunden, deren Workloads Sie selbst deployen. Der Mandant hat keinen direkten API-Zugriff. Isolation schützt vor Fehlern (falsches Namespace-Target, Ressourcen-Hunger), nicht vor gezielten Angriffen.

Hard Multi-Tenancy: Mandanten sind potenziell feindlich oder dürfen eigenen Code und eigene Manifeste einbringen. Hier muss die Isolation einem aktiven Angreifer standhalten — inklusive Container-Escape-Szenarien.

Zwei Begriffe entscheiden die Modellwahl:

  • Blast Radius: Was ist betroffen, wenn ein Mandant kompromittiert wird oder die Control Plane ausfällt? Bei Namespace-Isolation teilen alle Mandanten API-Server, etcd, Scheduler und CoreDNS. Ein etcd-Ausfall trifft alle.
  • Noisy Neighbor: Ein Mandant ohne ResourceQuota kann Nodes mit Pods fluten, den API-Server mit Watch-Requests belasten oder über einen CrashLoop das Image-Registry-Rate-Limit aller anderen aufbrauchen.

Wichtig und oft übersehen: Namespaces isolieren keine Cluster-weiten Ressourcen. CRDs, ClusterRoles, ValidatingWebhookConfigurations, StorageClasses und PriorityClasses sind global. Zwei Mandanten, die verschiedene Versionen desselben Operators brauchen (z. B. cert-manager v1.14 vs. v1.17 mit inkompatiblen CRD-Schemas), lassen sich mit Namespaces schlicht nicht trennen.

Isolations-Modelle im Vergleich

KriteriumNamespace-IsolationvClusterEigener Cluster pro Kunde
Control Planegeteilteigene virtuelle pro Mandantvollständig getrennt
CRDs / Operatoren pro Mandantnein (Cluster-weit)jaja
kubectl-Zugriff für Mandantenriskant, nur stark eingeschränktja (eigener API-Server)ja
Kernel / Nodesgeteiltgeteiltgetrennt
Schutz gegen Container-Escapeneinnein (gleiche Nodes)ja
Noisy-Neighbor-SchutzResourceQuota + LimitRangeQuota auf Host + Syncer-Limitsvollständig
Kosten pro Mandant (Schätzung)nahe 0 (nur Workload)~0,2–1 vCPU + 0,5–1 GiB für Control PlaneControl-Plane-Gebühr + Basis-Addons + Mindest-Nodes; realistisch ab ca. 150–300 EUR/Monat
Betriebsaufwand1 Cluster-Upgrade für alle1 Host-Upgrade, vCluster-Versionen einzelnn Upgrades, n Monitoring-Stacks
Provisionierungszeit neuer MandantSekunden (GitOps)~1–2 Minuten20–60 Minuten (Terraform + Addons)
Typischer EinsatzB2B-SaaS, Workloads vom Anbieter deploytMandanten mit eigenen Manifesten/CRDs, Preview-UmgebungenKRITIS, vertraglich geforderte Dedizierung

Die Kostenangaben sind Schätzungen auf Basis üblicher Cloud-Preise (Stand Mitte 2026); rechnen Sie Ihre konkreten Zahlen mit Ihrem eigenen Workload-Profil nach.

Faustregel: Namespace-Isolation als Default. vCluster für die Mandanten, die eine eigene Control Plane brauchen. Eigener Cluster nur, wenn der Vertrag es erzwingt — und dann mit eingepreistem Aufschlag.

Praxis A: Namespace-Isolation mit RBAC, NetworkPolicy und ResourceQuota

Das Fundament pro Mandant besteht aus vier Manifesten. Alle Beispiele sind auf Kubernetes 1.29+ lauffähig.

1. Namespace mit Pod Security Admissionrestricted blockiert privilegierte Container, hostPath-Mounts und root-User bereits bei der Admission:

apiVersion: v1
kind: Namespace
metadata:
  name: tenant-acme
  labels:
    tenant: acme
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: v1.31
    pod-security.kubernetes.io/warn: restricted

2. RBAC — der Deploy-ServiceAccount des Mandanten (z. B. für die CI-Pipeline) bekommt eine Role, die bewusst keine Secrets-Leserechte über list/watch enthält und keine Rechte außerhalb des Namespaces:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tenant-deployer
  namespace: tenant-acme
rules:
  - apiGroups: ['apps']
    resources: ['deployments', 'statefulsets']
    verbs: ['get', 'list', 'watch', 'create', 'update', 'patch']
  - apiGroups: ['']
    resources: ['services', 'configmaps', 'pods']
    verbs: ['get', 'list', 'watch', 'create', 'update', 'patch']
  - apiGroups: ['']
    resources: ['secrets']
    verbs: ['get', 'create', 'update']   # kein list/watch: verhindert Secret-Enumeration
  - apiGroups: ['']
    resources: ['pods/log']
    verbs: ['get']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenant-deployer-binding
  namespace: tenant-acme
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: tenant-acme
roleRef:
  kind: Role
  name: tenant-deployer
  apiGroup: rbac.authorization.k8s.io

Verifizieren Sie das Ergebnis mit kubectl auth can-i und rbac-lookup:

kubectl auth can-i list secrets --as=system:serviceaccount:tenant-acme:ci-deployer -n tenant-acme
# erwartetes Ergebnis: no
kubectl auth can-i get pods --as=system:serviceaccount:tenant-acme:ci-deployer -n tenant-b
# erwartetes Ergebnis: no
rbac-lookup ci-deployer --kind serviceaccount --output wide

3. NetworkPolicy — Default-Deny plus explizite Freigaben. Ohne CNI mit NetworkPolicy-Support (Cilium, Calico) sind diese Manifeste wirkungslos — bei Managed-Angeboten wie AKS mit Azure-CNI-Overlay muss die Policy-Engine explizit aktiviert sein:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: tenant-acme
spec:
  podSelector: {}
  policyTypes: ['Ingress', 'Egress']
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-intra-tenant-and-dns
  namespace: tenant-acme
spec:
  podSelector: {}
  policyTypes: ['Ingress', 'Egress']
  ingress:
    - from:
        - podSelector: {}          # nur Pods im eigenen Namespace
        - namespaceSelector:
            matchLabels:
              role: ingress        # Ingress-Controller-Namespace
  egress:
    - to:
        - podSelector: {}
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

4. ResourceQuota und LimitRange — gegen Noisy Neighbors auf Compute- und Objekt-Ebene:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: tenant-quota
  namespace: tenant-acme
spec:
  hard:
    requests.cpu: '8'
    requests.memory: 16Gi
    limits.cpu: '16'
    limits.memory: 32Gi
    persistentvolumeclaims: '10'
    services.loadbalancers: '0'    # Mandanten erzeugen keine eigenen LBs
    count/pods: '60'
---
apiVersion: v1
kind: LimitRange
metadata:
  name: tenant-defaults
  namespace: tenant-acme
spec:
  limits:
    - type: Container
      default:
        cpu: 500m
        memory: 512Mi
      defaultRequest:
        cpu: 100m
        memory: 128Mi

Die LimitRange ist Pflicht: Sobald eine ResourceQuota requests/limits erzwingt, werden Pods ohne explizite Requests abgelehnt — die LimitRange injiziert Defaults und verhindert genau diese Deployment-Fehler.

Bevor solche Manifeste in Produktion gehen, lohnt ein Durchlauf durch unseren YAML-Validator mit DSGVO/BSI-Checks — er findet fehlende SecurityContexts, offene NetworkPolicies und Quota-Lücken. Ergänzend checken kubeaudit all -n tenant-acme und Polaris die laufende Konfiguration, kube-bench den Node-Level nach CIS-Benchmark.

Wer dieses Muster über Dutzende Mandanten skaliert, sollte sich Capsule ansehen: Ein Tenant-CRD bündelt Namespaces, Quoten und erlaubte Registries pro Mandant, statt vier YAML-Dateien pro Kunde in Git zu duplizieren.

Praxis B: vCluster für harte Mandanten-Trennung

vCluster (Open Source, von Loft Labs) startet pro Mandant eine vollständige Kubernetes-Control-Plane — API-Server, Controller-Manager und eine SQLite/etcd-Datenhaltung — als StatefulSet innerhalb eines Namespaces des Host-Clusters. Ein Syncer übersetzt Pods aus dem virtuellen Cluster in den Host-Namespace; Deployments, CRDs, RBAC und Webhooks bleiben komplett im virtuellen Cluster.

Damit bekommt jeder Mandant:

  • einen eigenen API-Server mit eigenem Kubeconfig — kubectl-Vollzugriff ohne Risiko für andere Mandanten
  • eigene CRDs und Operatoren in beliebiger Version
  • eigene Admission-Webhooks, eigene RBAC-Welt

Die Nodes bleiben geteilt — gegen Kernel-Exploits hilft vCluster nicht. Dafür kombiniert man es bei Bedarf mit dedizierten Node-Pools pro Mandant (nodeSelector + Taints) oder einer Sandbox-Runtime wie Kata Containers.

Installation über die vCluster-CLI mit einer values.yaml (vCluster ab v0.20):

# vcluster-tenant-acme.yaml
sync:
  toHost:
    ingresses:
      enabled: true
controlPlane:
  statefulSet:
    highAvailability:
      replicas: 1
    resources:
      requests:
        cpu: 200m
        memory: 512Mi
      limits:
        memory: 2Gi
  backingStore:
    etcd:
      embedded:
        enabled: true
vcluster create acme --namespace tenant-acme --values vcluster-tenant-acme.yaml
vcluster connect acme --namespace tenant-acme -- kubectl get ns
# der Mandant sieht NUR seine virtuelle Cluster-Welt

Auf dem Host bleibt der Mandant ein gewöhnlicher Namespace — die ResourceQuota aus Praxis A begrenzt damit automatisch auch alles, was der Mandant in seinem virtuellen Cluster startet. Das ist der eigentliche Trick: Hard Multi-Tenancy auf API-Ebene, Kostenkontrolle auf Host-Ebene.

Wann sich der Aufwand lohnt (und wann nicht):

SignalEmpfehlung
Mandanten deployen eigene Helm-Charts/OperatorenvCluster
Kunden fordern kubectl-Zugriff im VertragvCluster
Sie brauchen Preview-Umgebungen pro Pull-RequestvCluster (Lebensdauer Minuten bis Tage)
Workloads deployt nur Ihr eigenes TeamNamespace reicht — vCluster wäre Overhead
Kunde fordert dedizierte Hardware / eigenes Netzeigener Cluster, kein vCluster

Rechnen Sie den Overhead ehrlich: 50 vCluster mit je 200m CPU-Request und 512 MiB belegen dauerhaft rund 10 vCPU und 25 GiB nur für Control Planes. Das ist deutlich billiger als 50 Managed-Cluster, aber nicht gratis. Eine tiefere Betrachtung von Sync-Strategien, HA-Setups und Upgrade-Pfaden finden Sie in unserem Artikel vCluster Multi-Tenancy im Detail.

Fallstricke aus der Praxis

Geteilte CRDs bei Namespace-Isolation. Der Klassiker: Mandant A braucht ein Feld aus cert-manager v1.17, Mandant B pinnt v1.14, beide CRD-Versionen kollidieren Cluster-weit. Bei Namespace-Isolation gehören Operatoren deshalb ausschließlich in Plattform-Hoheit — Mandanten bekommen nie CRD-Schreibrechte. Nachweisbar per kubectl auth can-i create customresourcedefinitions --as=<subject>.

Ingress-Host-Kollisionen. Zwei Mandanten claimen app.example.com in ihren Ingress-Objekten — je nach Controller gewinnt der ältere Eintrag oder das Verhalten ist undefiniert. Der Ingress-NGINX-Controller kennt dafür Admission-Validierung; robuster ist eine Kyverno- oder OPA-Gatekeeper-Policy, die Hostnames auf ein Mandanten-Suffix-Schema (*.acme.example.com) erzwingt.

Secret-Leakage über zu breite RBAC. get secrets klingt harmlos, list secrets auf Cluster-Ebene ist ein Datenabfluss aller Mandanten-Credentials in einem einzigen API-Aufruf. Auditieren Sie regelmäßig mit rbac-lookup und kubectl auth can-i --list --as=<subject>; kube-hunter simuliert die Angreifer-Sicht von innen, Falco alarmiert zur Laufzeit bei ungewöhnlichen Secret-Zugriffen.

DNS als Seitenkanal. Auch mit Default-Deny-NetworkPolicy kann jeder Pod per CoreDNS Services anderer Namespaces enumerieren (tenant-b-db.tenant-b.svc.cluster.local löst auf). Wer das schließen will, braucht CoreDNS-Policy-Konfiguration oder Cilium mit DNS-aware Policies — oder gleich vCluster mit eigenem CoreDNS pro Mandant.

Quota ohne Monitoring. Eine ResourceQuota, gegen die ein Mandant dauerhaft läuft, produziert schwer diagnostizierbare FailedCreate-Events im ReplicaSet statt lauter Alarme. Exportieren Sie kube_resourcequota-Metriken nach Prometheus und alarmieren Sie ab 80 % Auslastung.

FAQ

Ab wann lohnt sich ein eigener Cluster pro Kunde?

Wenn einer von drei Faktoren zutrifft: (1) vertragliche oder regulatorische Pflicht zu dedizierter Infrastruktur (KRITIS, TISAX-Anforderungen, Auslagerungsverträge von Banken), (2) der Kunde zahlt einen Enterprise-Aufschlag, der die realen Mehrkosten von grob 150–300 EUR/Monat plus anteiligem Betriebsaufwand deckt, (3) fundamental abweichende Anforderungen wie eigene Region oder eigene Kubernetes-Version. Für alles andere ist ein dedizierter Cluster Betriebsaufwand ohne Gegenwert.

Reicht Namespace-Isolation für DSGVO-konforme Mandantentrennung?

Die DSGVO schreibt kein Isolations-Modell vor — Art. 32 verlangt "geeignete technische und organisatorische Maßnahmen" nach Stand der Technik und Risiko. Namespace-Isolation mit Default-Deny-NetworkPolicies, RBAC, Verschlüsselung at-rest (etcd-Encryption, verschlüsselte Persistent Volumes) und Audit-Logging ist für typische B2B-Daten vertretbar und dokumentierbar. Entscheidend ist, dass Sie die Trennung im Verzeichnis der Verarbeitungstätigkeiten und in AV-Verträgen sauber beschreiben und technisch nachweisen können.

Kann ein Mandant aus einem vCluster ausbrechen?

Auf API-Ebene praktisch nicht — der Mandant spricht nur mit seinem eigenen API-Server, der Syncer läuft mit begrenzten Host-Rechten. Auf Node-Ebene ja: Ein Kernel-Exploit im Container betrifft alle Pods auf demselben Node, egal ob mit oder ohne vCluster. Wer feindliche Workloads erwartet, kombiniert vCluster mit dedizierten Node-Pools oder Kata Containers.

Was kostet Multi-Tenancy im Betrieb wirklich?

Grobe Schätzung für einen Host-Cluster mit 20 Mandanten: Das Namespace-Modell ist nahezu kostenneutral (nur Workload-Ressourcen plus Platform-Engineering-Zeit für Policies). Das vCluster-Modell kostet zusätzlich ~4–20 vCPU und 10–40 GiB für virtuelle Control Planes, je nach Sizing. Zum Vergleich: 20 dedizierte EKS-Cluster kosten allein ~1.460 USD/Monat Control-Plane-Gebühr, bevor der erste Node läuft — plus 20-fachen Upgrade- und Monitoring-Aufwand.

Wie onboarde ich neue Mandanten automatisiert?

GitOps: Ein Mandant ist ein Verzeichnis im Infrastruktur-Repo (Namespace, RBAC, Quota, NetworkPolicy als Kustomize-Overlay oder Helm-Release), Argo CD oder Flux rollt es aus. Mit Capsule reduziert sich das auf ein einzelnes Tenant-Objekt, mit vCluster auf ein HelmRelease pro Kunde. Ziel: neuer Mandant in unter fünf Minuten ohne manuelles kubectl.

Fazit

Die Isolations-Entscheidung ist eine Staffelung, kein Entweder-Oder: Namespace-Isolation mit RBAC, Default-Deny-NetworkPolicies, ResourceQuota und Pod Security Admission als Default für alle Mandanten. vCluster gezielt für Kunden, die eigene CRDs, Operatoren oder API-Zugriff brauchen. Dedizierte Cluster nur dort, wo Verträge oder Regulatorik sie erzwingen — und dann korrekt bepreist. Wer heute mit dem Namespace-Fundament startet, kann einzelne Mandanten später ohne Architektur-Bruch auf vCluster heben, weil die Host-seitigen Quoten und Policies identisch bleiben.

Wenn Sie Ihre Mandanten-Architektur konkret durchsprechen wollen — welches Modell zu Ihrem Kundenmix passt, wie die Migration vom Cluster-pro-Kunde-Wildwuchs zurück auf einen Host-Cluster aussieht: Vereinbaren Sie ein Erstgespräch. Wir betreiben solche Setups täglich und sagen Ihnen auch, wenn Namespace-Isolation für Ihren Fall schlicht reicht.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen