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 bereitstellt.
Wir betreiben Multi-Tenant-Kubernetes-Cluster für Enterprise-Kunden seit 2021. In dieser Zeit sind wir vom manuellen Anlegen von Namespaces und handgeschriebenen RBAC-YAMLs zu einem System gekommen, in dem das Onboarding eines neuen Teams aus einer einzigen values.yaml und einem Merge Request besteht.
Dieser Beitrag zeigt, welche technischen Entscheidungen wir auf dem Weg dorthin getroffen haben, was uns dabei kaputtgegangen ist und wie die Architektur heute aussieht.
Wo die meisten Setups scheitern
Fast jede Organisation, mit der wir arbeiten, beginnt gleich: Jemand legt einen Namespace an, gibt dem Team cluster-admin (oder etwas in der Art) und macht weiter. Das funktioniert für zwei Teams. Sobald es zehn werden, tauchen die Probleme überall gleichzeitig auf.
Das Problem ist nicht, dass Leute nachlässig wären. Es ist, dass Kubernetes keinen eingebauten Begriff von "Tenant" kennt. Sie bekommen Namespaces, RBAC und Resource Quotas als einzelne Bausteine. Diese über Teams, Umgebungen und Cluster hinweg konsistent zu verdrahten, ist die eigentliche Engineering-Aufgabe.
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-Anwendung verbrauchte 48 GB RAM auf einem geteilten Node und löste OOM-Kills in drei Production-Namespaces anderer Teams aus. Den Fehler hatten wir in 20 Minuten behoben. Das Vertrauen wieder aufzubauen, dauerte Monate.
Die Architektur: Schichten statt Monolithen
Nach einigen Iterationen sind wir bei einer Schichtenarchitektur gelandet. Jede Schicht hat eine Aufgabe und klar gezogene Grenzen.
Die zentrale Erkenntnis: Der Managed Cluster ist nur das Fundament. Erst alles darüber macht den Unterschied zwischen "wir haben Kubernetes" und "wir haben eine Plattform" aus.
Schicht 1 (Managed Cluster) übernimmt die Grundarbeit, die für alle gleich aussieht: Node-Bereitstellung, API-Server, etcd, CNI. Diese Schicht betreiben wir auf eigener Infrastruktur (Natron Cloud) oder auf Kunden-Infrastruktur über Flex Stack.
In den Schichten 2 bis 5 lebt das eigentliche Tenancy-Modell. Und genau dort kommt unser Helm-basiertes Toolkit zum Einsatz.
Warum Helm und kein eigener Operator
Wir haben drei Wege geprüft, um Tenants bereitzustellen:
- Manuelle YAMLs in einem Git-Repo. Funktioniert für fünf Tenants. Bricht bei zwanzig zusammen. Jeder Tenant braucht 8 bis 12 Ressourcen, und Copy-Paste-Fehler sind unvermeidlich.
- Eigener Kubernetes-Operator. Ein CRD wie
Tenant, das alle Ressourcen abgleicht. In der Theorie elegant. In der Praxis pflegen Sie eine Go-Codebase, kümmern sich um Upgrade-Pfade und debuggen Controller-Crashes um drei Uhr morgens. - Helm Chart und ArgoCD. Ein Chart, eine
values.yamlpro Tenant, ArgoCD übernimmt den Abgleich. Kein eigener Code, der gepflegt werden muss. Die Templating-Sprache von Helm ist nicht hübsch, aber sie ist erprobt, und jeder Platform Engineer kennt sie ohnehin.
Wir haben Variante 3 gewählt. Das Helm Chart erzeugt aus einer einzigen Values-Datei alles, was ein Tenant braucht:
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 komplette Tenant-Definition. Daraus rendert das Helm Chart 15 bis 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. Beides 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: Policies in YAML. Plattform-Teams denken bereits in YAML. Sie zusätzlich Rego (die Policy-Sprache von OPA) lernen zu lassen, schafft einen Engpass beim Wissen. In Kyverno sieht eine neue Policy aus wie jedes andere Kubernetes-Manifest, das im Team ohnehin geschrieben wird.
Grund 2: Mutation und Generierung. Kyverno validiert nicht nur. Es kann Ressourcen verändern (Labels einfügen, Defaults setzen) und neue Ressourcen erzeugen (etwa eine NetworkPolicy, sobald ein Namespace angelegt wird). OPA Gatekeeper kann nur validieren. Wir nutzen Mutation intensiv, um konsistente Labels durchzusetzen und Sidecar-Konfigurationen zu injizieren.
Grund 3: Geltungsbereich pro Tenant. Kyverno-Policies lassen sich über Label Selectors auf einzelne Namespaces einschränken. Diese Selectors bauen wir im Helm Chart als Templates auf, sodass jeder Tenant genau die Policies erhält, 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 in grossen Clustern (über 100 Policies) mehr Memory als OPA Gatekeeper. Wir entschärfen das, indem wir Kyverno im HA-Modus auf eigenen Node Pools fahren.
Wie das Onboarding tatsächlich abläuft
Wenn ein Kunde ein neues Team auf seine Plattform aufnehmen will, sieht der reale Ablauf so aus:
feat/onboard-team-data→mainDer Merge Request durchläuft drei automatisierte Prüfungen, bevor er gemerged werden darf:
- helm/template prüft, dass die
values.yamlzu gültigen Kubernetes-Manifesten rendert. - kyverno/validate wendet das Policy-Set des Clusters in der CI auf die gerenderten Manifeste an.
- argocd/sync läuft als Dry-Run, um Konflikte mit bestehenden Ressourcen zu finden.
Nach dem Merge erkennt ArgoCD die Änderung und gleicht innerhalb von drei Minuten ab. Das neue Team hat seine Namespaces, RBAC, Network Policies, Secrets und seinen Registry-Zugriff. Es kann sofort mit dem Deployen beginnen.
Wenn jemand eine Network Policy manuell löscht oder eine Resource Quota im Cluster anpasst, erkennt ArgoCD den Drift und stellt den in Git definierten Sollzustand wieder her. Wir haben das in der Produktion genau einmal 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. Ein paar Themen, an denen wir aktuell arbeiten:
Kommunikation zwischen Namespaces. Manche Teams müssen miteinander sprechen. Unsere Default-Deny-Policies blockieren das. Heute lösen wir es über explizite Ausnahmen in den Helm-Values. Das funktioniert, skaliert aber bei 30 Teams mit komplexen Abhängigkeiten nicht mehr gut. Wir prüfen die ClusterWide Network Policies von Cilium für einen deklarativeren Ansatz.
Tenant-bezogene Observability. Jeder Tenant erhält eigene Grafana-Dashboards und Alert Rules, das darunterliegende Prometheus ist aber gemeinsam genutzt. Bei wachsendem Volumen wird die Cardinality zum Problem. Wir prüfen Tenant-Level Metric Isolation über die Multi-Tenancy-Funktionen von Thanos oder Mimir.
Kostenzuordnung. Quotas zeigen, was ein Team nutzen darf, nicht was es tatsächlich nutzt. Wir bauen eine Anbindung an OpenCost, damit jeder Tenant Einblick in seinen realen Ressourcenverbrauch erhält.
Mehr erfahren
Die vollständige Architektur mit interaktiven Diagrammen finden Sie auf unserer Platform Design-Seite. Sie zeigt das verschachtelte Isolationsmodell, den Helm-Render-Flow und den GitOps-Onboarding-Workflow im Detail.
Wenn Sie eine Multi-Tenant-Kubernetes-Plattform aufbauen oder mit Ihrer bestehenden ringen, sprechen wir gerne über die Details. Termin vereinbaren und Architekturdiagramme mitbringen. 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.”
