Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Validation in-cluster simplifiée avec Kubernetes : les Validating Admission Policies

By Eyal ZekariaMay 30, 202310 min read

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

Simplifier la validation des ressources Kubernetes grâce aux Validating Admission Policies et au CEL

La validation in-cluster répond à de nombreux besoins concrets : empêcher la suppression accidentelle ou malveillante de ressources, plafonner le nombre de replicas d'un déploiement pour mieux gérer les ressources, ou encore exiger la présence (ou l'absence) de certaines annotations, labels ou variables d'environnement. Le principe de la validation des ressources au sein d'un cluster Kubernetes est simple : à chaque requête correspondante adressée à l'API, un ensemble de policies est exécuté pour décider si elle est autorisée ou refusée.

Ce mécanisme est précieux pour les opérateurs de cluster, car il leur permet d'imposer des standards et des règles spécifiques aux utilisateurs du cluster, comme les développeurs. Pas étonnant que le sujet soit devenu brûlant pour bon nombre de nos clients, et que de nombreux produits et entreprises aient vu le jour pour simplifier la création et l'exécution de validations sur les ressources Kubernetes.

En détaillant le rôle du Dynamic Admission Control (DAC) dans le processus de validation et les défis rencontrés par les opérateurs de cluster, cet article vous aidera à mieux mesurer la simplicité et les bénéfices apportés par les Validating Admission Policies (VAP), le Common Expression Language (CEL) et leur syntaxe inspirée du RBAC. Ce contexte vous permettra de décider en toute connaissance de cause de l'adoption de ces nouvelles fonctionnalités dans vos environnements Kubernetes.

Si vous êtes ici pour le code et les exemples concrets, rendez-vous directement à la section Validating Admission Policies. Pour des cas d'usage plus avancés, consultez l'article qui lui fait suite : Validating Admission Policies in Kubernetes: Advanced Use Cases.

Les défis de la mise en place du Dynamic Admission Control

Phases de l'Admission Controller ( source)

Mettre en place la validation des requêtes au sein de votre cluster n'a rien d'une promenade de santé. Cela suppose de recourir au DAC — un outil puissant, mais pas toujours commode pour les opérateurs de cluster.

Le DAC est un composant essentiel du processus de validation Kubernetes. Il permet aux administrateurs de cluster de gérer l'admission des ressources en interceptant les requêtes API et en les autorisant ou en les rejetant selon un ensemble de policies prédéfinies. On obtient ainsi un contrôle fin sur les ressources créées, mises à jour ou supprimées dans le cluster, en garantissant qu'elles respectent les standards et règles définis.

Toutefois, l'utilisation actuelle du DAC peut s'avérer ardue pour les opérateurs de cluster, en raison de sa complexité. En résumé, il faut écrire un serveur d'admission webhook, créer la ressource webhook, configurer l'authentification auprès du serveur API si nécessaire et gérer le certificat utilisé pour le TLS. Les opérateurs doivent en outre maîtriser les concepts Kubernetes ainsi que les bonnes pratiques de sécurité, et savoir programmer pour créer et maintenir la logique de validation.

Des outils comme OPA Gatekeeper peuvent fluidifier ce processus, mais les opérateurs de cluster doivent toujours déployer et gérer le workload qui s'exécute dans leur cluster. Rassurez-vous : Kubernetes a introduit un nouveau type de ressource baptisé Validating Admission Policies (VAPs), disponible en alpha à partir de la v1.26, qui propose une solution nettement plus simple. Cette simplicité, illustrée dans les sections suivantes, fera vraisemblablement des VAPs le standard de fait pour la validation in-cluster.

Les VAPs — le meilleur allié des opérateurs de cluster pour la validation des ressources

Les VAPs s'appuient sur le CEL de Google pour définir des expressions de validation qui seront évaluées sur les requêtes API correspondantes. Comme les expressions CEL sont évaluées directement dans le serveur API, inutile d'écrire ou de déployer des workloads personnalisés pour évaluer vos policies. La création de policies devient ainsi remarquablement directe et accessible, par rapport à des méthodes alternatives comme les Validating Admission Webhooks évoqués plus haut.

Si vous craignez de surcharger l'API et de perturber d'autres processus, pas d'inquiétude : le CEL est assorti de contraintes de ressources qui maintiennent l'ensemble sous contrôle. Mieux vaut se renseigner sur ces garde-fous en consultant la section consacrée aux contraintes de ressources CEL dans la documentation officielle de Kubernetes.

Le support de CEL a d'abord été introduit dans Kubernetes v1.23 pour la validation inline des Custom Resource Definitions (CRD) — actuellement en bêta depuis la v1.25. Cela a ouvert d'innombrables perspectives d'usage du CEL au sein de Kubernetes. Pour approfondir les origines du CEL dans Kubernetes et son potentiel, nous vous recommandons la conférence de Cici Huang, The Path to Self Contained CRDs, qui a inspiré notre exploration des Validating Admission Policies.

Tirer parti des VAPs et du CEL pour une validation efficace

L'un des principaux atouts du CEL et des VAPs, c'est leur simplicité. Comme la logique de validation est évaluée directement dans le serveur API, aucune surcharge n'est liée à des workloads personnalisés, ce qui facilite la mise à l'échelle du processus de validation à mesure que le cluster grandit. Le CEL offre par ailleurs une manière légère et performante d'exprimer des règles de validation, ce qui améliore les performances et réduit le risque de saturer le serveur API. Les contraintes de ressources intégrées garantissent en outre la stabilité et l'efficacité de l'ensemble.

En s'appuyant sur les Validating Admission Policies et le CEL, les opérateurs de cluster bénéficient d'un processus de validation plus direct et plus scalable. Ils peuvent ainsi imposer des standards et des règles au sein du cluster avec plus d'efficacité qu'avec d'autres méthodes. La facilité d'utilisation, la scalabilité et les gains de performance offerts par les VAPs et le CEL en font une alternative séduisante pour les opérateurs de cluster qui cherchent à rationaliser la validation in-cluster sur Kubernetes.

Validating Admission Policies

L'utilisation de cette fonctionnalité repose sur deux composants principaux :

  1. ValidatingAdmissionPolicy — définit la failure policy, les correspondances de requêtes et les expressions de validation CEL. Autrement dit : la policy.
  2. ValidatingAdmissionPolicyBinding — définit la portée de la policy ; il associe la policy à un ensemble de ressources correspondantes.

Bien que cette fonctionnalité soit en Alpha depuis la v1.26, certaines capacités ne sont disponibles qu'à partir de la v1.27 ( audit annotation, validation actions) ; tous les manifests qui suivent ont donc été testés sur un cluster Kubernetes en v1.27.1 (avec les fonctionnalités alpha et le feature gate ValidatingAdmissionPolicy activés).

Tous les exemples de cet article sont également disponibles dans un dépôt GitHub créé à cet effet.

Le premier exemple est tiré directement de la documentation. Nous allons créer et utiliser le namespace demo pour exécuter tous les exemples de cet article :

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

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

Créons ensuite une policy qui empêche les Deployments de dépasser cinq replicas :

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"

Le champ failurePolicy peut prendre les valeurs suivantes :

  • Fail signifie qu'une erreur lors de l'appel à la ValidatingAdmissionPolicy entraîne l'échec de l'admission et le rejet de la requête API.
  • Ignore signifie qu'une erreur lors de l'appel à la ValidatingAdmissionPolicy est ignorée et que la requête API peut se poursuivre.

Le champ matchConstraints sert à faire correspondre les requêtes entrantes ; il est configuré ici pour que la policy ne s'applique qu'aux requêtes API portant sur des Deployments dans l'API apps/v1, et uniquement aux requêtes CREATE ou UPDATE d'un Deployment.

Enfin, le champ validations contient les expressions CEL qui seront évaluées sur les requêtes API correspondantes. Toutes les expressions doivent renvoyer true pour que la requête soit admise.

Nous allons créer le binding suivant pour finaliser la configuration :

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

Nous associons la policy créée précédemment à tout namespace portant le label environment=demo — pratique pour configurer l'endroit où la validation doit s'appliquer.

D'autres options existent pour cibler des namespaces, comme matchExpressions pour un matching plus granulaire, ainsi que d'autres options de configuration : excludeResourceRules pour exclure certaines ressources, objectSelector pour cibler certains objets (déconseillé, puisque les développeurs peuvent omettre un label pour échapper à l'audit). Je couvrirai les options les plus intéressantes dans des exemples ultérieurs.

Une fois ces manifests appliqués au cluster, toute tentative de création d'un nouveau Deployment qui enfreint la policy renverra une erreur, comme prévu :

$ 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

C'est aussi le cas lorsqu'on tente un diff de la ressource :

$ 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

Plusieurs expressions de validation

Il arrive que vous souhaitiez effectuer plusieurs validations sur les ressources. Considérez les expressions de validation suivantes pour des Deployments :

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:
    # Les Deployments ne peuvent pas avoir plus de 3 replicas
    - expression: "object.spec.replicas <= 3"
    # Les containers du Deployment doivent utiliser des images hébergées dans l'Artifact Registry europe-west1 du projet test-eyal
    - expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('europe-west1-docker.pkg.dev/test-eyal/'))"
    # Le Deployment ne peut pas utiliser de volumes emptyDir
    - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))"

Un Deployment ciblé par cette policy devra voir toutes les expressions évaluées à true pour être admis dans le cluster.

À noter : les validations sont exécutées séquentiellement. Si une expression échoue, l'échec est immédiatement renvoyé au client — autrement dit, si votre ressource enfreint plusieurs expressions de validation, vous devrez procéder par itérations : refus, correction, nouvelle tentative.

Personnaliser le message de validation

Il est également possible de fournir un message personnalisé renvoyé au client en cas d'échec de validation. Vous pouvez même faire de l'interpolation au besoin, à l'aide d'un messageExpression. Une expression de message a accès à object, oldObject, request et params.

Mettons à jour notre dernière policy avec des messages personnalisés plutôt que des commentaires :

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]"

À noter : toute valeur interpolée dans le champ messageExpression doit être de type string, faute de quoi le message renverra une erreur d'évaluation à la place.

Sans message personnalisé, un deployment avec un volume emptyDir aurait échoué de la manière suivante :

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))

Difficile à interpréter du point de vue du client. Avec une expression de message personnalisée, on obtiendrait plutôt :

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

J'utilise ici un messageExpression plutôt qu'un simple message pour cette validation, afin d'illustrer la puissance du CEL. Ce n'est pas parce que vous pouvez faire quelque chose que vous devez le faire !

Ressources complémentaires

À mesure que Kubernetes évolue, le support du CEL apportera des capacités encore plus enthousiasmantes dans les prochaines versions. Nous vous invitons à explorer les Validating Admission Policies et à vous familiariser avec cette fonctionnalité puissante, vouée à devenir un outil incontournable pour les opérateurs de cluster.

Gardez à l'esprit que le feature gate Validating Admission Policy est actuellement en Alpha : des évolutions et des améliorations sont donc à prévoir avant son passage en disponibilité générale. Pour ne rien manquer des dernières évolutions, suivez le changelog et la documentation Kubernetes.

Compte tenu du caractère évolutif de cette fonctionnalité, le contenu de cet article pourra nécessiter des mises à jour. Si vous tombez sur un exemple qui ne fonctionne plus ou sur une affirmation qui n'est plus exacte, n'hésitez pas à nous contacter : nous reverrons et mettrons à jour l'information en conséquence.