Kyverno im Produktiveinsatz: Lessons Learned nach zwei Jahren
Zwei Jahre später
Ende 2021 haben wir in unserem Kyverno-Einführungsartikel beschrieben, warum wir uns für Kyverno als Policy Engine entschieden haben. Damals war Kyverno ein CNCF-Sandbox-Projekt in Version 1.5. Heute, gut zwei Jahre später, ist Kyverno ein CNCF Graduated Project -- derselbe Reifegrad wie Kubernetes selbst -- und wir laufen auf Version 1.11. Die Policy Engine hat sich in unseren Clustern als fester Bestandteil der Infrastruktur etabliert, und wir haben dabei eine Menge gelernt.
Dieser Artikel fasst unsere Erfahrungen zusammen: Was hat funktioniert, wo haben wir Fehler gemacht, und welche Policies halten wir mittlerweile für unverzichtbar.
Audit vs. Enforce: Die richtige Strategie
Die wichtigste Lektion der ersten Monate war: Niemals eine neue Policy direkt im Enforce-Modus ausrollen. Wir haben das genau einmal gemacht -- eine Policy, die fehlende Resource Limits blockieren sollte -- und damit am Montagmorgen ein Deployment blockiert, weil ein internes Tool ohne Limits konfiguriert war. Das Team war nicht amüsiert.
Seitdem folgen wir einem strikten Dreistufenplan:
- Audit-Modus deployen -- die Policy läuft, blockiert aber nichts. Kyverno erstellt PolicyReports für jeden Verstoß.
- Reports auswerten und Violations beheben -- typischerweise über zwei bis vier Wochen, abhängig von der Policy-Reichweite.
- Auf Enforce umstellen -- erst wenn keine Violations mehr existieren.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
annotations:
policies.kyverno.io/title: Require Resource Limits
policies.kyverno.io/severity: medium
spec:
# Schritt 1: Audit
# validationFailureAction: Audit
# Schritt 3: Enforce
validationFailureAction: Enforce
background: true
rules:
- name: check-limits
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Alle Container muessen CPU- und Memory-Limits definieren.
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"
Der Schlüssel liegt in der Disziplin, den Audit-Zeitraum tatsächlich einzuhalten. Es ist verlockend, eine offensichtlich sinnvolle Policy sofort zu enforcen. Aber in einem Cluster mit Dutzenden Services und mehreren Teams gibt es immer Ausnahmen, die man nicht auf dem Schirm hatte.
Policy Exceptions: Regeln mit Ausnahmen
Ab Kyverno 1.9 gibt es ein dediziertes Feature für Policy Exceptions -- und es hat unseren Umgang mit Sonderfällen grundlegend verbessert. Früher haben wir Exceptions direkt in die Policy eingebaut, was die Policies unübersichtlich gemacht hat. Jetzt sind Exceptions eigenständige Ressourcen:
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
name: allow-monitoring-without-limits
namespace: monitoring
spec:
exceptions:
- policyName: require-resource-limits
ruleNames:
- check-limits
match:
any:
- resources:
kinds:
- Pod
namespaces:
- monitoring
names:
- "prometheus-node-exporter-*"
Diese Exception erlaubt dem Prometheus Node Exporter, ohne Resource Limits zu laufen -- was bei DaemonSets auf Nodes mit unterschiedlicher Ausstattung sinnvoll sein kann. Die Exception ist klar dokumentiert, lebt im Git-Repository und durchläuft den gleichen Review-Prozess wie jede andere Änderung.
Unser Grundsatz: Jede Exception braucht einen Kommentar, warum sie existiert. Exceptions ohne Begründung werden im Review abgelehnt.
Policy-Testing mit der Kyverno CLI
Im Kyverno-Einführungsartikel hatten wir angekündigt, Policies in die CI-Pipeline integrieren zu wollen. Das haben wir mittlerweile umgesetzt, und es war eine der besten Entscheidungen im gesamten Setup.
Die Kyverno CLI bietet einen test-Befehl, der Policies gegen definierte Testfälle prüft -- lokal, ohne laufenden Cluster. Die Testdefinition ist eine YAML-Datei:
# tests/require-resource-limits/kyverno-test.yaml
apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata:
name: test-require-resource-limits
policies:
- ../../policies/require-resource-limits.yaml
resources:
- resources.yaml
results:
- policy: require-resource-limits
rule: check-limits
resource: pod-with-limits
kind: Pod
result: pass
- policy: require-resource-limits
rule: check-limits
resource: pod-without-limits
kind: Pod
result: fail
Dazu die Test-Ressourcen:
# tests/require-resource-limits/resources.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-with-limits
spec:
containers:
- name: app
image: nginx:1.25
resources:
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Pod
metadata:
name: pod-without-limits
spec:
containers:
- name: app
image: nginx:1.25
Ein kyverno test tests/require-resource-limits/ validiert, dass die Policy den Pod mit Limits akzeptiert und den ohne Limits ablehnt. Das läuft in unserer CI-Pipeline bei jedem Push, der Policy-Dateien verändert. Seit wir das eingeführt haben, hatten wir keinen einzigen Fall mehr, in dem eine fehlerhafte Policy in den Cluster gelangt ist.
CI/CD-Integration
Die Kyverno CLI leistet noch mehr als nur Tests. Wir nutzen sie auch, um unsere Helm Charts gegen die aktiven Policies zu validieren, bevor sie deployed werden. Der Workflow in der Pipeline sieht so aus:
# 1. Helm Templates rendern
helm template my-release ./charts/my-app \
-f values/production.yaml > rendered.yaml
# 2. Gegen Kyverno-Policies pruefen
kyverno apply policies/ --resource rendered.yaml
# 3. Nur bei Erfolg deployen
helmfile -e production apply
Der zweite Schritt schlägt fehl, wenn das gerenderte Manifest eine Enforce-Policy verletzen würde. Das ist ein entscheidender Vorteil gegenüber dem reinen Admission-Webhook-Ansatz: Fehler werden früh erkannt, nicht erst beim Deployment. In Kombination mit unserem Helmfile-Multi-Cluster-Setup validieren wir so die Manifeste für alle drei Cluster in einem einzigen Pipeline-Job.
Performance: Was wir beobachtet haben
Kyverno läuft als Admission Webhook und sitzt damit im kritischen Pfad jeder API-Anfrage. Performance ist kein theoretisches Problem -- es ist ein reales. Unsere Erfahrungen:
Speicherverbrauch: Kyverno hält den Policy-Status und gecachte Ressourcen im Speicher. Mit rund 25 ClusterPolicies und 10 PolicyExceptions liegt unser Kyverno-Pod bei etwa 350 MB RSS. Das ist akzeptabel, aber man sollte es im Auge behalten. Bei mehr als 50 Policies oder Policies mit komplexen Pattern-Matches steigt der Verbrauch spürbar.
Latenz: Im Durchschnitt fügt Kyverno 10-30ms zur API-Antwortzeit hinzu. Für reguläre Deployments ist das irrelevant. Bei Batch-Operationen -- etwa wenn ein Helmfile-Apply dreißig Releases gleichzeitig aktualisiert -- kann sich das summieren. Wir haben die Webhook-Timeout-Konfiguration auf 15 Sekunden belassen und hatten nie ein Timeout-Problem.
Replicas: Seit Kyverno 1.9 empfiehlt das Projekt drei Replicas für Production. Wir fahren mit zwei Replicas und einem PodDisruptionBudget von minAvailable: 1. Das reicht für unsere Cluster-Größe und übersteht Node-Wartungen ohne Ausfälle.
Must-have Policies: Unser Basis-Set
Nach zwei Jahren haben sich fünf Policies herauskristallisiert, die wir auf jedem Cluster deployen -- sie bilden das Minimum für einen sauber betriebenen Cluster:
Resource Limits erforderlich -- verhindert, dass Container ohne CPU- und Memory-Limits deployed werden. Ohne Limits kann ein einzelner Pod den gesamten Node destabilisieren.
Latest-Tag verbieten -- wie im Einführungsartikel beschrieben. Der
latest-Tag ist nicht deterministisch und hat in Production nichts zu suchen.Standard-Labels erzwingen -- jedes Deployment muss
app.kubernetes.io/name,app.kubernetes.io/versionund ein Team-Label tragen. Das ist die Grundlage für brauchbares Monitoring und Alerting.Erlaubte Image-Registries -- Container dürfen nur Images aus unserer privaten Registry und explizit freigegebenen Public Registries ziehen. Das verhindert, dass versehentlich ungeprüfte Images in den Cluster gelangen.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: Enforce
rules:
- name: validate-registries
match:
any:
- resources:
kinds:
- Pod
validate:
message: >-
Images duerfen nur aus erlaubten Registries stammen.
pattern:
spec:
containers:
- image: "registry.encircle360.com/* | docker.io/library/* | ghcr.io/*"
- Default-NetworkPolicy generieren -- jeder neue Namespace bekommt automatisch eine Deny-All-Ingress-Policy. Das war bereits im Einführungsartikel beschrieben und ist nach wie vor eine unserer wertvollsten Generate-Policies.
Diese fünf Policies fangen die häufigsten Konfigurationsfehler ab und etablieren eine Baseline, die für alle Teams gilt. Alles darüber hinaus -- spezifischere Validierungen, Compliance-Anforderungen, Team-spezifische Regeln -- bauen wir je nach Bedarf dazu.
Organisation der Policies im Repository
Unsere Policies leben im selben Git-Repository wie die Helmfile-Konfiguration. Die Verzeichnisstruktur:
kyverno/
policies/
require-resource-limits.yaml
disallow-latest-tag.yaml
require-labels.yaml
restrict-image-registries.yaml
generate-default-networkpolicy.yaml
exceptions/
monitoring-limits-exception.yaml
tests/
require-resource-limits/
kyverno-test.yaml
resources.yaml
disallow-latest-tag/
kyverno-test.yaml
resources.yaml
Die Policies werden über ein Kyverno-Release in unserem Helmfile deployed. Änderungen an Policies durchlaufen denselben Pull-Request-Workflow wie jede andere Infrastruktur-Änderung: Branch, CI-Tests, Review, Merge, Deploy.
Fazit
Kyverno hat sich in zwei Jahren von einem vielversprechenden Sandbox-Projekt zu einem ausgereiften, CNCF-graduierten Tool entwickelt -- und in unserer Infrastruktur von einem Experiment zu einer tragenden Säule. Die Kombination aus YAML-basierten Policies, dem CLI-Testing-Framework und der nahtlosen Integration in bestehende Kubernetes-Workflows macht es für Teams unserer Größe zur idealen Policy Engine.
Die wichtigsten Lessons Learned in Kurzform: Immer zuerst Audit, dann Enforce. Policies testen wie Code. Exceptions explizit und dokumentiert verwalten. Performance im Auge behalten, aber nicht überingenieueren. Und mit einem soliden Basis-Set anfangen, statt gleich alles regeln zu wollen.
Wer Kyverno noch nicht einsetzt und Kubernetes in Production betreibt, dem legen wir den Einstieg ans Herz. Die Lernkurve ist flach, der Nutzen sofort spürbar -- und nach zwei Jahren können wir sagen: Es hält, was es verspricht.
Geschrieben von
Patrick HütterGründer & Software-Architekt
Softwarearchitekt, Ingenieur und Unternehmer. Patrick baut seit über einem Jahrzehnt Produkte und Plattformen — von Enterprise-Backends über Cloud-Native-Infrastruktur bis hin zu KI-gestützten Anwendungen. Als Gründer von encircle360 verbindet er technische Tiefe mit unternehmerischem Denken und treibt Open-Source-Projekte voran, die echten Impact haben.
Das könnte dich auch interessieren
CI-Runner in der microVM: Docker-Builds mit Kata Containers auf Kubernetes
31.08.2026 · 10 Min. Lesezeit
GitOps mit Helmfile und Kyverno: Unser Deployment-Workflow
10.03.2025 · 7 Min. Lesezeit
K3s und KubeVirt: Konvergente Infrastruktur auf Bare Metal
20.01.2025 · 6 Min. Lesezeit