Admission controllers: validando e mutando recursos antes do deploy

1. Introdução aos Admission Controllers no Kubernetes

Admission controllers são componentes essenciais do Kubernetes que interceptam requisições à API após a autenticação e autorização, mas antes da persistência no etcd. Eles funcionam como gatekeepers que podem modificar ou rejeitar objetos antes que sejam armazenados.

O fluxo completo de uma requisição é:

kubectl apply → Autenticação → Autorização → Admission Controllers → Persistência no etcd

Existem duas categorias principais:

  • Mutating Admission Controllers: modificam o recurso antes da validação. Executam primeiro.
  • Validating Admission Controllers: aprovam ou rejeitam o recurso após a mutação.

Os admission controllers podem ser built-in (embutidos no kube-apiserver) ou webhooks externos que estendem essa funcionalidade.

2. Tipos de Admission Controllers e Seus Mecanismos

MutatingAdmissionWebhook permite que você escreva código externo para modificar recursos. Por exemplo, injetar automaticamente um sidecar de logging em todo Pod.

ValidatingAdmissionWebhook rejeita recursos que não atendem a políticas específicas, como falta de labels obrigatórios.

Além dos webhooks, existem controllers built-in importantes:

PodSecurityPolicy: define políticas de segurança para Pods
LimitRanger: define limites padrão de recursos
ResourceQuota: restringe uso agregado de recursos por namespace

3. Construindo um Mutating Admission Webhook

Vamos construir um webhook que injeta automaticamente um sidecar de logging em todo Pod. A estrutura necessária inclui:

  1. Deployment do webhook
  2. Service para expor o endpoint
  3. Configuração TLS
  4. MutatingWebhookConfiguration

Servidor webhook em Go:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"

    admissionv1 "k8s.io/api/admission/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func mutatePod(w http.ResponseWriter, r *http.Request) {
    var review admissionv1.AdmissionReview
    json.NewDecoder(r.Body).Decode(&review)

    pod := &corev1.Pod{}
    json.Unmarshal(review.Request.Object.Raw, pod)

    // Adiciona sidecar de logging
    sidecar := corev1.Container{
        Name:  "log-sidecar",
        Image: "fluentd:latest",
        VolumeMounts: []corev1.VolumeMount{
            {Name: "varlog", MountPath: "/var/log"},
        },
    }
    pod.Spec.Containers = append(pod.Spec.Containers, sidecar)

    // Cria patch JSON
    patchBytes, _ := json.Marshal([]map[string]interface{}{
        {"op": "add", "path": "/spec/containers/-", "value": sidecar},
    })

    review.Response = &admissionv1.AdmissionResponse{
        UID:     review.Request.UID,
        Allowed: true,
        Patch:   patchBytes,
        PatchType: func() *admissionv1.PatchType {
            pt := admissionv1.PatchTypeJSONPatch
            return &pt
        }(),
    }

    json.NewEncoder(w).Encode(review)
}

func main() {
    http.HandleFunc("/mutate", mutatePod)
    http.ListenAndServeTLS(":8443", "tls.crt", "tls.key", nil)
}

Configuração do webhook no Kubernetes:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: sidecar-injector-webhook
webhooks:
- name: sidecar-injector.example.com
  clientConfig:
    service:
      name: sidecar-injector
      namespace: default
      path: /mutate
    caBundle: <base64-ca-cert>
  rules:
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  admissionReviewVersions: ["v1"]
  sideEffects: None

4. Construindo um Validating Admission Webhook

Agora, um webhook que rejeita Pods usando imagens sem tag fixa ou com vulnerabilidades conhecidas:

func validatePod(w http.ResponseWriter, r *http.Request) {
    var review admissionv1.AdmissionReview
    json.NewDecoder(r.Body).Decode(&review)

    pod := &corev1.Pod{}
    json.Unmarshal(review.Request.Object.Raw, pod)

    var allowed bool = true
    var message string

    for _, container := range pod.Spec.Containers {
        // Verifica se a imagem tem tag fixa
        if !strings.Contains(container.Image, ":") {
            allowed = false
            message = "Imagem sem tag fixa: " + container.Image
            break
        }

        // Verifica se não é latest
        if strings.HasSuffix(container.Image, ":latest") {
            allowed = false
            message = "Uso de tag latest não permitido: " + container.Image
            break
        }
    }

    review.Response = &admissionv1.AdmissionResponse{
        UID:     review.Request.UID,
        Allowed: allowed,
        Result: &metav1.Status{
            Message: message,
        },
    }

    json.NewEncoder(w).Encode(review)
}

Configuração com parâmetros de resiliência:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: image-validator
webhooks:
- name: image-validator.example.com
  clientConfig:
    service:
      name: image-validator
      namespace: default
      path: /validate
    caBundle: <base64-ca-cert>
  rules:
  - operations: ["CREATE", "UPDATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  failurePolicy: Fail
  timeoutSeconds: 5
  admissionReviewVersions: ["v1"]
  sideEffects: None

5. Segurança e Boas Práticas em Webhooks

Certificados TLS com cert-manager:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: webhook-tls
  namespace: default
spec:
  secretName: webhook-tls-secret
  duration: 2160h
  renewBefore: 360h
  dnsNames:
  - sidecar-injector.default.svc
  issuerRef:
    name: selfsigned-issuer
    kind: Issuer

Evitando loops infinitos: Use labels ou anotações específicas para marcar recursos que já foram processados pelo seu webhook. Configure objectSelector para ignorar certos namespaces ou recursos.

Monitoramento: Exporte métricas Prometheus do seu webhook:

# Métricas recomendadas
admission_webhook_requests_total{status="allowed|denied|error"}
admission_webhook_duration_seconds
admission_webhook_errors_total

6. Testando e Depurando Admission Controllers

Simulando requisições com curl dentro do cluster:

# Teste manual do webhook
kubectl run test-pod --image=nginx

# Verificar logs do webhook
kubectl logs -l app=sidecar-injector

# Testar diretamente o endpoint
curl -k -X POST https://sidecar-injector.default.svc:8443/mutate \
  -H "Content-Type: application/json" \
  -d @test-request.json

Ferramentas úteis:

# admission-webhook-boot: framework para testes
go get github.com/openshift/admission-webhook-boot

# kubectl admission-webhook: plugin para depuração
kubectl krew install admission-webhook
kubectl admission-webhook inspect

7. Casos de Uso Avançados e Integração com DevOps

OPA/Gatekeeper como Validating Admission Controller:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels:
      - key: "team"
        allowedRegex: "^[a-zA-Z]+$"

Integração com GitOps (Flux/ArgoCD):

# Pipeline de validação antes do merge
steps:
  - name: validate-manifests
    run: |
      kubectl admission-webhook simulate \
        --webhook image-validator \
        --manifest deployment.yaml

8. Considerações Finais e Próximos Passos

Admission controllers são ferramentas poderosas para governança e automação no Kubernetes. Comparado a outras abordagens:

  • PodSecurity: mais simples, mas menos flexível que webhooks
  • Kyverno: alternativa declarativa que não requer codificação
  • OPA/Gatekeeper: ideal para políticas complexas de compliance

Impacto na performance: Cada webhook adiciona latência ao kube-apiserver. Configure timeoutSeconds adequadamente (5-10s) e use failurePolicy: Ignore para webhooks não críticos.

Próximos passos: Explore CRDs e controller pattern para criar operadores que vão além da validação, automatizando operações complexas baseadas em eventos do cluster.

Referências