Artigos / Kubernetes & Azure

AKS privado, ACR e Private Link: o que quebra primeiro

Ligar cluster privado no AKS não isola a plataforma. O que costuma cair primeiro é o caminho operacional: DNS do Private Link, pull de imagem no ACR, kubectl fora da VNet e a saída dos nodes para os serviços do Azure.

O erro não é deixar a API pública. É achar que privado é um toggle

Em muitos projetos, “AKS privado” entra no checklist de segurança junto com “ACR sem acesso público”. O desenho parece simples: API só na VNet, registry só via Private Endpoint, fim da exposição na internet.

Em produção, o cluster continua dependendo de identidade, DNS, roteamento e de um caminho humano para operar. Quando qualquer um desses pedaços fica incompleto, o sintoma não é “API exposta”. É deploy parado, node que não sobe, ImagePullBackOff e time de plataforma trancado fora do cluster.

Cluster privado reduz a superfície da API Kubernetes na internet. Ele não substitui Private DNS, identidade do kubelet, Firewall de saída nem um salto operacional (VPN, ExpressRoute ou jump box).

O que realmente muda no AKS privado

Com API server privado, o endpoint do Kubernetes deixa de ter um endereço público utilizável. O plano de controle continua sendo gerenciado pela Microsoft; o que muda é o caminho até ele.

Os nodes falam com a API por um Private Endpoint gerenciado. Quem está fora da VNet (laptop, pipeline em outra rede, agente self-hosted mal posicionado) simplesmente não resolve ou não roteia até esse IP.

Operador / pipeline → VPN / hub / jump → Private DNS → API privada do AKS

Authorized IP ranges em cluster público resolvem outro problema: restringir origem na internet. Misturar os dois modelos sem intenção clara é comum e gera a ilusão de que a API “já está privada” quando ainda responde na borda pública.

ACR: o pull de imagem quebra antes da teoria de Zero Trust

O segundo toggle é desligar o acesso público do Azure Container Registry. Sem Private Endpoint (ou sem um caminho equivalente), o kubelet não baixa imagem. Com Private Endpoint mal DNS-ado, o nome público do ACR continua resolvendo para IP público. Ou, pior, resolve de um jeito no hub e de outro no on-premises.

O nome que o cluster usa continua sendo o FQDN do registry (meuregistro.azurecr.io). Private Link não inventa um hostname paralelo para a aplicação. Ele exige que a zona privatelink.azurecr.io (e o registro A correto) esteja ligada às VNets que precisam resolver o IP privado.

Kubelet (MSI) → FQDN do ACR → Private DNS → Private Endpoint

Identidade é a outra metade. Nodes precisam de permissão para pull (AcrPull na identidade do kubelet). Private Link sem RBAC no registry também falha, só que a mensagem parece “rede”. RBAC sem DNS também falha, e aí a mensagem parece “autenticação”.

O que quebra primeiro (ordem que eu vejo no campo)

  1. kubectl do laptop. Cluster privado sem VPN, ExpressRoute ou jump box. O portal até mostra o recurso; o kubeconfig aponta para um FQDN que não resolve na rede do analista.
  2. DNS split-brain no ACR. Zona privada criada só na spoke, não ligada ao hub. Pipeline no hub resolve IP público; node na spoke resolve privado. Ou o contrário.
  3. Saída dos nodes. UDR para Azure Firewall sem as FQDNs que o AKS ainda precisa na internet ou via serviço. Node pool não sobe, extensão não instala, pull de imagens da Microsoft falha.
  4. Pipeline na nuvem errada. Microsoft-hosted agent tentando falar com API privada. O job “funcionava” no cluster público e morre no privado sem um agent na VNet.
  5. Private Endpoint de ACR em outra subscription sem o DNS e o approval corretos. O diagrama no PowerPoint está certo; o registro A não.

Critérios de decisão: não copie o lab de tutorial

API privada

Faz sentido quando:

  • já existe hub-spoke, VPN ou ExpressRoute de verdade;
  • existe jump box ou agent de CI dentro da rede;
  • o risco de API Kubernetes na internet é inaceitável;
  • Private DNS é operado como plataforma, não como afterthought.
API pública restrita

Ainda é válida quando:

  • o time opera sem rede corporativa estável;
  • o pipeline ainda é Microsoft-hosted e não há agent interno;
  • authorized IPs + Entra + RBAC cobrem o risco atual;
  • privado viraria um lockout operacional, não um controle.

ACR 100% privado segue a mesma lógica. Se o único consumidor é o AKS na VNet, feche o public network access e trate DNS como contrato de plataforma. Se ainda existem builds em SaaS, scanners ou parceiros puxando tag pela internet, “registry privado” vira exceção mal documentada, ou um firewall de IP que ninguém revisa.

Arquitetura mínima que dá para reproduzir

Não é um tutorial de clique. É o recorte que eu esperaria ver num desenho de landing zone antes de marcar o cluster como privado:

  • Hub com resolução DNS central (Private DNS Zones ligadas ao hub, não espalhadas só na spoke).
  • Spoke do AKS: subnet de nodes, subnet de Private Endpoints, UDR consistente com o Firewall.
  • Zonas relevantes no hub: AKS (privatelink.<region>.azmk8s.io no modelo atual) e ACR (privatelink.azurecr.io).
  • ACR com Private Endpoint na spoke (ou no hub, se essa for a norma de PE da empresa) e AcrPull na identidade do kubelet.
  • Caminho operacional explícito: VPN, ExpressRoute ou jump box com kubectl e, no Azure DevOps/GitHub, agent na VNet.
Hub DNS + Firewall → Spoke AKS → PE ACR → Imagem no node

O laboratório público azure-aks-lab cobre o caminho Terraform → ACR → AKS. Cluster privado e PE de ACR são a evolução natural desse lab, não um desenho paralelo.

Consequências operacionais

Segurança. API fora da internet reduz uma classe de ataque. Não reduz Secret mal guardado, RBAC largo nem imagem vulnerável. Private Link no ACR reduz exfiltração por pull anônimo; não substitui scan nem imutabilidade de tag.

Custo. Private Endpoint, Firewall, jump box e DNS privado não são “grátis no Terraform”. Cluster privado com Firewall inspecionando tudo aumenta SKU e complexidade. O cluster que “ficou mais seguro” no board às vezes ficou mais caro e mais frágil no DNS.

Raio de explosão. Um Private DNS Zone desligado da VNet errada derruba pull de imagem em todos os namespaces. Um UDR errado derruba node bootstrap. Falha de rede de plataforma parece falha de aplicação, e o on-call da app leva o chamado.

O que não fazer

  • Marcar AKS privado sem definir quem vai rodar kubectl no dia seguinte.
  • Criar Private Endpoint de ACR e esquecer a zona privatelink.azurecr.io no hub.
  • Desligar public access do ACR enquanto o pipeline ainda resolve o registry pela internet.
  • Tratar Service Endpoint e Private Endpoint como equivalentes. Não são. O modelo de DNS e o controle de exposição são diferentes.
  • Assumir que Azure Monitor, Policy e add-ons “continuam iguais” sem revisar saída e FQDNs.
  • Usar cluster privado como substituto de Entra ID, Azure RBAC e menor privilégio. Isolamento de API não é autorização. Isso já foi o assunto do artigo sobre Azure RBAC vs Kubernetes RBAC.

Recomendação

Se a organização já opera hub-spoke e identidade no Entra, API privada + ACR privado é o alvo certo, desde que DNS e o caminho de operação entrem no mesmo pull request que o flag private_cluster_enabled.

Se a organização ainda acessa Azure pelo laptop na internet e o CI é hosted, feche o que for possível (authorized IPs, disable local accounts, RBAC) e planeje o salto de rede. Ligar privado no meio do caminho só troca um risco conhecido por um incidente de disponibilidade.

Conclusão

AKS privado e ACR via Private Link são decisões de plataforma, não de checkbox no recurso. O que quebra primeiro quase nunca é “a API ainda pública”. É DNS, pull, pipeline e a pessoa que precisava entrar no cluster às 2h da manhã.

Desenhe o caminho até a API e o caminho até a imagem com o mesmo rigor que você desenha o node pool. O resto da série (Workload Identity, Policy, FinOps do cluster) só funciona se esse caminho existir de verdade.