Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Validação descomplicada no cluster Kubernetes: conheça as Validating Admission Policies

By Eyal ZekariaMay 30, 202310 min read

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

Simplifique a validação de recursos do Kubernetes com Validating Admission Policies e CEL

A validação dentro do cluster tem várias aplicações práticas: evitar a exclusão acidental ou maliciosa de recursos, limitar o número de réplicas que um deployment pode ter para um melhor gerenciamento de recursos e exigir que determinadas annotations, labels ou variáveis de ambiente estejam presentes (ou ausentes), entre outras coisas. Entender como funciona a validação de recursos dentro de um cluster Kubernetes é bem simples: sempre que uma requisição compatível chega à API, um conjunto de políticas é executado para determinar se ela será permitida ou negada.

Isso pode ser extremamente valioso para operadores de cluster, pois ajuda a impor padrões e regras específicos aos usuários do cluster, como os desenvolvedores. Não é à toa que esse tema virou assunto quente entre muitos dos nossos clientes, e diversos produtos e empresas surgiram para simplificar a criação e a execução de validações para recursos do Kubernetes.

Ao detalhar o papel do Dynamic Admission Control (DAC) no processo de validação e os desafios enfrentados pelos operadores de cluster, este artigo vai te ajudar a entender melhor a simplicidade e os benefícios das Validating Admission Policies (VAP), da Common Expression Language (CEL) e da sua sintaxe parecida com RBAC. Com esse contexto, fica mais fácil decidir, com base sólida, se vale a pena adotar esses novos recursos nos seus ambientes Kubernetes.

Se você está aqui pelo código e pelos exemplos, pode pular direto para a seção Validating Admission Policies. Para casos de uso mais avançados, dê uma olhada na continuação: Validating Admission Policies in Kubernetes: Advanced Use Cases.

Desafios na implementação do Dynamic Admission Control

Fases do Admission Controller ( fonte)

Implementar a validação de requisições dentro do seu cluster nem sempre é moleza. O processo envolve o uso do DAC — uma ferramenta poderosa, mas nem sempre amigável para operadores de cluster.

O DAC é um componente crítico no processo de validação do Kubernetes. Ele permite que administradores de cluster gerenciem a admissão de recursos no cluster interceptando requisições da API e permitindo ou rejeitando-as com base em um conjunto de políticas predefinidas. Isso oferece controle granular sobre os recursos que estão sendo criados, atualizados ou excluídos no cluster, garantindo que sigam padrões e regras específicas.

No entanto, usar o DAC hoje pode ser desafiador para operadores de cluster por causa da complexidade. Em resumo, o processo envolve escrever um servidor de admission webhook, criar o recurso de webhook, configurar a autenticação com o servidor da API, se necessário, e gerenciar o certificado usado para TLS. Além disso, os operadores precisam dominar conceitos do Kubernetes e boas práticas de segurança e ter fluência em linguagens de programação para criar e manter a lógica de validação.

Embora ferramentas como o OPA Gatekeeper consigam simplificar esse processo, os operadores de cluster ainda precisam fazer o deploy e gerenciar os workloads em execução no cluster. Mas calma! O Kubernetes introduziu um novo tipo de recurso chamado Validating Admission Policies (VAPs), disponível em alfa a partir da v1.26, que oferece uma solução mais simples. A simplicidade que vamos demonstrar nas próximas seções provavelmente fará com que as VAPs se tornem o padrão de fato para validação no cluster.

Conheça as VAPs — a melhor amiga do operador de cluster na validação de recursos

As VAPs usam a CEL do Google para definir expressões de validação que serão executadas contra requisições compatíveis da API. Como as expressões CEL são avaliadas diretamente no servidor da API, não é preciso escrever nem fazer deploy de workloads customizados para avaliar as suas políticas. Isso torna a criação de políticas incrivelmente direta e acessível em comparação com métodos alternativos, como os já mencionados Validating Admission Webhooks.

Se você está com receio de sobrecarregar a API e impactar outros processos, fique tranquilo — a CEL vem com restrições de recursos para manter tudo sob controle. Vale conhecer melhor essas proteções lendo sobre restrições de recursos da CEL na documentação oficial do Kubernetes.

O suporte à CEL foi introduzido pela primeira vez no Kubernetes v1.23 para validação inline de Custom Resource Definitions (CRD) — atualmente em beta a partir da v1.25. Isso abriu inúmeras possibilidades de uso da CEL no Kubernetes. Para um aprofundamento sobre as origens da CEL no Kubernetes e seu potencial, recomendamos a palestra de Cici Huang, " The Path to Self Contained CRDs", que inspirou nossa exploração das Validating Admission Policies.

Aproveitando VAPs e CEL para uma validação eficiente

Uma das principais vantagens de usar CEL e VAPs é a simplicidade. Como a lógica de validação é avaliada diretamente no servidor da API, não há sobrecarga adicional de workloads customizados, o que facilita escalar o processo de validação à medida que o cluster cresce. Além disso, a CEL oferece uma forma leve e eficiente de expressar regras de validação, melhorando a performance e reduzindo o risco de sobrecarregar o servidor da API. As restrições de recursos integradas reforçam ainda mais a estabilidade e a eficiência do processo.

Com as Validating Admission Policies e a CEL, os operadores de cluster passam a contar com um processo de validação mais direto e escalável. Isso permite impor padrões e regras dentro do cluster com mais eficiência e eficácia do que com outros métodos. A facilidade de uso, a escalabilidade e os ganhos de performance das VAPs e da CEL fazem delas uma alternativa atraente para quem quer simplificar a validação no cluster Kubernetes.

Validating Admission Policies

Para usar esse recurso, são necessários dois componentes principais:

  1. ValidatingAdmissionPolicy — define a política de falha, os matches de requisições e as expressões de validação CEL. Ou seja, a política em si
  2. ValidatingAdmissionPolicyBinding — define o escopo da política; vincula a política a um conjunto de recursos correspondentes

Embora esse recurso esteja em Alfa desde a v1.26, alguns recursos só aparecem a partir da v1.27 ( audit annotation, validation actions), então todos os manifestos a seguir foram testados em um cluster Kubernetes rodando v1.27.1 (com recursos alfa e o feature gate ValidatingAdmissionPolicy habilitados).

Todos os exemplos deste post também estão disponíveis em um repositório no GitHub criado para esse fim.

O primeiro exemplo vem direto da documentação. Vamos criar e usar o namespace demo para rodar todos os exemplos deste post:

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

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

Em seguida, vamos criar uma política que impede que Deployments tenham mais de cinco réplicas:

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"

O campo failurePolicy aceita os seguintes valores:

  • Fail significa que um erro ao chamar o ValidatingAdmissionPolicy faz com que a admissão falhe e a requisição da API seja rejeitada.
  • Ignore significa que um erro ao chamar o ValidatingAdmissionPolicy é ignorado e a requisição da API segue adiante.

O campo matchConstraints é usado para fazer o match das requisições recebidas e está configurado para que a política se aplique apenas a requisições da API para Deployments na API apps/v1, e somente em requisições CREATE ou UPDATE de Deployment.

Por fim, o campo validations contém as expressões CEL que serão executadas contra as requisições correspondentes da API. Todas as expressões precisam ser avaliadas como true para que a requisição seja admitida.

Vamos criar o seguinte binding para concluir a configuração:

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

Estamos vinculando a política criada anteriormente a qualquer namespace que tenha a label environment=demo definida — isso é útil para configurar onde queremos aplicar a validação da política.

Existem outras opções para fazer o match de namespaces, como matchExpressions, para correspondências mais granulares, além de outras opções de configuração: excludeResourceRules para excluir determinados recursos e objectSelector para corresponder a determinados objetos (não recomendado, já que os desenvolvedores podem omitir uma label para escapar da auditoria). Vou abordar as opções mais interessantes em exemplos posteriores.

Depois que esses manifestos forem aplicados ao cluster, tentar criar um novo Deployment que viole a política gera um erro, como esperado:

$ 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

Isso também funciona ao tentar fazer um diff do recurso:

$ 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

Múltiplas expressões de validação

Às vezes você quer aplicar várias validações em um mesmo recurso. Veja, por exemplo, as seguintes expressões de validação para 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))"

Um Deployment correspondente a essa política precisará ter todas as expressões avaliadas como true para ser admitido no cluster.

Vale notar que as validações são executadas sequencialmente. Se uma expressão falhar, ela retorna a falha ao cliente na hora — ou seja, se o seu recurso violar mais de uma expressão de validação, será um processo iterativo: ser negado, corrigir a violação e tentar de novo.

Personalize a mensagem de validação

Também é possível fornecer uma message customizada para retornar ao cliente em caso de falha na validação. Você pode até fazer interpolação, se necessário, usando uma messageExpression. Uma message expression tem acesso a object, oldObject, request e params.

Vamos atualizar nossa última política com mensagens customizadas no lugar dos comentários:

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

Importante: qualquer valor interpolado no campo messageExpression precisa ser do tipo string, caso contrário a mensagem retorna um erro pela falha na avaliação.

Sem uma mensagem customizada, um deployment com volume emptyDir teria falhado com a seguinte mensagem:

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

Isso pode ficar meio difícil de entender do lado do cliente. Já com a expressão de mensagem customizada, teríamos:

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

Note que estou usando uma messageExpression em vez de uma message comum nessa validação só para mostrar o poder da CEL. O fato de você poder fazer algo nem sempre significa que deveria!

Recursos adicionais

À medida que o Kubernetes evolui, o suporte à CEL deve trazer recursos ainda mais empolgantes em versões futuras. Recomendamos que você explore as Validating Admission Policies e se familiarize com esse recurso poderoso, já que ele tem tudo para se tornar a ferramenta preferida dos operadores de cluster.

Lembre-se: o feature gate Validating Admission Policy está atualmente em Alfa, ou seja, mudanças e melhorias são esperadas conforme ele caminha rumo à General Availability. Acompanhe o changelog e a documentação do Kubernetes para ficar por dentro das novidades.

Como esse recurso ainda está em desenvolvimento, o conteúdo deste artigo pode precisar de atualizações ao longo do tempo. Se você se deparar com um exemplo que não funciona mais ou uma afirmação que não está mais correta, entre em contato — vamos revisar e atualizar as informações.