La lección anterior terminó con una pregunta que alguien del equipo iba a hacer esta misma semana: todo el mundo habla de Kubernetes. Es el estándar de facto de la orquestación de contenedores, tiene un ecosistema enorme, funciona igual en cualquier nube y hay mucha gente que sabe usarlo. Si MercadoFresco acaba de mover toda su tienda a contenedores y se ha quedado en ECS, ¿se está equivocando?
Esta lección responde con datos. Verás qué es Kubernetes de verdad y qué resuelve que ECS no resuelve, qué parte gestiona Amazon EKS y qué sigue siendo trabajo tuyo, cómo se despliega la tienda de MercadoFresco con manifiestos completos, y cuál es el coste que casi nunca aparece en las comparativas: el trabajo recurrente de las actualizaciones de versión. Al final, la decisión razonada de MercadoFresco y los criterios objetivos que la cambiarían. Y con ella, el cierre del módulo y el salto al módulo 11.
Aviso de coste. El plano de control de EKS cuesta 0,10 USD por hora y clúster, unos 73 USD al mes, independientemente del tamaño. Con soporte extendido —cuando la versión de Kubernetes sale del soporte estándar— pasa a 0,60 USD por hora, unos 438 USD al mes, y es un cargo automático que sorprende a mucha gente. A eso se suman los nodos (EC2 o Fargate), el balanceador y los complementos. Un clúster de pruebas olvidado es de los residuos más caros del curso: sigue la limpieza del final. Datos e identificadores ficticios.
Contenido
- Qué es Kubernetes y qué resuelve que ECS no
- Vocabulario mínimo pero real
- El modelo declarativo y el bucle de reconciliación
- Qué gestiona EKS y qué sigue siendo tuyo
- Opciones de cómputo: nodos, Fargate y Auto Mode
- Crear un clúster con
eksctly con CDK kubectly el acceso al clúster- Autenticación: entradas de acceso frente a
aws-auth - IRSA y EKS Pod Identity: permisos de AWS para un pod
- Los manifiestos de la tienda de MercadoFresco
- Ingress y el AWS Load Balancer Controller
- Autoescalado: HPA, Cluster Autoscaler y Karpenter
- Complementos: por qué un EKS vacío no sirve para nada
- Actualizaciones de versión: el coste oculto
- Observabilidad: Container Insights, Prometheus y Grafana
- ECS frente a EKS: la comparativa honesta
- La decisión de MercadoFresco y qué la cambiaría
- Coste y limpieza
- Errores comunes y consejos
- Ejercicios
- Cierre del módulo y transición al módulo 11
Qué es Kubernetes y qué resuelve que ECS no
Kubernetes es un orquestador de contenedores de código abierto, nacido en Google y donado a la Cloud Native Computing Foundation. Hace lo mismo que ECS —mantener contenedores en ejecución, repartirlos entre máquinas, reponerlos, exponerlos por red— con una diferencia fundamental de diseño: Kubernetes es una plataforma extensible con una API propia, no un servicio de un proveedor.
Esa diferencia produce tres capacidades que ECS no tiene:
- Portabilidad real. Un manifiesto de Kubernetes funciona en EKS, en Google Kubernetes Engine, en Azure Kubernetes Service, en un clúster propio y en el portátil de Luis con
kind. Una definición de tarea de ECS solo funciona en AWS. Para MercadoFresco esto no es urgente hoy, pero es el argumento con más peso a favor de EKS. - Un ecosistema que resuelve problemas que tú no quieres resolver. Existen miles de componentes listos para instalar: gestión de certificados (
cert-manager), despliegues progresivos (Argo Rollouts, Flagger), GitOps (Argo CD, Flux), mallas de servicio (Istio, Linkerd), operadores que gestionan bases de datos, colas o motores de búsqueda como si fueran recursos nativos. En ECS, casi todo eso lo pone AWS o lo escribes tú. - Extensibilidad mediante la API. Con CRD (Custom Resource Definitions) y operadores se pueden crear tipos de recurso propios. Es lo que permite escribir
kubectl applysobre un fichero que declara «quiero un clúster de Kafka con tres réplicas» y que un operador lo construya y lo mantenga. ECS no tiene equivalente.
Y produce un coste que hay que nombrar con la misma claridad: Kubernetes es sustancialmente más complejo. Hay más conceptos, más piezas que instalar, más configuración que puede estar mal y más versiones que actualizar. La pregunta correcta no es «¿cuál es mejor?» sino «¿el problema que tengo justifica esa complejidad?».
Vocabulario mínimo pero real
Estos son los objetos que hay que conocer para leer un manifiesto sin perderse. La columna de la derecha ancla cada uno a lo que ya sabes de los módulos anteriores.
| Objeto | Qué es | Equivalente aproximado |
|---|---|---|
| Clúster | El conjunto: plano de control más nodos | Clúster de ECS |
| Nodo (node) | Una máquina que ejecuta cargas; suele ser una instancia EC2 | Instancia del clúster de ECS |
| Pod | La unidad mínima: uno o varios contenedores que comparten red y almacenamiento | Tarea de ECS |
| ReplicaSet | Garantiza que hay N pods iguales | La parte del servicio de ECS que repone |
| Deployment | Gestiona ReplicaSets y orquesta actualizaciones progresivas | Servicio de ECS |
| Service | Nombre e IP estables para un conjunto de pods | Cloud Map o ECS Service Connect |
| Ingress | Enrutado HTTP externo hacia Services | Reglas de un ALB (03-03) |
| Namespace | Partición lógica dentro del clúster | Agrupación por proyecto o entorno |
| ConfigMap | Configuración no sensible como pares clave-valor | environment de la definición de tarea |
| Secret | Configuración sensible (codificada en base64, no cifrada por omisión) | secrets desde Secrets Manager |
| DaemonSet | Un pod por nodo | No existe en Fargate; sí en ECS sobre EC2 |
| Job / CronJob | Tarea que termina, puntual o programada | run-task / EventBridge Scheduler |
Dos advertencias sobre esa tabla, porque son fuente constante de errores:
- Un
Secretde Kubernetes no está cifrado por omisión. Está codificado en base64, que no es cifrado: cualquiera con permiso de lectura sobre el namespace lo ve en claro. Se resuelve con cifrado en etcd mediante KMS y, mejor aún, con el controlador CSI de secretos que trae los valores desde Secrets Manager (más abajo). - El
Deploymentno es el servicio y elServiceno es el Deployment. El Deployment gestiona los pods; el Service les da un nombre estable. En ECS, ambas cosas están dentro del objeto «servicio», y esa fusión es una de las razones por las que ECS es más simple y menos flexible.
El modelo declarativo y el bucle de reconciliación
Kubernetes no ejecuta órdenes: compara estados. Tú declaras el estado deseado y un conjunto de controladores trabaja continuamente para que el estado real coincida.
graph LR
U["kubectl apply -f<br/>manifiesto.yaml"] --> API["Servidor de API<br/>plano de control"]
API --> ETCD[("etcd<br/>estado deseado")]
API --> CM["Controladores<br/>(Deployment, ReplicaSet...)"]
CM -->|"compara deseado<br/>con real"| API
CM --> SCH["Planificador<br/>elige nodo"]
SCH --> KL["kubelet en el nodo"]
KL --> POD["Pods en ejecucion"]
POD -->|"estado real"| API
API -.->|"diferencia detectada"| CM
La consecuencia práctica es que borrar un pod a mano no sirve de nada: el ReplicaSet detecta que faltan réplicas y crea otro en segundos. Es la misma lógica del desiredCount de ECS, generalizada a todos los objetos del sistema y expuesta como una API que cualquiera puede extender.
Y la consecuencia cultural es más importante: en Kubernetes el manifiesto es la infraestructura. Esto encaja de forma natural con lo aprendido en el módulo 9 y lleva a GitOps: un repositorio Git es la fuente de verdad y un agente en el clúster (Argo CD o Flux) aplica lo que haya en él, revirtiendo cualquier cambio manual. Es una forma de trabajar más rigurosa que la del módulo 8, y también una pieza más que instalar, actualizar y operar.
Qué gestiona EKS y qué sigue siendo tuyo
Amazon EKS es Kubernetes gestionado: AWS opera el plano de control y tú operas todo lo demás. La lista concreta importa porque casi todas las decepciones con EKS vienen de esperar más de lo que ofrece.
| AWS gestiona | Detalle |
|---|---|
| El plano de control | Servidor de API, planificador, controladores y etcd, replicados en tres AZ |
| Su disponibilidad y escalado | Se dimensiona solo; no hay maestros que administrar |
Las copias de etcd |
Automáticas; no las ves ni las gestionas |
| El proceso de actualización de versión | Un botón o un comando, con validaciones previas |
| La integración con IAM | Autenticación de usuarios y roles de AWS contra el clúster |
| Los complementos gestionados | VPC CNI, CoreDNS, kube-proxy, EBS CSI y otros, con versiones compatibles |
| Sigue siendo tuyo | Detalle |
|---|---|
| Los nodos | Su tipo, su AMI, su parcheo y su reemplazo (salvo Fargate o Auto Mode) |
| Decidir cuándo actualizar | Y probarlo, y ejecutarlo, tres o cuatro veces al año |
| La compatibilidad de tus manifiestos | Las API de Kubernetes se retiran entre versiones y rompen cosas |
| Todo el software del clúster | Controlador de balanceadores, autoescalador, controlador de secretos, métricas |
| La red | VPC, subredes, grupos de seguridad, asignación de IP a pods |
| La seguridad dentro del clúster | RBAC, políticas de red, políticas de seguridad de pods |
| La observabilidad | Métricas, registros y trazas, igual que en ECS |
La frase que hay que retener: EKS gestiona el plano de control, no el clúster. Un clúster de EKS recién creado no puede desplegar nada útil: le falta el controlador de balanceadores, el autoescalador, el controlador de secretos y las métricas. Esa distancia entre «clúster creado» y «clúster operativo» es donde vive la mayor parte del trabajo.
Opciones de cómputo: nodos, Fargate y Auto Mode
Los pods tienen que ejecutarse en algún sitio, y EKS ofrece cuatro modelos.
| Opción | Quién gestiona los nodos | Parcheo | Escalado | Cuándo tiene sentido |
|---|---|---|---|---|
| Grupos de nodos gestionados | AWS crea y sustituye; tú lanzas la actualización | Guiado por AWS, sin corte si lo configuras | Cluster Autoscaler o Karpenter | El caso general |
| Nodos autogestionados | Tú, con un ASG propio | Todo tuyo | Tuyo | AMI personalizada, requisitos muy concretos |
| EKS sobre Fargate | Nadie: no hay nodos | Ninguno | Por pod | Cargas aisladas, sin DaemonSet |
| EKS Auto Mode | AWS gestiona nodos, escalado y parches | AWS, con reciclado automático | Integrado, basado en Karpenter | Quien quiere EKS con la operación mínima |
EKS sobre Fargate funciona con perfiles de Fargate: se declara qué namespaces o etiquetas hacen que un pod se ejecute sin nodo. Tiene limitaciones importantes que hay que conocer antes de contar con él: no admite DaemonSet, ni almacenamiento persistente distinto de EFS, ni hostPort, ni pods privilegiados, y un pod por tarea de Fargate con el redondeo de recursos que eso implica. La consecuencia práctica es que muchos complementos —que se instalan como DaemonSet— no pueden ejecutarse sobre Fargate, así que casi siempre hace falta al menos un grupo de nodos pequeño para ellos.
EKS Auto Mode es la respuesta de AWS al argumento de esta lección: gestiona los nodos, el escalado, el parcheo y varios complementos esenciales, reciclando los nodos automáticamente cada cierto tiempo. Reduce mucho el trabajo operativo a cambio de un recargo sobre el precio del cómputo y de menos control. Lo que no elimina es la parte que más pesa: sigues decidiendo y ejecutando las actualizaciones de versión de Kubernetes y sigues siendo responsable de que tus manifiestos sean compatibles.
Crear un clúster con eksctl y con CDK
eksctl es la herramienta oficial de la comunidad y la vía más rápida. Es declarativa: se le da un fichero y construye la VPC —o usa la tuya—, el plano de control, los grupos de nodos y los complementos.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: eks-mercadofresco
region: eu-west-1
version: "1.31"
tags:
Proyecto: mercadofresco
Entorno: desarrollo
Componente: orquestacion
Propietario: plataforma
CentroCoste: tecnologia
vpc:
id: vpc-mercadofresco # se reutiliza la VPC del modulo 3
subnets:
private:
eu-west-1a: { id: snet-mercadofresco-app-a }
eu-west-1b: { id: snet-mercadofresco-app-b }
public:
eu-west-1a: { id: snet-mercadofresco-publica-a }
eu-west-1b: { id: snet-mercadofresco-publica-b }
clusterEndpoints:
publicAccess: true # restringido por CIDR mas abajo
privateAccess: true
publicAccessCIDRs: ["203.0.113.0/24"] # solo la red corporativa
managedNodeGroups:
- name: ng-mercadofresco-general
instanceTypes: ["m6g.large"] # Graviton, como en 10-02
amiFamily: AmazonLinux2023
minSize: 2
maxSize: 8
desiredCapacity: 3
privateNetworking: true # nodos solo en subredes privadas
volumeSize: 50
volumeType: gp3
labels: { rol: general }
updateConfig: { maxUnavailablePercentage: 25 }
addons:
- name: vpc-cni
version: latest
configurationValues: '{"env":{"ENABLE_PREFIX_DELEGATION":"true"}}'
- name: coredns
- name: kube-proxy
- name: aws-ebs-csi-driver
- name: eks-pod-identity-agent
iam:
withOIDC: true # imprescindible para IRSA
serviceAccounts:
- metadata: { name: aws-load-balancer-controller, namespace: kube-system }
wellKnownPolicies: { awsLoadBalancerController: true }
cloudWatch:
clusterLogging:
enableTypes: ["api", "audit", "authenticator"]Cuatro decisiones de ese fichero que merecen atención:
publicAccessCIDRsrestringido. Por omisión el endpoint del servidor de API es accesible desde cualquier IP de internet —protegido por autenticación, pero expuesto—. Restringirlo a la red corporativa o pasar a solo acceso privado es una de las primeras cosas que revisa una auditoría.privateNetworking: true. Los nodos en subredes privadas, coherente con todo lo que hizo MercadoFresco en el módulo 3 y con lo aprendido en 10-02 sobre endpoints de VPC. Los mismos endpoints de la lección anterior son necesarios aquí.withOIDC: true. Sin el proveedor OIDC no hay IRSA, y sin IRSA la única forma de dar permisos de AWS a un pod es el rol del nodo, que es un mal grave: todos los pods de ese nodo heredarían esos permisos.ENABLE_PREFIX_DELEGATION. Sin esto, cada pod consume una IP secundaria de la ENI del nodo y el límite de pods por nodo se alcanza mucho antes de agotar CPU o memoria. Es el equivalente en EKS delawsvpcTrunkingde 10-01, y sorprende igual.
En CDK (09-02), el equivalente es más corto pero exige entender lo mismo:
const cluster = new eks.Cluster(this, 'MercadoFrescoEks', {
clusterName: 'eks-mercadofresco',
version: eks.KubernetesVersion.V1_31,
kubectlLayer: new KubectlV31Layer(this, 'CapaKubectl'),
vpc,
vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }],
defaultCapacity: 0, // los nodos se declaran aparte
authenticationMode: eks.AuthenticationMode.API, // entradas de acceso, sin aws-auth
clusterLogging: [eks.ClusterLoggingTypes.API, eks.ClusterLoggingTypes.AUDIT],
});
cluster.addNodegroupCapacity('General', {
instanceTypes: [new ec2.InstanceType('m6g.large')],
amiType: eks.NodegroupAmiType.AL2023_ARM_64_STANDARD,
minSize: 2, maxSize: 8, desiredSize: 3,
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});
// Los manifiestos tambien son infraestructura: se despliegan con la pila.
cluster.addHelmChart('LoadBalancerController', {
chart: 'aws-load-balancer-controller',
repository: 'https://aws.github.io/eks-charts',
namespace: 'kube-system',
values: { clusterName: cluster.clusterName, serviceAccount: { create: false, name: 'aws-load-balancer-controller' } },
});kubectl y el acceso al clúster
kubectl es el cliente. Se configura con un comando que escribe el contexto en ~/.kube/config usando las credenciales de AWS actuales:
aws eks update-kubeconfig --name eks-mercadofresco --region eu-west-1
kubectl get nodes # los 3 nodos en Ready
kubectl get pods -A # los pods del sistema: coredns, aws-node, kube-proxy
kubectl config current-context # verificar SIEMPRE contra que cluster apuntasEl último comando parece trivial y no lo es: kubectl apunta al último clúster configurado, y kubectl delete deployment tienda no pregunta en qué entorno estás. Es el cdk destroy --all de 09-02 con otra forma. La disciplina que lo evita es doble: cuentas separadas por entorno (09-04), de modo que las credenciales de desarrollo no puedan tocar el clúster de producción, y un indicador del contexto activo en el prompt del terminal.
Autenticación: entradas de acceso frente a aws-auth
Kubernetes tiene su propio sistema de permisos, RBAC, con Role, ClusterRole, RoleBinding y ClusterRoleBinding. EKS conecta ese sistema con IAM: IAM dice quién eres, RBAC dice qué puedes hacer dentro del clúster.
Históricamente esa conexión se hacía con un ConfigMap llamado aws-auth, y era una fuente notoria de incidentes: un error de sintaxis al editarlo podía dejar a todo el mundo fuera del clúster de forma irreversible, sin más salida que recrearlo. Hoy la forma correcta son las entradas de acceso (access entries), que son API de AWS: se crean con la CLI o con CDK, se auditan en CloudTrail y no dependen de editar YAML a mano.
# El equipo de plataforma, administrador del cluster
aws eks create-access-entry --cluster-name eks-mercadofresco \
--principal-arn arn:aws:iam::333344445555:role/AWSReservedSSO_AdministracionPlataforma_abc \
--type STANDARD
aws eks associate-access-policy --cluster-name eks-mercadofresco \
--principal-arn arn:aws:iam::333344445555:role/AWSReservedSSO_AdministracionPlataforma_abc \
--access-scope type=cluster \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
# Los desarrolladores, solo en su namespace y sin poder borrar el cluster
aws eks associate-access-policy --cluster-name eks-mercadofresco \
--principal-arn arn:aws:iam::333344445555:role/AWSReservedSSO_DesarrolloCompleto_def \
--access-scope type=namespace,namespaces=mercadofresco \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicyLos conjuntos de permisos son los de 09-04: AdministracionPlataforma, DesarrolloCompleto y AnalisisDatos. La correspondencia queda limpia: el mismo mecanismo de identidad que gobierna las cinco cuentas gobierna también el clúster, sin usuarios de Kubernetes paralelos ni credenciales aparte. Es una de las ventajas reales de EKS frente a un Kubernetes autogestionado.
IRSA y EKS Pod Identity: permisos de AWS para un pod
El equivalente exacto del rol de tarea de ECS (10-01). Un pod de la tienda necesita enviar a cola-mercadofresco-pedidos y leer mercadofresco-carritos, y necesita hacerlo con permisos propios, no con los del nodo.
| Mecanismo | Cómo funciona | Ventajas | Inconvenientes |
|---|---|---|---|
| Rol del nodo | Todos los pods heredan el rol de la instancia | Cero configuración | Inaceptable: cualquier pod hace todo lo que hace cualquier otro |
| IRSA (roles para cuentas de servicio) | Proveedor OIDC del clúster + política de confianza que lo referencia | Maduro, funciona en todas partes | Configuración por rol algo laboriosa, ligada a un clúster |
| EKS Pod Identity | Un agente en el nodo y una asociación entre cuenta de servicio y rol | Más simple, reutilizable entre clústeres, sin OIDC | Requiere el complemento; no aplica a EKS sobre Fargate |
Con Pod Identity, que es la opción recomendada hoy:
aws eks create-pod-identity-association --cluster-name eks-mercadofresco \
--namespace mercadofresco --service-account sa-tienda \
--role-arn arn:aws:iam::333344445555:role/rol-pod-mercadofresco-tiendaY la política de confianza del rol, que es lo que cambia respecto a un rol normal:
{"Version": "2012-10-17", "Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]}La política de permisos es literalmente la misma que la del rol de tarea de ECS en 10-01: sqs:SendMessage sobre la cola, dynamodb:*Item sobre la tabla de carritos y s3:GetObject sobre las fotos del catálogo. El mismo principio de mínimo privilegio, distinta forma de anclarlo a la carga.
Los manifiestos de la tienda de MercadoFresco
Aquí está lo que en ECS era una definición de tarea más un servicio. Empezamos por el Namespace, la ServiceAccount y el ConfigMap:
apiVersion: v1
kind: Namespace
metadata:
name: mercadofresco
labels: { proyecto: mercadofresco, entorno: desarrollo }
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa-tienda
namespace: mercadofresco
# Sin anotaciones: con Pod Identity la asociacion se hace desde la API de AWS.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cfg-tienda
namespace: mercadofresco
data:
ENTORNO: "desarrollo"
COLA_PEDIDOS: "cola-mercadofresco-pedidos"
TABLA_CARRITOS: "mercadofresco-carritos"El Deployment, que es el corazón del despliegue:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tienda
namespace: mercadofresco
labels: { app: tienda, proyecto: mercadofresco }
spec:
replicas: 3
revisionHistoryLimit: 5 # cuantas revisiones se pueden revertir
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # equivale a minimumHealthyPercent 100 de ECS
maxSurge: 2 # equivale a maximumPercent 200
selector:
matchLabels: { app: tienda }
template:
metadata:
labels: { app: tienda, proyecto: mercadofresco }
spec:
serviceAccountName: sa-tienda
# Reparte los pods entre AZ: el equivalente de spread por AZ en ECS.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels: { app: tienda }
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
terminationGracePeriodSeconds: 30 # equivale a stopTimeout de ECS
containers:
- name: tienda
image: 555566667777.dkr.ecr.eu-west-1.amazonaws.com/mercadofresco/tienda@sha256:9c1e...f4a2
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef: { name: cfg-tienda }
env:
- name: BD_CONTRASENA
valueFrom:
secretKeyRef: { name: sec-tienda-rds, key: password }
resources:
requests: { cpu: "500m", memory: "1Gi" } # lo que el planificador reserva
limits: { cpu: "1000m", memory: "2Gi" } # el techo duro
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
volumeMounts:
- { name: temporal, mountPath: /tmp }
startupProbe: # da tiempo a arrancar sin que liveness mate
httpGet: { path: /salud, port: 8080 }
periodSeconds: 5
failureThreshold: 12 # hasta 60 s para el primer arranque
readinessProbe: # decide si recibe trafico
httpGet: { path: /salud, port: 8080 }
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe: # decide si se reinicia el contenedor
httpGet: { path: /salud, port: 8080 }
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
lifecycle:
preStop:
exec: { command: ["sleep", "5"] } # margen para que el ALB desregistre
volumes:
- name: temporal
emptyDir: { sizeLimit: 1Gi }Los puntos que hay que entender de ese manifiesto:
- Las tres sondas son tres preguntas distintas y confundirlas causa incidentes.
startupProberesponde «¿ha terminado de arrancar?» y suspende a las otras dos hasta que acierta.readinessProberesponde «¿puede recibir tráfico ahora?» y su fallo saca al pod del balanceo sin matarlo.livenessProberesponde «¿está colgado?» y su fallo reinicia el contenedor. El error clásico es apuntarlivenessProbea un/saludque consulta Aurora: cuando la base de datos va lenta, Kubernetes reinicia todos los pods a la vez y convierte un problema de latencia en una caída total. requestsylimitsno son lo mismo.requestses lo que el planificador reserva para decidir en qué nodo cabe el pod;limitses el techo. Si la CPU supera el límite, el pod se estrangula (se ralentiza); si la memoria lo supera, el kernel lo mata con OOM y verásOOMKilleden los eventos. Ponerrequestsmuy por debajo del uso real produce nodos sobrecomprometidos que se caen a la vez.maxUnavailable: 0conmaxSurge: 2es exactamente el100/200de ECS: nunca hay menos capacidad sana que la deseada, a costa de pagar unos minutos de capacidad doble durante el despliegue.preStopcon una espera corta resuelve el mismo problema que el drenaje del grupo de destino: cuando Kubernetes decide terminar un pod, el ALB puede tardar unos segundos en dejar de mandarle tráfico. Sin esa espera, se pierden peticiones en cada despliegue.- El
Secretreferenciado no se escribe a mano. MercadoFresco usa el controlador CSI de secretos con el proveedor de AWS, que montamercadofresco/produccion/rds/mfadmindesde Secrets Manager y opcionalmente lo sincroniza comoSecretde Kubernetes. Así el secreto no vive en Git ni enetcden base64.
El Service, que da a los pods un nombre estable:
apiVersion: v1
kind: Service
metadata:
name: svc-tienda
namespace: mercadofresco
spec:
type: ClusterIP # solo interno: quien expone al exterior es el Ingress
selector: { app: tienda }
ports:
- port: 80
targetPort: 8080
name: httpIngress y el AWS Load Balancer Controller
Un Ingress por sí solo no hace nada: es una declaración que necesita un controlador que la traduzca a un balanceador real. En EKS, ese controlador es el AWS Load Balancer Controller, que crea y configura un ALB de verdad (03-03) a partir de las anotaciones.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ing-tienda
namespace: mercadofresco
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip # directo al pod, sin salto por el nodo
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-1:333344445555:certificate/abc-123
alb.ingress.kubernetes.io/healthcheck-path: /salud
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '15'
alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:eu-west-1:333344445555:regional/webacl/waf-mercadofresco-cdn/abc
alb.ingress.kubernetes.io/tags: Proyecto=mercadofresco,Entorno=desarrollo,Componente=tienda,Propietario=plataforma,CentroCoste=tecnologia
alb.ingress.kubernetes.io/group.name: mercadofresco # varios Ingress comparten un ALB
spec:
ingressClassName: alb
rules:
- host: tienda.mercadofresco.example
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc-tienda
port: { number: 80 }Tres observaciones que conectan con módulos anteriores. target-type: ip hace que el ALB envíe tráfico directamente a la IP del pod, sin pasar por el NodePort del nodo: es el equivalente exacto del grupo de destino de tipo ip de 10-01, y ahorra un salto de red. group.name permite que varios Ingress compartan un único ALB, lo que importa porque cada ALB cuesta unos 17 USD al mes de coste fijo y crear uno por servicio se paga rápido. Y las anotaciones de WAF y certificado demuestran que, por debajo, sigue siendo el mismo ALB del módulo 3 con las mismas protecciones del módulo 4: EKS cambia cómo se declara, no qué se obtiene.
Autoescalado: HPA, Cluster Autoscaler y Karpenter
En Kubernetes hay dos escalados y son independientes: el de pods y el de nodos. Confundirlos es la causa de la queja «he configurado el autoescalado y no escala».
El HorizontalPodAutoscaler es el equivalente del autoescalado del servicio de ECS:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-tienda
namespace: mercadofresco
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: tienda }
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 65 } }
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # crecer rapido, como en 10-01
policies: [{ type: Percent, value: 100, periodSeconds: 30 }]
scaleDown:
stabilizationWindowSeconds: 300 # decrecer despacio
policies: [{ type: Percent, value: 25, periodSeconds: 60 }]Escalar por peticiones por destino —que en 10-01 se demostró mejor que la CPU— exige aquí instalar además KEDA o el adaptador de métricas de CloudWatch, porque el HPA nativo solo entiende CPU y memoria. Es un ejemplo pequeño y representativo del patrón general: lo que en ECS es un parámetro, en Kubernetes suele ser un componente más que instalar y mantener.
Para los nodos hay dos opciones:
| Cluster Autoscaler | Karpenter | |
|---|---|---|
| Cómo decide | Ajusta el tamaño de grupos de nodos predefinidos | Elige el tipo de instancia adecuado para los pods pendientes |
| Velocidad | Minutos | Segundos hasta lanzar; el arranque sigue siendo de EC2 |
| Eficiencia | Limitada por los tipos del grupo | Alta: consolida y elige el tamaño óptimo |
| Spot | Posible, con grupos aparte | Nativo, con sustitución al recibir el aviso |
| Complejidad | Baja, es lo tradicional | Media: otro controlador que instalar y actualizar |
Karpenter es hoy la opción recomendada y es genuinamente buena: observa los pods que no caben, decide qué instancia los alojaría mejor, la lanza y, cuando sobra, consolida las cargas y apaga nodos. Pero conviene ver el conjunto con perspectiva: para conseguir en EKS lo que en Fargate viene puesto —que la capacidad aparezca cuando hace falta y desaparezca cuando no—, hay que instalar, configurar, actualizar y vigilar un controlador más. Y aun con Karpenter, el pod nuevo espera a que arranque una instancia EC2: los dos minutos que MercadoFresco quería eliminar reaparecen cuando no hay hueco.
Complementos: por qué un EKS vacío no sirve para nada
Un clúster recién creado tiene tres cosas y le faltan muchas. Esta es la lista mínima real para poder ejecutar la tienda de MercadoFresco:
| Complemento | Para qué | ¿Gestionado por AWS? |
|---|---|---|
VPC CNI (aws-node) |
Da a cada pod una IP de la VPC | Sí |
| CoreDNS | Resolución DNS dentro del clúster | Sí |
| kube-proxy | Reglas de red para los Services | Sí |
| EKS Pod Identity Agent | Permisos de AWS por pod | Sí |
| EBS CSI / EFS CSI | Volúmenes persistentes | Sí |
| AWS Load Balancer Controller | Convierte Ingress en ALB | No: lo instalas y actualizas tú |
| Secrets Store CSI + proveedor de AWS | Trae secretos desde Secrets Manager | No |
| Metrics Server | Métricas que necesita el HPA | No |
| Karpenter o Cluster Autoscaler | Escalado de nodos | No |
| CloudWatch Observability | Container Insights y registros | Sí (complemento) |
| KEDA (opcional) | Escalado por métricas externas | No |
| cert-manager (opcional) | Certificados internos | No |
Los cinco marcados como «No» son software que MercadoFresco tendría que instalar con Helm, versionar, probar y actualizar, cada uno con su propio ciclo de vida y su propia matriz de compatibilidad con las versiones de Kubernetes. Ninguno es difícil por separado. La suma sí lo es, y es la parte de EKS que no aparece en las comparativas de una tarde.
En ECS, el equivalente de esa tabla entera es: nada. El balanceador es un parámetro del servicio, los secretos son un campo de la definición de tarea, el escalado es Application Auto Scaling y las métricas vienen con Container Insights.
Actualizaciones de versión: el coste oculto
Este es el argumento central de la lección y el que más peso tiene en la decisión final.
Kubernetes publica unas tres versiones menores al año. EKS da soporte estándar durante unos 14 meses por versión; después entra automáticamente en soporte extendido, que dura unos 12 meses más y cuesta 0,60 USD por hora en lugar de 0,10, es decir, unos 438 USD al mes por clúster en lugar de 73. Al terminar el extendido, AWS actualiza el clúster por su cuenta.
Lo que implica en trabajo real, tres o cuatro veces al año y por clúster:
- Leer las notas de la versión y detectar las API retiradas. Kubernetes retira versiones de API entre versiones menores, y un manifiesto que funcionaba deja de aplicarse. Herramientas como
kubentayudan a encontrarlos, pero la revisión es tuya. - Comprobar la compatibilidad de cada complemento: el controlador de balanceadores, Karpenter, el CSI de secretos, KEDA, el CNI. Cada uno tiene su matriz, y a veces hay que actualizar el complemento antes que el clúster.
- Actualizar el plano de control, que tarda unos 25-40 minutos y no se puede revertir.
- Actualizar los nodos, sustituyendo instancias en tandas y drenando pods, con el riesgo habitual de que un
PodDisruptionBudgetmal puesto bloquee el drenaje. - Probarlo todo en un clúster de preproducción antes, lo que implica mantener ese clúster, con su plano de control de 73 USD al mes y sus mismos complementos.
| Actividad | Frecuencia | Esfuerzo estimado |
|---|---|---|
| Actualización de versión menor (preproducción y producción) | 3 al año | 2-4 días de una persona cada vez |
| Actualización de complementos fuera de ciclo | 6-10 al año | 2-4 horas cada vez |
| Seguimiento de CVE de los componentes del clúster | Continuo | 2-4 horas al mes |
| Total anual aproximado | 20-30 días de trabajo de plataforma |
Veinte o treinta días al año es más de un mes de una persona. Ese es el coste oculto de EKS, y no lo elimina Auto Mode: Auto Mode se encarga de los nodos, pero las versiones de Kubernetes, la compatibilidad de tus manifiestos y los complementos que instales siguen siendo tuyos.
En ECS ese trabajo simplemente no existe. No hay versión de ECS que actualizar; AWS evoluciona el servicio sin que nadie planifique nada. Es la comparación más honesta que se puede hacer entre los dos, y para un equipo de plataforma pequeño es decisiva.
Observabilidad: Container Insights, Prometheus y Grafana
Todo lo del módulo 5 sigue aplicándose; cambian las herramientas nativas del ecosistema.
- Container Insights se activa con el complemento de observabilidad de CloudWatch y da métricas de clúster, nodo, pod y contenedor, más los registros de los contenedores en
/aws/containerinsights/eks-mercadofresco/application. Es la vía más directa y la que menos piezas añade. - Amazon Managed Service for Prometheus es un Prometheus gestionado y compatible con
PromQL. Encaja bien porque la mayoría de los componentes del ecosistema —el controlador de balanceadores, Karpenter, KEDA— ya exponen métricas en formato Prometheus. Se recoge con el agente de CloudWatch o con el recolector de OpenTelemetry, y se paga por muestras ingeridas y almacenadas. - Amazon Managed Grafana es el Grafana gestionado, con acceso mediante IAM Identity Center (09-04) y fuentes de datos hacia Prometheus, CloudWatch y X-Ray. Se paga por usuario activo al mes.
- Los registros del plano de control —
api,audit,authenticator— se activan en el clúster y van a CloudWatch Logs. El de auditoría es el que responde «quién borró ese Deployment», y conviene activarlo desde el primer día porque no es retroactivo.
La observación honesta: la combinación Prometheus más Grafana es más potente que CloudWatch para métricas de Kubernetes, con mejor consulta y un catálogo de paneles listos. Y también es más piezas: dos servicios más que configurar, dos facturas más y dos sitios donde mirar durante un incidente. Para MercadoFresco, con los paneles mercadofresco-produccion y -negocio ya construidos en 05-01, la ganancia no compensa la fragmentación.
ECS frente a EKS: la comparativa honesta
| Criterio | Amazon ECS | Amazon EKS |
|---|---|---|
| Curva de aprendizaje | Días: cinco conceptos | Semanas o meses: decenas de objetos y patrones |
| Coste del plano de control | 0 USD | 73 USD/mes, o 438 con soporte extendido |
| Portabilidad | Ninguna: solo AWS | Total: cualquier nube o servidor propio |
| Ecosistema | Lo que ofrece AWS | Enorme: operadores, GitOps, mallas, miles de gráficos de Helm |
| Extensibilidad | Muy limitada | CRD y operadores: se extiende la API |
| Integración con AWS | Nativa y sin piezas | Buena, pero mediante controladores que instalas tú |
| Actualizaciones | Ninguna: AWS lo hace | 3 al año, 20-30 días de trabajo anuales |
| Complementos a mantener | Ninguno | 5-8 componentes con su propio ciclo |
| Personal necesario | Cualquier equipo con AWS | Alguien que sepa Kubernetes, no que lo esté aprendiendo |
| Mercado laboral | Menor | Amplio: es una habilidad transferible |
| Fargate | Sí, plenamente integrado | Sí, con limitaciones (sin DaemonSet, sin EBS) |
| Multi-nube y híbrido | No | Sí, incluido en instalaciones propias |
| Velocidad para llegar a producción | Días | Semanas |
| Techo de complejidad que soporta | Medio | Muy alto |
Ninguna de las dos columnas es «la buena». La lectura correcta es: EKS compra flexibilidad, portabilidad y ecosistema, y lo paga con complejidad, personal y trabajo recurrente. Si necesitas lo que compra, el precio es razonable. Si no lo necesitas, has comprado trabajo.
La decisión de MercadoFresco y qué la cambiaría
MercadoFresco se queda en ECS con Fargate. Y conviene que la razón esté escrita, porque una decisión sin argumentos escritos se revisa cada seis meses en función de la última conferencia a la que fue alguien.
Los argumentos, en orden de peso:
- El equipo de plataforma es Marta y medio Luis. Veinte o treinta días al año de actualizaciones sobre dos personas que además llevan la red, la seguridad, el pipeline y las guardias no es un coste asumible: sería el 10-15 % de su capacidad total dedicado a mantener el orquestador, no la tienda.
- No hay ningún requisito que ECS no cubra. La tienda es un servicio HTTP con estado externo, unos trabajadores que consumen colas y unas funciones por eventos. Nada de eso necesita CRD, operadores, mallas de servicio ni programación avanzada de pods.
- No hay requisito multi-nube. MercadoFresco está en AWS y no hay plan ni presión comercial para cambiar eso. La portabilidad de Kubernetes es un seguro, y un seguro que no se va a usar es solo una prima.
- La velocidad importa. El módulo 10 ha llevado la tienda de EC2 a contenedores en semanas. Con EKS, esas mismas semanas habrían sido para levantar el clúster, los complementos y el aprendizaje del equipo, sin haber movido todavía la tienda.
- El coste directo también cuenta, aunque sea el argumento menor: 73 USD al mes por clúster y por entorno son unos 220 USD mensuales para desarrollo, preproducción y producción, frente a 0 USD de ECS. Es poco dinero comparado con el coste de personal, pero no es cero.
Y los criterios objetivos que cambiarían la decisión, escritos por adelantado para que la revisión futura sea una comprobación y no un debate:
| Criterio | Umbral concreto | Por qué inclinaría la balanza |
|---|---|---|
| Experiencia previa del equipo | Dos o más personas con Kubernetes en producción | La curva deja de ser un coste y pasa a ser un activo |
| Requisito multi-nube o híbrido | Un contrato, una normativa o un almacén con cómputo local | Es lo único que ECS no puede dar de ninguna forma |
| Necesidad de operadores del ecosistema | Un componente que solo existe como operador de Kubernetes | Reimplementarlo cuesta más que adoptar EKS |
| Tamaño del equipo de plataforma | Cuatro o más personas dedicadas | Hay capacidad para absorber el trabajo recurrente |
| Número de servicios | Más de 30-40 servicios con equipos distintos | Los namespaces, RBAC y GitOps empiezan a ganar de verdad |
| Requisitos de programación avanzados | Afinidades complejas, GPU compartidas, prioridad entre cargas | El planificador de Kubernetes es muy superior |
Mientras ninguno de esos umbrales se cumpla, la respuesta a «¿deberíamos pasarnos a Kubernetes?» es no, y aquí está por qué. El día que dos se cumplan, la respuesta cambia, y esta tabla dice exactamente cuándo.
Con un matiz que evita el error contrario: Luis debería aprender Kubernetes igualmente. Es una habilidad de mercado y da criterio para juzgar arquitecturas ajenas. Aprenderlo no obliga a adoptarlo, y adoptarlo sin saberlo es la peor combinación posible.
Coste y limpieza
| Concepto | Coste aproximado mensual |
|---|---|
| Plano de control, soporte estándar | 73 USD por clúster |
| Plano de control, soporte extendido | 438 USD por clúster |
3 nodos m6g.large bajo demanda |
≈ 165 USD |
| Volúmenes EBS de los nodos (3 × 50 GB gp3) | ≈ 12 USD |
| ALB creado por el Ingress | ≈ 17 USD más consumo |
| Managed Prometheus y Grafana | Desde ~20 USD según uso y usuarios |
| Clúster de desarrollo mínimo y realista | ≈ 270-290 USD/mes |
Multiplicado por tres entornos, un EKS equivalente al montaje actual de MercadoFresco costaría del orden de 800 USD al mes frente a los aproximadamente 250 USD de ECS con Fargate y arm64 —y esa comparación no incluye los 20-30 días anuales de trabajo de plataforma, que es el coste mayor.
Limpieza, y aquí el orden importa más que en ninguna otra lección: los recursos de AWS creados desde dentro del clúster —el ALB del Ingress, los volúmenes EBS de los PersistentVolumeClaim— no se borran al borrar el clúster y quedan huérfanos facturando.
# 1. PRIMERO los objetos de Kubernetes que crean recursos de AWS
kubectl delete ingress --all -n mercadofresco # borra el ALB
kubectl delete pvc --all -n mercadofresco # borra los volumenes EBS
kubectl delete svc --all -n mercadofresco # borra cualquier NLB de type LoadBalancer
# 2. Comprobar que de verdad han desaparecido antes de seguir
aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName,`k8s`)].LoadBalancerArn'
aws ec2 describe-volumes --filters Name=status,Values=available --query 'Volumes[].VolumeId'
# 3. Ahora si, el cluster completo (eksctl borra nodos, roles y la pila)
eksctl delete cluster --name eks-mercadofresco --region eu-west-1
# 4. Residuos habituales: grupos de registros y el proveedor OIDC
aws logs delete-log-group --log-group-name /aws/eks/eks-mercadofresco/cluster
aws iam list-open-id-connect-providersSi se borra el clúster antes que los Ingress, el controlador que sabía borrar el ALB desaparece con él y el balanceador se queda facturando indefinidamente. Es el residuo más caro y más frecuente de EKS.
Errores Comunes y Consejos
Error: livenessProbe que consulta la base de datos. Cuando Aurora va lenta, Kubernetes reinicia todos los pods a la vez y una degradación se convierte en caída. Consejo: liveness comprueba solo que el proceso responde; las dependencias van en readiness.
Error: no poner startupProbe. Una aplicación que tarda 40 segundos en arrancar es reiniciada por liveness antes de terminar, en bucle. Consejo: startupProbe con failureThreshold generoso; suspende a las otras dos hasta que acierta.
Error: requests muy por debajo del uso real. Los nodos quedan sobrecomprometidos y caen varios pods a la vez. Consejo: requests cercano al percentil 95 real y limits con margen, medido con Container Insights.
Error: dar permisos de AWS mediante el rol del nodo. Todos los pods del nodo heredan todo. Consejo: Pod Identity o IRSA siempre, incluso en desarrollo, porque lo que se hace en desarrollo se copia a producción.
Error: editar aws-auth a mano. Un error de sintaxis deja a todo el mundo fuera del clúster sin vuelta atrás. Consejo: usa entradas de acceso (AuthenticationMode: API) y no toques ese ConfigMap.
Error: guardar Secret de Kubernetes en Git. Base64 no es cifrado. Consejo: controlador CSI de secretos con Secrets Manager, y cifrado de etcd con KMS activado en la creación del clúster.
Error: borrar el clúster antes que los Ingress. El ALB queda huérfano facturando. Consejo: el orden de limpieza de arriba, con verificación entre pasos.
Error: dejar que el clúster entre en soporte extendido sin darse cuenta. La factura se multiplica por seis en silencio. Consejo: una alarma en el calendario a los 12 meses de cada versión y un presupuesto por cuenta (11-04) que detecte el salto.
Error: un ALB por cada Ingress. Diez servicios, diez balanceadores, 170 USD al mes. Consejo: alb.ingress.kubernetes.io/group.name para compartir un ALB entre Ingress.
Error: elegir EKS por currículum. Es una razón real y sincera, y una mala razón técnica. Consejo: si el equipo quiere aprender Kubernetes, que monte un clúster de laboratorio con presupuesto propio; la producción se decide con los criterios de la tabla.
Consejo: si eliges EKS, elige también Karpenter y Auto Mode desde el principio. Empezar con Cluster Autoscaler y migrar después es trabajo duplicado.
Consejo: activa los registros de auditoría del plano de control el primer día. No son retroactivos, y son lo único que responde «quién borró ese Deployment».
Consejo: fija topologySpreadConstraints por zona en todo Deployment de producción. Sin ellos, el planificador puede poner las tres réplicas en la misma AZ y una zona caída te deja sin servicio.
Ejercicios
Ejercicio 1: traducir el servicio de trabajadores a Kubernetes
Los trabajadores de cola-mercadofresco-pedidos corren hoy en Fargate con 0,5 vCPU y 1 GB, escalando de 2 a 8 tareas según el retraso de la cola, con un reparto 1:4 entre bajo demanda y Spot. Escribe el equivalente en Kubernetes: (a) el Deployment completo, con las sondas que correspondan a un proceso sin servidor HTTP y el apagado ordenado; (b) cómo resuelves el autoescalado por profundidad de cola, indicando qué componente adicional hace falta y por qué el HPA nativo no basta; (c) cómo consigues el equivalente del reparto entre capacidad bajo demanda y Spot; y (d) enumera todas las piezas que has tenido que añadir al clúster para lograr lo que en ECS eran tres parámetros.
Ejercicio 2: el incidente de las sondas
Un viernes a las 19:20, con el pico en marcha, Aurora sufre una degradación y las consultas pasan de 20 ms a 900 ms. A los dos minutos, la tienda desaparece entera: todos los pods están en CrashLoopBackOff, el ALB no tiene destinos sanos y mercadofresco-alb-latencia-alta ha dado paso a un error total. La base de datos, mientras tanto, sigue respondiendo, lenta pero viva. Explica (a) la cadena causal exacta que ha convertido una degradación en una caída total; (b) qué configuración concreta del manifiesto es la culpable y por qué en ECS con la comprobación de estado del grupo de destino el resultado habría sido distinto; (c) cómo se rediseñan las tres sondas para que este incidente sea una degradación y no una caída; (d) qué mecanismo añadirías en el código de la tienda para degradar con elegancia; y (e) qué alarma habría avisado antes.
Ejercicio 3: la revisión de la decisión, dieciocho meses después
Han pasado dieciocho meses. MercadoFresco ha crecido: opera en cuatro ciudades, tiene 22 servicios en lugar de 3, el equipo de plataforma son ahora 4 personas de las que 2 han trabajado con Kubernetes en su empleo anterior, y el mayor cliente mayorista exige por contrato que su instancia del servicio de pedidos pueda ejecutarse en su propio centro de datos. Además, el equipo de datos quiere usar un operador de Kubernetes para gestionar sus flujos de procesamiento. Aplica la tabla de criterios de la lección y responde: (a) qué umbrales se cumplen ahora; (b) cuál es tu recomendación y con qué nivel de confianza; (c) si recomiendas migrar, cómo lo harías sin repetir los errores del plan de migración de 09-04; (d) qué se queda en ECS aunque migres; y (e) qué coste anual total estimarías para la decisión, incluyendo personal.
Soluciones
Solución 1
(a) El Deployment.
apiVersion: apps/v1
kind: Deployment
metadata: { name: trabajadores-pedidos, namespace: mercadofresco }
spec:
replicas: 2
selector: { matchLabels: { app: trabajadores-pedidos } }
template:
metadata: { labels: { app: trabajadores-pedidos } }
spec:
serviceAccountName: sa-trabajadores
terminationGracePeriodSeconds: 120 # equivale a stopTimeout 120 de ECS
topologySpreadConstraints:
- { maxSkew: 1, topologyKey: topology.kubernetes.io/zone,
whenUnsatisfiable: ScheduleAnyway, labelSelector: { matchLabels: { app: trabajadores-pedidos } } }
containers:
- name: trabajador
image: 555566667777.dkr.ecr.eu-west-1.amazonaws.com/mercadofresco/trabajadores@sha256:71ab...c3d9
resources:
requests: { cpu: "500m", memory: "1Gi" }
limits: { cpu: "500m", memory: "1Gi" }
securityContext: { runAsNonRoot: true, runAsUser: 10002, readOnlyRootFilesystem: true }
# Sin HTTP: se ejecuta un comando que comprueba la marca de latido en /tmp.
livenessProbe:
exec: { command: ["python", "-m", "trabajadores.salud"] }
periodSeconds: 30
failureThreshold: 3
startupProbe:
exec: { command: ["python", "-m", "trabajadores.salud"] }
periodSeconds: 5
failureThreshold: 12
volumeMounts: [{ name: temporal, mountPath: /tmp }]
volumes: [{ name: temporal, emptyDir: {} }]No hay readinessProbe y es deliberado: readiness decide si un pod recibe tráfico de un Service, y a un trabajador de cola nadie le manda tráfico. Poner una sería ruido sin efecto. liveness sí es imprescindible, por la misma razón que en 10-01: un trabajador colgado no muere, deja de consumir la cola y nadie se entera. El apagado ordenado es el mismo manejador de SIGTERM de aquel ejercicio, con terminationGracePeriodSeconds: 120 como equivalente del stopTimeout.
(b) Autoescalado por profundidad de cola. El HPA nativo solo entiende CPU y memoria de las métricas de recursos, y ya se vio que la CPU de un consumidor de cola no refleja el retraso. Hace falta KEDA, que instala un adaptador de métricas externas y trae un escalador específico para SQS:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata: { name: escalado-trabajadores, namespace: mercadofresco }
spec:
scaleTargetRef: { name: trabajadores-pedidos }
minReplicaCount: 2
maxReplicaCount: 8
cooldownPeriod: 300
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.eu-west-1.amazonaws.com/333344445555/cola-mercadofresco-pedidos
queueLength: "15" # mensajes por replica, como en 10-02
awsRegion: eu-west-1
identityOwner: operatorLa alternativa sin KEDA es el adaptador de métricas de CloudWatch más un HPA con métrica externa, que es más artesanal. En cualquier caso, es un componente más que instalar, versionar y actualizar para conseguir lo que en ECS era una política de Application Auto Scaling.
(c) El equivalente del reparto bajo demanda / Spot. No hay un capacityProviderStrategy; se consigue con Karpenter declarando dos NodePool, uno con capacity-type: on-demand y otro con spot, y usando en el Deployment nodeAffinity preferida hacia Spot más topologySpreadConstraints sobre la etiqueta karpenter.sh/capacity-type para garantizar que al menos una réplica caiga en bajo demanda. También hace falta el manejador de interrupciones, que Karpenter incluye: escucha el aviso de dos minutos de EC2 y drena el nodo. Resultado: lo que en ECS eran dos líneas de --capacity-provider-strategy aquí son dos NodePool, reglas de afinidad, restricciones de topología y un controlador.
(d) Las piezas añadidas. Para igualar tres parámetros de ECS ha hecho falta: Karpenter (nodos y Spot), KEDA (escalado por cola), Metrics Server (base del HPA), EKS Pod Identity Agent (permisos de SQS del pod) y, si se quiere observabilidad equivalente, el complemento de CloudWatch Observability. Cinco componentes, cada uno con su versión, su compatibilidad con la versión de Kubernetes y su propio ciclo de actualización. Esa es exactamente la aritmética de la sección de complementos.
Solución 2
(a) La cadena causal. Aurora se degrada a 900 ms. El /salud de la tienda consulta la base de datos, así que empieza a tardar más de los 3 segundos de timeoutSeconds. La livenessProbe acumula 3 fallos consecutivos —unos 60 segundos con periodSeconds: 20— y Kubernetes reinicia el contenedor. Al reiniciarse, el pod tiene que arrancar de nuevo, precargar la caché del catálogo y volver a conectar con la misma Aurora lenta, con lo que falla también el arranque. Como todos los pods consultan la misma base de datos, esto le ocurre a los tres a la vez y de forma sincronizada. Los reinicios repetidos llevan a CrashLoopBackOff, con esperas crecientes entre intentos. Y hay un agravante: los reinicios masivos abren y cierran conexiones contra una Aurora ya saturada, empeorando la causa raíz. Un bucle de realimentación completo.
(b) La culpable y la diferencia con ECS. La culpable es la livenessProbe apuntando a un /salud que comprueba dependencias externas. En ECS, la comprobación de estado del grupo de destino tiene un efecto distinto: un destino no sano se saca del balanceo, y el servicio solo repone tareas si la tarea muere o si el despliegue lo exige. El equivalente de liveness en ECS es el healthCheck del contenedor, que la mayoría de la gente configura de forma más laxa o ni siquiera pone. Es decir: Kubernetes es más agresivo por omisión y te da una herramienta más peligrosa, y por eso exige entenderla. La diferencia no es que ECS sea mejor, es que Kubernetes te deja disparar más lejos.
(c) El rediseño de las tres sondas.
livenessProbe→/vivo, un endpoint que solo confirma que el proceso responde y el bucle de eventos no está bloqueado. Sin tocar Aurora, sin tocar la caché, sin tocar la red. ConperiodSeconds: 20yfailureThreshold: 5, para no reiniciar por un pico transitorio.readinessProbe→/listo, que sí comprueba las dependencias: conexión a Aurora y a ElastiCache. Su fallo saca al pod del balanceo pero no lo mata, así que cuando Aurora se recupera el pod vuelve a recibir tráfico sin haber reiniciado. Aquí el fallo es reversible y barato.startupProbe→/listoconfailureThresholdamplio (12 × 5 s = 60 s), que cubre la precarga del catálogo y suspende a las otras dos mientras tanto.
Con ese diseño, el incidente del viernes habría sido: todos los pods marcados como no listos, el ALB devolviendo 503 en las peticiones que necesitan base de datos, los pods vivos y con su caché caliente, y recuperación inmediata al recuperarse Aurora. Degradación, no caída.
(d) Degradación con elegancia en el código. Que /listo distinga entre «no puedo hacer nada» y «puedo hacer parte». La tienda puede servir el catálogo desde mercadofresco-catalogo en ElastiCache aunque Aurora esté lenta; lo que no puede es confirmar pedidos. Con un interruptor de circuito (07-05) alrededor de las llamadas a Aurora, la tienda deja de intentarlo tras N fallos, devuelve un mensaje claro en el paso de pago y sigue sirviendo navegación. El cliente ve «no podemos confirmar pedidos ahora mismo» en lugar de una página en blanco, y Aurora deja de recibir la avalancha de reintentos que impedía su recuperación.
(e) La alarma que habría avisado antes. La de latencia de Aurora, ReadLatency y WriteLatency, con umbral sobre el percentil 99 y no sobre la media —la media tarda mucho en moverse—. Habría saltado al minuto de la degradación, antes de que la primera livenessProbe acumulara sus tres fallos, dando un margen de acción real. Complementariamente, una alarma sobre reinicios de pods (pod_number_of_container_restarts en Container Insights) habría identificado el bucle de realimentación de inmediato, que es la información que faltaba para diagnosticar en caliente.
Solución 3
(a) Umbrales que se cumplen. Cuatro de los seis:
| Criterio | ¿Se cumple? | Detalle |
|---|---|---|
| Experiencia previa | Sí | 2 de 4 personas con Kubernetes en producción |
| Multi-nube o híbrido | Sí, y es contractual | El mayorista exige ejecución en su centro de datos |
| Operadores del ecosistema | Sí | El equipo de datos quiere un operador de flujos |
| Tamaño del equipo | Sí | 4 personas dedicadas |
| Número de servicios | Parcialmente | 22, por debajo del umbral de 30-40 pero cerca |
| Programación avanzada | No | Nada lo exige todavía |
(b) Recomendación. Sí, migrar a EKS, con confianza alta. El criterio decisivo es el segundo: el requisito híbrido contractual es lo único que ECS no puede satisfacer de ninguna manera, y no es una preferencia sino una obligación con un cliente. Los otros tres refuerzan que ahora el coste es asumible: hay experiencia, hay equipo y hay una necesidad concreta de ecosistema. La decisión de hace dieciocho meses no era errónea: era correcta con los datos de entonces, y esa es precisamente la utilidad de haber escrito los criterios por adelantado.
(c) Cómo migrar. Aplicando las lecciones de 09-04 y del propio módulo 10:
- Un clúster nuevo, no una conversión. El clúster de desarrollo primero, con Auto Mode o Karpenter desde el principio para no duplicar trabajo.
- Primero un servicio no crítico. El servicio de informes o el de notificaciones, no la tienda. Se aprende con algo cuyo fallo no cuesta pedidos.
- Los complementos antes que las cargas, con sus versiones fijadas en Git y desplegados con Helm desde el pipeline, nunca a mano.
- Convivencia real y prolongada. ECS y EKS a la vez durante meses, con el ALB repartiendo por pesos igual que en 10-01 y 10-02. Nada de una noche de corte.
- La tienda, la última, y con el mismo reparto 90/10 que se usó para pasar de EC2 a Fargate.
- Fecha límite explícita por fase, que es el error que 09-04 señalaba: las migraciones a medias duran años si nadie pone hitos.
- La instalación del cliente mayorista al final, cuando el equipo ya opere EKS con soltura, no como primer proyecto.
(d) Qué se queda en ECS aunque migres. Lo que no gana nada con el cambio: las Lambdas (-cobrar-pago, -reservar-stock, -generar-miniaturas, -asignar-reparto), que no son contenedores y siguen siendo la mejor opción para trabajo esporádico; las tareas programadas de carga a Redshift, que funcionan perfectamente con EventBridge Scheduler; y probablemente los trabajadores de las colas menos críticas durante bastante tiempo, porque migrarlos no aporta y sí consume atención. Migrar todo por coherencia estética es un error caro: la coherencia se mide en criterios, no en tecnologías.
(e) Coste anual estimado. Infraestructura: 3 clústeres × 73 USD × 12 = 2.628 USD de planos de control, más el diferencial de nodos frente a Fargate, que con Karpenter y Spot bien usados puede ser incluso favorable —digamos neutro—. Personal: 25 días al año de trabajo de plataforma en actualizaciones y mantenimiento del clúster, más un coste único de 2-3 meses-persona para la migración. Con un coste interno estimado de 400 USD por día, el recurrente son unos 10.000 USD al año y la migración, unos 20.000-25.000 USD de una vez. Total del primer año: del orden de 35.000 USD. Ese es el número que hay que poner delante de la dirección junto al contrato del cliente mayorista, porque la decisión no es técnica: es una inversión con una justificación comercial concreta.
Conclusión
MercadoFresco ha terminado el módulo con la tienda fuera de las máquinas y con una respuesta razonada a la pregunta de Kubernetes.
Tienes Kubernetes entendido de verdad: un orquestador extensible con API propia, cuyo valor real está en la portabilidad, el ecosistema y la extensibilidad con CRD y operadores; su vocabulario mínimo —Pod, ReplicaSet, Deployment, Service, Ingress, Namespace, ConfigMap y Secret, con la advertencia de que un Secret es base64 y no cifrado—; y el modelo declarativo con bucle de reconciliación que explica por qué borrar un pod a mano no sirve de nada y por qué GitOps encaja de forma natural. Tienes claro qué gestiona EKS —plano de control en tres AZ, etcd, el mecanismo de actualización y la integración con IAM— y la frase que evita la mayoría de las decepciones: EKS gestiona el plano de control, no el clúster.
Tienes las cuatro opciones de cómputo con sus límites reales —incluida la de que EKS sobre Fargate no admite DaemonSet, lo que obliga a mantener nodos para los complementos—, la creación del clúster con eksctl y con CDK, aws eks update-kubeconfig y la disciplina de mirar el contexto antes de escribir un delete. Y la autenticación moderna: entradas de acceso en lugar de aws-auth, con los conjuntos de permisos de 09-04 gobernando también el clúster, y Pod Identity como equivalente exacto del rol de tarea de ECS.
Tienes los manifiestos completos de la tienda: Deployment con las tres sondas bien separadas —startup para arrancar, readiness para recibir tráfico, liveness para reiniciar—, requests y limits como conceptos distintos, reparto por AZ con topologySpreadConstraints, preStop para no perder peticiones y contexto de seguridad sin privilegios; el Service; y el Ingress con el AWS Load Balancer Controller, que crea el mismo ALB del módulo 3 con el WAF del módulo 4, con target-type: ip y group.name para no pagar un balanceador por servicio. Más el autoescalado en dos planos: HPA para los pods, Karpenter para los nodos, y la observación de que para igualar tres parámetros de ECS hicieron falta cinco componentes.
Y tienes el argumento que casi nadie pone por escrito: el coste oculto de las actualizaciones. Tres versiones al año, 14 meses de soporte estándar, un salto automático de 73 a 438 USD mensuales al entrar en extendido, y 20-30 días de trabajo de plataforma al año revisando API retiradas, matrices de compatibilidad y drenajes de nodos. En ECS ese trabajo no existe. Con la comparativa honesta entre ambos —EKS compra flexibilidad, portabilidad y ecosistema, y lo paga con complejidad, personal y trabajo recurrente— y la decisión de MercadoFresco: se queda en ECS con Fargate, por un equipo de plataforma de dos personas, la ausencia de requisitos que ECS no cubra, la ausencia de necesidad multi-nube y la velocidad; con los seis criterios objetivos, escritos por adelantado, que cambiarían esa decisión el día que se cumplan.
Qué ha cambiado para MercadoFresco en este módulo. Ya no hay AMI que reconstruir cuando cambia una dependencia: hay una imagen en mercadofresco/tienda versionada por commit y desplegada por digest. Ya no hay sistema operativo que parchear: el anfitrión es de AWS y el escaneo continuo de Inspector avisa de lo que sí es tuyo. Y ya no hay arranques de dos minutos: hay tareas que reciben tráfico en cuarenta segundos, con un escalado programado que además las tiene listas antes de que llegue el pico del viernes.
graph TB
U[Clientes] --> CF["CloudFront E2QWERTY123ABC<br/>+ waf-mercadofresco-cdn"]
CF --> ALB["alb-mercadofresco-tienda<br/>tg-mercadofresco-tienda / -verde"]
ALB --> FG["ECS Fargate<br/>svc-mercadofresco-tienda-fg<br/>arm64, 2-20 tareas"]
FG --> AU[("aurora-mercadofresco-pedidos")]
FG --> EC[("mercadofresco-catalogo<br/>ElastiCache")]
FG --> DY[("mercadofresco-carritos")]
FG --> SQS["cola-mercadofresco-pedidos"]
SQS --> TR["ECS Fargate + Spot<br/>svc-mercadofresco-trabajadores"]
TR --> SF["mercadofresco-procesar-pedido<br/>Lambdas de pago, stock y reparto"]
ECR["ECR 555566667777<br/>mercadofresco/tienda"] -.digest.-> FG
PIPE["pipeline-mercadofresco-tienda<br/>blue/green canario"] -.despliega.-> FG
OBS["Container Insights, X-Ray<br/>mercadofresco-produccion"] -.observa.-> FG
Y aquí llega la pregunta que nadie ha hecho todavía y que el gerente hará el lunes por la mañana. La arquitectura está completa: es elástica, segura, observable, desplegable desde un pipeline, descrita en código y repartida en cinco cuentas. Pero ¿cuánto cuesta todo esto? ¿Está bien gastado? ¿Cómo se reparte entre producción, preproducción y desarrollo, y entre las cuatro ciudades? ¿Hay algo encendido que nadie usa? ¿Y qué se podría comprometer por adelantado para pagar menos?
En el módulo 11, «Mejores prácticas y gestión de costos», se responde con método. 11-01 revisa la arquitectura completa con el Well-Architected Framework y sus seis pilares, que es la forma estructurada de auditar lo construido. 11-02 pone orden en el etiquetado y la asignación de costes, convirtiendo las cinco etiquetas obligatorias en informes que alguien pueda leer. 11-03 analiza la factura de verdad con Cost Explorer. 11-04 pone límites y avisos con Budgets. 11-05 compromete capacidad con Savings Plans e instancias reservadas, que es donde la comparación Fargate frente a EC2 de 10-02 queda por fin cerrada. Y 11-06 cierra el curso con el proyecto final: diseñar, justificar y presupuestar una arquitectura completa para MercadoFresco, con todo lo aprendido en once módulos.
Curso de AWS
Módulo 1: Introducción a AWS
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
