Cluster Autoscaler no EKS compartilhado, com IRSA

TLDR: Instalar o Cluster Autoscaler no EKS compartilhado usando IRSA (OIDC), para que o managed node group escale entre min=1 e max=3 automaticamente — resolvendo os pods Pending e deixando o OIDC pronto para uma futura migração para Karpenter.

Contexto

Depois de migrar o n8n (staging e produção) para o EKS compartilhado, réplicas de worker e webhook ficaram Pending com Insufficient cpu/memory — 0/1 nodes are available. O cluster tinha um único nó t3.medium.

O node group está declarado com min_nodes = 1 e max_nodes = 3, mas desired_size = min_nodes e nenhum autoscaler instalado. Um managed node group do EKS não escala sozinho: um Cluster Autoscaler (ou Karpenter) precisa observar pods Pending e ajustar a capacidade desejada. Sem isso o cluster nunca passa de 1 nó.

Decisão: instalar o Cluster Autoscaler (mais simples, funciona com o managed node group existente) usando IRSA (OIDC) para as permissões de IAM. IRSA foi escolhido em vez do atalho de anexar permissão à role do nó porque o Karpenter — o autoscaler pretendido no futuro — exige OIDC/IRSA de qualquer forma. Fazendo agora, o provider OIDC já fica pronto e a migração futura é só trocar a role e o release Helm.

Objetivos

  • Cluster Autoscaler rodando, escalando o node group de 1 a 3 conforme pods pendentes.
  • Provider OIDC do EKS criado, mais uma role IAM de menor privilégio para a service account do autoscaler.
  • Node group com as tags de autodescoberta.
  • Nenhum pod do n8n Pending depois do rollout.
  • Provider OIDC reutilizável por uma futura migração para Karpenter.

Fora de escopo

  • Migrar para Karpenter. Esta spec é o alicerce (o OIDC), não a migração.
  • Apply local. Ver as restrições de execução abaixo.

Mudanças

src/modules/kubernetes/providers/aws/main.tf

  • Provider OIDC: data.tls_certificate do issuer do cluster + aws_iam_openid_connect_provider (thumbprint, audience sts.amazonaws.com). ARN e URL expostos como outputs do módulo.
  • Tags de autodescoberta em aws_eks_node_group.this: k8s.io/cluster-autoscaler/enabled = "true" e k8s.io/cluster-autoscaler/${cluster_name} = "owned". Elas precisam estar no ASG subjacente — o EKS não as propaga do node group.
  • Role IRSA: aws_iam_role com trust policy federada ao provider OIDC, escopada a system:serviceaccount:kube-system:cluster-autoscaler, com as ações autoscaling:Describe*, SetDesiredCapacity, TerminateInstanceInAutoScalingGroup, ec2:DescribeLaunchTemplateVersions, ec2:DescribeInstanceTypes e eks:DescribeNodegroup.

src/modules/kubernetes/cluster-autoscaler.tf

Os outros addons (datadog, metrics-server, ingress) são helm_release no topo do módulo, usando o provider helm já cabeado no stack. O Cluster Autoscaler segue o mesmo padrão, com count = var.provider_type == "aws" ? 1 : 0: chart cluster-autoscaler do repo https://kubernetes.github.io/autoscaler, namespace kube-system, autoDiscovery.clusterName, a anotação eks.amazonaws.com/role-arn na service account, balance-similar-node-groups, skip-nodes-with-system-pods=false e image.tag pinado na release de CA correspondente ao minor do Kubernetes. depends_on = [module.aws], para que a role e as tags existam primeiro.

src/bootstrap/aws-account/terraform/main.tf

As ações de provider OIDC já estavam na TerraformPolicy. A role IRSA usa uma inline policy (aws_iam_role_policy), que exige iam:PutRolePolicy, iam:GetRolePolicy e iam:DeleteRolePolicy — adicionadas à statement IAMForEKS.

CI — corrigido junto nesta mudança

O CI do kubernetes compartilhado ainda estava cabeado para o layout antigo: apontava para src/shared/kubernetes/terraform com autenticação do provedor antigo, então nunca conseguiria aplicar o stack de EKS. Recabeado para o padrão do messagebroker: working-directory em src/shared/kubernetes/aws/terraform, secrets via ward exec --, sem os passos de SOPS e de kubeconfig do provedor antigo. infra-pr.yml ganhou um job kubernetes-plan — PRs antes não rodavam plan nenhum de k8s.

Bloqueios encontrados na implementação (corrigidos aqui)

Como o stack de EKS nunca havia aplicado via CI, dois problemas pré-existentes só apareceram quando o plan finalmente rodou:

  1. O EKS se auto-atualizou 1.33 → 1.34 fora do Terraform. A versão divergiu, marcando aws_eks_cluster para update in-place, o que tornava cluster_endpoint known after apply. Corrigido subindo TF_VAR_kubernetes_version para 1.34 no vault, para casar com a realidade.
  2. Os providers kubernetes/helm/kubectl caíam em http://localhost. Eles buscavam host/token nos outputs de module.kubernetes, mas o módulo também contém data sources do Kubernetes, então o host podia ser desconhecido em tempo de plan. O versions.tf foi refeito para buscar endpoint e CA de um data.aws_eks_cluster pelo nome fixo do cluster e o token pelo plugin exec (aws eks get-token) — sem depender de output de módulo e sem ciclo.

Como verificar

  • O job de plan do PR mostra os recursos novos (provider OIDC, role/policy IRSA, tags do node group, release do autoscaler) e nenhum destroy inesperado.
  • Depois do merge, o job de apply fecha verde.
  • kubectl -n kube-system get pods | grep cluster-autoscaler → Running.
  • kubectl -n kube-system logs deploy/cluster-autoscaler → descobre o ASG, sem erro de autenticação.
  • Com pods Pending, um nó novo entra em 2–3 min; kubectl get nodes mostra 2 (até 3) e os pods agendam.
  • Ao remover carga, o autoscaler remove o nó extra respeitando min=1.
  • aws iam list-open-id-connect-providers mostra o provider OIDC do EKS.

Restrições de execução

  • Tudo por Terraform. Nenhuma mutação manual de aws/kubectl/helm. Provider OIDC, role/policy IRSA, tags do node group e o release Helm são todos recursos Terraform declarados. O único passo manual permitido é inspeção read-only para depurar.
  • Aplicado por CI, não localmente. Não rodar terraform apply de estação de trabalho. O stack é aplicado pelo workflow: o PR roda plan, o merge roda apply.
  • Ordenação: o provider OIDC e a role IRSA (módulo) precisam aplicar antes do release Helm (stack), que consome o ARN da role. Um único apply no stack resolve pelo grafo de dependência.

Documentação