Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Validazione in-cluster su Kubernetes senza complicazioni: ecco le Validating Admission Policies

By Eyal ZekariaMay 30, 202310 min read

Questa pagina è disponibile anche in English, Deutsch, Español, Français, 日本語 e Português.

Come semplificare la validazione delle risorse Kubernetes con Validating Admission Policies e CEL

La validazione in-cluster ha numerose applicazioni pratiche: impedire l'eliminazione accidentale o malevola di risorse, limitare il numero di repliche di un deployment per gestire meglio le risorse, imporre la presenza (o l'assenza) di determinate annotazioni, label o variabili d'ambiente, e molto altro ancora. Il principio è piuttosto semplice: ogni volta che all'API arriva una richiesta corrispondente, viene eseguito un insieme di policy per stabilire se accettarla o rifiutarla.

Si tratta di uno strumento di grande valore per i cluster operator, perché permette di applicare standard e regole specifiche agli utenti del cluster, come gli sviluppatori. Non sorprende, quindi, che sia diventato un tema caldo per molti dei nostri clienti e che siano nati numerosi prodotti e aziende per semplificare la creazione e l'esecuzione delle validazioni sulle risorse Kubernetes.

Approfondendo il ruolo del Dynamic Admission Control (DAC) nel processo di validazione e le difficoltà che incontrano i cluster operator, l'articolo aiuterà i lettori a cogliere appieno la semplicità e i vantaggi offerti dalle Validating Admission Policies (VAP), dal Common Expression Language (CEL) e dalla loro sintassi simile a quella di RBAC. Un contesto utile per scegliere con maggiore consapevolezza se adottare queste nuove funzionalità nei propri ambienti Kubernetes.

Se sei qui per il codice e gli esempi, vai direttamente alla sezione Validating Admission Policies. Per casi d'uso più avanzati, dai un'occhiata al post di approfondimento: Validating Admission Policies in Kubernetes: Advanced Use Cases.

Le criticità nell'implementazione del Dynamic Admission Control

Le fasi dell'Admission Controller ( fonte)

Implementare la validazione delle richieste all'interno del cluster non è sempre una passeggiata. Significa ricorrere al DAC, uno strumento potente ma non sempre intuitivo per i cluster operator.

Il DAC è un componente fondamentale nel processo di validazione di Kubernetes. Consente agli amministratori del cluster di gestire l'ammissione delle risorse intercettando le richieste API e accettandole o rifiutandole sulla base di un insieme di policy predefinite. Offre così un controllo granulare sulle risorse create, aggiornate o eliminate nel cluster, garantendo che rispettino standard e regole specifiche.

Oggi, però, l'utilizzo del DAC può risultare complesso per i cluster operator. In sintesi, il processo prevede la scrittura di un admission webhook server, la creazione della risorsa webhook, la configurazione dell'autenticazione verso l'API server (se necessaria) e la gestione del certificato per il TLS. Inoltre, gli operator devono avere una solida padronanza dei concetti di Kubernetes e delle best practice di sicurezza, oltre a saper usare i linguaggi di programmazione necessari per scrivere e mantenere la logica di validazione.

Anche se strumenti come OPA Gatekeeper possono semplificare il processo, i cluster operator devono comunque distribuire e gestire i workloads in esecuzione nel cluster. Niente paura, però: Kubernetes ha introdotto un nuovo tipo di risorsa chiamato Validating Admission Policies (VAPs), disponibile in alpha a partire dalla v1.26, che propone una soluzione molto più semplice. La loro semplicità, che mostreremo nelle prossime sezioni, renderà probabilmente le VAP lo standard de facto per la validazione in-cluster.

Le VAP: il miglior alleato del cluster operator per la validazione delle risorse

Le VAP sfruttano il CEL di Google per definire espressioni di validazione che vengono eseguite sulle richieste API corrispondenti. Poiché le espressioni CEL vengono valutate direttamente nell'API server, non serve scrivere o distribuire workloads personalizzati per valutare le policy. Creare una policy diventa quindi estremamente semplice e immediato rispetto a metodi alternativi, come i già citati Validating Admission Webhooks.

Se temi di sovraccaricare l'API e di compromettere altri processi, niente paura: il CEL prevede dei vincoli sulle risorse che mantengono tutto sotto controllo. Vale la pena approfondire questi meccanismi di sicurezza leggendo la sezione sui CEL resource constraints nella documentazione ufficiale di Kubernetes.

Il supporto al CEL è stato introdotto per la prima volta in Kubernetes v1.23 per la validazione inline delle Custom Resource Definitions (CRD), attualmente in beta a partire dalla v1.25. Una novità che ha aperto numerose possibilità per i casi d'uso del CEL all'interno di Kubernetes. Per approfondire le origini del CEL in Kubernetes e il suo potenziale, consigliamo di guardare il talk di Cici Huang, " The Path to Self Contained CRDs", che ha ispirato la nostra esplorazione delle Validating Admission Policies.

Sfruttare VAP e CEL per una validazione efficiente

Uno dei principali vantaggi di CEL e VAP è la loro semplicità. Poiché la logica di validazione viene valutata direttamente all'interno dell'API server, non c'è alcun overhead aggiuntivo dovuto a workloads personalizzati: scalare il processo di validazione man mano che il cluster cresce diventa così molto più semplice. Inoltre, il CEL offre un modo leggero ed efficiente per esprimere le regole di validazione, migliorando le prestazioni e riducendo il rischio di sovraccaricare l'API server. I vincoli sulle risorse integrati garantiscono ulteriormente la stabilità e l'efficienza del processo di validazione.

Grazie alle Validating Admission Policies e al CEL, i cluster operator possono adottare un processo di validazione più semplice e scalabile, applicando standard e regole all'interno del cluster in modo più efficiente ed efficace rispetto agli altri metodi. Facilità d'uso, scalabilità e prestazioni elevate fanno di VAP e CEL un'alternativa convincente per chi vuole semplificare la validazione in-cluster su Kubernetes.

Validating Admission Policies

Per utilizzare questa funzionalità servono due componenti principali:

  1. ValidatingAdmissionPolicy: definisce la failure policy, le richieste corrispondenti e le espressioni di validazione CEL. In altre parole, la policy stessa.
  2. ValidatingAdmissionPolicyBinding: definisce l'ambito della policy, associandola a un insieme di risorse corrispondenti.

Anche se questa funzionalità è in Alpha dalla v1.26, alcune caratteristiche sono disponibili solo a partire dalla v1.27 ( audit annotation, validation actions): tutti i manifest che seguono sono stati testati su un cluster Kubernetes in versione v1.27.1 (con le funzionalità alpha e il feature gate ValidatingAdmissionPolicy abilitati).

Tutti gli esempi presenti in questo articolo sono disponibili anche in un repository GitHub creato per l'occasione.

Il primo esempio è preso direttamente dalla documentazione. Creeremo e useremo il namespace demo per eseguire tutti gli esempi:

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

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

Successivamente, creeremo una policy che impedisce ai Deployment di avere più di cinque repliche:

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"

Il campo failurePolicy può assumere i seguenti valori:

  • Fail: un errore nella chiamata della ValidatingAdmissionPolicy causa il fallimento dell'admission e il rifiuto della richiesta API.
  • Ignore: un errore nella chiamata della ValidatingAdmissionPolicy viene ignorato e la richiesta API può proseguire.

Il campo matchConstraints serve a intercettare le richieste in arrivo ed è configurato in modo che la policy si applichi solo alle richieste API per i Deployment nell'API apps/v1 e solo alle operazioni di tipo CREATE o UPDATE su un Deployment.

Infine, il campo validations contiene le espressioni CEL effettive che verranno eseguite sulle richieste API corrispondenti. Tutte le espressioni devono essere valutate true affinché la richiesta venga ammessa.

Per completare la configurazione, creiamo il seguente binding:

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

Stiamo associando la policy creata in precedenza a qualsiasi namespace con la label environment=demo: un'opzione utile per stabilire dove vogliamo applicare la validazione.

Esistono altre opzioni per il matching dei namespace, come matchExpressions per criteri più granulari, oltre ad altre opzioni di configurazione: excludeResourceRules per escludere determinate risorse, objectSelector per intercettare oggetti specifici (sconsigliato, perché gli sviluppatori potrebbero omettere una label per evitare l'audit). Tratterò le opzioni più interessanti negli esempi successivi.

Una volta applicati questi manifest al cluster, il tentativo di creare un nuovo Deployment che viola la policy genererà un errore, come previsto:

$ 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

Lo stesso vale quando si prova a fare il diff della risorsa:

$ 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

Espressioni di validazione multiple

A volte può essere necessario eseguire più validazioni sulle risorse; consideriamo le seguenti espressioni di validazione per i 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:
    # Deployments can't have more than 3 replicas
    - expression: "object.spec.replicas <= 3"
    # Deployment containers must be using images hosted in europe-west1 Artifact Registry in project test-eyal
    - expression: "object.spec.template.spec.containers.all(c, c.image.startsWith('europe-west1-docker.pkg.dev/test-eyal/'))"
    # Deployment cannot use emptyDir volumes
    - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(v, !has(v.emptyDir))"

Un Deployment intercettato da questa policy dovrà soddisfare tutte le espressioni (valutate a true) per essere ammesso nel cluster.

Da notare che le validazioni vengono eseguite in sequenza. Se un'espressione fallisce, l'errore viene restituito immediatamente al client: ciò significa che, se la risorsa viola più di un'espressione di validazione, sarà necessario un processo iterativo fatto di rifiuto, correzione della violazione e nuovo tentativo.

Personalizzare il messaggio di validazione

È anche possibile fornire un message personalizzato da restituire al client in caso di validazione fallita. Se serve, si può addirittura eseguire l'interpolazione tramite un messageExpression. Una message expression ha accesso a object, oldObject, request e params.

Aggiorniamo l'ultima policy con messaggi personalizzati al posto dei commenti:

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

Attenzione: qualsiasi valore interpolato nel campo messageExpression deve essere di tipo string, altrimenti il messaggio restituirà un errore di valutazione fallita.

Senza un messaggio personalizzato, un deployment con un volume emptyDir avrebbe fallito con il seguente errore:

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

Dal punto di vista del client, un messaggio del genere può risultare poco comprensibile. Con la message expression personalizzata, otterremmo invece:

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

Sto usando una messageExpression al posto di un normale message solo per mostrare la potenza del CEL. Il fatto che qualcosa si possa fare non significa che si debba sempre farlo!

Risorse aggiuntive

Con la continua evoluzione di Kubernetes, il supporto al CEL è destinato a portare funzionalità sempre più interessanti nelle prossime release. Ti invitiamo a esplorare le Validating Admission Policies e a familiarizzare con questa potente funzionalità, che con ogni probabilità diventerà uno strumento di riferimento per i cluster operator.

Ricorda che il feature gate Validating Admission Policy è attualmente in Alpha: sono quindi attesi cambiamenti e miglioramenti man mano che ci si avvicina alla General Availability. Per restare allineato con gli ultimi sviluppi, segui il changelog e la documentazione di Kubernetes.

Trattandosi di una funzionalità in continua evoluzione, il contenuto di questo articolo potrebbe richiedere aggiornamenti nel tempo. Se ti imbatti in un esempio che non funziona più o in un'affermazione non più accurata, contattaci: revisioneremo e aggiorneremo le informazioni di conseguenza.