Networking

Network Policies no AKS: isolamento de tráfego entre pods com Azure CNI

O pod do frontend abre uma conexão direto com o banco de dados. Um pod de um namespace chama o serviço interno de outro time. Nada disso foi planejado, e nada disso é bloqueado: por padrão, todo pod de um cluster Kubernetes fala com todo pod.

Em desenvolvimento, isso é conveniente. Em produção, com dados sensíveis ou mais de um time no mesmo cluster, é o equivalente a uma rede plana sem NSG. Se um pod for comprometido, ele alcança todo o resto.

Network Policies são o mecanismo do Kubernetes para dizer quem pode falar com quem. No AKS, a decisão que vem antes do primeiro YAML é qual motor vai aplicar essas regras.

Escolhendo o motor de política

O AKS oferece três motores:

Azure Network Policy Manager (NPM). O motor original da Microsoft. Está em aposentadoria: no Windows, o suporte termina em 30 de setembro de 2026; no Linux, em 30 de setembro de 2028. Não é uma boa escolha para cluster novo.

Calico. Motor de código aberto, maduro, com recursos além do padrão do Kubernetes. Continua suportado.

Cilium, com o Azure CNI Powered by Cilium. É o que a Microsoft recomenda hoje. Usa eBPF no lugar de iptables, o que escala melhor em clusters grandes, e é a base para políticas por nome de domínio e de camada 7.

Para um cluster novo, eu começo com Azure CNI em modo overlay e o dataplane Cilium:

az aks create \
  --name aks-producao \
  --resource-group rg-plataforma \
  --location brazilsouth \
  --network-plugin azure \
  --network-plugin-mode overlay \
  --pod-cidr 192.168.0.0/16 \
  --network-dataplane cilium \
  --generate-ssh-keys

Com o dataplane Cilium, a aplicação das Network Policies já vem junto. Não é preciso escolher um motor separado.

E se o cluster já existe sem política

Durante muito tempo, habilitar network policy exigia recriar o cluster. Hoje dá para ativar o NPM ou o Calico num cluster existente com az aks update --network-policy, e migrar para o Cilium com az aks update --network-dataplane cilium, que remove o motor anterior. Os dois têm um custo operacional: a imagem de todos os pools de nós é recriada ao mesmo tempo, sem opção de fazer um pool por vez, e o Cilium só passa a aplicar as políticas depois que todos os nós terminam. A migração para o Cilium também não é possível em clusters com pools Windows. Planeje como uma janela de manutenção, não como um ajuste rápido.

Comece bloqueando tudo

A abordagem que funciona é a mesma de um firewall: negar tudo no namespace e liberar explicitamente o que precisa.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: pagamentos
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

podSelector: {} seleciona todos os pods do namespace. Com os dois tipos de política listados e nenhuma regra, nada entra e nada sai.

Não esqueça o DNS

Esse é o erro mais comum que eu vejo depois do default deny: os pods param de resolver nomes, e quase tudo quebra ao mesmo tempo. A liberação do DNS vai junto com o bloqueio, nunca depois:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: pagamentos
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Liberando o que precisa, por identidade do pod

Com o bloqueio no lugar, cada fluxo vira uma regra. A melhor forma de escrever é por label, não por IP, porque o IP do pod muda a cada reinício:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-para-banco
  namespace: pagamentos
spec:
  podSelector:
    matchLabels:
      app: banco
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api
    ports:
    - protocol: TCP
      port: 5432

A regra diz: o banco só aceita conexões na porta 5432, e só de pods com a label app: api. O frontend, que tinha acesso direto na abertura, perdeu esse caminho.

Para destinos fora do cluster, como Private Endpoints de serviços PaaS, a regra de saída usa ipBlock com a faixa da sub-rede de Private Endpoints. Com o Cilium, o ipBlock não serve para liberar IPs de nós ou de pods; para esses casos, use seletores de pod e de namespace.

Saída por nome de domínio

A Network Policy padrão só entende IP e porta. Isso é um problema para a saída para a internet: liberar uma API externa por IP é frágil, porque os IPs mudam.

Com o dataplane Cilium e o Advanced Container Networking Services (ACNS) habilitado, dá para escrever políticas de saída por FQDN e regras de camada 7, como permitir só determinados caminhos HTTP. O ACNS é cobrado por nó, então vale habilitar onde a exigência de controle de saída justifica, e não por padrão em todo cluster.

Três erros que eu vejo com frequência

Default deny sem DNS. Já descrito acima, e responsável pela maioria dos rollbacks de política.

Política só de entrada. Proteger o banco contra quem chega e deixar todo pod sair para qualquer destino resolve metade do problema. Um pod comprometido continua podendo exfiltrar dados.

Política fora do Git. Regra aplicada à mão com kubectl apply e nunca versionada vira uma configuração que ninguém sabe explicar seis meses depois. As políticas vivem no mesmo repositório dos manifests da aplicação e passam por revisão.

Como testar

Antes de ir para produção, teste cada fluxo que precisa funcionar e um que não deveria:

# Deve funcionar: a API chega ao banco
kubectl exec -n pagamentos deploy/api -- nc -zv banco 5432

# Deve falhar: o frontend não chega ao banco
kubectl exec -n pagamentos deploy/frontend -- nc -zv -w 3 banco 5432

O segundo teste é o que mais importa. Uma política que nunca foi vista bloqueando algo não foi testada.

O que fica

Network Policy no AKS é o equivalente ao NSG para o tráfego entre pods, e ela só funciona com um motor por trás. Para cluster novo, Cilium; para cluster existente, uma janela de manutenção para ativar. Depois, bloqueio total, DNS liberado e cada fluxo escrito em código.

No seu cluster de produção, qual pod conseguiria chegar ao banco de dados hoje sem precisar?