Policy as Code no Kubernetes: OPA e Gatekeeper como Guarda de Entrada do Cluster
Guia técnico de OPA e Gatekeeper: admission control, a linguagem Rego, ConstraintTemplates, modos audit e enforce, e as armadilhas que travam o cluster inteiro.
Neste artigo
Todo cluster de Kubernetes acumula, com o tempo, uma lista de "regras que todo mundo deveria seguir". Nunca use a tag :latest numa imagem de produção. Todo Pod tem que declarar requests e limits. Ninguém sobe container privileged. Todo recurso precisa de um label de time e de centro de custo. Nenhum Service do tipo LoadBalancer sem aprovação. Essas regras costumam morar num documento de wiki, numa checklist de code review, ou — na versão mais frágil de todas — na memória da pessoa mais experiente do time. E documento, checklist e memória têm o mesmo defeito fatal: eles dependem de alguém lembrar de aplicá-los, toda vez, sem falhar. Basta uma pressa, um plantão cansado, um colega novo que não leu a wiki, e a regra é violada. A violação só é descoberta depois, quando já virou incidente.
Policy as code resolve isso mudando onde a regra vive. Em vez de estar num texto que humanos precisam obedecer, a regra vira código executável que a própria plataforma aplica automaticamente, no momento exato em que um recurso é criado ou alterado. Uma imagem :latest simplesmente não entra. Um Pod sem requests é rejeitado na hora, com uma mensagem explicando por quê. O OPA (Open Policy Agent) e o Gatekeeper são o par de ferramentas mais estabelecido para trazer essa disciplina ao Kubernetes, e este guia mostra como eles se encaixam no fluxo da API, como se escreve uma política, e onde essa poderosa capacidade pode, se mal usada, derrubar o cluster inteiro.
Onde a política se encaixa: admission control#
Para entender OPA e Gatekeeper, você precisa saber por onde passa uma requisição no Kubernetes. Quando você roda kubectl apply, o objeto não vai direto para o etcd. Ele atravessa uma esteira de estágios no API Server: primeiro autenticação (quem é você?), depois autorização (você pode fazer isso? — é aqui que o RBAC atua), e então os admission controllers. Só depois disso o objeto é persistido.
Os admission controllers são o ponto de interceptação onde a política de conteúdo age, e vale distinguir duas famílias. Os validating admission webhooks olham o objeto e respondem sim ou não: aceito ou rejeito, sem alterar nada. Os mutating admission webhooks podem modificar o objeto antes de ele ser salvo — injetar um label padrão, preencher um campo ausente. O Gatekeeper opera principalmente como um validating webhook (com suporte também a mutação), o que significa que ele é literalmente um porteiro: cada criação ou alteração de recurso passa por ele e recebe um veredito antes de existir de fato no cluster.
Repare como isso é diferente do RBAC, porque a confusão é comum. O RBAC responde "quem pode executar qual verbo sobre qual tipo de recurso" — o usuário X pode criar Pods no namespace Y. Ele não olha o conteúdo do Pod. O RBAC autoriza a ação; ele não tem opinião sobre se o Pod que você está criando usa :latest ou roda como root. O OPA/Gatekeeper cuida exatamente do que o RBAC ignora: como o recurso deve ser. São camadas complementares — uma governa a ação, a outra governa o conteúdo — e você precisa das duas.
Por que "as code" muda tudo#
O adjetivo importa. Quando a política é código, ela herda todas as boas práticas que já aplicamos a código. Ela vive num repositório Git, com histórico de quem mudou o quê e por quê. Ela passa por pull request, onde outra pessoa revisa antes de a regra entrar em vigor. Ela pode ser testada: você escreve casos que provam que a política aceita o que deve aceitar e rejeita o que deve rejeitar. Ela é versionada, então você sabe exatamente qual conjunto de regras estava valendo em qualquer data. E ela é aplicada por uma máquina que nunca esquece, nunca tem pressa e nunca abre exceção "só dessa vez". Uma checklist manual não oferece nada disso. Essa é a diferença entre governança que existe no papel e governança que existe de verdade.
Rego, ConstraintTemplate e Constraint#
O OPA usa uma linguagem de política declarativa chamada Rego, feita para expressar regras sobre dados estruturados. No mundo Gatekeeper, você não escreve Rego solto; você o empacota num ConstraintTemplate, que define uma classe de política parametrizável, e depois cria Constraints, que são instâncias concretas daquela classe aplicadas a certos tipos de recurso. Essa separação é elegante: o template diz como verificar, e a constraint diz onde aplicar e com quais parâmetros.
Veja um exemplo que proíbe a tag :latest em imagens. Primeiro o template, com a lógica em Rego:
```yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8sproibirlatest spec: crd: spec: names: kind: K8sProibirLatest targets:
- target: admission.k8s.gatekeeper.sh
rego: | package k8sproibirlatest
violation[{"msg": msg}] { container := input.review.object.spec.containers[_] endswith(container.image, ":latest") msg := sprintf("imagem com tag :latest é proibida: %v", [container.image]) }
violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not contains(container.image, ":") msg := sprintf("imagem sem tag explícita é proibida: %v", [container.image]) } ```
A lógica em Rego funciona por violações: a regra descreve as condições sob as quais algo é uma violação, e se a regra "encontra" pelo menos uma, o admissão é negado. O primeiro bloco flagra qualquer container cuja imagem termina em :latest; o segundo flagra imagens sem tag alguma (que também resolvem para latest). Repare que a política enxerga o objeto inteiro via input.review.object e pode inspecionar qualquer campo dele.
Agora a Constraint, que aplica esse template aos Pods:
```yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sProibirLatest metadata: name: proibir-latest-em-pods spec: enforcementAction: deny match: kinds:
- apiGroups: [""]
kinds: ["Pod"] excludedNamespaces: ["kube-system", "gatekeeper-system"] ```
O match restringe onde a regra vale, e note o excludedNamespaces — voltaremos a ele nas armadilhas, porque isolar os namespaces de sistema não é opcional. Com esses dois objetos aplicados, qualquer tentativa de criar um Pod com :latest recebe uma negação com a mensagem que você escreveu no msg, e o desenvolvedor descobre o erro no kubectl apply, não num incidente três semanas depois.
Audit versus enforce: nunca ligue no escuro#
Aqui está o conselho operacional mais valioso de todo o tema. Nunca aplique uma política nova diretamente em modo de bloqueio. O enforcementAction da constraint aceita, além de deny, os modos dryrun e warn. Em dryrun, o Gatekeeper não bloqueia nada — ele apenas registra, no status da constraint, todos os recursos existentes que violariam a regra se ela estivesse ativa. Isso é ouro: você liga a política em dryrun, olha o relatório de auditoria, e descobre que 47 workloads legítimos, incluindo coisas críticas, seriam bloqueados. Você conserta esses casos com calma, ou ajusta a regra, e só depois vira o modo para deny. Ligar uma política em deny de cara, sem essa fase de auditoria, é como fechar a fábrica sem avisar ninguém — você descobre o estrago pelos gritos.
O Gatekeeper também audita continuamente os recursos já existentes, não só os novos. Isso significa que ele te dá um inventário vivo de conformidade: quais recursos, neste momento, violam quais políticas. Mesmo antes de bloquear qualquer coisa, esse relatório sozinho já justifica a adoção, porque expõe a dívida de conformidade que estava invisível.
O catálogo de políticas que todo cluster quer#
Algumas políticas são tão universais que existem prontas em bibliotecas mantidas pela comunidade. Vale conhecer as clássicas: bloquear :latest (você não sabe o que está rodando); exigir requests e limits em todo container (sem isso, o scheduler e os autoscalers ficam cegos e um Pod ganancioso derruba o nó); negar containers privileged, hostNetwork, hostPID e montagens de hostPath (fugas do isolamento); obrigar labels de propriedade como time e centro de custo (sem isso, você não sabe de quem é o gasto nem quem chamar quando quebra); restringir registries de imagem a uma allowlist (para não rodar imagem de origem desconhecida); e proibir Services LoadBalancer ou NodePort fora de um processo aprovado. Perceba que várias dessas políticas são, na prática, o enforcement automático de boas práticas de hardening: em vez de torcer para que todo mundo lembre de não rodar como root, você faz o cluster recusar quem tenta.
Mutation: consertar em vez de só recusar#
Além de validar, o Gatekeeper pode mutar recursos, aplicando defaults automaticamente. Em vez de rejeitar um Pod que não tem seccompProfile, você pode configurar uma mutação que injeta um perfil seguro por padrão. Em vez de negar um recurso sem certos labels, você pode preenchê-los. Mutação reduz o atrito — o desenvolvedor não precisa saber de cor cada campo obrigatório — mas exige cuidado dobrado, porque agora a plataforma está alterando silenciosamente o que as pessoas pediram. Use mutação para defaults sensatos e óbvios, e validação para as regras que devem ser recusadas em alto e bom som.
OPA/Gatekeeper, Kyverno ou Pod Security Admission?#
É justo saber que existem alternativas. O Pod Security Admission é um mecanismo embutido no próprio Kubernetes que aplica os Pod Security Standards (privileged, baseline, restricted) por namespace. Ele não exige instalar nada, mas é limitado: só cuida de segurança de Pod e não é extensível para regras arbitrárias. O Kyverno é um concorrente popular do Gatekeeper cuja grande diferença é escrever políticas em YAML, sem uma linguagem separada — mais acessível para quem não quer aprender Rego, à custa de menos expressividade em casos muito complexos. O OPA/Gatekeeper brilha quando você precisa de lógica de política rica, reuso via templates, e a mesma engine (OPA) governando políticas fora do Kubernetes também. Não há resposta única: para o mínimo de segurança de Pod, comece pelo Pod Security Admission; para governança ampla e expressiva, Gatekeeper ou Kyverno conforme o apetite do time por Rego.
As armadilhas que derrubam o cluster#
A mais perigosa de todas mora no failurePolicy do webhook. Quando o Gatekeeper é registrado como admission webhook, o API Server passa a chamá-lo a cada criação de recurso. Se o failurePolicy está em Fail e o pod do Gatekeeper fica indisponível — um deploy ruim dele mesmo, um problema de rede, os pods reiniciando —, o API Server passa a rejeitar as operações que dependeriam daquele webhook. O resultado é aterrador: você não consegue mais criar nem alterar recursos, e às vezes nem consertar o próprio Gatekeeper, porque a criação dos pods dele também passa pelo caminho travado. A defesa é dupla: excluir rigorosamente os namespaces de sistema (kube-system, gatekeeper-system) do escopo das políticas, garantindo que a plataforma consiga se recuperar, e rodar o Gatekeeper com alta disponibilidade. Um webhook mal configurado deixa de ser guarda e vira sabotador.
A segunda armadilha é a política que trava deploy legítimo. Uma regra bem-intencionada, mas mal calibrada, pode bloquear um workload perfeitamente válido — e o desenvolvedor, sem entender por quê, fica travado. Mensagens de violação claras (aquele msg que você escreve) e a fase de dryrun mitigam isso, mas o princípio é: toda política nova é uma mudança de contrato com quem usa o cluster, e merece comunicação e período de graça.
A terceira é o custo de latência. Cada admissão agora faz uma chamada síncrona ao Gatekeeper. Com muitas políticas e Rego ineficiente, isso adiciona latência ao caminho de criação de recursos e pode, em escala, pressionar o API Server. Escreva políticas enxutas e monitore a latência de admissão.
A quarta é não testar a política. Rego é código, e código sem teste engana. É perfeitamente possível escrever uma política que parece bloquear :latest mas tem um bug que a torna inócua — e você só descobre quando a imagem errada já está em produção. Escreva casos de teste que provem tanto a rejeição quanto a aceitação, e rode-os no CI antes de a política chegar ao cluster.
Fechando#
Policy as code com OPA e Gatekeeper é o que transforma "todo mundo deveria seguir essas regras" em "o cluster não deixa ninguém desobedecer". A esteira de admission control é o ponto natural para isso: cada recurso passa por um porteiro que verifica seu conteúdo — algo que o RBAC, focado em quem pode agir, nunca fez. Escrevendo políticas em Rego, empacotadas em ConstraintTemplates reutilizáveis e aplicadas por Constraints parametrizadas, você ganha governança versionada, revisável e testável, aplicada por uma máquina incansável. O poder vem com responsabilidade: um webhook em Fail sem os namespaces de sistema excluídos pode congelar o cluster inteiro, e uma política nunca auditada pode bloquear produção ou, pior, não bloquear nada e te dar falsa segurança. Comece em dryrun, leia o relatório de auditoria, teste suas regras, isole a plataforma, e vire para enforce só quando os números confirmarem que você não vai derrubar ninguém legítimo. Feito assim, o cluster deixa de depender da memória das pessoas e passa a garantir, sozinho, que só entra o que deve entrar.