Kubernetes Multi-Tenancy richtig umgesetzt
Die technischen Entscheidungen hinter unserer Multi-Tenant-Kubernetes-Plattform - warum Kyverno statt OPA, Helm statt Custom Operators, und wie eine einzige values.yaml einen kompletten Tenant provisioniert
Wir betreiben Multi-Tenant-Kubernetes-Cluster für Enterprise-Kunden seit 2021. In dieser Zeit sind wir von manueller Namespace-Provisionierung und handgeschriebenen RBAC-YAMLs zu einem System gekommen, bei dem das Onboarding eines neuen Teams eine einzige values.yaml-Datei und ein Merge Request ist.
Dieser Beitrag beschreibt die technischen Entscheidungen, die wir dabei getroffen haben, was dabei kaputtgegangen ist und wie die Architektur heute aussieht.
Wo die meisten Setups scheitern
Fast jede Organisation, mit der wir arbeiten, startet gleich: Jemand erstellt einen Namespace, gibt dem Team cluster-admin (oder etwas Ähnliches) und geht weiter. Das funktioniert für zwei Teams. Sobald es zehn sind, tauchen überall Probleme auf.
Das Problem ist nicht, dass Leute nachlässig sind. Es ist, dass Kubernetes kein eingebautes Konzept eines "Tenants" hat. Man bekommt Namespaces, RBAC und Resource Quotas als separate Primitiven. Diese konsistent über Teams, Umgebungen und Cluster hinweg zu verdrahten, ist die eigentliche Engineering-Herausforderung.
Wir haben das auf die harte Tour gelernt. Früh hatte ein Staging-Workload eines Kunden keine Resource Quotas. Ein Memory Leak in einer Java-Applikation verbrauchte 48 GB RAM auf einem Shared Node, was OOM Kills in drei Production-Namespaces anderer Teams auslöste. Der Fix dauerte 20 Minuten. Das Vertrauen wiederherzustellen dauerte Monate.
Die Architektur: Schichten statt Monolithen
Nach einigen Iterationen haben wir uns auf eine geschichtete Architektur festgelegt. Jede Schicht hat eine Aufgabe und klare Grenzen.
Die zentrale Erkenntnis: Der Managed Cluster ist nur das Fundament. Alles darüber ist das, was den Unterschied macht zwischen "wir haben Kubernetes" und "wir haben eine Plattform".
Schicht 1 (Managed Cluster) übernimmt die undifferenzierte Schwerstarbeit - Node-Provisionierung, API Server, etcd, CNI. Das betreiben wir auf unserer eigenen Infrastruktur (Natron Cloud) oder auf Kunden-Infrastruktur via Flex Stack.
Schichten 2-5 sind dort, wo das Tenancy-Modell lebt. Und hier kommt unser Helm-basiertes Toolkit ins Spiel.
Warum Helm und nicht ein Custom Operator
Wir haben drei Ansätze für Tenant-Provisionierung evaluiert:
- Manuelle YAMLs in einem Git-Repo. Funktioniert für fünf Tenants. Scheitert bei zwanzig. Jeder Tenant braucht 8-12 Ressourcen, und Copy-Paste-Fehler sind unvermeidlich.
- Custom Kubernetes Operator. Ein CRD wie
Tenant, das alle Ressourcen reconciled. Elegant in der Theorie. In der Praxis wartet man eine Go-Codebase, kümmert sich um Upgrade-Pfade und debuggt Controller-Crashes um 3 Uhr morgens. - Helm Chart + ArgoCD. Ein Chart, eine
values.yamlpro Tenant, ArgoCD übernimmt die Reconciliation. Kein Custom Code zu pflegen. Die Helm-Templating-Sprache ist hässlich, aber sie ist kampferprobt und jeder Platform Engineer kennt sie bereits.
Wir haben Option 3 gewählt. Das Helm Chart templated alles, was ein Tenant braucht, aus einer einzigen Values-Datei:
Die values.yaml für einen Tenant sieht so aus:
tenant:
name: team-data
namespaces:
- team-data-dev
- team-data-staging
- team-data-prod
quotas:
cpu: "8"
memory: 16Gi
storage: 100Gi
rbac:
clusterRole: namespace-admin
groups:
- "oidc:team-data-devs"
networkPolicy: restricted
registry:
project: team-data
allowList:
- "registry.natron.io/team-data/**"
- "docker.io/library/**"
vault:
path: kv/team-data/*
kyverno:
disallowPrivileged: true
requireRunAsNonRoot: true
requireReadOnlyRoot: trueDas ist die gesamte Tenant-Definition. Das Helm Chart rendert daraus 15-20 Kubernetes-Ressourcen: Namespaces, Resource Quotas, Limit Ranges, Role Bindings, Network Policies, Kyverno Policies, ClusterSecretStores, Registry Pull Secrets und mehr.
Keine Tickets. Keine manuellen Schritte. Kein "Ich habe die Network Policy vergessen".
Warum Kyverno statt OPA Gatekeeper
Das ist vermutlich die Entscheidung, nach der wir am häufigsten gefragt werden. Beide sind CNCF-Projekte. Beide machen Admission Control. Wir haben uns aus drei konkreten Gründen für Kyverno entschieden.
Policies in Rego (custom language)
Separate ConstraintTemplates + Constraints
Validation only (no mutation, no generation)
Steep learning curve for platform teams
Policies in YAML (native to K8s)
Single ClusterPolicy resource
Validate + Mutate + Generate
Platform teams already know YAML
Grund 1: YAML-native Policies. Platform-Teams denken bereits in YAML. Sie aufzufordern, Rego (OPAs Policy-Sprache) zu lernen, schafft einen Wissens-Engpass. Mit Kyverno sieht eine neue Policy aus wie jedes andere Kubernetes-Manifest, das sie bereits schreiben.
Grund 2: Mutation und Generation. Kyverno validiert nicht nur. Es kann Ressourcen mutieren (Labels injizieren, Defaults setzen) und neue Ressourcen generieren (eine NetworkPolicy erstellen, wenn ein Namespace angelegt wird). OPA Gatekeeper kann nur validieren. Wir nutzen Mutation intensiv, um konsistentes Labeling durchzusetzen und Sidecar-Konfigurationen zu injizieren.
Grund 3: Per-Tenant-Scoping. Kyverno Policies können über Label Selectors auf spezifische Namespaces beschränkt werden. Wir templaten diese Selectors im Helm Chart, sodass jeder Tenant genau die Policies bekommt, die in seiner values.yaml definiert sind. Ein Tenant, der PCI-Daten verarbeitet, bekommt strengere Image Policies als ein internes Tooling-Team.
Der Trade-off: Kyverno verbraucht mehr Memory als OPA Gatekeeper in grossen Clustern (100+ Policies). Wir mitigieren das durch Kyverno im HA-Modus mit dedizierten Node Pools.
Wie Onboarding tatsächlich funktioniert
Wenn ein Kunde ein neues Team zu seiner Plattform hinzufügen möchte, ist das der tatsächliche Workflow:
feat/onboard-team-data→mainDer Merge Request durchläuft drei automatisierte Checks, bevor er gemerged werden kann:
- helm/template validiert, dass die
values.yamlgültige Kubernetes-Manifeste rendert - kyverno/validate führt das Policy-Set des Clusters gegen die gerenderten Manifeste in der CI aus
- argocd/sync führt einen Dry-Run-Sync durch, um Konflikte mit bestehenden Ressourcen zu erkennen
Nach dem Merge erkennt ArgoCD die Änderung und synchronisiert innerhalb von 3 Minuten. Das neue Team hat seine Namespaces, RBAC, Network Policies, Secrets und Registry-Zugriff. Sie können sofort mit dem Deployen beginnen.
Falls jemand manuell eine Network Policy löscht oder eine Resource Quota im Cluster verändert, erkennt ArgoCD den Drift und synchronisiert zurück auf den gewünschten Zustand in Git. Wir haben das genau einmal in Production erlebt (ein Engineer, der "nur kurz etwas testen" wollte). Das Self-Healing griff innerhalb von 90 Sekunden.
Woran wir noch arbeiten
Diese Architektur ist nicht perfekt. Einige Dinge, an denen wir aktiv arbeiten:
Cross-Namespace-Kommunikation. Manche Teams müssen miteinander kommunizieren. Unsere Default-Deny Network Policies blockieren das. Heute lösen wir das mit expliziten Ausnahmen in den Helm-Chart-Values. Es funktioniert, skaliert aber nicht gut bei 30 Teams mit komplexen Dependency-Graphen. Wir evaluieren Ciliums ClusterWide Network Policies für einen deklarativeren Ansatz.
Tenant-spezifische Observability. Jeder Tenant bekommt eigene Grafana-Dashboards und Alert Rules, aber das darunterliegende Prometheus ist shared. Bei Skalierung wird Cardinality zum Problem. Wir prüfen Tenant-Level Metric Isolation via Thanos oder Mimir Multi-Tenancy Features.
Kostenattribution. Quotas sagen, was ein Team nutzen darf, nicht was es tatsächlich nutzt. Wir bauen Integration mit OpenCost, um jedem Tenant Sichtbarkeit in seinen tatsächlichen Ressourcenverbrauch zu geben.
Mehr erfahren
Wir haben die vollständige Architektur mit interaktiven Diagrammen auf unserer Platform Design-Seite dokumentiert. Sie zeigt das geschachtelte Isolationsmodell, den Helm-Render-Flow und den GitOps-Onboarding-Workflow im Detail.
Wenn Sie eine Multi-Tenant-Kubernetes-Plattform aufbauen, oder mit Ihrer bestehenden kämpfen, sprechen wir gerne über die Details. Termin vereinbaren und bringen Sie Ihre Architekturdiagramme mit. Das ist ein Design-Engagement, kein Verkaufsgespräch.

Über den Autor
Jan Lauber
Cloud Engineer und Partner bei Natron Tech, baut Multi-Tenant-Kubernetes-Plattformen für Enterprise-Organisationen in der Schweiz.
“Ein neues Team sollte ein Pull Request sein, kein Support-Ticket.”