Operar o plano de controle do Kubernetes — manter o etcd replicado, atualizar os componentes do control plane, garantir sua disponibilidade, monitorar sua saúde — é um trabalho de tempo integral que exige profundo conhecimento do sistema. Para a maioria das organizações, esse esforço operacional não gera vantagem competitiva: o negócio se beneficia de ter aplicações bem orquestradas, não de operar o orquestrador em si.
O Amazon Elastic Kubernetes Service — EKS — resolve esse problema gerenciando o plano de controle da AWS. A AWS garante a disponibilidade do kube-apiserver, do etcd e dos demais componentes do control plane, aplica patches de segurança e disponibiliza novas versões do Kubernetes. O time de engenharia mantém o controle sobre os nós worker, os workloads e a configuração do cluster — sem a responsabilidade de operar o que está abaixo.
O EKS é compatível com todas as ferramentas e manifestos Kubernetes padrão. Um cluster EKS aceita os mesmos YAMLs que rodam em um cluster local criado com kind ou kubeadm — a portabilidade é uma das promessas fundamentais do Kubernetes.
Provisionando um Cluster EKS com Terraform
A criação de um cluster EKS envolve múltiplos componentes: o próprio cluster, os node groups, as configurações de rede e as permissões IAM. O módulo oficial terraform-aws-modules/eks/aws encapsula essa complexidade:
# eks.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.31"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.12"
}
}
}
# Dados do cluster após criação — necessários para configurar providers
data "aws_eks_cluster" "principal" {
name = module.eks.cluster_name
}
data "aws_eks_cluster_auth" "principal" {
name = module.eks.cluster_name
}
# Provider Kubernetes aponta para o cluster EKS
provider "kubernetes" {
host = data.aws_eks_cluster.principal.endpoint
cluster_ca_certificate = base64decode(
data.aws_eks_cluster.principal.certificate_authority[0].data
)
token = data.aws_eks_cluster_auth.principal.token
}
provider "helm" {
kubernetes {
host = data.aws_eks_cluster.principal.endpoint
cluster_ca_certificate = base64decode(
data.aws_eks_cluster.principal.certificate_authority[0].data
)
token = data.aws_eks_cluster_auth.principal.token
}
}
# Módulo EKS — provisiona cluster e node groups
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "${var.project_name}-${var.environment}"
cluster_version = "1.29"
# Rede
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.ids_subnets_privadas
control_plane_subnet_ids = module.vpc.ids_subnets_privadas
# Acesso à API pública — restringe por CIDR
cluster_endpoint_public_access = true
cluster_endpoint_public_access_cidrs = var.eks_public_access_cidrs
cluster_endpoint_private_access = true
# Criptografia dos secrets do etcd com KMS
cluster_encryption_config = {
provider_key_arn = aws_kms_key.eks.arn
resources = ["secrets"]
}
# Add-ons gerenciados pela AWS
cluster_addons = {
coredns = {
most_recent = true
configuration_values = jsonencode({
replicaCount = 2
resources = {
requests = { cpu = "100m", memory = "70Mi" }
limits = { memory = "170Mi" }
}
})
}
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
service_account_role_arn = module.vpc_cni_irsa.iam_role_arn
configuration_values = jsonencode({
enableNetworkPolicy = "true"
})
}
aws-ebs-csi-driver = {
most_recent = true
service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
}
}
# Node Groups gerenciados
eks_managed_node_groups = {
# Node group para cargas de trabalho gerais
geral = {
name = "${var.project_name}-${var.environment}-geral"
instance_types = ["m6i.large", "m6a.large", "m7i.large"]
capacity_type = "ON_DEMAND"
min_size = 2
max_size = 10
desired_size = 3
# Spread entre AZs para resiliência
subnet_ids = module.vpc.ids_subnets_privadas
labels = {
role = "geral"
environment = var.environment
}
taints = []
block_device_mappings = {
xvda = {
device_name = "/dev/xvda"
ebs = {
volume_size = 50
volume_type = "gp3"
iops = 3000
encrypted = true
kms_key_id = aws_kms_key.eks_nodes.arn
delete_on_termination = true
}
}
}
update_config = {
max_unavailable_percentage = 33
}
tags = local.tags_comuns
}
# Node group para workloads com GPU (ML/inferência)
gpu = {
name = "${var.project_name}-${var.environment}-gpu"
instance_types = ["g4dn.xlarge"]
capacity_type = "SPOT"
min_size = 0
max_size = 5
desired_size = 0
labels = {
role = "gpu"
"nvidia.com/gpu" = "true"
}
taints = [{
key = "nvidia.com/gpu"
value = "true"
effect = "NO_SCHEDULE"
}]
tags = local.tags_comuns
}
}
# Acesso ao cluster — administradores
access_entries = {
admin = {
kubernetes_groups = []
principal_arn = "arn:aws:iam::${data.aws_caller_identity.atual.account_id}:role/eks-admin"
policy_associations = {
admin = {
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
access_scope = {
type = "cluster"
}
}
}
}
}
tags = local.tags_comuns
}
# KMS para criptografia dos secrets do etcd
resource "aws_kms_key" "eks" {
description = "KMS key para secrets do cluster EKS"
deletion_window_in_days = 7
enable_key_rotation = true
tags = local.tags_comuns
}
resource "aws_kms_key" "eks_nodes" {
description = "KMS key para volumes EBS dos nós EKS"
deletion_window_in_days = 7
enable_key_rotation = true
tags = local.tags_comuns
}
IRSA: IAM Roles for Service Accounts
O IRSA — IAM Roles for Service Accounts — é o mecanismo que permite que pods no EKS assumam IAM Roles sem precisar de chaves de acesso estáticas. Funciona via OIDC: o cluster EKS age como um provedor de identidade OIDC, emitindo tokens JWT para as service accounts dos pods. A AWS verifica esses tokens e assume a IAM Role correspondente.
# irsa.tf
# OIDC Provider do cluster EKS
data "tls_certificate" "eks" {
url = module.eks.cluster_oidc_issuer_url
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = module.eks.cluster_oidc_issuer_url
tags = local.tags_comuns
}
# IRSA para o VPC CNI — gerencia ENIs para os pods
module "vpc_cni_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "${var.project_name}-${var.environment}-vpc-cni"
attach_vpc_cni_policy = true
vpc_cni_enable_ipv4 = true
oidc_providers = {
main = {
provider_arn = aws_iam_openid_connect_provider.eks.arn
namespace_service_accounts = ["kube-system:aws-node"]
}
}
}
# IRSA para o EBS CSI Driver — gerencia volumes EBS
module "ebs_csi_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "${var.project_name}-${var.environment}-ebs-csi"
attach_ebs_csi_policy = true
oidc_providers = {
main = {
provider_arn = aws_iam_openid_connect_provider.eks.arn
namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
}
}
}
# IRSA para a aplicação — acesso ao S3 e Secrets Manager
module "minha_api_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "${var.project_name}-${var.environment}-minha-api"
oidc_providers = {
main = {
provider_arn = aws_iam_openid_connect_provider.eks.arn
namespace_service_accounts = ["producao:minha-api"]
}
}
}
resource "aws_iam_role_policy" "minha_api" {
name = "permissoes-minha-api"
role = module.minha_api_irsa.iam_role_name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
Resource = "${aws_s3_bucket.assets.arn}/*"
},
{
Effect = "Allow"
Action = ["secretsmanager:GetSecretValue"]
Resource = "arn:aws:secretsmanager:${var.aws_region}:*:secret:${var.project_name}/*"
}
]
})
}
A Service Account que referencia a IAM Role:
# kubernetes/service-account.yml
apiVersion: v1
kind: ServiceAccount
metadata:
name: minha-api
namespace: producao
annotations:
# Referencia a IAM Role via IRSA
eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/minha-api-producao-minha-api
eks.amazonaws.com/token-expiration: "86400"
O Deployment que usa a Service Account:
spec:
template:
spec:
serviceAccountName: minha-api
# Não é necessário configurar credenciais AWS —
# o SDK detecta automaticamente via IRSA
containers:
- name: api
image: ghcr.io/empresa/minha-api:1.5.0
Karpenter: Auto Scaling Inteligente de Nós
O Karpenter é o provisionador de nós recomendado para EKS — mais rápido e eficiente que o Cluster Autoscaler tradicional. Enquanto o Cluster Autoscaler escala node groups inteiros, o Karpenter provisiona nós individualmente com o tipo exato que melhor atende às necessidades dos pods pendentes.
# karpenter.tf
# IRSA para o Karpenter
module "karpenter_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "${var.project_name}-${var.environment}-karpenter"
attach_karpenter_controller_policy = true
karpenter_controller_cluster_name = module.eks.cluster_name
karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["geral"].iam_role_arn]
oidc_providers = {
main = {
provider_arn = aws_iam_openid_connect_provider.eks.arn
namespace_service_accounts = ["kube-system:karpenter"]
}
}
}
# Instala o Karpenter via Helm
resource "helm_release" "karpenter" {
namespace = "kube-system"
name = "karpenter"
repository = "oci://public.ecr.aws/karpenter"
chart = "karpenter"
version = "0.34.0"
create_namespace = false
set {
name = "settings.clusterName"
value = module.eks.cluster_name
}
set {
name = "settings.interruptionQueue"
value = aws_sqs_queue.karpenter.name
}
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = module.karpenter_irsa.iam_role_arn
}
set {
name = "controller.resources.requests.cpu"
value = "100m"
}
set {
name = "controller.resources.requests.memory"
value = "256Mi"
}
set {
name = "controller.resources.limits.memory"
value = "512Mi"
}
}
# Fila SQS para notificações de interrupção de instâncias Spot
resource "aws_sqs_queue" "karpenter" {
name = "${var.project_name}-${var.environment}-karpenter"
message_retention_seconds = 300
sqs_managed_sse_enabled = true
tags = local.tags_comuns
}
Com o Karpenter instalado, configura-se um NodePool — o conjunto de regras que define quais tipos de nós podem ser provisionados:
# kubernetes/karpenter/nodepool.yml
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: geral
spec:
template:
metadata:
labels:
role: geral
spec:
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
name: geral
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["m", "c", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"]
# Expira nós após 720h para forçar renovação com AMIs atualizadas
expireAfter: 720h
limits:
cpu: "200"
memory: "800Gi"
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: geral
spec:
amiFamily: AL2
role: "${var.project_name}-${var.environment}-node"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "${var.project_name}-${var.environment}"
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "${var.project_name}-${var.environment}"
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
iops: 3000
encrypted: true
deleteOnTermination: true
tags:
Name: "${var.project_name}-${var.environment}-karpenter-node"
Environment: "${var.environment}"
Instalando Add-ons Essenciais com Helm
Um cluster EKS em produção precisa de uma série de componentes adicionais instalados via Helm:
# addons.tf
# AWS Load Balancer Controller — cria ALBs a partir de Ingress resources
resource "helm_release" "aws_load_balancer_controller" {
name = "aws-load-balancer-controller"
repository = "https://aws.github.io/eks-charts"
chart = "aws-load-balancer-controller"
namespace = "kube-system"
version = "1.7.1"
set {
name = "clusterName"
value = module.eks.cluster_name
}
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = module.aws_load_balancer_controller_irsa.iam_role_arn
}
set {
name = "replicaCount"
value = "2"
}
}
# External Secrets Operator — sincroniza secrets do Secrets Manager para K8s
resource "helm_release" "external_secrets" {
name = "external-secrets"
repository = "https://charts.external-secrets.io"
chart = "external-secrets"
namespace = "external-secrets"
create_namespace = true
version = "0.9.13"
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = module.external_secrets_irsa.iam_role_arn
}
}
# Metrics Server — necessário para HPA funcionar
resource "helm_release" "metrics_server" {
name = "metrics-server"
repository = "https://kubernetes-sigs.github.io/metrics-server/"
chart = "metrics-server"
namespace = "kube-system"
version = "3.11.0"
}
# Prometheus Stack — monitoramento completo do cluster
resource "helm_release" "prometheus_stack" {
name = "kube-prometheus-stack"
repository = "https://prometheus-community.github.io/helm-charts"
chart = "kube-prometheus-stack"
namespace = "monitoring"
create_namespace = true
version = "56.6.2"
values = [
yamlencode({
grafana = {
enabled = true
adminPassword = var.grafana_password
ingress = {
enabled = true
ingressClassName = "alb"
annotations = {
"alb.ingress.kubernetes.io/scheme" = "internet-facing"
"alb.ingress.kubernetes.io/target-type" = "ip"
}
hosts = ["grafana.${var.domain_name}"]
}
}
prometheus = {
prometheusSpec = {
retention = "15d"
storageSpec = {
volumeClaimTemplate = {
spec = {
storageClassName = "gp3"
accessModes = ["ReadWriteOnce"]
resources = {
requests = { storage = "50Gi" }
}
}
}
}
}
}
alertmanager = {
alertmanagerSpec = {
storage = {
volumeClaimTemplate = {
spec = {
storageClassName = "gp3"
accessModes = ["ReadWriteOnce"]
resources = {
requests = { storage = "10Gi" }
}
}
}
}
}
}
})
]
}
External Secrets: Sincronizando Secrets do AWS Secrets Manager
Com o External Secrets Operator instalado, secrets do AWS Secrets Manager são automaticamente sincronizados como Kubernetes Secrets — sem armazená-los em repositórios Git ou gerenciá-los manualmente:
# kubernetes/external-secrets/cluster-secret-store.yml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets
---
# kubernetes/external-secrets/minha-api-secrets.yml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: minha-api-secrets
namespace: producao
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: minha-api-secrets
creationPolicy: Owner
template:
type: Opaque
data:
- secretKey: database-url
remoteRef:
key: minha-api/producao
property: DATABASE_URL
- secretKey: redis-url
remoteRef:
key: minha-api/producao
property: REDIS_URL
- secretKey: jwt-secret
remoteRef:
key: minha-api/producao
property: JWT_SECRET
Deploy no EKS via GitHub Actions
# .github/workflows/deploy-eks.yml
name: Deploy no EKS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- name: Configura credenciais AWS via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
aws-region: us-east-1
- name: Login no ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Constrói e publica imagem
id: build
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/minha-api:$IMAGE_TAG .
docker push $ECR_REGISTRY/minha-api:$IMAGE_TAG
echo "image=$ECR_REGISTRY/minha-api:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Configura kubectl
run: |
aws eks update-kubeconfig \
--name minha-api-production \
--region us-east-1
- name: Atualiza imagem no Deployment
run: |
kubectl set image deployment/minha-api \
api=${{ steps.build.outputs.image }} \
-n producao
- name: Aguarda rollout completar
run: |
kubectl rollout status deployment/minha-api \
-n producao \
--timeout=300s
- name: Verifica pods saudáveis
run: |
kubectl get pods -n producao -l app=minha-api
READY=$(kubectl get deployment minha-api -n producao \
-o jsonpath='{.status.readyReplicas}')
DESIRED=$(kubectl get deployment minha-api -n producao \
-o jsonpath='{.spec.replicas}')
if [ "$READY" != "$DESIRED" ]; then
echo "ERRO: $READY/$DESIRED pods prontos"
kubectl describe deployment minha-api -n producao
exit 1
fi
echo "Deploy concluído: $READY/$DESIRED pods saudáveis"
StorageClass com EBS CSI Driver
Para workloads stateful — bancos de dados, filas, caches — o EBS CSI Driver provê volumes persistentes via EBS:
# kubernetes/storage/storage-class.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
kmsKeyId: arn:aws:kms:us-east-1:123456789:key/abc-def
O Que Vem a Seguir
O próximo artigo encerra o tema abordando as práticas avançadas de Kubernetes em produção — segurança com Pod Security Standards, GitOps com ArgoCD para deploys declarativos, e estratégias de deploy avançadas como Blue-Green e Canary no contexto do Kubernetes.
Referências para Aprofundamento
Documentação oficial - Amazon EKS Documentation — docs.aws.amazon.com — Documentação completa do EKS, incluindo guias de provisionamento, add-ons gerenciados, IRSA e boas práticas de segurança. - Karpenter Documentation — karpenter.sh — Documentação oficial do Karpenter, incluindo conceitos de NodePool, EC2NodeClass e estratégias de consolidação.
Módulos Terraform - terraform-aws-modules/eks — GitHub — Módulo oficial para provisionamento de clusters EKS, com exemplos completos de configuração para diferentes casos de uso. - terraform-aws-modules/iam — GitHub — Módulo para criação de IAM roles com IRSA, simplificando significativamente a configuração de permissões para workloads EKS.
Ferramentas do ecossistema - External Secrets Operator — external-secrets.io — Documentação do External Secrets Operator com guias de integração para AWS Secrets Manager, HashiCorp Vault e outros provedores. - AWS Load Balancer Controller — kubernetes-sigs.github.io — Documentação do AWS Load Balancer Controller com referência completa de annotations para configuração de ALBs via Ingress.