DevOps desde Cero: EKS, Corriendo Kubernetes en AWS
Apoya este blog
Si te resulta util este contenido, considera apoyar el blog.
Introduccion
Bienvenido al articulo trece de la serie DevOps desde Cero. En el articulo anterior empaquetamos nuestra API TypeScript como un Helm chart. Ahora es momento de darle a ese chart un hogar real en AWS provisionando un cluster EKS.
Amazon Elastic Kubernetes Service (EKS) es la oferta de Kubernetes gestionado de AWS. Obtenes un control plane de nivel produccion que AWS parchea, escala y mantiene altamente disponible. Vos solo te preocupas por tus workloads y los worker nodes que los corren. Si veniste siguiendo la serie, ya sabes como funciona ECS del articulo ocho. EKS toma un enfoque diferente: en vez de una API propietaria, obtenes Kubernetes estandar, lo que significa que todo lo que aprendiste en los articulos once y doce (fundamentos de Kubernetes y Helm) se aplica directamente.
Si queres ver como se hacia Kubernetes en AWS antes de que EKS se convirtiera en el default, mira From zero to hero with kops and AWS. Ese articulo cubre kops, una herramienta que provisiona clusters autogestionados. EKS se convirtio desde entonces en la opcion principal para la mayoria de los equipos porque te saca de encima la carga de gestionar el control plane vos mismo.
En este articulo vamos a cubrir que es EKS, compararlo con ECS, provisionar un cluster completo con Terraform, explorar las opciones de node groups, configurar IAM Roles for Service Accounts, configurar Karpenter para autoscaling, instalar el AWS Load Balancer Controller, deployear nuestra API TypeScript, y discutir storage y consideraciones de costo. Vamos a meterle.
Que es EKS?
EKS te da un control plane de Kubernetes gestionado. Eso significa que AWS corre el API server, etcd, el scheduler, y el controller manager por vos. Estos componentes corren en multiples availability zones para alta disponibilidad, y AWS se encarga de upgrades, parches y backups.
Tus responsabilidades son:
- Worker nodes: Vos provisionas las instancias EC2 (o Fargate profiles) donde corren tus pods. AWS ofrece managed node groups que automatizan el ciclo de vida de estas instancias, pero vos seguis decidiendo tipos de instancia, tamanos y escalado.
- Networking: EKS se integra con tu VPC. Los pods obtienen direcciones IP de las subnets de tu VPC usando el plugin VPC CNI, lo que significa que son ciudadanos de primera clase en la red.
- Add-ons: Cosas como CoreDNS, kube-proxy, y el VPC CNI se instalan por defecto, pero vos gestionas sus versiones y configuracion.
- Workloads: Todo lo que deployeas, desde Deployments hasta StatefulSets y CronJobs, es tu responsabilidad.
El control plane de EKS cuesta $0.10 por hora (aproximadamente $73 por mes). Ademas de eso pagas por el compute que uses para worker nodes. Esto es importante tenerlo en cuenta cuando discutamos costos mas adelante.
EKS vs ECS: cuando usar cada uno
Tanto EKS como ECS corren containers en AWS, pero resuelven el problema de manera diferente. Asi es como pensar la eleccion:
- EKS es Kubernetes estandar. Si tu equipo ya conoce Kubernetes, si necesitas portabilidad entre nubes, o si estas corriendo arquitecturas complejas de microservicios con operadores custom, service meshes, o scheduling avanzado, EKS es la eleccion correcta. El ecosistema es enorme, y casi todas las herramientas del landscape de CNCF funcionan out of the box.
- ECS es nativo de AWS. Si tus workloads son sencillos, si tu equipo es chico y no quiere aprender Kubernetes, o si queres integracion estrecha con servicios de AWS sin controllers extra, ECS es mas simple y mas barato (sin costo de control plane). El tipo de lanzamiento Fargate significa que no gestionas infraestructura en absoluto.
Una regla practica: si tenes menos de cinco servicios y no tenes requerimiento de multi-cloud, empeza con ECS. Si tenes un equipo de plataforma en crecimiento, necesitas el ecosistema Kubernetes, o planeas correr en multiples proveedores, anda con EKS.
Para esta serie estamos cubriendo ambos porque los equipos reales se encuentran con los dos. Ya deployeaste a ECS en el articulo ocho. Ahora vas a ver como se compara EKS de manera practica.
Prerrequisitos
Antes de empezar, asegurate de tener lo siguiente instalado:
# AWS CLI v2
aws --version
# Terraform
terraform --version
# kubectl
kubectl version --client
# Helm
helm version
# eksctl (opcional pero util para debugging)
eksctl version
Tambien necesitas una cuenta de AWS con permisos para crear VPCs, clusters EKS, roles IAM, e instancias EC2. Si seguiste el articulo seis (AWS desde cero), ya tenes esto configurado.
Provisionando la VPC con Terraform
Los clusters EKS viven dentro de una VPC. La VPC necesita subnets publicas (para load balancers) y subnets privadas (para worker nodes). Empecemos con la base de red.
Crea un nuevo proyecto de Terraform:
mkdir -p eks-cluster/terraform
cd eks-cluster/terraform
Primero, la configuracion del provider y backend:
# providers.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.12"
}
kubectl = {
source = "alx-v/kubectl"
version = "~> 2.1"
}
}
}
provider "aws" {
region = var.region
}
provider "helm" {
kubernetes {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name]
}
}
}
Ahora las variables:
# variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
default = "devops-zero-to-hero"
}
variable "cluster_version" {
description = "Kubernetes version"
type = string
default = "1.31"
}
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
default = "10.0.0.0/16"
}
Y la VPC usando el modulo oficial de AWS:
# vpc.tf
data "aws_availability_zones" "available" {
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
locals {
azs = slice(data.aws_availability_zones.available.names, 0, 3)
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.cluster_name}-vpc"
cidr = var.vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 4, k)]
public_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 48)]
intra_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 52)]
enable_nat_gateway = true
single_nat_gateway = true
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
"karpenter.sh/discovery" = var.cluster_name
}
tags = {
Project = "devops-zero-to-hero"
Environment = "dev"
}
}
Algunas cosas a notar sobre los tags de las subnets:
kubernetes.io/role/elben subnets publicas le dice al AWS Load Balancer Controller donde colocar ALBs con acceso a internet.kubernetes.io/role/internal-elben subnets privadas es para load balancers internos.karpenter.sh/discoveryen subnets privadas permite que Karpenter encuentre subnets para lanzar nodos.
Usamos un solo NAT gateway para mantener los costos bajos en un entorno de dev. En produccion querrias uno por availability zone para redundancia.
Provisionando el cluster EKS
Ahora el evento principal. Vamos a usar el modulo oficial de EKS para Terraform, que envuelve mucha complejidad en una interfaz limpia:
# eks.tf
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = var.cluster_name
cluster_version = var.cluster_version
# Acceso al cluster
cluster_endpoint_public_access = true
# Add-ons del cluster
cluster_addons = {
coredns = {}
eks-pod-identity-agent = {}
kube-proxy = {}
vpc-cni = {}
}
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
# Dar acceso admin al identity de Terraform
enable_cluster_creator_admin_permissions = true
# Managed node groups
eks_managed_node_groups = {
default = {
instance_types = ["t3.medium"]
min_size = 2
max_size = 5
desired_size = 2
labels = {
role = "general"
}
tags = {
"karpenter.sh/discovery" = var.cluster_name
}
}
}
tags = {
Project = "devops-zero-to-hero"
Environment = "dev"
}
}
Esto crea un cluster EKS con un managed node group de dos instancias t3.medium. Desglosemos
lo que esta pasando:
cluster_endpoint_public_access: Hace que la API de Kubernetes sea accesible desde internet. Para produccion podrias restringir esto a bloques CIDR especificos o usar una VPN.cluster_addons: Estos son los add-ons esenciales de EKS. CoreDNS maneja service discovery, kube-proxy gestiona reglas de red, y vpc-cni les da a los pods direcciones IP nativas de la VPC.enable_cluster_creator_admin_permissions: Otorga acceso admin completo al identity de IAM que crea el cluster. Sin esto, podes quedar bloqueado.eks_managed_node_groups: Definimos un node group con auto-scaling entre 2 y 5 nodos.
Node groups: entendiendo tus opciones
EKS te da tres formas de correr tus workloads. Cada una tiene sus trade-offs:
- Managed node groups: AWS maneja el ciclo de vida de las instancias EC2. Vos elegis tipos de instancia y tamanos, y AWS se encarga de provisionar, drenar, y actualizar nodos. Esta es la opcion default para la mayoria de los equipos. El ejemplo de arriba usa managed node groups.
- Self-managed node groups: Vos creas y gestionas las instancias EC2 usando Auto Scaling Groups. Esto te da control total pero mas overhead operacional. Usa esto solo si necesitas AMIs custom, GPUs con drivers especificos, o configuraciones de instancia inusuales.
- Fargate profiles: AWS corre tus pods en compute serverless. Nada de instancias EC2 que gestionar. Cada pod tiene su propia micro-VM aislada. Esto esta genial para batch jobs o workloads con escalado impredecible, pero tiene limitaciones: nada de DaemonSets, nada de persistent volumes respaldados por EBS, y mayor costo por pod comparado con instancias EC2 bien utilizadas.
Para la mayoria de los workloads, empeza con managed node groups. Si necesitas escalado mas sofisticado (que vamos a configurar en un rato), agrega Karpenter encima.
IAM Roles for Service Accounts (IRSA)
Este es uno de los conceptos mas importantes de EKS para entender. Tus pods muchas veces necesitan hablar con servicios de AWS: leer de S3, escribir en DynamoDB, enviar mensajes a SQS. El enfoque viejo era adjuntar politicas IAM al instance profile del nodo, pero eso significa que cada pod en ese nodo obtiene los mismos permisos. Eso es una pesadilla de seguridad.
IRSA resuelve esto permitiendote mapear un ServiceAccount de Kubernetes a un rol IAM especifico. Solo los pods que usan ese ServiceAccount obtienen esos permisos. Asi funciona internamente:
Pod (con anotacion de ServiceAccount)
--> Kubernetes monta un token proyectado
--> AWS STS valida el token via OIDC
--> El pod asume el rol IAM
--> El pod obtiene credenciales temporales de AWS
EKS crea un proveedor OpenID Connect (OIDC) para tu cluster. Cuando un pod arranca, Kubernetes inyecta un token JWT firmado. AWS STS valida este token contra el proveedor OIDC y emite credenciales temporales para el rol IAM mapeado. Sin credenciales de larga duracion, sin permisos compartidos.
Asi es como configuras IRSA para un pod que necesita acceso a S3:
# irsa.tf
# El proveedor OIDC lo crea el modulo EKS automaticamente
# Solo necesitamos crear el rol IAM y la politica
module "s3_reader_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "${var.cluster_name}-s3-reader"
role_policy_arns = {
policy = aws_iam_policy.s3_read.arn
}
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["default:s3-reader"]
}
}
}
resource "aws_iam_policy" "s3_read" {
name = "${var.cluster_name}-s3-read"
description = "Allow reading from the application S3 bucket"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
}
]
})
}
Despues en tu manifiesto de Kubernetes (o en los values de Helm), anotas el ServiceAccount:
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-reader
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/devops-zero-to-hero-s3-reader
Cualquier pod que use este ServiceAccount va a recibir automaticamente credenciales temporales de AWS con el scope de ese rol IAM. Esta es la forma correcta de manejar permisos de AWS en EKS.
Cluster autoscaler vs Karpenter
Cuando tus workloads crecen, necesitas mas nodos. Hay dos opciones principales para autoscaling de nodos en EKS:
- Cluster Autoscaler: El enfoque tradicional de Kubernetes. Observa pods que no se pueden schedulear por recursos insuficientes, y despues agrega nodos de tus node groups existentes. Funciona, pero esta limitado por las configuraciones pre-definidas de tus node groups. Si necesitas una instancia con GPU pero tu node group solo tiene
t3.medium, quedaste.- Karpenter: El provisionador de nodos open-source de AWS. En vez de escalar node groups pre-definidos, Karpenter mira los requerimientos de los pods pendientes y provisiona el tipo de instancia correcto sobre la marcha. Puede mezclar tipos de instancia, usar instancias Spot, y dimensionar los nodos basado en las necesidades reales del workload. Es mas rapido, mas inteligente, y mas costo-efectivo.
Para clusters nuevos, Karpenter es la mejor opcion. Vamos a configurarlo.
Configurando Karpenter con Terraform
Karpenter necesita permisos IAM para lanzar instancias EC2 y gestionar su ciclo de vida. El modulo oficial de Karpenter para Terraform lo hace bastante directo:
# karpenter.tf
module "karpenter" {
source = "terraform-aws-modules/eks/aws//modules/karpenter"
version = "~> 20.0"
cluster_name = module.eks.cluster_name
# Crear el rol IAM para el controller de Karpenter
enable_v1_permissions = true
# Crear el rol IAM del nodo que los nodos provisionados por Karpenter van a usar
node_iam_role_additional_policies = {
AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
tags = {
Project = "devops-zero-to-hero"
Environment = "dev"
}
}
# Instalar Karpenter usando Helm
resource "helm_release" "karpenter" {
namespace = "kube-system"
name = "karpenter"
repository = "oci://public.ecr.aws/karpenter"
chart = "karpenter"
version = "1.1.1"
wait = false
values = [
<<-EOT
serviceAccount:
name: ${module.karpenter.service_account}
settings:
clusterName: ${module.eks.cluster_name}
clusterEndpoint: ${module.eks.cluster_endpoint}
interruptionQueue: ${module.karpenter.queue_name}
EOT
]
}
Despues de instalar Karpenter, necesitas definir un NodePool y un EC2NodeClass que le digan
a Karpenter que tipo de nodos provisionar:
# karpenter-nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r", "t"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["4"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
expireAfter: 720h
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
limits:
cpu: "100"
memory: 200Gi
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: al2023@latest
role: "KarpenterNodeRole-devops-zero-to-hero"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: devops-zero-to-hero
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: devops-zero-to-hero
tags:
Project: devops-zero-to-hero
ManagedBy: karpenter
Aplica los recursos de Karpenter despues de que el cluster este listo:
kubectl apply -f karpenter-nodepool.yaml
Esto es lo que pasa en esta configuracion:
- NodePool: Define restricciones para los nodos. Permitimos instancias tanto on-demand como spot, restringimos a familias de instancias modernas (c, m, r, t con generacion > 4), y ponemos limites de recursos para que Karpenter no levante compute ilimitado.
expireAfter: Los nodos se reciclan despues de 30 dias. Esto asegura que agarren las ultimas AMIs y parches de seguridad.consolidationPolicy: Karpenter consolida workloads activamente. Si los nodos estan vacios o subutilizados, mueve pods y termina los nodos sobrantes para ahorrar costo.- EC2NodeClass: Define configuraciones especificas de AWS como la AMI, el rol IAM, y los selectores de subnets/security groups.
Con Karpenter corriendo, podes bajar tu managed node group a solo uno o dos nodos para workloads de sistema, y dejar que Karpenter maneje todo lo demas dinamicamente.
AWS Load Balancer Controller
Por defecto, los servicios de Kubernetes de tipo LoadBalancer crean Classic Load Balancers en
AWS. Estos estan desactualizados. El AWS Load Balancer Controller reemplaza ese comportamiento
con ALBs modernos (para HTTP/HTTPS) y NLBs (para TCP/UDP).
El controller observa recursos Ingress y anotaciones de Service, y despues crea y configura los load balancers de AWS correspondientes automaticamente. Vamos a instalarlo:
# alb-controller.tf
module "lb_controller_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "${var.cluster_name}-lb-controller"
attach_load_balancer_controller_policy = true
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
}
}
}
resource "helm_release" "aws_lb_controller" {
namespace = "kube-system"
name = "aws-load-balancer-controller"
repository = "https://aws.github.io/eks-charts"
chart = "aws-load-balancer-controller"
version = "1.9.2"
set {
name = "clusterName"
value = module.eks.cluster_name
}
set {
name = "serviceAccount.name"
value = "aws-load-balancer-controller"
}
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = module.lb_controller_irsa.iam_role_arn
}
set {
name = "vpcId"
value = module.vpc.vpc_id
}
}
Fijate como usamos IRSA aca. El Load Balancer Controller necesita permisos para crear ALBs, gestionar target groups, y leer tags de subnets. En vez de darle esos permisos al nodo, creamos un rol IAM dedicado y lo vinculamos al ServiceAccount del controller.
Una vez instalado, podes crear recursos Ingress que provisionen ALBs automaticamente:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: task-api
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/abc-123
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: task-api
port:
number: 3000
El controller lee las anotaciones, crea un ALB en tus subnets publicas, adjunta el certificado ACM para TLS, y rutea trafico a tus pods. Ya no necesitas gestionar load balancers manualmente.
Configurando kubeconfig
Despues de provisionar el cluster, necesitas configurar kubectl para comunicarse con el. La AWS CLI lo hace simple:
# Actualizar tu kubeconfig
aws eks update-kubeconfig --region us-east-1 --name devops-zero-to-hero
# Verificar la conexion
kubectl get nodes
Deberias ver las instancias de tu managed node group:
NAME STATUS ROLES AGE VERSION
ip-10-0-1-42.ec2.internal Ready <none> 5m v1.31.2-eks-7f9249a
ip-10-0-2-87.ec2.internal Ready <none> 5m v1.31.2-eks-7f9249a
Si trabajas con multiples clusters, podes cambiar entre ellos usando contextos:
# Listar todos los contextos
kubectl config get-contexts
# Cambiar a un contexto especifico
kubectl config use-context arn:aws:eks:us-east-1:123456789012:cluster/devops-zero-to-hero
# Renombrar un contexto por conveniencia
kubectl config rename-context \
arn:aws:eks:us-east-1:123456789012:cluster/devops-zero-to-hero \
eks-dev
Deployeando la API TypeScript a EKS
Te acordas del Helm chart que construimos en el articulo doce? Ahora lo ponemos a trabajar. Si tenes tu chart en un registry OCI, el deploy es un solo comando:
# Crear un namespace para la aplicacion
kubectl create namespace task-api
# Instalar el chart
helm install task-api oci://ghcr.io/your-org/charts/task-api \
--version 0.1.0 \
--namespace task-api \
-f values-eks.yaml
Asi se ve el archivo de values especifico para EKS:
# values-eks.yaml
replicaCount: 2
image:
repository: 123456789012.dkr.ecr.us-east-1.amazonaws.com/task-api
tag: "1.0.0"
service:
type: ClusterIP
port: 3000
ingress:
enabled: true
className: alb
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/abc-123
alb.ingress.kubernetes.io/healthcheck-path: /health
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
serviceAccount:
create: true
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/task-api-role
Despues de que el deploy termine, podes verificar que todo esta corriendo:
# Chequear los pods
kubectl get pods -n task-api
NAME READY STATUS RESTARTS AGE
task-api-6d8f9c7b4a-k2m5n 1/1 Running 0 2m
task-api-6d8f9c7b4a-x9p3r 1/1 Running 0 2m
# Chequear el ingress (el ALB tarda un minuto o dos en provisionar)
kubectl get ingress -n task-api
NAME CLASS HOSTS ADDRESS PORTS AGE
task-api alb api.example.com k8s-taskapi-xxxxx.us-east-1.elb.amazonaws.com 80 3m
# Testear el endpoint
curl https://api.example.com/health
{"status": "ok"}
El AWS Load Balancer Controller ve el recurso Ingress, crea un ALB, configura target groups apuntando a las IPs de tus pods, y adjunta el certificado TLS. El trafico fluye desde internet a traves del ALB directamente a tus pods.
Storage: EBS CSI driver
Si tus workloads necesitan storage persistente (bases de datos, caches, subida de archivos), necesitas el EBS CSI driver. Este driver permite que los PersistentVolumes de Kubernetes esten respaldados por volumenes EBS.
Agregalo como add-on de EKS en tu Terraform:
# Agregar a los cluster_addons en eks.tf
cluster_addons = {
coredns = {}
eks-pod-identity-agent = {}
kube-proxy = {}
vpc-cni = {}
aws-ebs-csi-driver = {
service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
}
}
# ebs-csi.tf
module "ebs_csi_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "${var.cluster_name}-ebs-csi"
attach_ebs_csi_policy = true
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
}
}
}
Despues crea un StorageClass y usalo en tus workloads:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-volume
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3
resources:
requests:
storage: 10Gi
El modo de binding WaitForFirstConsumer es importante. Retrasa la creacion del volumen hasta
que un pod realmente lo necesite, asegurando que el volumen se cree en la misma availability
zone que el pod. Sin esto, podes terminar con un volumen en una AZ y un pod que necesita correr
en otra.
Consideraciones de costo
EKS no es barato, especialmente comparado con ECS con Fargate para workloads chicos. Esto es lo que estas pagando:
- Control plane: $0.10/hora ($73/mes). Esto es fijo sin importar cuantos nodos corras.
- Worker nodes: Precios estandar de EC2. Un
t3.medium(2 vCPU, 4 GB) cuesta aproximadamente $30/mes on-demand.- Instancias Spot: Hasta 90% mas baratas que on-demand, pero pueden ser interrumpidas. Karpenter hace facil usar Spot diversificando entre tipos de instancia. Genial para workloads stateless, no recomendado para bases de datos.
- NAT gateway: $32/mes mas transferencia de datos. Este es generalmente el costo solapado que sorprende a la gente. Usa un solo NAT gateway para dev, uno por AZ para produccion.
- Load balancers: Los ALBs cuestan aproximadamente $16/mes mas transferencia de datos. Cada recurso Ingress puede compartir un solo ALB usando IngressGroups para evitar provisionar uno por servicio.
- Transferencia de datos: El trafico inter-AZ cuesta $0.01/GB en cada direccion. La comunicacion pod-a-pod entre AZs se acumula en arquitecturas de microservicios con mucha comunicacion.
Tips para ahorrar costos:
- Usa Karpenter con instancias Spot para workloads stateless. Diversifica entre muchos tipos de instancia para reducir tasas de interrupcion.
- Dimensiona bien tus nodos. Karpenter ayuda aca eligiendo el tipo de instancia optimo para tu mix de workloads.
- Consolida ALBs usando anotaciones de IngressGroup para que multiples servicios compartan un ALB.
- Usa un solo NAT gateway para entornos de no-produccion.
- Pone resource requests y limits en cada pod para que Karpenter pueda empaquetar eficientemente.
- Considera Savings Plans o Reserved Instances para capacidad base que sabes que siempre vas a necesitar.
Un entorno dev EKS minimo (control plane + 2 nodos t3.medium + NAT gateway + ALB) cuesta
aproximadamente $180/mes. Un setup de produccion con mas nodos, NAT multi-AZ, y monitoreo va a
ser significativamente mas. Compara esto con ECS con Fargate donde solo pagas por el compute
que tus containers realmente usan.
Poniendo todo junto
Recorramos el flujo completo de provisionamiento:
# Inicializar Terraform
cd eks-cluster/terraform
terraform init
# Revisar el plan
terraform plan -out=tfplan
# Aplicar (esto tarda 15-20 minutos, mayormente la creacion del cluster EKS)
terraform apply tfplan
# Configurar kubectl
aws eks update-kubeconfig --region us-east-1 --name devops-zero-to-hero
# Verificar el cluster
kubectl get nodes
kubectl get pods -n kube-system
# Aplicar recursos de Karpenter
kubectl apply -f karpenter-nodepool.yaml
# Deployear la aplicacion
kubectl create namespace task-api
helm install task-api oci://ghcr.io/your-org/charts/task-api \
--version 0.1.0 \
--namespace task-api \
-f values-eks.yaml
# Verificar que todo esta corriendo
kubectl get all -n task-api
Despues de unos 20 minutos, vas a tener un cluster EKS completamente funcional con managed node groups, Karpenter para escalado dinamico, el AWS Load Balancer Controller para provisionamiento automatico de ALBs, IRSA para permisos de AWS seguros a nivel pod, y el EBS CSI driver para storage persistente.
Limpieza
Si estas siguiendo y no queres mantener el cluster corriendo, tiralo abajo:
# Eliminar recursos de la aplicacion primero
helm uninstall task-api -n task-api
kubectl delete -f karpenter-nodepool.yaml
# Destruir todo con Terraform
terraform destroy
Siempre elimina los recursos de Kubernetes antes de destruir la infraestructura. Si destruis la VPC mientras los ALBs todavia existen, Terraform se va a colgar esperando que los load balancers se eliminen, y vas a tener que limpiarlos manualmente en la consola de AWS.
Notas finales
EKS te da todo el poder de Kubernetes sin la carga operacional de gestionar el control plane. En este articulo provisionamos un cluster completo con Terraform, configuramos managed node groups para compute base, configuramos Karpenter para autoscaling inteligente, usamos IRSA para permisos de AWS seguros a nivel pod, instalamos el AWS Load Balancer Controller para gestion automatica de ALBs, y deployeamos nuestra API TypeScript del Helm chart que construimos en el articulo anterior.
El trade-off comparado con ECS es complejidad y costo. EKS requiere mas conocimiento de infraestructura, mas partes moviles, y un costo base incluso cuando nada esta corriendo. Pero a cambio obtenes todo el ecosistema de Kubernetes, portabilidad entre nubes, y la capacidad de manejar workloads complejos que serian dificiles de modelar en ECS.
En el proximo articulo vamos a meternos con monitoreo y observabilidad, porque tener un cluster corriendo es solo el comienzo. Necesitas saber que esta pasando adentro.
Espero que te haya resultado util y lo hayas disfrutado! Hasta la proxima!
Errata
Si encontras algun error o tenes alguna sugerencia, por favor mandame un mensaje para que se corrija.
Tambien podes revisar el codigo fuente y los cambios en las fuentes aca
$ Comentarios
Online: 0Por favor inicie sesión para poder escribir comentarios.