Azure Kubernetes Service (AKS)

[323] Azure Kubernetes Service (AKS)

O Kubernetes é o mesmo; o que muda são as integrações com a nuvem: o cluster provisionado com Terraform e o CNI Overlay que poupa IPs, o Workload Identity no lugar do IRSA, o KEDA escalando por eventos do Service Bus até zero réplicas, o ACR, o GitOps com Flux e as queries KQL.
DevOps

18 min de leitura

O Kubernetes em si é a mesma plataforma, seja no EKS da AWS ou no AKS do Azure. Os manifestos YAML que descrevem Deployments, Services, ConfigMaps e HPAs funcionam sem modificação entre os dois. O que muda são as integrações com a nuvem subjacente — e é aí que o AKS tem características próprias que valem conhecer.

Este artigo cobre a criação de um cluster AKS com Terraform, a integração com Microsoft Entra ID para autenticação, o Workload Identity como substituto do IRSA da AWS, o KEDA (Kubernetes Event-Driven Autoscaling) para escalonamento baseado em eventos do Azure Service Bus, e o GitOps com Flux — a alternativa nativa ao ArgoCD amplamente usada no ecossistema Azure.

AKS vs. EKS: As Diferenças que Importam

Ambos os serviços gerenciam o control plane do Kubernetes, mas com modelos ligeiramente diferentes.

No EKS, o control plane tem custo fixo por hora (aproximadamente USD 0,10/hora), independentemente do número de nós. Os nós são instâncias EC2 gerenciadas pelo usuário ou pelo EKS Managed Node Groups. O Karpenter é o provisionador de nós recomendado para escalonamento rápido.

No AKS, o control plane é gratuito no tier Free (sem SLA de uptime garantido) e tem custo no tier Standard (SLA de 99,95% para o API server). Os nós são Azure VMs em Node Pools. O escalonamento de nós é feito pelo Cluster Autoscaler nativo, e o KEDA cuida do escalonamento de pods baseado em eventos.

O AKS Free Tier é especialmente útil para ambientes de desenvolvimento e staging — o control plane gratuito representa uma economia significativa comparada ao EKS, que cobra pelo control plane independentemente do ambiente.

Provisionando um Cluster AKS com Terraform

O cluster AKS no Terraform é declarado com o recurso azurerm_kubernetes_cluster. Os pontos mais importantes são: o sku_tier = "Standard" para o SLA de produção; o automatic_channel_upgrade = "patch" para atualizações automáticas de segurança; o default_node_pool com only_critical_addons_enabled = true reservando esse pool apenas para componentes do sistema; e o oidc_issuer_enabled = true com workload_identity_enabled = true que habilita o equivalente ao OIDC do EKS para o Workload Identity.

A rede é configurada com o profile azure e network_plugin_mode = "overlay" — o Azure CNI Overlay é recomendado para clusters grandes pois não consome IPs da VNet para cada pod, diferente do CNI tradicional. A integração com Entra ID é declarada no bloco azure_active_directory_role_based_access_control com azure_rbac_enabled = true e um grupo de administradores.

Os node pools de aplicação são declarados separadamente com azurerm_kubernetes_cluster_node_pool. A boa prática no AKS é manter node pools distintos para o sistema e para as aplicações — o pool do sistema tem o taint implícito CriticalAddonsOnly, garantindo que workloads de aplicação não compitam com componentes do cluster. Um terceiro pool Spot com priority = "Spot" e min_count = 0 permite escalar para zero quando não há carga, zerando custos de compute em horários de baixa.

Para autenticar o kubectl no AKS, usa-se az aks get-credentials — equivalente ao aws eks update-kubeconfig do EKS. O kubelogin é o componente que faz a autenticação interativa via Entra ID, equivalente ao aws-iam-authenticator do EKS.

Workload Identity: O IRSA do Azure

No EKS, o IRSA (IAM Roles for Service Accounts) permitia que pods assumissem IAM Roles sem credenciais hardcoded — bastava anotar a ServiceAccount com o ARN do role. O AKS tem o equivalente chamado Workload Identity, que usa o mesmo mecanismo OIDC por baixo mas com a sintaxe do ecossistema Azure.

O fluxo é o mesmo: o cluster emite tokens OIDC para os pods, a Azure aceita esses tokens como prova de identidade e emite tokens de acesso aos recursos Azure solicitados.

Na AWS com IRSA, cria-se um IAM Role com trust policy apontando para o OIDC provider do EKS e um Condition restringindo ao namespace e ServiceAccount específicos. A ServiceAccount é anotada com o ARN do role.

No Azure com Workload Identity, cria-se uma User Assigned Managed Identity, depois um Federated Identity Credential que liga a ServiceAccount Kubernetes à identidade (declarando o issuer como o URL OIDC do cluster e o subject como system:serviceaccount:namespace:nome-da-sa). A ServiceAccount é anotada com o client-id da Managed Identity. O Deployment precisa do label azure.workload.identity/use: "true" para que os webhooks do Workload Identity injetem as variáveis de ambiente e o token no pod.

O SDK Azure para Node.js, Python, Java e outras linguagens detecta automaticamente o Workload Identity via variáveis de ambiente — new DefaultAzureCredential() funciona sem configuração adicional tanto em desenvolvimento (usando a CLI do Azure) quanto em produção (usando o Workload Identity).

Para montar secrets do Key Vault diretamente como variáveis de ambiente ou arquivos no pod, usa-se o recurso SecretProviderClass do Secrets Store CSI Driver — habilitado no AKS via o bloco key_vault_secrets_provider no Terraform.

KEDA: Escalonamento Baseado em Eventos

O KEDA (Kubernetes Event-Driven Autoscaling) é um componente open-source criado pela Microsoft e Red Hat que estende o HPA do Kubernetes para escalonar pods baseado em fontes de eventos externas — não apenas CPU e memória.

No EKS, o escalonamento baseado em tamanho de fila SQS requer configurar métricas customizadas no CloudWatch, expô-las via adapter e configurar o HPA — um processo relativamente complexo. No AKS com KEDA, a integração com Azure Service Bus é nativa e declarativa.

O KEDA é instalado via Helm com kedacore/keda no namespace keda. A autenticação com os serviços Azure é configurada via TriggerAuthentication com provider: azure-workload — sem connection strings estáticas. O ScaledObject declara o Deployment alvo, os limites de réplicas (minReplicaCount: 0 para scale to zero) e os triggers. Para Service Bus, o trigger especifica o namespace, o nome da fila e a proporção de mensagens por réplica — por exemplo, messageCount: "100" cria uma nova réplica para cada 100 mensagens na fila.

O KEDA suporta dezenas de scalers além do Service Bus: Azure Storage Queue, Azure Event Hubs, Redis, PostgreSQL, MySQL, HTTP requests, métricas do Prometheus e muitos outros. Essa flexibilidade é uma das razões pelas quais o KEDA se tornou padrão de facto em clusters Kubernetes independentemente da nuvem.

Azure Container Registry (ACR)

O Azure Container Registry é o equivalente do Amazon ECR. A integração com o AKS é nativa: basta uma azurerm_role_assignment atribuindo o role AcrPull ao kubelet_identity do cluster. Após isso, o Kubernetes faz pull de imagens do ACR sem image pull secrets configurados nos pods.

O SKU Premium do ACR oferece geo-replicação (disponibilidade global das imagens com baixa latência), scan de vulnerabilidades automático ao fazer push, e políticas de retenção de imagens. O az acr build permite fazer build da imagem diretamente no ACR sem precisar de Docker local — o build roda em infraestrutura gerenciada da Microsoft.

GitOps com Flux no AKS

No currículo principal, o GitOps foi implementado com ArgoCD. No ecossistema Azure, a alternativa nativa e igualmente popular é o Flux — o outro projeto de GitOps da CNCF. O AKS oferece Flux como extensão gerenciada, eliminando a necessidade de instalar e manter o Flux manualmente.

A extensão é instalada via azurerm_kubernetes_cluster_extension com extension_type = "microsoft.flux". A configuração do GitOps é declarada com azurerm_kubernetes_flux_configuration, apontando para um repositório Git (URL, branch, intervalo de sync) e definindo Kustomizations — subpaths do repositório para aplicar. É possível declarar dependências entre Kustomizations (apps dependem de infraestrutura estar sincronizada primeiro).

No repositório GitOps, o Flux usa dois recursos principais: HelmRelease para instalar charts Helm (equivalente ao ArgoCD Application com source do tipo Helm) e Kustomization para aplicar manifestos YAML organizados com Kustomize. O prune: true garante que recursos removidos do Git sejam deletados do cluster — equivalente ao syncPolicy.automated.prune do ArgoCD.

Monitoramento com Azure Monitor e KQL

O KQL (Kusto Query Language) é a linguagem de query do Log Analytics Workspace — mais poderosa que o CloudWatch Insights. Queries úteis para operação do AKS incluem: listar pods em estado não-Running nos últimos 30 minutos (KubePodInventory | where PodStatus != "Running"); calcular uso de CPU por namespace com Perf e agregação por bin de tempo; detectar evictions com KubeEvents | where Reason == "Evicted"; e identificar containers com muitas reinicializações com KubePodInventory | where RestartCount > 3.

Referências para Aprofundamento

Exercícios

Exercício 1

O artigo indica network_plugin_mode = "overlay" porque o Azure CNI Overlay "não consome IPs da VNet para cada pod". Relacione isso com o dimensionamento de subnets visto no capstone e explique o trade-off que o modo overlay impõe.

Ver resposta

✓ Resposta: O overlay resolve exatamente o esgotamento de IPs que aparece quando cada pod recebe um endereço da rede virtual.

No CNI tradicional — e no VPC CNI da AWS —, todo pod ocupa um IP real da subnet. Com subnets /24, são 251 endereços por zona, e algumas dezenas de nós já consomem tudo, muito antes de faltar CPU ou memória. E como não se redimensiona uma subnet existente, a correção exige recriar a rede.

No overlay, os pods recebem endereços de um CIDR privado separado, que existe apenas dentro do cluster e não pertence à VNet. Apenas os nós consomem IPs da subnet. O tráfego entre pods de nós diferentes é encapsulado, e a saída para fora do cluster passa por NAT no nó. Uma subnet pequena passa a comportar um cluster grande, e o CIDR de pods pode ser generoso sem custo nenhum, porque não disputa espaço com o resto da rede.

O trade-off é que os pods deixam de ser endereçáveis diretamente a partir da VNet. Isso tem consequências concretas:

  • Um recurso fora do cluster — uma VM, um Application Gateway, um serviço em outra VNet emparelhada — não consegue abrir conexão direta para o IP de um pod. O acesso passa obrigatoriamente por um Service do tipo LoadBalancer ou por um Ingress.
  • Integrações que dependem de rotear para o IP do pod exigem configuração específica — o padrão do Application Gateway Ingress Controller, por exemplo, é enviar tráfego diretamente ao pod.
  • Ferramentas de rede que inspecionam tráfego no nível da VNet passam a ver apenas os IPs dos nós, e não a origem real dentro do cluster.

Para a maioria das arquiteturas — que expõem serviços por Ingress e mantêm a comunicação interna dentro do cluster — nada disso pesa, e o overlay é a escolha padrão recomendada. A decisão é a mesma feita no capstone entre gastar endereçamento ou aceitar uma camada de indireção, com a diferença de que aqui ela pode ser tomada no dia da criação do cluster e não depois.

Exercício 2

Um time escolhe o AKS Free Tier para staging, sabendo que não há SLA do API server. Uma manhã, o control plane fica indisponível por 40 minutos. As aplicações em staging saem do ar? O que exatamente deixa de funcionar?

Ver resposta

✓ Resposta: As aplicações continuam no ar e servindo tráfego normalmente. O que para é a capacidade de mudar ou reagir — não a de servir.

A razão está na arquitetura do Kubernetes. O kubelet de cada nó já tem a especificação dos pods que deve manter rodando, e continua fazendo isso sem consultar ninguém. O kube-proxy mantém as regras de rede localmente, então os Services continuam roteando. O load balancer externo não passa pelo control plane. Um cluster com control plane fora continua atendendo requisições indefinidamente.

O que deixa de funcionar é tudo que depende do API server:

  • Deploys e rollbackskubectl apply e a sincronização do Flux falham; nenhuma mudança entra.
  • Autoscaling — HPA, KEDA e Cluster Autoscaler param de agir. Se o tráfego subir, não há escala.
  • Recuperação automática — este é o mais sério: um pod que morrer não é recriado, e um nó que falhar não tem suas cargas reagendadas. A capacidade só diminui durante a janela.
  • Observaçãokubectl get, logs e exec ficam indisponíveis, justamente quando se quer diagnosticar.

Ou seja, o cluster entra em um estado congelado: estável enquanto nada dá errado, e sem defesas se algo der. É por isso que a escolha do Free Tier é razoável em desenvolvimento e staging — onde uma janela sem deploys é tolerável — e inadequada em produção, onde o valor do control plane está precisamente em reagir ao inesperado.

Vale registrar a diferença de modelo de custo apontada no artigo: o EKS cobra o control plane por cluster e por hora, em qualquer ambiente, enquanto o AKS oferece a opção gratuita sem SLA. Para uma organização com muitos clusters de desenvolvimento e efêmeros, isso muda bastante a conta — e explica por que ambientes por PR são mais comuns no ecossistema Azure.

Exercício 3

Um ScaledObject do KEDA aponta para uma fila do Service Bus com messageCount: "100" e minReplicaCount: 0. A fila acumula 450 mensagens. Quantas réplicas o KEDA cria? E, com zero pods rodando, como ele percebe que há mensagens?

Ver resposta

✓ Resposta: 5 réplicas. O messageCount é o alvo de mensagens por réplica, e o cálculo arredonda para cima: ceil(450 / 100) = 5. Com 400 mensagens seriam 4; com 401, já seriam 5.

Quanto à segunda pergunta — que é o ponto realmente interessante —, quem observa a fila não é o pod, é o operador do KEDA. Ele roda no seu próprio namespace, consulta o Service Bus periodicamente e, ao detectar mensagens com o Deployment em zero, faz a ativação: escala de 0 para 1. A partir daí ele passa a alimentar um HPA gerado automaticamente, que cuida do ajuste fino de 1 até maxReplicaCount.

Essa divisão explica uma limitação importante do Kubernetes: o HPA nativo não consegue escalar a partir de zero. Ele calcula réplicas a partir da utilização média dos pods existentes, e sem nenhum pod não há métrica — a conta seria uma divisão por zero. O KEDA contorna isso assumindo a transição 0↔1 e delegando o resto ao HPA. É a diferença entre escalonamento orientado a recursos (o que meus pods estão consumindo) e orientado a eventos (quanto trabalho está esperando lá fora).

Três cuidados na configuração, que decorrem desse desenho:

  • O intervalo de polling (pollingInterval, 30 s por padrão) define a latência da ativação. Um worker que precisa reagir em segundos exige valor menor — ao custo de mais chamadas à API do Service Bus.
  • O cooldown (cooldownPeriod, 300 s por padrão) é quanto o KEDA espera sem mensagens antes de voltar a zero. Curto demais gera ciclos de sobe-desce em tráfego intermitente, cada um pagando o tempo de inicialização do pod.
  • Com minReplicaCount: 0, a primeira mensagem depois de um período ocioso espera o ciclo de polling mais o start do container. Para processamento em lote é irrelevante; para algo com expectativa de resposta, não.

Note ainda que a TriggerAuthentication com provider: azure-workload resolve um problema real: sem ela, seria preciso guardar uma connection string do Service Bus em um Secret — exatamente a credencial estática de longa duração que o Workload Identity existe para eliminar.

Exercício 4

No Workload Identity, o Federated Identity Credential declara o subject como system:serviceaccount:producao:minha-api. Um desenvolvedor cria, no namespace staging, uma ServiceAccount também chamada minha-api e a anota com o mesmo client-id. Os pods de staging conseguem acessar os recursos de produção?

Ver resposta

✓ Resposta: Não. O subject inclui o namespace, e o token emitido para um pod em staging carrega system:serviceaccount:staging:minha-api — que não corresponde ao credential federado registrado. O Entra ID rejeita a troca de token, e o SDK falha na autenticação.

É exatamente a mesma proteção do IRSA no EKS, onde a trust policy do IAM Role traz uma condição sobre sub com namespace e ServiceAccount. Em ambos os casos, a identidade não é "quem tem a anotação certa", e sim quem apresenta um token assinado pelo cluster afirmando ser aquela ServiceAccount naquele namespace. A anotação apenas informa ao pod qual identidade tentar assumir — copiar a anotação não copia a permissão, e é isso que torna o mecanismo seguro contra cópia de manifesto entre ambientes.

A falha, quando acontece, é confusa para quem não conhece o mecanismo: o pod sobe normalmente, e o erro só aparece na primeira chamada, como AADSTS70021: No matching federated identity record found. A mensagem cita o subject recebido, e comparar essa string com a registrada no credential é o caminho mais rápido de diagnóstico.

Vale contrastar com o modo como isso falha na ausência da configuração correta. No EKS, um pod sem serviceAccountName cai na cadeia de credenciais e acaba usando a identidade do nó, herdando silenciosamente as permissões do node group. No AKS com Workload Identity, faltando o label azure.workload.identity/use: "true" no pod, os webhooks não injetam nada — e o DefaultAzureCredential simplesmente não encontra credencial alguma, falhando de forma explícita. É a diferença entre falhar em aberto e falhar fechado, e o segundo comportamento é preferível.

Para o caso legítimo — a mesma aplicação em dois namespaces —, a solução é registrar dois federated identity credentials na mesma Managed Identity (um por namespace), ou, melhor, uma identidade por ambiente, com permissões distintas: staging acessando o Key Vault de staging, produção o de produção. Compartilhar identidade entre ambientes anula a separação que os namespaces deveriam garantir.

Exercício 5

O cluster é criado com automatic_channel_upgrade = "patch". Que benefício isso traz e que risco introduz para as aplicações? O que precisa existir no cluster para que essa configuração seja segura?

Ver resposta

✓ Resposta: O benefício é receber correções de segurança do Kubernetes automaticamente, sem depender de alguém lembrar de atualizar. O risco é que aplicar um patch significa recriar nós — e a recriação despeja pods, em um momento que o time não escolheu.

O canal patch é o mais conservador dos automáticos: ele avança apenas na terceira casa da versão (de 1.29.2 para 1.29.4, por exemplo), sem nunca mudar a versão minor. Isso evita quebras de API — que é o que costuma doer em upgrades de Kubernetes —, e por isso é uma escolha razoável. Mas o processo em si é disruptivo: os nós são substituídos em lotes, cada um sendo cordoned, drenado e destruído.

O que torna isso seguro é o mesmo par de mecanismos exigido pela consolidação do Karpenter:

  • PodDisruptionBudget em cada workload, limitando quantas réplicas podem ficar indisponíveis ao mesmo tempo. Sem ele, a drenagem de um nó pode remover todas as réplicas de um serviço de uma vez — e como todas estão sendo despejadas "voluntariamente", nada impede.
  • Encerramento gracioso nas aplicações, com tratamento de SIGTERM e terminationGracePeriodSeconds compatível com o trabalho em andamento. Um consumidor de fila sem isso perde ou duplica mensagens a cada upgrade — como no serviço de notificações do capstone.

Duas configurações adicionais controlam quando e quão rápido isso acontece, e ambas valem a pena em produção: a maintenance window (maintenance_window_auto_upgrade), que restringe as atualizações a uma janela definida — madrugada de terça, por exemplo, e não a sexta à tarde —, e o max_surge do node pool, que determina quantos nós extras são criados durante a substituição: um valor maior acelera o upgrade, e um valor menor reduz a pressão simultânea sobre o cluster.

O calibre certo é uma decisão de risco, não de conveniência: não atualizar também é uma escolha, e das piores — um cluster parado em uma versão sem suporte acumula vulnerabilidades conhecidas e, quando finalmente precisa ser atualizado, o salto é grande demais para ser seguro. Automação com PDB e janela de manutenção troca um risco grande e adiado por um risco pequeno e recorrente.

Comentários

Mais em DevOps

Notificações, Logs e Monitoramento do Pipeline
Notificações, Logs e Monitoramento do Pipeline

O pipeline que falha em silêncio e o alerta genérico que ninguém lê…

AIOps na Prática: O Que a IA Já Faz em Operações Hoje
AIOps na Prática: O Que a IA Já Faz em Operações Hoje

Avaliação honesta do AIOps em quatro faixas de maturidade: detecção de…

Geração de Infraestrutura com IA
Geração de Infraestrutura com IA

Geração de Terraform e manifestos Kubernetes por LLM com validação no laço: os…