Subir um cluster EKS pelo console leva poucos cliques, mas deixa para trás uma VPC sem padrão, IAM roles com nomes aleatórios e nenhum registro do que foi criado. Com Terraform, o mesmo cluster vira código revisável, que você recria igual em outra conta ou região e destrói sem sobras.
Este é o primeiro artigo da série artigos-eks. Aqui montamos a base que os próximos artigos vão reaproveitar: VPC com subnets públicas e privadas, control plane do Amazon EKS com autenticação por Access Entries, um managed node group e os add-ons essenciais. Tudo com módulos reutilizáveis, um por responsabilidade.
Segurança: os comandos deste guia usam o perfil do AWS CLI para autenticar. Nunca coloque access keys no código ou em .tfvars versionado; prefira aws sso login ou um perfil com credenciais temporárias.
Sumário
- Pré-requisitos
- Arquitetura do cluster EKS
- VPC: subnets públicas, privadas e NAT Gateway
- Control plane do cluster EKS
- Managed node group
- Add-ons essenciais
- terraform plan
- Outputs do Terraform
- Acessando o cluster com kubectl
- Custos e limpeza
- Conclusão
- Referências oficiais
Pré-requisitos
Para criar o cluster EKS você vai precisar do AWS CLI v2 autenticado, do Terraform >= 1.5, do kubectl e de uma identidade IAM com permissão para criar VPC, IAM roles e recursos do EKS. O provider usado é o hashicorp/aws ~> 6.0.
aws sts get-caller-identity --query Arn --output text
terraform version
kubectl version --client
Antes de escolher a versão do Kubernetes, consulte quais o EKS suporta e até quando cada uma fica em suporte padrão:
aws eks describe-cluster-versions \
--query 'clusterVersions[].[clusterVersion,versionStatus,endOfStandardSupportDate]' \
--output table
Neste artigo o cluster nasce na 1.34. A versão fica numa variável, então trocar depois é só mudar o valor (o upgrade sem downtime tem um artigo próprio mais adiante na série).
Se o seu Terraform for antigo e o provider reclamar da versão do core, o post Unsupported terraform core version aws mostra como atualizar.
Arquitetura do cluster EKS
O desenho é o mínimo recomendado para um cluster EKS que vai receber aplicações de verdade:
- VPC em 2 Availability Zones, com uma subnet pública e uma privada em cada uma. O EKS exige subnets em pelo menos duas AZs.
- Nós nas subnets privadas, saindo para a internet pelo NAT Gateway (para baixar imagens, por exemplo).
- Subnets públicas reservadas para load balancers internet-facing.
- Control plane gerenciado pela AWS, com endpoint privado sempre ligado e endpoint público restrito ao IP da sua rede.
Cada peça é um módulo separado no repositório: terraform-vpc-modules, terraform-eks-modules, terraform-eks-node-group-modules e terraform-eks-addon-modules. O arquivo do ambiente só compõe os módulos.
VPC: subnets públicas, privadas e NAT Gateway
module "vpc" {
source = "../../terraform-vpc-modules"
name = "vpc-${var.cluster_name}"
cidr_block = var.vpc_cidr
az_count = 2
single_nat_gateway = true
}
Dentro do módulo, cada subnet já sai com a tag que o EKS usa para descobrir onde criar load balancers. Sem essas tags, um Service do tipo LoadBalancer ou um Ingress com ALB não acha subnet nenhuma:
resource "aws_subnet" "public" {
count = var.az_count
vpc_id = aws_vpc.this.id
availability_zone = local.azs[count.index]
cidr_block = cidrsubnet(var.cidr_block, var.subnet_newbits, count.index)
map_public_ip_on_launch = true
tags = merge(var.tags, var.public_subnet_tags, {
Name = "${var.name}-public-${local.azs[count.index]}"
"kubernetes.io/role/elb" = "1"
})
}
resource "aws_subnet" "private" {
count = var.az_count
vpc_id = aws_vpc.this.id
availability_zone = local.azs[count.index]
cidr_block = cidrsubnet(var.cidr_block, var.subnet_newbits, count.index + var.az_count)
tags = merge(var.tags, var.private_subnet_tags, {
Name = "${var.name}-private-${local.azs[count.index]}"
"kubernetes.io/role/internal-elb" = "1"
})
}
Com 10.0.0.0/16 e subnet_newbits = 4, o plan mostra as quatro subnets em /20: 10.0.0.0/20 e 10.0.16.0/20 públicas, 10.0.32.0/20 e 10.0.48.0/20 privadas. São 4.096 endereços por subnet privada. Isso importa no EKS porque, com o VPC CNI padrão, cada pod recebe um IP da subnet; subnet pequena acaba antes da CPU dos nós.
single_nat_gateway = true economiza num lab, mas é um ponto único de falha: se a AZ do NAT cair, os nós da outra AZ ficam sem saída. Em produção, use false para ter um NAT Gateway por AZ.
Control plane do cluster EKS
module "eks" {
source = "../../terraform-eks-modules"
name = var.cluster_name
kubernetes_version = var.kubernetes_version
subnet_ids = module.vpc.private_subnet_ids
public_access_cidrs = var.public_access_cidrs
}
O coração do módulo é o aws_eks_cluster. Três blocos merecem atenção:
resource "aws_eks_cluster" "this" {
name = var.name
version = var.kubernetes_version
role_arn = aws_iam_role.cluster.arn
access_config {
authentication_mode = var.authentication_mode
bootstrap_cluster_creator_admin_permissions = var.bootstrap_cluster_creator_admin_permissions
}
vpc_config {
subnet_ids = var.subnet_ids
endpoint_private_access = true
endpoint_public_access = var.endpoint_public_access
public_access_cidrs = var.public_access_cidrs
}
upgrade_policy {
support_type = var.support_type
}
enabled_cluster_log_types = var.enabled_cluster_log_types
tags = var.tags
depends_on = [aws_iam_role_policy_attachment.cluster]
}
access_config: o modoAPIcontrola o acesso ao cluster por Access Entries, recursos da API do EKS, em vez do antigo ConfigMapaws-auth. Obootstrap_cluster_creator_admin_permissions = truedá admin a quem rodou o apply, para você não ficar trancado fora no primeiro acesso.vpc_config: endpoint privado sempre ligado (os nós falam com a API por dentro da VPC) e endpoint público filtrado porpublic_access_cidrs. Deixar0.0.0.0/0expõe a API do Kubernetes para a internet inteira, protegida só pela autenticação.upgrade_policy: comSTANDARD, quando a versão sai do suporte padrão o EKS atualiza o control plane sozinho. ComEXTENDED, o cluster fica mais 12 meses na versão, mas a hora do control plane passa a custar bem mais.
A variável public_access_cidrs é obrigatória de propósito: o ambiente não aceita rodar sem você informar o IP que pode chamar a API. Descubra o seu com curl -s https://checkip.amazonaws.com e use /32.
Managed node group
module "ng_default" {
source = "../../terraform-eks-node-group-modules"
cluster_name = module.eks.name
name = "default"
subnet_ids = module.vpc.private_subnet_ids
instance_types = ["t3.medium"]
min_size = 1
desired_size = 2
max_size = 3
}
No managed node group, a AWS cuida do Auto Scaling Group, da AMI otimizada e da troca dos nós em upgrades. O módulo cria uma IAM role para os nós com quatro políticas: AmazonEKSWorkerNodePolicy, AmazonEKS_CNI_Policy, AmazonEC2ContainerRegistryPullOnly (só leitura de imagens no ECR) e AmazonSSMManagedInstanceCore, que permite entrar no nó pelo Session Manager sem chave SSH e sem porta 22 aberta.
A AMI padrão do módulo é a AL2023_x86_64_STANDARD (Amazon Linux 2023). O Amazon Linux 2 está em fim de vida, e o EKS deixou de publicar AMIs AL2 otimizadas a partir do Kubernetes 1.33.
Um detalhe que evita dor de cabeça: o módulo ignora mudanças em desired_size depois da criação. Quando um autoscaler (ou você, pelo console) muda a quantidade de nós, o próximo terraform plan não tenta voltar ao número do código.
Se você já criou node groups pelo console, compare com o passo a passo do post Create Node Groups EKS AWS: são os mesmos campos, só que agora versionados.
Add-ons essenciais
module "addons" {
source = "../../terraform-eks-addon-modules"
cluster_name = module.eks.name
kubernetes_version = module.eks.kubernetes_version
addons = {
vpc-cni = {}
coredns = {}
kube-proxy = {}
eks-pod-identity-agent = {}
}
depends_on = [module.ng_default]
}
Instalar vpc-cni, coredns e kube-proxy como add-ons gerenciados faz o EKS cuidar das versões compatíveis com o cluster. Sem versão informada, o módulo consulta a mais recente compatível com aws_eks_addon_version. O eks-pod-identity-agent já entra agora porque a série usa EKS Pod Identity para dar permissões de AWS aos pods.
O depends_on no node group não está ali à toa: o CoreDNS só fica ACTIVE quando existe nó para rodar os pods dele. Sem a dependência, o Terraform pode criar o add-on antes dos nós e esperar até estourar o timeout.
terraform plan
terraform init
terraform validate
terraform plan -var 'public_access_cidrs=["SEU_IP/32"]' -out=tfplan
O código deste artigo foi validado com terraform validate e terraform plan contra uma conta AWS real. O plan fecha em 28 recursos:
# module.vpc.aws_vpc.this will be created
# module.vpc.aws_subnet.private[0] will be created
# module.vpc.aws_nat_gateway.this[0] will be created
# module.eks.aws_eks_cluster.this will be created
# module.ng_default.aws_eks_node_group.this will be created
# module.addons.aws_eks_addon.this["coredns"] will be created
...
Plan: 28 to add, 0 to change, 0 to destroy.
Ao aplicar (terraform apply tfplan), conte com algo entre 10 e 15 minutos: o control plane é a etapa mais demorada, e o node group só começa depois dele.
Outputs do Terraform
O ambiente expõe oito outputs. Eles servem para encaixar o cluster EKS em outras automações (pipeline, outro state, scripts) sem precisar abrir o console. No plan, só os valores que vêm das variáveis já aparecem; o resto fica como (known after apply) até o apply:
Changes to Outputs:
+ addon_versions = {
+ coredns = (known after apply)
+ eks-pod-identity-agent = (known after apply)
+ kube-proxy = (known after apply)
+ vpc-cni = (known after apply)
}
+ cluster_endpoint = (known after apply)
+ cluster_name = "eks-blog-castilho"
+ kubeconfig_command = "aws eks update-kubeconfig --region us-east-1 --name eks-blog-castilho"
+ kubernetes_version = "1.34"
+ node_role_arn = (known after apply)
+ private_subnet_ids = [
+ (known after apply),
+ (known after apply),
]
+ vpc_id = (known after apply)
cluster_nameekubernetes_version: nome e versão do control plane, úteis em pipelines que chamam o AWS CLI ou checam a versão antes de um deploy.cluster_endpoint: URL da API do Kubernetes, para configurar os providerskubernetesehelmem outro state.vpc_ideprivate_subnet_ids: rede do cluster, para criar banco de dados, cache ou VPC endpoints na mesma VPC.node_role_arn: IAM role dos nós, quando outro recurso precisa confiar nela (uma bucket policy, por exemplo).addon_versions: versão que o EKS escolheu para cada add-on, o registro que você vai querer antes de um upgrade.kubeconfig_command: o comando pronto para configurar okubectl.
Depois do apply, leia um output com terraform output. O -raw tira as aspas, e o -json é o formato para scripts:
terraform output # todos
terraform output -raw cluster_endpoint
terraform output -json private_subnet_ids | jq -r '.[]'
Acessando o cluster com kubectl
O output kubeconfig_command já traz o comando:
terraform output -raw kubeconfig_command
# aws eks update-kubeconfig --region us-east-1 --name eks-blog-castilho
aws eks update-kubeconfig --region us-east-1 --name eks-blog-castilho
kubectl get nodes -o wide
kubectl get pods -n kube-system
Como a identidade que rodou o apply ganhou admin pelo bootstrap_cluster_creator_admin_permissions, o kubectl funciona de primeira. Para dar acesso a outras pessoas e times, com permissão por namespace, a série tem um artigo dedicado às Access Entries.
Custos e limpeza
Antes de aplicar, saiba o que entra na conta (valores de referência em us-east-1, confira na página de preços):
- Control plane do EKS: US$ 0,10 por hora em suporte padrão, cerca de US$ 73 por mês, com ou sem pods rodando.
- NAT Gateway: cobrança por hora e por GB processado. É comum ser o segundo maior item da fatura de um lab.
- Nós EC2: dois
t3.mediumsob demanda, mais os volumes EBS.
Para um lab, destrua tudo ao terminar. O destroy remove os add-ons, o node group, o cluster e a VPC na ordem certa:
terraform destroy
Se você tiver criado um Service do tipo LoadBalancer pelo kubectl, apague-o antes do destroy. O load balancer criado pelo Kubernetes não está no state do Terraform e prende as subnets, travando a exclusão da VPC.
Conclusão
Com quatro módulos e um arquivo de ambiente curto, o cluster EKS sai com as decisões que costumam ficar para depois já tomadas: nós em subnet privada, subnets com as tags dos load balancers, endpoint da API restrito, acesso por Access Entries e add-ons gerenciados pelo próprio EKS.
Os próximos artigos da série partem deste mesmo cluster e acrescentam uma peça por vez: node groups com instâncias Spot, controle de acesso por time, permissões de AWS para os pods, armazenamento, Ingress, autoscaling com Karpenter, observabilidade e, por fim, o upgrade de versão sem downtime.
Referências oficiais
- Create an Amazon EKS cluster — AWS Documentation
- VPC and subnet requirements — AWS Documentation
- Managed node groups — AWS Documentation
- aws_eks_cluster — Terraform Registry
- Amazon EKS pricing
Interessado em saber mais sobre esse assunto?