MÓDULO 12 · CONCEITO 09 DE 12

Kubernetes Avançado — Operators, Custom Resources e automação de plataforma

O padrão Operator: extend Kubernetes com lógica de domínio. CRDs: spec, status, validation. Operator SDK e kubebuilder. Exemplos canônicos: cert-manager, external-secrets-operator, crossplane. Admission webhooks: validação e mutação.

Tempo de leitura ~24 min Pré-requisito 03 · Kubernetes em Produção · 05 · GitOps Próximo 10 · Platform Engineering →

Em 2016, Brandon Philips (CTO da CoreOS) publicou um post que introduziu o padrão Operator: a ideia de que o conhecimento operacional de sistemas complexos — como operar um cluster etcd, fazer upgrades sem downtime, recuperar de falhas — pode ser codificado em um controlador Kubernetes em vez de em runbooks que dependem de intervenção humana. O princípio é simples: se um engenheiro sênior sabe o que fazer quando o etcd primário cai, esse conhecimento pode ser codificado em um programa que reage automaticamente ao mesmo evento.

Operators elevam o Kubernetes de um orquestrador de containers para uma plataforma de automação de operações. A pergunta muda de "como faço deploy desse banco de dados?" para "como codifico o conhecimento de operar esse banco de dados de forma que a plataforma o faça automaticamente?".

Custom Resource Definitions (CRDs)

Antes de falar em Operators, é necessário entender CRDs. O Kubernetes é extensível: você pode adicionar novos tipos de recursos além dos built-in (Deployment, Service, ConfigMap). Um CRD define um novo tipo de recurso com seu próprio schema:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: bancosdedados.minha.org
spec:
  group: minha.org
  names:
    kind: BancoDeDados
    listKind: BancoDeDadosList
    plural: bancosdedados
    singular: bancodedados
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: [engine, version, storage]
              properties:
                engine:
                  type: string
                  enum: [postgres, mysql]
                version:
                  type: string
                  pattern: '^[0-9]+\.[0-9]+$'
                storage:
                  type: string      # "10Gi", "100Gi"
                  pattern: '^[0-9]+(Mi|Gi)$'
                highAvailability:
                  type: boolean
                  default: false
            status:
              type: object
              properties:
                phase:
                  type: string
                  enum: [Pending, Provisioning, Ready, Failed]
                endpoint:
                  type: string
                lastError:
                  type: string

Depois de aplicar esse CRD, você pode criar recursos do tipo BancoDeDados:

apiVersion: minha.org/v1
kind: BancoDeDados
metadata:
  name: banco-producao
  namespace: meu-servico
spec:
  engine: postgres
  version: "16.2"
  storage: "100Gi"
  highAvailability: true

O recurso é armazenado no etcd, mas sem um controller, nada acontece. É o Operator que age sobre o recurso.

O padrão Operator — reconciliation loop

Um Operator é um controller que observa recursos (CRDs ou recursos built-in) e reconcilia o estado real com o estado desejado. O loop de reconciliação é o coração do padrão:

// Go — controller com kubebuilder (simplificado)
func (r *BancoDeDadosReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // 1. Buscar o recurso atual
    banco := &minhav1.BancoDeDados{}
    if err := r.Get(ctx, req.NamespacedName, banco); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 2. Determinar o estado atual vs desejado
    switch banco.Status.Phase {
    case "":
        // Primeira vez — iniciar provisionamento
        banco.Status.Phase = "Pending"
        r.Status().Update(ctx, banco)
        return ctrl.Result{Requeue: true}, nil

    case "Pending":
        // Criar os recursos Kubernetes necessários
        if err := r.createStatefulSet(ctx, banco); err != nil {
            banco.Status.LastError = err.Error()
            banco.Status.Phase = "Failed"
            r.Status().Update(ctx, banco)
            return ctrl.Result{}, err
        }
        banco.Status.Phase = "Provisioning"
        r.Status().Update(ctx, banco)
        return ctrl.Result{RequeueAfter: 30 * time.Second}, nil

    case "Provisioning":
        // Verificar se o StatefulSet está pronto
        if r.isReady(ctx, banco) {
            banco.Status.Phase = "Ready"
            banco.Status.Endpoint = r.getEndpoint(banco)
            r.Status().Update(ctx, banco)
        }
        return ctrl.Result{RequeueAfter: 10 * time.Second}, nil

    case "Ready":
        // Reconciliar continuamente — detectar drift
        if r.specChanged(banco) {
            return r.handleSpecChange(ctx, banco)
        }
        return ctrl.Result{RequeueAfter: 60 * time.Second}, nil
    }

    return ctrl.Result{}, nil
}

O controller é notificado pelo API server quando qualquer objeto BancoDeDados é criado, modificado, ou deletado — e também periodicamente (via RequeueAfter). A reconciliação é idempotente: se chamada múltiplas vezes com o mesmo estado, o resultado é o mesmo. Isso é o que torna o loop de reconciliação robusto a falhas parciais.

Kubebuilder e Operator SDK

Escrever um Operator do zero envolve muita boilerplate de interação com o API server. Kubebuilder e Operator SDK são frameworks que geram o scaffolding:

# Criar um novo projeto Operator com kubebuilder
kubebuilder init --domain minha.org --repo github.com/minha-org/meu-operator

# Criar CRD e controller
kubebuilder create api --group banco --version v1 --kind BancoDeDados

# Estrutura gerada
meu-operator/
├── api/v1/
│   ├── bancodedados_types.go    # estrutura do CRD (você preenche)
│   └── zz_generated.deepcopy.go
├── controllers/
│   └── bancodedados_controller.go   # lógica de reconciliação (você implementa)
├── config/
│   ├── crd/                     # CRD gerado automaticamente
│   └── rbac/                    # RBAC para o controller
└── main.go

O Operator SDK (Red Hat) é similar ao kubebuilder mas também suporta Operators em Ansible e Helm — útil para encapsular charts Helm existentes em um Operator sem escrever Go.

Operators canônicos — exemplos de produção

cert-manager: gerencia certificados TLS dentro do cluster. Você cria um recurso Certificate ou anota um Ingress, e o cert-manager emite automaticamente o certificado via Let's Encrypt (ACME), Vault, ou CA interno — e o renova antes da expiração. Elimina o toil de gestão manual de certificados.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: meu-servico-tls
spec:
  secretName: meu-servico-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - api.minha-empresa.com

External Secrets Operator (ESO): sincroniza secrets de sistemas externos (Vault, AWS Secrets Manager, GCP Secret Manager) para Kubernetes Secrets. O secret não fica armazenado no git — apenas a referência a onde ele está:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials   # Kubernetes Secret criado
  data:
    - secretKey: password
      remoteRef:
        key: producao/banco-de-dados
        property: password

Crossplane: provision de infraestrutura cloud via recursos Kubernetes. Em vez de usar Terraform ou Pulumi, você cria um recurso RDSInstance no Kubernetes e o Crossplane cria o banco de dados na AWS. O cluster Kubernetes se torna o plano de controle de toda a infraestrutura:

apiVersion: rds.aws.crossplane.io/v1alpha1
kind: DBInstance
metadata:
  name: producao-postgres
spec:
  forProvider:
    region: us-east-1
    dbInstanceClass: db.t3.medium
    engine: postgres
    engineVersion: "16.2"
    multiAZ: true
  providerConfigRef:
    name: aws-provider

Prometheus Operator: gerencia configuração do Prometheus via CRDs — ServiceMonitor, PodMonitor, PrometheusRule. Times de produto adicionam um ServiceMonitor ao seu serviço para expor métricas sem editar o arquivo de configuração central do Prometheus.

Admission Webhooks — validação e mutação

Admission webhooks são plugins que interceptam requisições ao API server antes que recursos sejam persistidos no etcd. Existem dois tipos:

Validating Admission Webhook: valida um recurso e pode rejeitar a criação/modificação. Exemplos de uso: impedir Deployments sem resource limits, bloquear imagens de registries não aprovados, validar que todos os recursos têm as tags obrigatórias.

Mutating Admission Webhook: modifica um recurso antes de persistir. Exemplos: injetar sidecar de logging automaticamente, adicionar labels padrão, ou setar valores padrão que o usuário não especificou.

// Go — Validating webhook que rejeita Pods sem resource limits
func (v *PodValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
    pod := &corev1.Pod{}
    if err := v.decoder.Decode(req, pod); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }

    for _, container := range pod.Spec.Containers {
        if container.Resources.Limits == nil {
            return admission.Denied(fmt.Sprintf(
                "container %q não tem resource limits definidos — obrigatório em produção",
                container.Name,
            ))
        }
        if container.Resources.Limits.Memory().IsZero() {
            return admission.Denied(fmt.Sprintf(
                "container %q não tem memory limit — pode causar OOM no nó",
                container.Name,
            ))
        }
    }

    return admission.Allowed("resource limits OK")
}

Webhooks são registrados como ValidatingWebhookConfiguration e MutatingWebhookConfiguration. O API server envia a requisição ao webhook via HTTPS antes de persistir — o webhook precisa ter alta disponibilidade, pois sua indisponibilidade bloqueia operações no cluster.

alta disponibilidade de webhooks

Um webhook com failurePolicy: Fail (o default) que fica indisponível bloqueia toda criação de Pod no cluster — incluindo novos Pods do próprio webhook. Configure failurePolicy: Ignore para webhooks não-críticos, garanta no mínimo 2 réplicas com PodAntiAffinity por zona, e use PodDisruptionBudget. Webhooks de segurança críticos (como Open Policy Agent) com failurePolicy: Fail devem ser os workloads mais resilientes do cluster.

Policy enforcement com OPA/Gatekeeper

Open Policy Agent (OPA) com Gatekeeper é o padrão para policy enforcement em Kubernetes. Gatekeeper é implementado como admission webhook, e as políticas são escritas em Rego:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    labels: ["team", "service", "environment"]

---
# ConstraintTemplate que define a lógica de validação
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels

        violation[{"msg": msg}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("labels obrigatórias ausentes: %v", [missing])
        }
quando construir um Operator

Operators fazem sentido quando você tem operações manuais repetitivas que seguem um padrão previsível: "quando X acontece, faça Y e Z". Se o seu time tem um runbook de 5 etapas para um evento específico — e esse evento acontece com frequência — é candidato para um Operator. Não construa Operators para automatizar operações que acontecem uma vez por ano: o custo de manter o código supera o benefício. Os melhores Operators são aqueles que empacodam décadas de conhecimento operacional de um sistema complexo (banco de dados, Kafka, Elasticsearch) — e por isso os Operators de projetos com ecossistemas maduros (PostgreSQL com CloudNativePG, Kafka com Strimzi) geralmente são melhores que Operators internos escritos em um sprint.

Decisões de engenharia

Operator interno vs Operator comunitário existente
Sempre prefira um Operator comunitário maduro quando existir: cert-manager, ESO, Strimzi (Kafka), CloudNativePG (PostgreSQL), Prometheus Operator — esses empacotam anos de conhecimento operacional que um Operator interno levaria meses para alcançar. Construa internamente apenas quando: (1) o domínio é específico do seu negócio (ex: "orquestrar o ciclo de vida de um job de machine learning do tipo que usamos"); (2) nenhum Operator comunitário cobre o caso de uso; (3) o Operator comunitário é imaturo ou abandonado. Um Operator escrito em um sprint geralmente não cobre casos de erro, upgrades, e recuperação de falhas — exatamente o que faz um Operator valioso.
Kubebuilder vs Operator SDK
Kubebuilder para Operators em Go quando você quer o controle máximo e a melhor integração com o ecossistema controller-runtime — é o framework de referência da Kubernetes SIG. Operator SDK quando o time não tem expertise em Go mas tem expertise em Ansible (pode encapsular playbooks Ansible em um Operator sem escrever código) ou quer encapsular um Helm chart existente em um Operator rapidamente. O Operator SDK também gera scaffolding compatível com kubebuilder — você pode começar com Helm e migrar para Go depois se precisar de lógica mais complexa.
Validating vs Mutating webhook — qual usar
Validating webhook para policies de compliance e segurança: rejeitar Pods sem resource limits, bloquear imagens não assinadas, impedir Deployments em namespace errado. O erro é explícito — o usuário sabe exatamente o que faltou. Mutating webhook para defaults e injeção automática: adicionar labels padrão que o usuário esqueceu, injetar sidecar de tracing automaticamente, setar valores de segurança (securityContext) que o usuário omitiu. Mutating webhooks são mais poderosos mas mais perigosos: uma mutação inesperada é difícil de debugar. Prefira sempre validação com mensagem de erro clara; use mutação apenas para defaults que nunca mudam o comportamento lógico do recurso.
failurePolicy: Fail vs Ignore para webhooks
failurePolicy: Fail (padrão) para webhooks de segurança críticos — OPA/Gatekeeper, validação de imagens assinadas. Se o webhook estiver indisponível, nenhum recurso é criado no cluster. Isso é o comportamento correto para segurança: melhor bloquear deploys do que permitir recursos não conformes. Requer que o webhook tenha ao menos 2 réplicas com PodAntiAffinity e PDB. failurePolicy: Ignore para webhooks de conveniência — adicionar labels padrão, injetar sidecars opcionais. Se o webhook estiver indisponível, o recurso é criado sem a mutação — aceitável para funcionalidade não-crítica. Nunca use Fail em webhooks sem alta disponibilidade comprovada — você pode travar o cluster.

Perguntas de entrevista

O que é o padrão Operator e por que idempotência do loop de reconciliação é fundamental?

O padrão Operator é a combinação de CRDs (que estendem a API do Kubernetes com tipos de recurso novos) com controllers (que observam esses recursos e reconciliam o estado real com o estado desejado). É a codificação do conhecimento operacional humano — runbooks, procedimentos de failover, rotinas de manutenção — em software que reage automaticamente a eventos.

Idempotência é fundamental porque o loop de reconciliação é invocado múltiplas vezes para o mesmo estado: quando o recurso é criado, quando é modificado, quando o controller reinicia, e periodicamente (via RequeueAfter). Uma função idempotente chamada N vezes com o mesmo input produz o mesmo resultado que chamada uma única vez. Se a reconciliação não for idempotente, múltiplas invocações podem criar duplicatas, fazer mudanças duplas, ou gerar estado inconsistente.

A forma prática de garantir idempotência é: (1) verificar se o recurso já existe antes de criar (use CreateOrUpdate); (2) usar server-side apply em vez de replace; (3) derivar o estado desejado apenas do spec do CRD, nunca de estado externo; (4) tratar cada invocação como se fosse a primeira — não assumir que invocações anteriores completaram com sucesso.

Qual a diferença entre Validating e Mutating Admission Webhook, e por que webhooks exigem alta disponibilidade?

Admission webhooks são plugins que o API server invoca antes de persistir um recurso no etcd. Mutating webhooks rodam primeiro: podem modificar o objeto (adicionar labels, injetar containers, setar defaults). Validating webhooks rodam depois: podem aprovar ou rejeitar o objeto mas não modificá-lo. A ordem importa: um Mutating webhook pode adicionar um campo que o Validating webhook depois valida.

A diferença prática: use Mutating para injeção automática de sidecars (Istio proxy, Vault agent), adição de labels padrão, e valores default. Use Validating para policy enforcement — rejeitar Pods sem resource limits, bloquear imagens de registries não aprovados, impedir Deployments com réplicas = 0 em namespace de produção. Mensagens de erro de Validating webhooks chegam diretamente ao usuário via kubectl apply.

Alta disponibilidade é obrigatória porque o API server invoca o webhook de forma síncrona na pipeline de admissão. Com failurePolicy: Fail (padrão para webhooks de segurança), se o webhook estiver indisponível, toda operação de criação/modificação de recursos cobertos pelo webhook falha — incluindo criação de novos Pods do próprio webhook, criando um deadlock. A configuração mínima é 2 réplicas com PodAntiAffinity entre zonas e PodDisruptionBudget.

Como o External Secrets Operator resolve o problema de secrets em GitOps e quais são suas alternativas?

O problema central de GitOps com secrets é que o princípio "tudo no git" conflita com "nunca commite credentials no git". Se o state desejado do cluster vive no git, e o estado inclui um Secret com uma senha de banco, você não pode armazenar o secret diretamente — ele ficaria exposto no histórico do repositório.

O ESO resolve isso com uma camada de indireção: você armazena no git apenas a referência a onde o secret está (ExternalSecret apontando para AWS Secrets Manager, Vault, ou GCP Secret Manager), e o ESO cria o Kubernetes Secret no cluster sincronizando do sistema externo. O secret nunca passa pelo git. O ESO também garante rotação: com refreshInterval: 1h, o Kubernetes Secret é atualizado toda hora com o valor mais recente do Secrets Manager.

Alternativas: (1) Sealed Secrets (Bitnami) — encripta o secret com a chave pública do cluster, o secret encriptado pode ser commitado no git com segurança; o controller decripta no cluster. Mais simples que ESO mas não integra com sistemas externos de secrets management. (2) Vault Agent Injector — injeta secrets do Vault diretamente em containers via sidecar; mais integrado com Vault mas mais complexo de operar. (3) Secrets Store CSI Driver — monta secrets de providers externos como volumes; mais flexível mas requer configuração por pod.

O que é OPA/Gatekeeper e como ele implementa policy-as-code para Kubernetes?

Open Policy Agent (OPA) é um engine de policy generalista que avalia rules escritas em Rego — uma linguagem declarativa para consultas de dados estruturados. Gatekeeper adapta o OPA para Kubernetes, implementado como Validating Admission Webhook que chama o OPA para cada recurso criado ou modificado.

O modelo do Gatekeeper tem duas camadas: ConstraintTemplate (define a lógica da regra em Rego) e Constraint (instância da regra com parâmetros específicos). Isso permite reutilização: você escreve um ConstraintTemplate para "recursos devem ter labels obrigatórias" e cria múltiplas Constraints com listas diferentes de labels. Times de plataforma controlam os ConstraintTemplates; times de produto configuram Constraints para seus namespaces.

A vantagem sobre webhooks Go customizados é que as regras em Rego são declarativas, testáveis unitariamente (opa test), e auditáveis — você pode ver exatamente por que um recurso foi rejeitado. O Gatekeeper também suporta modo de auditoria (scan de recursos já existentes que violam policies novas) sem bloquear deploys — útil para migrar policies gradualmente.

Qual é a estrutura de status de um CRD e por que separar spec e status é importante para o padrão Operator?

No design de CRDs, spec representa o estado desejado declarado pelo usuário (o que o usuário quer), e status representa o estado observado atual (o que o Operator relata sobre o que existe). Essa separação espelha o modelo de toda a API do Kubernetes: o usuário escreve spec.replicas: 3 (o que quer), e o controller atualiza status.readyReplicas: 2 (o que existe agora).

A separação é fundamental por duas razões: (1) Controle de acesso — usuários escrevem spec, controllers escrevem status. O RBAC pode permitir que um usuário atualize spec mas não status (via /status subresource), prevenindo que usuários manipulem o estado observado; (2) Idempotência e reconciliação — o Operator lê spec para saber o estado desejado e lê status para saber o estado atual, calculando o delta. Se spec e status fossem o mesmo campo, o Operator não saberia o que o usuário queria vs o que ele mesmo reportou.

Boas práticas de design de status: inclua um campo phase com enum de estados (Pending, Provisioning, Ready, Failed), um campo conditions com o formato padrão de Kubernetes Conditions (Type, Status, Reason, Message, LastTransitionTime), e campos de output relevantes (endpoint, version deployed, etc.). Conditions são preferíveis a phase para sistemas com múltiplos aspectos de saúde.

Exercícios práticos

01 · CRD simples com kubebuilder e controller de reconciliação

Crie um Operator simples com kubebuilder para o CRD WebApp com spec contendo image (string), replicas (int), e port (int). O controller deve reconciliar criando/atualizando um Deployment e um Service baseados no spec. Implemente status com campos phase (Pending/Ready) e availableReplicas. Teste a reconciliação: crie um WebApp, verifique que o Deployment é criado; modifique replicas no WebApp, verifique que o Deployment é atualizado; delete o WebApp e verifique que os recursos filho são removidos (owner references).

Critério: Criar um recurso WebApp resulta em um Deployment e Service funcionais; modificar o spec atualiza os recursos filho dentro de 30 segundos; deletar o WebApp cascata para os recursos filho via owner references; o status.phase reflete corretamente o estado do Deployment.
02 · Instalar cert-manager com ClusterIssuer Let's Encrypt

Instale o cert-manager via Helm em um cluster com Ingress controller (nginx-ingress ou traefik). Configure um ClusterIssuer para Let's Encrypt staging (use staging para testes — evita rate limits). Crie um Ingress com a anotação cert-manager.io/cluster-issuer e um campo tls apontando para um secretName. Verifique que o cert-manager emite o certificado automaticamente e popula o Secret. Observe o status do recurso Certificate e os eventos do CertificateRequest durante o processo de emissão.

Critério: O Ingress serve HTTPS com certificado válido emitido automaticamente; o Secret TLS é criado com os campos tls.crt e tls.key; o recurso Certificate tem status Ready: True; sem intervenção manual em nenhuma etapa.
03 · External Secrets Operator com AWS Secrets Manager

Instale o External Secrets Operator via Helm. Configure um SecretStore (ou ClusterSecretStore) apontando para AWS Secrets Manager usando IRSA (IAM Roles for Service Accounts) — sem AWS credentials no cluster. Crie um secret no AWS Secrets Manager com um JSON de credenciais de banco. Crie um ExternalSecret no cluster que sincroniza o secret do AWS para um Kubernetes Secret. Verifique que o Secret é criado com os campos corretos. Atualize o valor no AWS Secrets Manager e aguarde o refreshInterval para verificar que o Kubernetes Secret é atualizado automaticamente.

Critério: Nenhuma AWS credential é armazenada como Kubernetes Secret — a autenticação usa IRSA; o ExternalSecret cria o Kubernetes Secret com os campos corretos; após atualizar o valor no AWS Secrets Manager, o Kubernetes Secret é atualizado dentro do refreshInterval configurado.
04 · Validating Webhook que impede Pods sem resource limits

Implemente um Validating Admission Webhook em Go usando o kubebuilder webhook scaffolding. O webhook deve rejeitar qualquer Pod onde algum container não tenha resources.limits.cpu e resources.limits.memory definidos. Configure o ValidatingWebhookConfiguration para interceptar apenas Pods em namespaces com a label enforce-limits: "true". Implemente testes unitários para o handler usando admission.Request simulado. Deploy o webhook com 2 réplicas e PodAntiAffinity por zona, com failurePolicy: Fail.

Critério: Um Pod sem resource limits em namespace com a label é rejeitado com mensagem de erro clara; um Pod com limits corretos é aceito; o webhook tem cobertura de testes unitários >80%; com uma réplica do webhook derrubada, Pods ainda podem ser criados (alta disponibilidade).
05 · OPA/Gatekeeper com políticas de labels e registries aprovados

Instale o Gatekeeper via Helm. Crie dois ConstraintTemplates com Rego: (1) K8sRequiredLabels que exige labels team e app em todos os Deployments de um namespace; (2) K8sAllowedRegistries que rejeita Pods com imagens de registries não aprovados (lista de prefixos aprovados como parâmetro). Configure as Constraints correspondentes. Teste: tente criar um Deployment sem as labels obrigatórias (deve falhar com mensagem clara); tente criar um Pod com imagem do Docker Hub quando apenas ghcr.io é aprovado (deve falhar). Use o modo de auditoria para verificar recursos já existentes que violam as policies.

Critério: Deployments sem as labels obrigatórias são rejeitados com mensagem especificando quais labels faltam; Pods com imagens de registries não aprovados são rejeitados; recursos existentes que violam policies aparecem em kubectl describe constraint na seção violations; policies são testáveis com opa test sem um cluster real.

Referências

  1. article Brandon Philips — Introducing Operators: Putting Operational Knowledge into Software coreos.com · 2016 · o post original que definiu o padrão Operator
  2. docs Kubebuilder Book book.kubebuilder.io · guia completo de desenvolvimento de Operators em Go com controller-runtime
  3. docs Open Policy Agent — Gatekeeper open-policy-agent.github.io/gatekeeper · policy enforcement declarativo com OPA em Kubernetes
  4. book Jason Dobies & Joshua Wood — Kubernetes Operators O'Reilly · 2020 · guia prático de automação com o padrão Operator usando Operator SDK
  5. docs OperatorHub.io operatorhub.io · catálogo de Operators open source prontos para produção — cert-manager, Strimzi, CloudNativePG
  6. docs cert-manager — Documentation cert-manager.io/docs · issuers, certificates, e integração com Let's Encrypt e Vault
  7. docs External Secrets Operator — Documentation external-secrets.io · integração com AWS Secrets Manager, Vault, GCP Secret Manager e Azure Key Vault
  8. docs Crossplane — Documentation crossplane.io/docs · provision de infraestrutura cloud via Kubernetes CRDs e Compositions
  9. docs Operator SDK — Documentation sdk.operatorframework.io · framework Red Hat para Operators em Go, Ansible e Helm
  10. article CNCF — Operator White Paper github.com/cncf/tag-app-delivery · definição formal do padrão Operator, casos de uso e maturidade
  11. article CloudNativePG — PostgreSQL Operator for Kubernetes cloudnative-pg.io · Operator de referência para PostgreSQL em produção no Kubernetes
  12. article Kubernetes — Admission Controllers Reference kubernetes.io/docs · documentação oficial de Validating e Mutating webhooks, failurePolicy e configuração