Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Mühelose In-Cluster-Validierung mit Kubernetes: Validating Admission Policies im Überblick

By Eyal ZekariaMay 30, 202310 min read

Diese Seite ist auch in English, Español, Français, Italiano, 日本語 und Português verfügbar.

Schlanke Validierung von Kubernetes-Ressourcen mit Validating Admission Policies und CEL

In-Cluster-Validierung hat viele praktische Einsatzgebiete: Sie verhindert das versehentliche oder böswillige Löschen von Ressourcen, begrenzt die Anzahl der Replicas eines Deployments für ein besseres Ressourcenmanagement und stellt sicher, dass bestimmte Annotations, Labels oder Umgebungsvariablen vorhanden (oder eben nicht vorhanden) sind. Das Prinzip dahinter ist denkbar einfach: Bei jeder passenden Anfrage an die API wird ein Satz Policies ausgeführt, um zu entscheiden, ob die Anfrage zugelassen oder abgelehnt wird.

Für Cluster-Operatoren ist das ungemein wertvoll, denn so lassen sich gegenüber den Cluster-Nutzern – etwa Engineers – konkrete Standards und Regeln durchsetzen. Kein Wunder also, dass dieses Thema bei vielen unserer Kunden ganz oben auf der Agenda steht und zahlreiche Produkte und Unternehmen entstanden sind, die das Erstellen und Ausführen von Validierungen für Kubernetes-Ressourcen vereinfachen wollen.

Indem dieser Artikel die Rolle von Dynamic Admission Control (DAC) im Validierungsprozess sowie die Herausforderungen für Cluster-Operatoren näher beleuchtet, hilft er dabei, die Einfachheit und die Vorteile von Validating Admission Policies (VAP), Common Expression Language (CEL) und ihrer RBAC-ähnlichen Syntax besser einzuordnen. Mit diesem Hintergrund lässt sich fundierter entscheiden, ob diese neuen Funktionen in der eigenen Kubernetes-Umgebung zum Einsatz kommen sollen.

Wer direkt zu Code und Beispielen springen möchte, findet diese im Abschnitt Validating Admission Policies. Für fortgeschrittene Anwendungsfälle empfehlen wir den Folgebeitrag: Validating Admission Policies in Kubernetes: Advanced Use Cases.

Herausforderungen bei der Umsetzung von Dynamic Admission Control

Phasen des Admission Controllers (Quelle)

Anfragen innerhalb des Clusters zu validieren, ist nicht immer ein Selbstläufer. Dafür braucht es DAC – ein mächtiges, aber für Cluster-Operatoren nicht immer komfortables Werkzeug.

DAC ist eine zentrale Komponente im Kubernetes-Validierungsprozess. Cluster-Administratoren steuern damit die Aufnahme von Ressourcen in den Cluster, indem API-Anfragen abgefangen und auf Basis vordefinierter Policies entweder zugelassen oder abgelehnt werden. So entsteht eine feingranulare Kontrolle darüber, welche Ressourcen im Cluster erstellt, aktualisiert oder gelöscht werden – und es lässt sich sicherstellen, dass diese den definierten Standards und Regeln entsprechen.

Der Einsatz von DAC ist für Cluster-Operatoren derzeit allerdings recht aufwendig. Kurz gesagt: Es muss ein Admission-Webhook-Server entwickelt, die Webhook-Ressource erstellt, gegebenenfalls die Authentifizierung gegenüber dem API-Server konfiguriert und das für TLS verwendete Zertifikat verwaltet werden. Hinzu kommt: Operatoren müssen mit Kubernetes-Konzepten und Security-Best-Practices bestens vertraut sein und Programmiersprachen sicher beherrschen, um die Validierungslogik zu schreiben und zu pflegen.

Tools wie OPA Gatekeeper können diesen Prozess zwar vereinfachen, doch auch hier müssen Cluster-Operatoren weiterhin den im Cluster laufenden Workload bereitstellen und betreiben. Aber keine Sorge: Mit den Validating Admission Policies (VAPs) hat Kubernetes einen neuen Ressourcentyp eingeführt – verfügbar als Alpha ab v1.26 – der eine deutlich einfachere Lösung bietet. Aufgrund ihrer Einfachheit, die wir in den nächsten Abschnitten zeigen, dürften sich VAPs zum De-facto-Standard für die In-Cluster-Validierung entwickeln.

VAPs vorgestellt – der beste Freund jedes Cluster-Operators bei der Ressourcenvalidierung

VAPs nutzen Googles CEL, um Validierungsausdrücke zu definieren, die gegen passende API-Anfragen ausgeführt werden. Da CEL-Ausdrücke direkt im API-Server ausgewertet werden, müssen keine eigenen Workloads geschrieben oder bereitgestellt werden, um die Policies auszuwerten. Das macht das Erstellen von Policies denkbar einfach und niedrigschwellig – verglichen mit Alternativen wie den bereits erwähnten Validating Admission Webhooks.

Wer Sorge hat, die API zu überlasten und andere Prozesse auszubremsen, kann beruhigt sein: CEL bringt eigene Ressourcenbeschränkungen mit, die das Ganze im Zaum halten. Ein Blick in die offizielle Kubernetes-Dokumentation zu den CEL resource constraints lohnt sich.

CEL-Unterstützung wurde erstmals in Kubernetes v1.23 für die Inline-Validierung von Custom Resource Definitions (CRD) eingeführt – aktuell in der Beta-Phase ab v1.25. Damit haben sich zahlreiche Möglichkeiten für CEL-Anwendungsfälle innerhalb von Kubernetes eröffnet. Für einen tieferen Einblick in die Ursprünge von CEL in Kubernetes und sein Potenzial empfehlen wir den Vortrag von Cici Huang, "The Path to Self Contained CRDs", der unsere Auseinandersetzung mit den Validating Admission Policies angestoßen hat.

VAPs und CEL für effiziente Validierung nutzen

Einer der größten Vorteile von CEL und VAPs liegt in ihrer Einfachheit. Da die Validierungslogik direkt im API-Server ausgewertet wird, entsteht kein zusätzlicher Overhead durch eigene Workloads – der Validierungsprozess lässt sich dadurch leichter mit dem Cluster mitwachsen lassen. Außerdem bietet CEL eine schlanke und effiziente Möglichkeit, Validierungsregeln zu formulieren; das verbessert die Performance und reduziert das Risiko, den API-Server zu überlasten. Die eingebauten Ressourcenbeschränkungen sorgen zusätzlich für Stabilität und Effizienz im Validierungsprozess.

Mit Validating Admission Policies und CEL bekommen Cluster-Operatoren einen unkomplizierteren und besser skalierbaren Validierungsprozess an die Hand. So lassen sich Standards und Regeln im Cluster effizienter und wirkungsvoller durchsetzen als mit anderen Methoden. Einfache Handhabung, Skalierbarkeit und Performance machen VAPs und CEL zu einer überzeugenden Alternative für alle Cluster-Operatoren, die ihre Kubernetes-In-Cluster-Validierung verschlanken möchten.

Validating Admission Policies

Für die Nutzung dieses Features braucht es zwei zentrale Komponenten:

  1. ValidatingAdmissionPolicy – legt Failure Policy, Request-Matches und CEL-Validierungsausdrücke fest. Also: die eigentliche Policy.
  2. ValidatingAdmissionPolicyBinding – definiert den Geltungsbereich der Policy und bindet sie an einen Satz passender Ressourcen.

Auch wenn dieses Feature seit v1.26 in Alpha verfügbar ist, sind einige Funktionen erst ab v1.27 vorhanden (Audit Annotations, Validation Actions). Alle folgenden Manifeste wurden daher auf einem Kubernetes-Cluster mit v1.27.1 getestet (mit aktivierten Alpha-Features und aktiviertem ValidatingAdmissionPolicy Feature Gate).

Sämtliche Beispiele aus diesem Beitrag finden Sie auch in einem dafür angelegten GitHub-Repository.

Das erste Beispiel stammt direkt aus der Dokumentation. Wir erstellen und nutzen den Namespace demo für alle Beispiele in diesem Beitrag:

$ echo 'apiVersion: v1
kind: Namespace
metadata:
  labels:
    environment: demo
  name: demo' | k apply -f-
namespace/demo created

$ k config set-context --current --namespace demo

Als Nächstes legen wir eine Policy an, die verhindert, dass Deployments mehr als fünf Replicas haben:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
 name: "demo-policy.example.com"
spec:
 failurePolicy: Fail
 matchConstraints:
   resourceRules:
     - apiGroups:   ["apps"]
       apiVersions: ["v1"]
       operations:  ["CREATE", "UPDATE"]
       resources:   ["deployments"]
 validations:
   - expression: "object.spec.replicas <= 5"

Das Feld failurePolicy kann auf folgende Werte gesetzt werden:

  • Fail bedeutet, dass ein Fehler beim Aufruf der ValidatingAdmissionPolicy dazu führt, dass die Admission fehlschlägt und die API-Anfrage abgelehnt wird.
  • Ignore bedeutet, dass ein Fehler beim Aufruf der ValidatingAdmissionPolicy ignoriert wird und die API-Anfrage weiterläuft.

Über das Feld matchConstraints werden eingehende Anfragen abgeglichen. Es ist hier so konfiguriert, dass die Policy nur auf API-Anfragen für Deployments in der API apps/v1 greift – und nur auf CREATE- oder UPDATE-Anfragen.

Im Feld validations stehen schließlich die eigentlichen CEL-Ausdrücke, die gegen die passenden API-Anfragen ausgeführt werden. Damit eine Anfrage zugelassen wird, müssen alle Ausdrücke zu true ausgewertet werden.

Mit folgendem Binding vervollständigen wir die Konfiguration:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "demo-binding-test.example.com"
spec:
  policyName: "demo-policy.example.com"
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: demo

Wir binden die zuvor erstellte Policy an jeden Namespace, der das Label environment=demo trägt – damit lässt sich gezielt steuern, wo die Policy-Validierung greifen soll.

Es gibt weitere Optionen für das Matching von Namespaces, etwa matchExpressions für feinere Abgleiche, sowie zusätzliche Konfigurationsmöglichkeiten: excludeResourceRules, um bestimmte Ressourcen auszuschließen, oder objectSelector, um bestimmte Objekte abzugleichen (davon ist eher abzuraten, da Engineers ein Label einfach weglassen können, um dem Auditing zu entgehen). Die spannenderen Optionen behandle ich in späteren Beispielen.

Sobald diese Manifeste auf den Cluster angewendet sind, schlägt der Versuch, ein neues Deployment anzulegen, das gegen die Policy verstößt, erwartungsgemäß fehl:

$ k create deployment nginx — image=nginx — replicas=10
error: failed to create deployment: deployments.apps "nginx" is forbidden: ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

Auch beim Diff der Ressource greift dies:

$ k create deployment nginx --image=nginx --replicas=10 --dry-run=client -oyaml | k diff -f -
The deployments "nginx" is invalid: : ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

Mehrere Validierungsausdrücke

Manchmal sollen mehrere Validierungen gleichzeitig auf eine Ressource angewendet werden. Sehen Sie sich dazu folgende Validierungsausdrücke für Deployments an:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups:   ["apps"]
        apiVersions: ["v1"]
        operations:  ["CREATE", "UPDATE"]
        resources:   ["deployments"]
  validations:
    # Deployments dürfen nicht mehr als 3 Replicas haben
    - expression: "object.spec.replicas <= 3"
    # Deployment-Container müssen Images aus der Artifact Registry in europe-west1 im Projekt test-eyal verwenden
    - expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('europe-west1-docker.pkg.dev/test-eyal/'))"
    # Deployment darf keine emptyDir-Volumes verwenden
    - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))"

Damit ein Deployment, auf das diese Policy zutrifft, im Cluster zugelassen wird, müssen alle Ausdrücke zu true ausgewertet werden.

Wichtig zu wissen: Die Validierungen werden sequenziell ausgeführt. Schlägt ein Ausdruck fehl, wird dieser Fehler sofort an den Client zurückgegeben – verstößt Ihre Ressource gegen mehr als einen der Validierungsausdrücke, läuft das also auf einen iterativen Prozess hinaus: Ablehnung, Korrektur, neuer Versuch.

Validierungsmeldung anpassen

Es ist zudem möglich, im Fehlerfall eine eigene message an den Client zurückzugeben. Bei Bedarf lässt sich über messageExpression sogar interpolieren. Eine Message-Expression hat Zugriff auf object, oldObject, request und params.

Aktualisieren wir unsere letzte Policy und ersetzen die Kommentare durch eigene Meldungen:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups:   ["apps"]
        apiVersions: ["v1"]
        operations:  ["CREATE", "UPDATE"]
        resources:   ["deployments"]
  validations:
    - expression: "object.spec.replicas <= 3"
      messageExpression: "'Deployments cannot have more than 3 replicas, this one has ' + string(object.spec.replicas)"
    - expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('europe-west1-docker.pkg.dev/test-eyal/'))"
      message: "Deployment containers must be using images hosted in europe-west1 Artifact Registry in project test-eyal"
    - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))"
      messageExpression: "'Deployment cannot use emptyDir volumes, change the following volume: ' + object.spec.template.spec.volumes.filter(v, has(v.emptyDir)).map(v, v.name)[0]"

Wichtig: Alle interpolierten Werte im Feld messageExpression müssen vom Typ string sein – andernfalls liefert die Meldung einen Fehler für die fehlgeschlagene Auswertung zurück.

Ohne eigene Meldung würde ein Deployment mit einem emptyDir-Volume etwa so fehlschlagen:

The deployments "nginx" is invalid: : ValidatingAdmissionPolicy ‘demo-policy.example.com’ with binding ‘demo-binding-test.example.com’ denied request: failed expression: !has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))

Aus Client-Sicht ist das nicht unbedingt leicht zu deuten. Mit der eigenen Message-Expression sieht das Ganze so aus:

The deployments "nginx" is invalid: : ValidatingAdmissionPolicy ‘demo-policy.example.com’ with binding ‘demo-binding-test.example.com’ denied request: Deployment cannot use emptyDir volumes, change the following volumes: test

Ich verwende hier bewusst messageExpression statt einer einfachen message, um die Möglichkeiten von CEL zu zeigen. Nur weil man etwas kann, heißt das aber nicht, dass man es immer tun sollte!

Weiterführende Ressourcen

Mit der Weiterentwicklung von Kubernetes wird die CEL-Unterstützung in kommenden Releases noch viele weitere spannende Funktionen mit sich bringen. Wir empfehlen, sich mit Validating Admission Policies vertraut zu machen – das Feature dürfte sich zu einem festen Werkzeug für Cluster-Operatoren entwickeln.

Beachten Sie, dass das Feature Gate für Validating Admission Policies derzeit noch in Alpha ist. Auf dem Weg zur General Availability sind also Änderungen und Verbesserungen zu erwarten. Bleiben Sie über das Kubernetes-Changelog und die Dokumentation auf dem Laufenden, um die neuesten Entwicklungen mitzubekommen.

Da sich dieses Feature ständig weiterentwickelt, kann der Inhalt dieses Artikels mit der Zeit aktualisierungsbedürftig sein. Sollten Sie auf ein Beispiel stoßen, das nicht mehr funktioniert, oder auf eine Aussage, die nicht mehr zutrifft, melden Sie sich gerne bei uns – wir prüfen die Inhalte und passen sie entsprechend an.