Com managed node groups, um mesmo cluster EKS pode ter nós On-Demand para o que não pode parar e nós Spot para o que aguenta interrupção. A diferença de preço é grande: instâncias Spot costumam sair bem mais baratas que as sob demanda. O preço disso é que a AWS pode pedir a instância de volta com dois minutos de aviso.
Este é o segundo artigo da série artigos-eks. O primeiro montou a VPC, o control plane e um node group único. Aqui o cluster ganha dois node groups com papéis diferentes, e as cargas são separadas com labels, taints e tolerations.
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
- Estratégia: dois managed node groups com papéis diferentes
- Node group On-Demand para o sistema
- Node group Spot com vários tipos de instância
- Labels, taints e tolerations
- Interrupção Spot e update_config
- terraform plan
- Outputs do Terraform
- Custos e limpeza
- Conclusão
- Referências oficiais
Estratégia: dois managed node groups com papéis diferentes
A regra prática é simples: o que mantém o cluster de pé fica em On-Demand, e o que pode ser reiniciado sem dor vai para Spot.
- system (ON_DEMAND): CoreDNS, controladores, bancos dentro do cluster e qualquer serviço com estado ou réplica única.
- spot (SPOT): workers de fila, jobs em lote, ambientes de teste e APIs sem estado com várias réplicas.
O ambiente reaproveita os módulos do primeiro artigo. A VPC, o cluster e os add-ons são os mesmos; o que muda é a chamada do terraform-eks-node-group-modules duas vezes, com parâmetros diferentes. O cluster sobe na versão 1.35.
Node group On-Demand para o sistema
module "ng_system" {
source = "../../terraform-eks-node-group-modules"
cluster_name = module.eks.name
name = "system"
subnet_ids = module.vpc.private_subnet_ids
capacity_type = "ON_DEMAND"
instance_types = ["t3.medium"]
min_size = 2
desired_size = 2
max_size = 3
labels = {
"workload" = "system"
}
}
O mínimo de dois nós garante que o CoreDNS e os controladores tenham onde rodar mesmo durante a troca de um nó. Esse node group não tem taint: qualquer pod sem preferência cai aqui.
Os add-ons dependem só do ng_system. Assim o CoreDNS fica ACTIVE sem esperar o node group Spot, que pode até começar com zero nós.
Node group Spot com vários tipos de instância
module "ng_spot" {
source = "../../terraform-eks-node-group-modules"
cluster_name = module.eks.name
name = "spot"
subnet_ids = module.vpc.private_subnet_ids
capacity_type = "SPOT"
instance_types = var.spot_instance_types
min_size = 0
desired_size = 2
max_size = 6
max_unavailable_percentage = 25
labels = {
"workload" = "spot"
}
taints = [
{
key = "workload"
value = "spot"
effect = "NO_SCHEDULE"
},
]
}
O ponto mais importante de um node group Spot é a lista de tipos de instância. Cada tipo em cada AZ é um “pool” de capacidade, e um pool pode esgotar. Quanto mais pools, menor a chance de o node group ficar sem nó:
variable "spot_instance_types" {
description = "Tipos de instância do node group Spot, todos com o mesmo tamanho"
type = list(string)
default = ["m5.large", "m5a.large", "m6i.large", "m6a.large", "m7i.large"]
}
Todos têm 2 vCPUs e 8 GiB de memória. Misturar tamanhos diferentes no mesmo managed node group atrapalha: o autoscaler calcula a capacidade de um nó pelo primeiro tipo da lista, e os requests dos pods deixam de caber de forma previsível.
Labels, taints e tolerations
Label e taint fazem coisas opostas, e você precisa das duas:
- Taint no node group Spot (
workload=spot:NoSchedule) afasta quem não declarou que aceita Spot. Sem ele, o scheduler coloca qualquer pod lá, inclusive os que não podem ser interrompidos. - Label +
nodeSelectoratrai o pod para o Spot. Só a toleration não basta: ela permite rodar no nó com taint, mas não obriga.
Além do label que definimos, o EKS coloca nos nós de managed node groups o label eks.amazonaws.com/capacityType com o valor ON_DEMAND ou SPOT. Ele também serve como seletor.
Um Deployment tolerante a interrupção fica assim (salve como deployment-spot.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker-relatorios
spec:
replicas: 4
selector:
matchLabels:
app: worker-relatorios
template:
metadata:
labels:
app: worker-relatorios
spec:
nodeSelector:
workload: spot
tolerations:
- key: workload
operator: Equal
value: spot
effect: NoSchedule
terminationGracePeriodSeconds: 60
containers:
- name: worker
image: public.ecr.aws/docker/library/busybox:stable
command: ["sh", "-c", "while true; do echo processando; sleep 30; done"]
resources:
requests:
cpu: 250m
memory: 256Mi
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-relatorios
spec:
minAvailable: 2
selector:
matchLabels:
app: worker-relatorios
Repare que no Terraform o efeito do taint é NO_SCHEDULE (o formato da API do EKS) e no manifesto é NoSchedule (o formato do Kubernetes). É a mesma coisa escrita de dois jeitos.
kubectl apply -f deployment-spot.yaml
kubectl get pods -l app=worker-relatorios -o wide
kubectl get nodes -L workload,eks.amazonaws.com/capacityType
Interrupção Spot e update_config
Quando a AWS precisa da capacidade de volta, a instância Spot recebe um aviso de interrupção com dois minutos de antecedência. Nos managed node groups, o EKS liga o Capacity Rebalancing no Auto Scaling Group: ao receber a recomendação de rebalanceamento, ele tenta subir um nó substituto e drena o nó em risco antes da interrupção.
O drenar só é suave se a aplicação colaborar:
- Várias réplicas espalhadas por nós diferentes. Uma réplica só em Spot é indisponibilidade garantida.
- PodDisruptionBudget, como o do exemplo, para que o drain não derrube todas as réplicas ao mesmo tempo.
terminationGracePeriodSecondscurto e tratamento deSIGTERMpara terminar o trabalho em andamento dentro da janela.
O max_unavailable_percentage = 25 controla outra situação: updates do próprio node group, como mudar a versão do Kubernetes ou a AMI. Com 25%, o EKS troca os nós aos poucos, respeitando os PodDisruptionBudgets. O plan confirma o valor no node group Spot:
+ taint {
+ effect = "NO_SCHEDULE"
+ key = "workload"
+ value = "spot"
}
+ update_config {
+ max_unavailable_percentage = 25
}
Se você já viu PVC travado em Terminating depois de trocar nós, o post Disks pvc status is terminating eks aws mostra como destravar. É mais um motivo para deixar cargas com volume persistente no node group On-Demand.
terraform plan
terraform init
terraform validate
terraform plan -var 'public_access_cidrs=["SEU_IP/32"]' -out=tfplan
O código foi validado com terraform validate e terraform plan contra uma conta AWS real, sem apply. São 34 recursos: os 28 do primeiro artigo mais o segundo node group, com IAM role própria e as quatro políticas anexadas.
# module.ng_system.aws_eks_node_group.this will be created
# module.ng_spot.aws_eks_node_group.this will be created
# module.ng_spot.aws_iam_role.node will be created
...
Plan: 34 to add, 0 to change, 0 to destroy.
Outputs do Terraform
O ambiente tem três outputs. Como não houve apply, os valores abaixo são os que o próprio plan mostrou no bloco Changes to Outputs:
Changes to Outputs:
+ cluster_name = "eks-blog-castilho"
+ kubeconfig_command = "aws eks update-kubeconfig --region us-east-1 --name eks-blog-castilho"
+ node_groups = {
+ spot = "spot"
+ system = "system"
}
cluster_name: nome do cluster EKS. Útil para scripts e para outros comandos do AWS CLI, comoaws eks describe-cluster --name $(terraform output -raw cluster_name).node_groups: nomes dos dois node groups. Use para consultar o estado de cada um, por exemploaws eks describe-nodegroup --cluster-name eks-blog-castilho --nodegroup-name spot.kubeconfig_command: o comando pronto para configurar okubectl. Rode com$(terraform output -raw kubeconfig_command).
Custos e limpeza
O control plane e o NAT Gateway custam o mesmo do primeiro artigo. A diferença está nos nós: dois t3.medium sob demanda no node group do sistema e até seis instâncias Spot, cobradas pelo preço Spot do momento. Com min_size = 0, o node group Spot pode ser reduzido a zero fora do horário de uso.
kubectl delete -f deployment-spot.yaml
terraform destroy
Conclusão
Dois managed node groups resolvem a maior parte dos casos de Spot no EKS sem ferramenta extra: On-Demand para o que sustenta o cluster, Spot com vários tipos de instância para o resto, e taint mais label para que cada pod caia no lugar certo. O restante é disciplina na aplicação: réplicas, PodDisruptionBudget e encerramento limpo.
Quando a lista de tipos e tamanhos começar a crescer demais, o próximo passo natural é deixar que o Karpenter escolha as instâncias. Esse tema tem um artigo próprio mais adiante na série.
Referências oficiais
- Managed node groups — AWS Documentation
- Prevent pods from being scheduled on specific nodes (taints) — AWS Documentation
- Spot Instance interruptions — AWS Documentation
- aws_eks_node_group — Terraform Registry
Interessado em saber mais sobre esse assunto?