La lección anterior contenerizó la tienda de MercadoFresco y la puso a correr en un clúster de ECS. El resultado fue bueno pero incompleto: debajo del clúster siguen existiendo instancias EC2 que hay que dimensionar, cuya AMI hay que mantener, que se pagan estén llenas o vacías, y que tardan dos minutos en arrancar cuando el clúster necesita capacidad nueva. Escondimos el problema detrás de un proveedor de capacidad; no lo eliminamos.

AWS Fargate lo elimina. Es el motor de cómputo sin servidor para contenedores: se declara cuánta CPU y cuánta memoria necesita cada tarea, y AWS pone el resto —el anfitrión, el kernel, los parches, el aislamiento— sin que exista una instancia que nadie pueda ver ni listar. Esta lección migra el servicio de la tienda a Fargate, ajusta su autoescalado al pico de los viernes, mete a los trabajadores de la cola en Fargate Spot y termina con la comparativa honesta entre Fargate, EC2 y Lambda.

Aviso de coste. Fargate se factura por vCPU-hora y GB-hora consumidos por la tarea, con redondeo al segundo y un mínimo de un minuto. En eu-west-1 ronda los 0,047 USD por vCPU-hora y 0,0051 USD por GB-hora en x86, y aproximadamente un 20 % menos en arm64/Graviton. Fargate Spot descuenta en torno al 70 % a cambio de poder ser interrumpido. No hay capa gratuita para Fargate: una tarea olvidada de 1 vCPU y 2 GB cuesta unos 41 USD al mes. Sigue la sección de limpieza al final. Los precios son aproximados y orientativos; el análisis de costes a fondo es el módulo 11. Datos e identificadores ficticios.

Contenido

  1. Qué es Fargate y qué deja de existir
  2. El mismo servicio sobre EC2 y sobre Fargate
  3. Modelo de recursos: CPU, memoria y almacenamiento efímero
  4. x86 y arm64: Graviton y el ahorro real
  5. Diferencias operativas: sin SSH, sin hostPort, sin DaemonSet
  6. ECS Exec: depurar sin servidor que tocar
  7. Registros con FireLens y Fluent Bit
  8. El arranque de tareas y el pico de los viernes
  9. Migrar el servicio de la tienda a Fargate
  10. Endpoints de VPC: dejar de depender del NAT
  11. Autoescalado: seguimiento, pasos y programación
  12. Fargate Spot y los trabajadores de la cola
  13. Tareas puntuales y programadas con EventBridge Scheduler
  14. Integración en el pipeline y blue/green
  15. El servicio en CDK con un constructo L3
  16. Fargate frente a EC2 frente a Lambda
  17. App Runner: un escalón más de gestión
  18. Coste comparado y limpieza
  19. Errores comunes y consejos
  20. Ejercicios
  21. Conclusión

Qué es Fargate y qué deja de existir

Fargate no es un servicio que se contrate aparte ni una consola distinta: es un tipo de lanzamiento —y, con más precisión, un proveedor de capacidad— dentro del mismo Amazon ECS de la lección anterior. Las definiciones de tarea, los servicios, los grupos de destino, el autoescalado y los roles son los mismos objetos. Lo que cambia es dónde se ejecuta la tarea y quién es responsable de la máquina.

Lo que deja de existir Qué significaba antes Qué implica ahora
La AMI Una imagen de sistema operativo que mantener y actualizar Solo existe tu imagen de contenedor
El parcheo del anfitrión Ventana mensual, reinicios escalonados, CVE del kernel AWS parchea el anfitrión sin que te enteres
El escalado del clúster Un ASG, un proveedor de capacidad, targetCapacity No hay clúster de máquinas que escalar
El empaquetado de tareas Estrategias binpack y spread, límites de ENI, huecos que no cuadran Cada tarea es su propia unidad
La capacidad ociosa Instancias encendidas a las 4 de la mañana de un martes Se paga la tarea, no el hueco
El acceso al anfitrión SSH, docker exec, volúmenes del host ECS Exec; ver más abajo
Lo que sigue siendo tuyo Por qué importa
La imagen Su tamaño, sus dependencias y sus vulnerabilidades siguen siendo responsabilidad tuya
CPU y memoria de la tarea Ya no hay instancia que absorba un error de dimensionamiento: aquí se paga exacto
La red Subredes, grupos de seguridad, rutas y endpoints siguen siendo decisiones tuyas
Los permisos Rol de ejecución y rol de tarea, exactamente igual que en 10-01
La disponibilidad Repartir tareas entre AZ, dimensionar el mínimo, definir el autoescalado

La frase que resume el cambio: Fargate no elimina el trabajo de operar una aplicación, elimina el trabajo de operar un servidor. Marta sigue teniendo que decidir cuántas tareas hay un viernes a las 19:00; lo que ya no tiene que decidir es de qué tamaño son las máquinas que las alojan.

Técnicamente, cada tarea de Fargate se ejecuta en su propio entorno aislado por virtualización ligera —la misma tecnología que sustenta Lambda—, lo que resuelve la advertencia de 10-01 sobre el kernel compartido: dos tareas nunca comparten kernel, ni siquiera dos tareas tuyas.

El mismo servicio sobre EC2 y sobre Fargate

graph TB
  subgraph EC2["ECS sobre EC2 (10-01)"]
    A1[ALB] --> S1[Servicio ECS]
    S1 --> I1["Instancia m5.large<br/>AMI + agente ECS<br/>PARCHEAR"]
    S1 --> I2["Instancia m5.large<br/>AMI + agente ECS<br/>PARCHEAR"]
    I1 --> T1[Tarea]
    I1 --> T2[Tarea]
    I2 --> T3[Tarea]
    I2 --> H["Hueco pagado<br/>y vacio"]
    ASG["ASG + proveedor de capacidad<br/>arranque: 2 minutos"] -.gestiona.-> I1
    ASG -.gestiona.-> I2
  end
  subgraph FG["ECS sobre Fargate (10-02)"]
    A2[ALB] --> S2[Servicio ECS]
    S2 --> F1["Tarea 1 vCPU / 2 GB<br/>AZ a"]
    S2 --> F2["Tarea 1 vCPU / 2 GB<br/>AZ a"]
    S2 --> F3["Tarea 1 vCPU / 2 GB<br/>AZ b"]
    S2 --> F4["Tarea 1 vCPU / 2 GB<br/>AZ b"]
    AAS["Application Auto Scaling<br/>arranque: 30-45 segundos"] -.gestiona.-> S2
  end

Los dos diagramas tienen el mismo ALB, el mismo servicio y las mismas tareas. La diferencia está en la capa intermedia: en el primero hay dos cajas que representan máquinas —con su AMI, su agente y su hueco vacío pagado—; en el segundo no hay ninguna.

Modelo de recursos: CPU, memoria y almacenamiento efímero

En Fargate, cpu y memory a nivel de tarea son obligatorios y solo admiten combinaciones concretas. No se puede pedir 1 vCPU con 1 GB, ni 3 vCPU con lo que sea.

vCPU (cpu) Memoria válida (memory) Incremento Caso típico en MercadoFresco
256 (0,25) 512 MB, 1 GB, 2 GB fijo Sidecar, tarea puntual pequeña
512 (0,5) 1 a 4 GB 1 GB Trabajador de cola-mercadofresco-correo
1024 (1) 2 a 8 GB 1 GB La tienda y los trabajadores de pedidos
2048 (2) 4 a 16 GB 1 GB Carga nocturna hacia Redshift
4096 (4) 8 a 30 GB 1 GB Procesos de análisis puntuales
8192 (8) 16 a 60 GB 4 GB Rara vez: conviene repartir en más tareas
16384 (16) 32 a 120 GB 8 GB Casi nunca

Dos consecuencias prácticas:

  • La combinación se elige antes de conocer la carga real, y casi siempre mal. El método correcto es el del módulo 5: mirar CpuUtilized y MemoryUtilized de Container Insights durante dos semanas, tomar el percentil 95 y añadir un 30 % de margen. Sobredimensionar en Fargate cuesta dinero desde el primer segundo, porque no hay una instancia ya pagada donde escondan el exceso.
  • La granularidad castiga a las aplicaciones con poca CPU y mucha memoria. Una tarea que necesita 6 GB obliga a pedir al menos 1 vCPU, aunque use el 5 %. Cuando eso pasa mucho, conviene revisar si la aplicación debería ser una Lambda o si hay una fuga de memoria.

El almacenamiento efímero son 20 GB por omisión, ampliables hasta 200 GB con ephemeralStorage. Se cobra solo lo que exceda de los 20 GB gratuitos. Es un disco temporal que desaparece al terminar la tarea: sirve para descomprimir un fichero o para una caché local, nunca para estado. Cuando hace falta persistencia compartida, se monta EFS (02-02), que Fargate soporta de forma nativa.

{
  "family": "mercadofresco-carga-analitica",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "2048", "memory": "8192",
  "ephemeralStorage": { "sizeInGiB": 60 },
  "runtimePlatform": { "cpuArchitecture": "ARM64", "operatingSystemFamily": "LINUX" }
}

x86 y arm64: Graviton y el ahorro real

Fargate ejecuta tareas en dos arquitecturas: X86_64 y ARM64, esta última sobre procesadores AWS Graviton. El cambio se hace con un campo de la definición de tarea, pero exige que la imagen esté construida para esa arquitectura.

Aspecto x86_64 arm64 (Graviton)
Precio por vCPU-hora ~0,047 USD ~0,037 USD (-20 %)
Rendimiento Referencia Igual o mejor en cargas web, Python, Java, Node
Imagen La habitual Hay que construirla para linux/arm64
Compatibilidad Total Falla con binarios nativos solo x86 o ruedas de Python sin versión arm
Disponible en Fargate Spot

La construcción multiarquitectura se resuelve en CodeBuild con docker buildx:

docker buildx create --use --name multiarco
docker buildx build --platform linux/amd64,linux/arm64 \
  --tag "${URI}:${ETIQUETA}" --push .
# Publica un indice de manifiestos: ECR guarda las dos imagenes bajo la misma etiqueta
# y cada tarea descarga la que corresponde a su arquitectura.

Para MercadoFresco el ahorro es real y medible: la tienda son 2 tareas base más hasta 2 en el pico, de 1 vCPU y 2 GB. Pasar a Graviton baja el coste mensual del servicio de unos 85 USD a unos 68 USD, un 20 % sin tocar una línea de código de negocio. El requisito es probarlo: Luis construye la imagen para las dos arquitecturas, despliega arm64 en preproducción durante una semana, compara latencia y errores en el panel mercadofresco-produccion, y solo entonces cambia producción.

El riesgo concreto es el de las dependencias: una rueda de Python que solo publica binarios x86 obliga a compilar en la construcción —lo que la etapa constructor del Dockerfile de 10-01 ya permite— o a bloquear el cambio. Es preferible descubrirlo en CodeBuild que en un despliegue.

Diferencias operativas: sin SSH, sin hostPort, sin DaemonSet

Fargate impone restricciones que no son arbitrarias: se derivan de que el anfitrión no existe para ti.

Restricción Por qué Qué se hace en su lugar
Sin SSH ni docker exec No hay máquina a la que conectarse ECS Exec (a continuación)
Sin hostPort distinto del containerPort No hay puertos del anfitrión que mapear awsvpc da una IP por tarea; no hay conflicto
Solo modo de red awsvpc Cada tarea es su propia ENI Grupos de seguridad por tarea, grupo de destino de tipo ip
Sin DaemonSet ni tareas de tipo DAEMON No hay nodos donde poner uno por nodo Sidecars: FireLens, X-Ray, OTel dentro de cada tarea
Sin volúmenes del anfitrión ni privileged El anfitrión no es tuyo Volúmenes efímeros, EFS, y linuxParameters limitados
Sin GPU ni familias de instancia especiales No eliges la máquina EC2 sigue existiendo para esos casos
Sin awsvpcTrunking que gestionar No hay instancia con límite de ENI Desaparece una de las trampas caras de 10-01

La restricción que más incomoda al principio es la primera, y la segunda que más, la del DaemonSet: mucha gente ejecutaba un contenedor de recogida de registros por instancia. En Fargate ese patrón se sustituye por un sidecar dentro de cada tarea, lo que cuesta algo de CPU y memoria por tarea pero elimina la coordinación entre nodos.

ECS Exec: depurar sin servidor que tocar

ECS Exec abre una sesión de comandos dentro de un contenedor en ejecución usando AWS Systems Manager, sin puertos abiertos, sin claves SSH y sin bastión. Requiere tres cosas:

  1. El servicio o la tarea con --enable-execute-command.
  2. El rol de la tarea —no el de ejecución— con permisos de canal de SSM.
  3. El agente de SSM, que Fargate ya incluye a partir de la versión de plataforma 1.4.
{"Version": "2012-10-17", "Statement": [{
  "Effect": "Allow", "Resource": "*",
  "Action": ["ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel",
             "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel"]
}]}
aws ecs execute-command --cluster ecs-mercadofresco \
  --task 9f2c1b7a4e0d4a6f8b3c5d7e1a2b3c4d \
  --container tienda --interactive --command "/bin/sh"

Tres advertencias que hay que interiorizar antes de usarlo en producción:

  • Todo queda auditado en CloudTrail (05-03) con el evento ExecuteCommand: quién, cuándo, en qué tarea y con qué comando. Además se puede exigir que la sesión se registre en CloudWatch Logs o en S3 con executeCommandConfiguration en el clúster, cifrando con alias/mercadofresco-datos. En MercadoFresco, usar ECS Exec en producción dispara una notificación a alertas-mercadofresco: no está prohibido, está vigilado.
  • Los cambios hechos dentro de un contenedor no sobreviven. Editar un fichero de configuración en la tarea funciona hasta que la tarea se repone, y entonces desaparece silenciosamente. ECS Exec sirve para diagnosticar, no para arreglar.
  • Con readonlyRootFilesystem: true el sistema de archivos es de solo lectura, que es exactamente lo que se quiere: si necesitas escribir para depurar, la respuesta suele ser que falta un log.

En la práctica, el 90 % de lo que la gente iba a buscar por SSH está ya en CloudWatch Logs Insights o en X-Ray. ECS Exec es para el 10 % restante: comprobar una resolución DNS, verificar que una variable de entorno llegó como se esperaba o confirmar que el proceso escucha en el puerto correcto.

Registros con FireLens y Fluent Bit

El controlador awslogs de 10-01 sigue funcionando y es lo correcto por omisión. Cuando hace falta más —enrutar a varios destinos, filtrar antes de enviar, reescribir el formato o mandar copia a un sistema externo—, la respuesta en Fargate es FireLens: un sidecar de Fluent Bit que ECS configura automáticamente.

[
  {
    "name": "registro",
    "image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable",
    "essential": true,
    "firelensConfiguration": { "type": "fluentbit", "options": { "enable-ecs-log-metadata": "true" } },
    "cpu": 64, "memoryReservation": 128
  },
  {
    "name": "tienda",
    "image": "555566667777.dkr.ecr.eu-west-1.amazonaws.com/mercadofresco/tienda@sha256:9c1e...f4a2",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awsfirelens",
      "options": { "Name": "cloudwatch_logs", "region": "eu-west-1",
                   "log_group_name": "/ecs/mercadofresco-tienda", "log_stream_prefix": "tienda-",
                   "auto_create_group": "false" }
    }
  }
]

El coste de FireLens es un contenedor más por tarea, unos 64 CPU y 128 MB, que en un servicio de cuatro tareas es dinero real. La regla de MercadoFresco: awslogs por omisión, FireLens solo cuando haya un requisito que awslogs no cubra, como filtrar los registros de acceso con datos personales antes de que salgan de la tarea.

El arranque de tareas y el pico de los viernes

Este es el punto por el que existe la lección. Una tarea de Fargate atraviesa estas fases:

  1. Aprovisionamiento (PROVISIONING): se crea la ENI en la subred y se le asocia el grupo de seguridad. De 3 a 10 segundos.
  2. Descarga de la imagen (PENDING): se autentica en ECR y se descargan las capas que falten. De 5 a 25 segundos según el tamaño de la imagen y si hay endpoint de VPC.
  3. Arranque del contenedor: el ENTRYPOINT se ejecuta. De 1 a 5 segundos.
  4. Comprobaciones de estado: el ALB registra el destino y espera a los aciertos consecutivos. De 15 a 40 segundos según la configuración del grupo de destino.
Fase ASG con AMI (antes) Fargate (ahora)
Arranque de la máquina 60-75 s (BIOS, kernel, cloud-init) 0 s: no hay máquina
Preparación de red Incluida 3-10 s (ENI)
Obtención del artefacto Incluida en la AMI 5-25 s (imagen desde ECR)
Arranque de la aplicación 20-30 s 1-5 s
Registro y comprobaciones 20-40 s 15-40 s
Total hasta recibir tráfico ≈ 120 s ≈ 35-45 s

Lo que significa para el viernes a las 19:00 con 900 pedidos/hora es concreto: cuando la alarma de ALBRequestCountPerTarget supera el umbral, la capacidad nueva empieza a atender peticiones a los 40 segundos en lugar de a los dos minutos. Con la subida de tráfico medida en MercadoFresco —de 300 a 900 pedidos/hora en unos ocho minutos—, esos 80 segundos de diferencia son la frontera entre una latencia que sube un poco y una latencia que dispara mercadofresco-alb-latencia-alta.

Y hay una consecuencia menos obvia y más valiosa: con arranques de 40 segundos, se puede permitir un mínimo más bajo. Antes hacían falta 2 instancias siempre encendidas para absorber lo que el ASG tardaba en reaccionar. Con Fargate, el mínimo puede seguir siendo 2 tareas por disponibilidad —nunca menos, para sobrevivir a la pérdida de una AZ—, pero el margen extra que se mantenía «por si acaso» deja de ser necesario.

La palanca que reduce la fase 2 es el tamaño de la imagen y la ruta hasta ECR. La imagen de 220 MB de la tienda tarda unos 8 segundos desde un endpoint de VPC y unos 20 a través del NAT. Es otro argumento para el Dockerfile cuidado de 10-01 y para la sección siguiente.

Migrar el servicio de la tienda a Fargate

El cambio, sorprendentemente, es pequeño: la definición de tarea de 10-01 ya declaraba requiresCompatibilities: ["EC2", "FARGATE"] y modo awsvpc, precisamente para esto.

Paso 1: fijar CPU y memoria con datos, no con intuición. Container Insights lleva dos semanas midiendo. La consulta que Marta ejecuta:

aws cloudwatch get-metric-statistics --namespace ECS/ContainerInsights \
  --metric-name CpuUtilized --statistics Maximum p95 --period 300 \
  --dimensions Name=ServiceName,Value=svc-mercadofresco-tienda Name=ClusterName,Value=ecs-mercadofresco \
  --start-time 2026-07-15T00:00:00Z --end-time 2026-07-29T00:00:00Z

El percentil 95 de CPU es de 0,62 vCPU y el de memoria de 1,4 GB, con un máximo absoluto de 0,81 vCPU en el pico del viernes. La combinación válida más ajustada por encima de eso con margen es 1 vCPU y 2 GB. Bajar a 0,5 vCPU / 2 GB sería tentador —ahorra un 25 %— pero deja la tarea del viernes al 160 % de su CPU: la latencia se degradaría exactamente cuando importa.

Paso 2: crear el servicio con el proveedor de capacidad de Fargate.

aws ecs create-service --cluster ecs-mercadofresco --region eu-west-1 \
  --service-name svc-mercadofresco-tienda-fg --task-definition mercadofresco-tienda:24 \
  --desired-count 4 \
  --capacity-provider-strategy capacityProvider=FARGATE,weight=1,base=2 \
  --platform-version LATEST \
  --network-configuration 'awsvpcConfiguration={subnets=[snet-mercadofresco-app-a,snet-mercadofresco-app-b],
      securityGroups=[sg-mercadofresco-tienda],assignPublicIp=DISABLED}' \
  --load-balancers 'targetGroupArn=...targetgroup/tg-mercadofresco-tienda/abc123,containerName=tienda,containerPort=8080' \
  --health-check-grace-period-seconds 45 \
  --deployment-configuration '{"minimumHealthyPercent": 100, "maximumPercent": 200,
      "deploymentCircuitBreaker": {"enable": true, "rollback": true}}' \
  --enable-execute-command --propagate-tags SERVICE

Tres detalles del comando:

  • assignPublicIp=DISABLED con subredes privadas. Las tareas van en snet-mercadofresco-app-a y -b, sin IP pública. Esto es lo correcto y lo que obliga a la sección siguiente: sin IP pública, la tarea necesita una ruta hacia ECR, S3, CloudWatch Logs y Secrets Manager, y esa ruta es un NAT caro o unos endpoints baratos.
  • No hay --placement-strategy. Las estrategias de colocación son de EC2. Fargate reparte las tareas entre las subredes declaradas, de modo que la disponibilidad se obtiene declarando subredes de las dos AZ, no con una estrategia.
  • --platform-version LATEST. La versión de plataforma es el equivalente a la AMI del anfitrión, y la gestiona AWS. Fijarla a una versión concreta solo tiene sentido si una novedad rompe algo, y entonces es una medida temporal, no permanente.

Paso 3: convivencia y corte. Igual que en 10-01, el ALB reparte por pesos entre el servicio de EC2 y el de Fargate: 90/10, luego 50/50, luego 0/100 a lo largo de dos semanas, vigilando TargetResponseTime y HTTPCode_Target_5XX. El día que el peso llega a 100, el ASG del clúster de EC2 se borra y con él la AMI, la plantilla de lanzamiento y el ciclo de parcheo.

Endpoints de VPC: dejar de depender del NAT

Una tarea de Fargate en subred privada habla con varios servicios de AWS solo para arrancar. Si esa comunicación sale por el NAT, se paga dos veces: la hora del NAT y cada gigabyte procesado.

Endpoint Tipo Para qué Qué falla sin él
com.amazonaws.eu-west-1.ecr.api Interfaz Autenticación y metadatos de ECR CannotPullContainerError
com.amazonaws.eu-west-1.ecr.dkr Interfaz Protocolo de descarga de imágenes CannotPullContainerError
com.amazonaws.eu-west-1.s3 Pasarela Las capas de imagen viven en S3 La descarga se cuelga y expira
com.amazonaws.eu-west-1.logs Interfaz CloudWatch Logs La tarea arranca y no se ve ni un log
com.amazonaws.eu-west-1.secretsmanager Interfaz Resolver los secrets ResourceInitializationError
com.amazonaws.eu-west-1.ssm Interfaz Parameter Store Igual que el anterior
com.amazonaws.eu-west-1.ssmmessages Interfaz ECS Exec ECS Exec no conecta
com.amazonaws.eu-west-1.kms Interfaz Descifrar secretos e imagen Falla el descifrado

El de S3 es el que más se olvida y el más importante: es de tipo pasarela, es gratuito y sin él la descarga de imágenes no funciona aunque los dos de ECR estén puestos. MercadoFresco ya tenía vpce-mercadofresco-s3 desde el módulo 3, así que ese está resuelto.

for SERVICIO in ecr.api ecr.dkr logs secretsmanager ssm ssmmessages kms; do
  aws ec2 create-vpc-endpoint --vpc-id vpc-mercadofresco \
    --vpc-endpoint-type Interface \
    --service-name "com.amazonaws.eu-west-1.${SERVICIO}" \
    --subnet-ids snet-mercadofresco-app-a snet-mercadofresco-app-b \
    --security-group-ids sg-mercadofresco-endpoints \
    --private-dns-enabled \
    --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Proyecto,Value=mercadofresco},
        {Key=Entorno,Value=produccion},{Key=Componente,Value=endpoints},
        {Key=Propietario,Value=plataforma},{Key=CentroCoste,Value=tecnologia}]'
done

El cálculo del ahorro para MercadoFresco, con 4,8 despliegues por semana, reposiciones de tareas y escalado diario:

Concepto Con NAT Con endpoints de interfaz
Descarga de imágenes ~55 GB/mes × 0,045 USD = 2,50 USD Tráfico por el endpoint: ~0,55 USD
Registros y llamadas a API ~30 GB/mes × 0,045 = 1,35 USD ~0,30 USD
Coste fijo 2 NAT × 0,045 USD/h ≈ 66 USD/mes 7 endpoints × 2 AZ × 0,011 USD/h ≈ 112 USD/mes
Total ≈ 70 USD/mes ≈ 113 USD/mes

Y aquí conviene ser honesto en lugar de repetir el eslogan: con siete endpoints en dos AZ, los endpoints salen más caros que el NAT en una arquitectura del tamaño de MercadoFresco. El argumento a favor no es solo el precio, son otros tres: el tráfico no sale de la red de AWS, lo que es un requisito de seguridad defendible ante una auditoría; el arranque de tareas es más rápido y más predecible; y el NAT deja de ser un punto único de fallo para el arranque. La decisión de Marta es intermedia y razonada: endpoints para ECR, S3 y Logs —los del camino crítico de arranque, que son cuatro y cuestan unos 64 USD— y mantener el NAT para el resto, sabiendo que en el módulo 11 se revisará si compensa.

Autoescalado: seguimiento, pasos y programación

Los tres tipos de política se combinan, y cada uno resuelve un problema distinto.

Tipo Cómo decide Fortaleza Debilidad
Seguimiento de destino Mantiene una métrica en un valor Simple, se autorregula Reacciona después del cambio
Escalado por pasos Umbrales con saltos distintos Respuesta agresiva ante saltos grandes Hay que calibrar los escalones a mano
Escalado programado Reloj Actúa antes de que llegue la carga Ciego a lo inesperado

La configuración completa de MercadoFresco usa los tres. El seguimiento de destino sobre ALBRequestCountPerTarget de 10-01 se mantiene como base. Encima, el escalado programado del viernes, que es la pieza que faltaba:

DESTINO="--service-namespace ecs --scalable-dimension ecs:service:DesiredCount \
  --resource-id service/ecs-mercadofresco/svc-mercadofresco-tienda-fg"

# 16:45, quince minutos antes del pico: subir el minimo a 6 tareas.
aws application-autoscaling put-scheduled-action $DESTINO \
  --scheduled-action-name pico-viernes-inicio \
  --schedule "cron(45 16 ? * FRI *)" --timezone "Europe/Madrid" \
  --scalable-target-action MinCapacity=6,MaxCapacity=20

# 21:30, media hora despues del pico: devolver el minimo a 2.
aws application-autoscaling put-scheduled-action $DESTINO \
  --scheduled-action-name pico-viernes-fin \
  --schedule "cron(30 21 ? * FRI *)" --timezone "Europe/Madrid" \
  --scalable-target-action MinCapacity=2,MaxCapacity=20

Tres decisiones de diseño en esas ocho líneas:

  • Se programa el mínimo, no el número deseado. Fijar DesiredCount desactivaría de hecho el seguimiento de destino durante la ventana. Subiendo el mínimo, la capacidad está garantizada y el seguimiento sigue pudiendo crecer por encima si el viernes es mejor de lo previsto.
  • --timezone "Europe/Madrid". Sin él, el cron es UTC y en verano el pico se anticipa una hora respecto a la acción programada. Es un error que solo aparece al cambiar la hora, en octubre, cuando nadie recuerda haber tocado nada.
  • Quince minutos antes y media hora después. Antes, para que las tareas estén sanas cuando llegue el tráfico; después, con generosidad, porque bajar demasiado pronto es lo que provoca la oscilación que se ve como picos de latencia a las 21:05.

El coste de esa ventana es fácil de calcular y de defender: 4 tareas extra × 5 horas × 4,3 viernes × 0,057 USD/h ≈ 4,9 USD al mes. Es probablemente el mejor dinero que gasta MercadoFresco.

Fargate Spot y los trabajadores de la cola

Fargate Spot ejecuta tareas sobre capacidad sobrante de AWS con un descuento cercano al 70 %, a cambio de que AWS pueda reclamarla. Cuando lo hace:

  1. Emite un evento de cambio de estado a EventBridge y envía SIGTERM al contenedor.
  2. Espera el stopTimeout de la definición de tarea, con un máximo de dos minutos.
  3. Envía SIGKILL y la tarea desaparece.

Dos minutos de aviso son mucho o poco según la carga:

Carga de MercadoFresco ¿Fargate Spot? Razón
Tienda (svc-mercadofresco-tienda-fg) No Es tráfico de usuario: una interrupción durante el pico del viernes es exactamente lo que estamos evitando
Trabajadores de cola-mercadofresco-pedidos Sí, parcialmente El mensaje vuelve a la cola al expirar la visibilidad y otro trabajador lo procesa; con idempotencia (07-05) no hay duplicados
Trabajadores de -correo, -almacen, -analitica Sí, casi todo Tolerancia máxima: nada es síncrono ni urgente
Carga nocturna a Redshift Se reintenta; si tarda diez minutos más, no lo nota nadie
Tareas puntuales de migración de datos No Una interrupción a mitad puede dejar estado inconsistente

La forma correcta de aplicarlo no es «todo Spot» sino un reparto por proveedores de capacidad, que garantiza un suelo bajo demanda:

aws ecs update-service --cluster ecs-mercadofresco \
  --service svc-mercadofresco-trabajadores \
  --capacity-provider-strategy \
      capacityProvider=FARGATE,weight=1,base=1 \
      capacityProvider=FARGATE_SPOT,weight=4,base=0 \
  --force-new-deployment

base=1 en el proveedor bajo demanda significa que la primera tarea siempre es estable; el weight reparte todas las demás en proporción 1 a 4. Con 10 tareas: 1 base + 9 repartidas como 1,8 bajo demanda y 7,2 Spot, es decir, aproximadamente 3 bajo demanda y 7 Spot.

Escenario de trabajadores Composición Coste mensual aproximado
Todo bajo demanda 10 × (0,5 vCPU / 1 GB) ≈ 190 USD
Reparto 1:4 con base 1 3 bajo demanda + 7 Spot ≈ 97 USD
Todo Spot (no recomendado) 10 Spot ≈ 57 USD

El ahorro del reparto 1:4 es de casi el 50 % y mantiene un suelo que sobrevive a una retirada masiva de capacidad Spot. El requisito técnico que lo hace seguro es el del ejercicio de 10-01: manejador de SIGTERM que termina el mensaje en curso y no pide otro, con stopTimeout: 120 y tiempo de visibilidad de la cola holgado.

Tareas puntuales y programadas con EventBridge Scheduler

No todo es un servicio. La carga nocturna que alimenta wg-mercadofresco-analitica (06-04) se ejecuta una vez al día y termina. En EC2 eso era una instancia encendida o un cron en una máquina que alguien tenía que cuidar; en Fargate es una tarea puntual programada.

aws scheduler create-schedule --name mercadofresco-carga-analitica-nocturna \
  --schedule-expression "cron(0 3 * * ? *)" --schedule-expression-timezone "Europe/Madrid" \
  --flexible-time-window '{"Mode": "FLEXIBLE", "MaximumWindowInMinutes": 30}' \
  --target '{
    "Arn": "arn:aws:ecs:eu-west-1:111122223333:cluster/ecs-mercadofresco",
    "RoleArn": "arn:aws:iam::111122223333:role/rol-scheduler-mercadofresco",
    "EcsParameters": {
      "TaskDefinitionArn": "arn:aws:ecs:eu-west-1:111122223333:task-definition/mercadofresco-carga-analitica:7",
      "LaunchType": "FARGATE", "TaskCount": 1,
      "CapacityProviderStrategy": [{"capacityProvider": "FARGATE_SPOT", "weight": 1}],
      "NetworkConfiguration": {"awsvpcConfiguration": {
        "Subnets": ["snet-mercadofresco-app-a", "snet-mercadofresco-app-b"],
        "SecurityGroups": ["sg-mercadofresco-tienda"], "AssignPublicIp": "DISABLED"}}
    },
    "RetryPolicy": {"MaximumRetryAttempts": 2, "MaximumEventAgeInSeconds": 3600},
    "DeadLetterConfig": {"Arn": "arn:aws:sqs:eu-west-1:111122223333:mercadofresco-pedidos-fallidos"}
  }'

Lo relevante de esa configuración: la ventana flexible de 30 minutos deja que AWS elija el momento dentro de la ventana, lo que reduce la contención y encaja perfectamente con Spot; la política de reintentos convierte un fallo transitorio en un reintento automático; y la DLQ garantiza que un fallo persistente deja rastro en lugar de desaparecer. Es el mismo patrón que 07-05 aplicado a una tarea programada.

Para una ejecución puntual —una migración, una reparación de datos— basta con aws ecs run-task con la misma configuración de red y --launch-type FARGATE. Si el trabajo tiene varios pasos con dependencias entre ellos, la herramienta correcta es Step Functions (07-04), que puede lanzar tareas de ECS y esperar a que terminen.

Integración en el pipeline y blue/green

El pipeline-mercadofresco-tienda del módulo 8 cambia poco: donde antes había un despliegue de CodeDeploy sobre un ASG, ahora hay un despliegue sobre un servicio de ECS.

graph LR
  G["GitHub<br/>mercadofresco-tienda<br/>conn-mercadofresco-github"] --> B["CodeBuild<br/>build-mercadofresco-tienda<br/>docker build + push"]
  B --> E["ECR 555566667777<br/>mercadofresco/tienda:v1.7.0-c4d8e12"]
  E --> R["Replicacion<br/>a 111122223333"]
  R --> P["CodePipeline<br/>despliegue"]
  P --> Q["Lambda<br/>mercadofresco-puerta-calidad<br/>escaneo + pruebas"]
  Q --> D["CodeDeploy blue/green<br/>app-mercadofresco-tienda"]
  D --> V["tg-mercadofresco-verde<br/>puerto de pruebas 8443"]
  V --> S["build-mercadofresco-humo"]
  S --> T["tg-mercadofresco-tienda<br/>trafico real"]

El blue/green de CodeDeploy sobre ECS funciona distinto que sobre EC2, y la diferencia es una mejora: CodeDeploy crea un conjunto de tareas nuevo completo con la revisión nueva, lo registra en tg-mercadofresco-verde, permite ejecutar las pruebas de humo contra el puerto de pruebas del ALB sin tráfico real, y solo entonces desplaza el oyente de producción hacia el grupo verde. El conjunto azul se mantiene durante el tiempo de espera de terminación, de modo que una reversión es un cambio de oyente: segundos.

# appspec.yaml para CodeDeploy sobre ECS
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>          # lo sustituye el pipeline
        LoadBalancerInfo:
          ContainerName: "tienda"
          ContainerPort: 8080
        PlatformVersion: "LATEST"
Hooks:
  - AfterAllowTestTraffic: "arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-puerta-calidad"

Con la configuración de despliegue canaria ECSCanary10Percent5Minutes, el 10 % del tráfico va al conjunto verde durante 5 minutos; si mercadofresco-alb-latencia-alta o mercadofresco-pedidos-fallidos saltan en ese intervalo, CodeDeploy revierte solo. Es la mejora sobre el circuit breaker de 10-01: aquel detecta tareas que no arrancan; este detecta tareas que arrancan y responden mal.

Las métricas DORA de MercadoFresco mejoran donde era de esperar: la restauración pasa de 4 minutos a menos de 1, porque revertir ya no significa desplegar nada, solo mover un oyente.

El servicio en CDK con un constructo L3

Todo lo anterior son unas veinte líneas en el proyecto infra-cdk/ de 09-02, gracias a un constructo de nivel 3 que crea el ALB, el grupo de destino, el servicio, la definición de tarea, los roles y el grupo de registros.

import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsp from 'aws-cdk-lib/aws-ecs-patterns';

const servicio = new ecsp.ApplicationLoadBalancedFargateService(this, 'Tienda', {
  cluster,
  serviceName: `svc-mercadofresco-tienda-${config.nombre}`,
  cpu: 1024,
  memoryLimitMiB: 2048,
  desiredCount: config.nombre === 'produccion' ? 4 : 2,
  runtimePlatform: { cpuArchitecture: ecs.CpuArchitecture.ARM64 },
  taskImageOptions: {
    image: ecs.ContainerImage.fromEcrRepository(repositorio, etiqueta),   // por digest en produccion
    containerPort: 8080,
    environment: { ENTORNO: config.nombre, COLA_PEDIDOS: cola.queueName },
    secrets: {
      BD_CONTRASENA: ecs.Secret.fromSecretsManager(secretoRds, 'password'),
      ENDPOINT_CACHE: ecs.Secret.fromSsmParameter(parametroCache),
    },
    logDriver: ecs.LogDrivers.awsLogs({ streamPrefix: 'tienda', logRetention: 30 }),
  },
  taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
  publicLoadBalancer: true,
  circuitBreaker: { rollback: true },
  enableExecuteCommand: config.nombre !== 'produccion',
});

servicio.targetGroup.configureHealthCheck({
  path: '/salud', healthyThresholdCount: 2, interval: cdk.Duration.seconds(15),
});

// Permisos minimos con los metodos grant*: incluyen los de KMS que casi nadie recuerda.
cola.grantSendMessages(servicio.taskDefinition.taskRole);
tablaCarritos.grantReadWriteData(servicio.taskDefinition.taskRole);

const escalado = servicio.service.autoScaleTaskCount({ minCapacity: 2, maxCapacity: 20 });
escalado.scaleOnRequestCount('PorPeticiones', {
  requestsPerTarget: 120,
  targetGroup: servicio.targetGroup,
  scaleOutCooldown: cdk.Duration.seconds(30),
  scaleInCooldown: cdk.Duration.minutes(5),
});
escalado.scaleOnSchedule('PicoViernes', {
  schedule: appscaling.Schedule.cron({ weekDay: 'FRI', hour: '16', minute: '45' }),
  minCapacity: 6,
});

La advertencia de 09-02 sigue vigente: cdk synth y leer la plantilla la primera vez. Este constructo toma decisiones por ti —crea un ALB público, abre su grupo de seguridad al 0.0.0.0/0 en el puerto 80, crea un grupo de registros con retención por omisión— y hay que verificar que coinciden con lo que quieres. En producción, MercadoFresco añade el certificado y el redirectHTTP: true para no servir nunca por HTTP.

Fíjate también en enableExecuteCommand: config.nombre !== 'produccion': ECS Exec activado en desarrollo y preproducción, y desactivado en producción salvo activación explícita durante un incidente. Es una decisión de gobierno expresada en una línea de infraestructura.

Fargate frente a EC2 frente a Lambda

Los tres ejecutan código sin que tú compres hardware, y la elección se hace por criterios objetivos.

Criterio EC2 Fargate Lambda
Unidad La instancia La tarea (contenedor) La invocación
Modelo de coste Por instancia-hora, llena o vacía Por vCPU-hora y GB-hora de la tarea Por invocación y GB-segundo, con capa gratuita
Coste con carga constante 24/7 El más bajo con buen empaquetado y Savings Plans Intermedio El más alto
Coste con carga intermitente El más alto (paga el ocio) Intermedio El más bajo (no paga el ocio)
Arranque en frío 2 min (instancia nueva) 35-45 s 100 ms - 2 s
Duración máxima Ilimitada Ilimitada 15 minutos
Memoria máxima Hasta TB 120 GB 10 GB
Estado local Disco persistente Efímero (o EFS) Efímero (o EFS)
Control del entorno Total: kernel, GPU, familias Imagen y recursos Runtime gestionado o imagen
Trabajo operativo Alto: AMI, parches, escalado Bajo: solo la imagen Mínimo
Portabilidad AMI, ligada a AWS Imagen OCI: corre en cualquier sitio Ligada al modelo de Lambda
Cuándo elegirlo GPU, licencias por núcleo, cargas enormes y constantes, requisitos de kernel Servicios de larga duración contenerizados: la opción por omisión Eventos, picos irregulares, integraciones cortas

La decisión razonada de MercadoFresco, componente a componente:

Componente Elección Por qué
Tienda Fargate bajo demanda Proceso de larga duración, tráfico continuo, arranque de 40 s suficiente, sin ocio que pagar
Trabajadores de pedidos Fargate, 1 bajo demanda + 4 Spot Tolerante a interrupción con idempotencia; -50 % de coste
Trabajadores de correo y almacén Fargate Spot Nada urgente ni síncrono
mercadofresco-generar-miniaturas Lambda Se dispara por evento de S3, dura segundos, con largos periodos sin trabajo
-cobrar-pago, -reservar-stock, -asignar-reparto Lambda Pasos cortos de la máquina mercadofresco-procesar-pedido; contenerizarlos sería un retroceso
Carga nocturna a Redshift Tarea puntual en Fargate Spot Dura 25 minutos: pasa de los 15 de Lambda
Nada EC2 Ningún componente necesita ya kernel, GPU ni licencia por núcleo

El criterio que resuelve la mayoría de las dudas: si el proceso vive esperando peticiones, es Fargate; si el proceso se despierta ante un evento y muere, es Lambda; si necesitas el control de la máquina, es EC2. Y el criterio económico, que se afina en el módulo 11: por debajo de una utilización sostenida alta, Fargate gana a EC2 porque no paga el ocio; por encima, EC2 con Savings Plans (11-05) recupera la ventaja.

App Runner: un escalón más de gestión

AWS App Runner va un paso más allá: se le da una imagen de contenedor o un repositorio de código, y crea el servicio, el balanceador, el certificado, el dominio y el autoescalado, incluido escalar a cero. No hay VPC que diseñar, ni grupo de destino, ni definición de tarea.

Lo relevante es dónde deja de encajar, y por eso MercadoFresco no lo usa: da mucho menos control de red —el acceso a la VPC exige un conector y sigue habiendo limitaciones—, no admite sidecars ni tareas multicontenedor, el modelo de despliegue no ofrece el blue/green canario de CodeDeploy, y su coste por unidad es superior al de Fargate. Es una opción excelente para un servicio HTTP sencillo o un prototipo, y un mal encaje para una tienda con Aurora en subred privada, colas, caché y un pipeline con puerta de calidad. Se menciona porque es la pregunta que siempre aparece: la respuesta es que la escala de gestión no es un ranking, es un intercambio entre comodidad y control.

Coste comparado y limpieza

El cálculo completo del servicio de la tienda, con precios aproximados de eu-west-1:

Fargate x86, tarea de 1 vCPU y 2 GB: 1 × 0,04656 + 2 × 0,00511 = 0,05678 USD/hora.

Escenario Cálculo Mensual
2 tareas base 24/7 2 × 0,05678 × 730 82,9 USD
+ ventana del viernes (4 extra × 5 h × 4,3) 4 × 5 × 4,3 × 0,05678 4,9 USD
Total Fargate x86 ≈ 88 USD
Total Fargate arm64 (-20 %) ≈ 70 USD
ASG anterior: 2 × m5.large bajo demanda 2 × 0,107 × 730 156 USD
+ EBS 2 × 30 GB gp3 2,4 USD
Total EC2 anterior ≈ 158 USD

MercadoFresco pasa de unos 158 USD a unos 70 USD al mes en la tienda, con arm64, y elimina de paso el trabajo de mantener AMI y parches. Con los trabajadores en Spot, el ahorro total del módulo ronda los 150 USD mensuales.

Con dos matices honestos, porque la comparación no es completa sin ellos. El primero: las instancias EC2 podrían comprarse con Savings Plans y bajar un 30-40 %, lo que estrecha mucho la diferencia —y Fargate también admite Compute Savings Plans, tema de 11-05—. El segundo: si el clúster de EC2 estuviera muy bien empaquetado, con la tienda y los trabajadores compartiendo instancias al 85 % de ocupación, EC2 sería competitivo. El problema es que ese empaquetado hay que conseguirlo y mantenerlo, y esa es exactamente la clase de trabajo que Fargate elimina.

Limpieza, en orden estricto:

# 1. Acciones programadas y politicas de escalado: si no, reponen tareas
aws application-autoscaling delete-scheduled-action $DESTINO --scheduled-action-name pico-viernes-inicio
aws application-autoscaling deregister-scalable-target $DESTINO
# 2. El servicio a cero y borrado
aws ecs update-service --cluster ecs-mercadofresco --service svc-mercadofresco-tienda-fg --desired-count 0
aws ecs delete-service --cluster ecs-mercadofresco --service svc-mercadofresco-tienda-fg --force
# 3. Programaciones de EventBridge Scheduler
aws scheduler delete-schedule --name mercadofresco-carga-analitica-nocturna
# 4. LOS ENDPOINTS DE INTERFAZ: cuestan por hora aunque no pase trafico
aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=vpc-mercadofresco \
  --query 'VpcEndpoints[?VpcEndpointType==`Interface`].VpcEndpointId' --output text \
  | xargs -r aws ec2 delete-vpc-endpoints --vpc-endpoint-ids
# 5. El cluster y el grupo de registros
aws ecs delete-cluster --cluster ecs-mercadofresco
aws logs delete-log-group --log-group-name /ecs/mercadofresco-tienda

El paso 4 es el residuo caro de esta lección: siete endpoints de interfaz en dos AZ cuestan unos 112 USD al mes aunque no pase un solo byte. Un endpoint olvidado es la fuga silenciosa más habitual después de las direcciones IP elásticas sin asociar.

Errores Comunes y Consejos

Error: pedir una combinación de CPU y memoria inválida. RegisterTaskDefinition falla con un mensaje poco claro. Consejo: ten la tabla a mano; con el CDK, memoryLimitMiB y cpu se validan en la síntesis, que es antes.

Error: dimensionar por intuición. Sobredimensionar en Fargate se paga desde el primer segundo. Consejo: percentil 95 de Container Insights durante dos semanas, más un 30 %, y revisar al mes.

Error: subredes privadas sin endpoints ni NAT. La tarea se queda en PROVISIONING y muere con CannotPullContainerError. Consejo: antes de migrar, comprueba la ruta a ECR api, ECR dkr, S3, Logs y Secrets Manager; el de S3 es de pasarela, es gratis y es el que más se olvida.

Error: creer que sin NAT es siempre más barato. Siete endpoints de interfaz en dos AZ superan al NAT en una arquitectura mediana. Consejo: pon endpoints solo para el camino crítico de arranque y decide el resto con datos del módulo 11.

Error: programar DesiredCount en lugar del mínimo. Desactiva de hecho el seguimiento de destino durante la ventana. Consejo: programa MinCapacity y deja que la política reactiva siga trabajando por encima.

Error: olvidar --timezone en el escalado programado. El pico se desplaza una hora al cambiar la hora. Consejo: Europe/Madrid explícito en toda acción programada, y revisar la última semana de octubre.

Error: poner la tienda en Fargate Spot para ahorrar. Una interrupción con dos minutos de aviso durante el pico del viernes es justo lo que el curso lleva diez módulos evitando. Consejo: Spot solo donde la interrupción se reintenta sola, y siempre con base bajo demanda.

Error: no manejar SIGTERM en los trabajadores. Con Spot, cada interrupción se convierte en un mensaje reprocesado o perdido. Consejo: manejador que termina el mensaje en curso y no pide otro, stopTimeout: 120 y tiempo de visibilidad holgado.

Error: usar ECS Exec para arreglar algo. El cambio desaparece en la siguiente reposición y deja un sistema que nadie puede reproducir. Consejo: ECS Exec para diagnosticar; el arreglo va por el pipeline. Y en producción, con notificación a alertas-mercadofresco.

Error: cambiar a arm64 sin probar. Una rueda de Python sin binario arm rompe el arranque. Consejo: docker buildx con las dos plataformas, una semana en preproducción y comparación de latencia antes de tocar producción.

Consejo: fija --platform-version LATEST y no la ancles. Anclar una versión de plataforma es renunciar a las mejoras y a los parches, que es justo lo que has venido a comprar.

Consejo: mantén minimumHealthyPercent: 100 en producción. Con Fargate ya no hay un hueco de instancia que limite el maximumPercent, así que 100/200 no cuesta capacidad reservada, solo unos minutos de doble facturación por despliegue.

Consejo: pon una alarma sobre RunningTaskCount frente a DesiredTaskCount. Es la señal más temprana de un problema de arranque, de cuota de Fargate o de agotamiento de IP en la subred.

Ejercicios

Ejercicio 1: dimensionar y decidir el reparto de capacidad

Los trabajadores de cola-mercadofresco-pedidos procesan cada mensaje en 12 segundos de media, con un pico de 900 mensajes/hora los viernes de 17:00 a 21:00 y unos 150 mensajes/hora el resto del tiempo. Container Insights indica que cada trabajador usa el percentil 95 de 0,31 vCPU y 780 MB. El acuerdo de servicio interno es que ningún pedido espere más de 3 minutos en la cola. Diseña: (a) la combinación de CPU y memoria de la tarea, justificada; (b) cuántas tareas hacen falta en el pico y fuera de él, con el cálculo; (c) la política de autoescalado completa, indicando qué métrica usas y por qué no la CPU; (d) el reparto entre FARGATE y FARGATE_SPOT con su base y sus weight, y qué pasa si AWS retira toda la capacidad Spot durante el pico; y (e) el coste mensual aproximado de tu diseño.

Ejercicio 2: la migración que se quedó en PROVISIONING

Luis migra el servicio de la tienda a Fargate en preproducción. Las tareas se quedan varios minutos en PROVISIONING y terminan con ResourceInitializationError: unable to pull secrets or registry auth. Comprueba lo siguiente y todo parece correcto: el rol de ejecución tiene los permisos de ECR y de Secrets Manager, la definición de tarea es la misma que funcionaba en EC2, y las subredes son snet-mercadofresco-app-a y -b. Diagnostica (a) las tres causas de red posibles, en orden de probabilidad, con la comprobación concreta de cada una; (b) por qué el mismo rol y la misma definición sí funcionaban en el tipo de lanzamiento EC2; (c) qué endpoint de VPC concreto arreglaría el caso más probable y por qué su tipo importa; y (d) qué dos comprobaciones añadirías al pipeline para que este fallo no vuelva a llegar a un despliegue.

Ejercicio 3: la propuesta de pasarlo todo a Lambda

Sara vuelve de una conferencia con una propuesta: eliminar Fargate y pasar toda la tienda a Lambda detrás de API Gateway, «porque solo se paga por petición y escala a cero». Aporta el dato de que la tienda recibe unos 300 pedidos/hora fuera de pico y que a las 4 de la mañana no hay tráfico. Responde con criterio técnico y económico: (a) tres razones técnicas concretas por las que la tienda de MercadoFresco no encaja bien en Lambda; (b) el cálculo aproximado que compara el coste de ambas opciones con el tráfico real; (c) qué parte de su propuesta sí tiene razón y dónde ya se está aplicando en la arquitectura actual; (d) qué medirías antes de descartarla del todo; y (e) cómo se lo explicarías en una frase que no suene a «no».

Soluciones

Solución 1

(a) La combinación. El percentil 95 es 0,31 vCPU y 780 MB. Añadiendo un 30 % de margen: 0,40 vCPU y 1,01 GB. La combinación válida inmediatamente superior es 512 (0,5 vCPU) con 1 GB de memoria. Subir a 1 vCPU duplicaría el coste para un 20 % de CPU sin usar, y bajar a 0,25 vCPU no es posible porque no llega. Coste por tarea: 0,5 × 0,04656 + 1 × 0,00511 = 0,0279 USD/hora.

(b) Cuántas tareas. Un trabajador procesa 3600 / 12 = 300 mensajes/hora. Fuera de pico, 150 mensajes/hora exigen 1 tarea, pero el mínimo debe ser 2 por disponibilidad: una sola tarea significa cero capacidad mientras se repone. En el pico, 900 mensajes/hora exigen 900 / 300 = 3 tareas para ir al día, y ese es exactamente el punto donde la cola no crece pero tampoco se recupera de un retraso. Para cumplir los 3 minutos con margen y absorber la variabilidad se usan 5 tareas en el pico, que dan 1500 mensajes/hora de capacidad y permiten drenar un retraso acumulado.

(c) La política de autoescalado. La CPU no sirve: un trabajador que consume mensajes está siempre igual de ocupado, tenga la cola 10 o 10.000 mensajes, así que la CPU no refleja el retraso. La métrica correcta es el retraso por tarea, que se calcula con una métrica matemática de CloudWatch: ApproximateNumberOfMessagesVisible / RunningTaskCount. Con un objetivo de 3 minutos y 300 mensajes/hora por tarea, el destino es 300 / 60 × 3 = 15 mensajes por tarea.

{"TargetValue": 15.0, "ScaleOutCooldown": 60, "ScaleInCooldown": 300,
 "CustomizedMetricSpecification": {"Metrics": [
   {"Id": "visibles", "MetricStat": {"Metric": {"Namespace": "AWS/SQS",
      "MetricName": "ApproximateNumberOfMessagesVisible",
      "Dimensions": [{"Name": "QueueName", "Value": "cola-mercadofresco-pedidos"}]},
      "Stat": "Average"}, "ReturnData": false},
   {"Id": "tareas", "MetricStat": {"Metric": {"Namespace": "ECS/ContainerInsights",
      "MetricName": "RunningTaskCount",
      "Dimensions": [{"Name": "ServiceName", "Value": "svc-mercadofresco-trabajadores"},
                     {"Name": "ClusterName", "Value": "ecs-mercadofresco"}]},
      "Stat": "Average"}, "ReturnData": false},
   {"Id": "retraso", "Expression": "visibles / MAX([tareas, 1])", "ReturnData": true}]}}

Se añade una acción programada para los viernes que sube MinCapacity a 5 a las 16:45 y la devuelve a 2 a las 21:30, con --timezone "Europe/Madrid": subir por reloj evita los primeros minutos de retraso que la política reactiva no puede evitar por definición.

(d) El reparto de capacidad. capacityProvider=FARGATE,weight=1,base=2 más capacityProvider=FARGATE_SPOT,weight=3,base=0. Las dos tareas del suelo son estables, y de las tres extra del pico aproximadamente 0,75 van bajo demanda y 2,25 a Spot. Si AWS retira toda la capacidad Spot durante el pico, quedan las 2 tareas bajo demanda con capacidad para 600 mensajes/hora frente a 900 entrantes: la cola crece unos 300 mensajes/hora y el acuerdo de 3 minutos se incumple en unos veinte minutos. La mitigación tiene dos partes: la política de autoescalado detecta el retraso y pide tareas nuevas, que el proveedor bajo demanda sí puede servir —el reparto por weight se aplica a las tareas nuevas, y con Spot no disponible ECS lanza bajo demanda—; y una alarma sobre ApproximateAgeOfOldestMessage avisa a alertas-mercadofresco si el mensaje más antiguo supera los 3 minutos. Lo que no hay que hacer es poner base=0: el suelo estable es lo que convierte una retirada de Spot en una degradación en vez de una caída.

(e) Coste mensual. Fuera de pico: 2 tareas × 0,0279 × (730 − 17) ≈ 39,8 USD. En el pico (4 h × 4,3 viernes = 17,2 h): 2 bajo demanda × 0,0279 × 17,2 ≈ 0,96 USD, más 3 tareas extra mayoritariamente Spot ≈ 3 × 0,0084 × 17,2 ≈ 0,43 USD. Total ≈ 41 USD al mes, frente a los 156 USD del ASG dedicado de trabajadores que había antes.

Solución 2

(a) Las tres causas de red, por probabilidad.

  1. Faltan los endpoints de interfaz de ECR y Secrets Manager, y no hay NAT en la subred de preproducción. Es la causa más probable porque el error menciona registry auth y secrets, que son exactamente las dos primeras llamadas salientes de una tarea. Comprobación: aws ec2 describe-route-tables sobre las subredes -app-a y -b buscando la ruta 0.0.0.0/0, y aws ec2 describe-vpc-endpoints filtrando por la VPC.
  2. El grupo de seguridad de los endpoints no permite la entrada desde el de las tareas. Los endpoints de interfaz son ENI con grupo de seguridad propio: si sg-mercadofresco-endpoints no acepta el puerto 443 desde sg-mercadofresco-tienda, existen pero no se pueden usar. Comprobación: reglas de entrada de sg-mercadofresco-endpoints.
  3. El DNS privado del endpoint está desactivado. Sin --private-dns-enabled, el nombre secretsmanager.eu-west-1.amazonaws.com sigue resolviendo a la IP pública y el tráfico intenta salir por donde no hay salida. Comprobación: el campo PrivateDnsEnabled del endpoint.

(b) Por qué sí funcionaba en EC2. Porque en el tipo de lanzamiento EC2, quien descarga la imagen y resuelve los secretos es el agente de ECS de la instancia, que usa la ruta de red de la instancia y su grupo de seguridad. Si esas instancias estaban en una subred con NAT o tenían un grupo de seguridad más permisivo, todo funcionaba. Al pasar a Fargate, la conectividad pasa a depender de la ENI de la tarea, con las subredes y el grupo de seguridad declarados en networkConfiguration. Es el mismo cambio de frontera del que hablaba awsvpc en 10-01, ahora visible: la red ya no es de la máquina, es de la tarea.

(c) El endpoint concreto y por qué importa su tipo. Para el caso más probable hacen falta tres: ecr.api, ecr.dkr y secretsmanager, los tres de interfaz (ENI privada con grupo de seguridad, se paga por hora y por GB). Pero el que suele faltar y no da la cara es s3, que es de pasarela: no es una ENI sino una entrada en la tabla de rutas, es gratuito, y sin él las capas de la imagen —que se almacenan en S3— no se descargan aunque los dos endpoints de ECR estén perfectos. Confundir los dos tipos lleva a poner un endpoint de interfaz para S3, que funciona pero cuesta dinero innecesariamente en este caso.

(d) Dos comprobaciones en el pipeline. La primera, una prueba de despliegue en preproducción con la misma configuración de red que producción: el fallo aparece donde debe. La segunda, una regla de AWS Config (05-04) o una prueba del CDK (09-02) que verifique que toda subred usada por un servicio de ECS tiene, o bien ruta a un NAT, o bien los cinco endpoints del camino crítico. Es una invariante de arquitectura y por tanto se prueba como código, no se recuerda. Como refuerzo, la comprobación de que el circuit breaker está activado con rollback: true habría convertido este incidente en una reversión automática en lugar de en tareas girando en vacío.

Solución 3

(a) Tres razones técnicas.

  1. La conexión a Aurora. Cada Lambda concurrente abre su propia conexión, y con 300 pedidos/hora en picos de concurrencia eso agota el pool de aurora-mercadofresco-pedidos rápidamente. Se resuelve con RDS Proxy, que añade coste y una pieza más; en Fargate, cada tarea mantiene un pool estable y el problema no existe.
  2. El arranque en frío con estado caliente. La tienda mantiene en memoria la caché del catálogo que precarga desde mercadofresco-catalogo al arrancar. En Fargate ese coste se paga una vez por tarea y dura horas; en Lambda se pagaría en cada arranque en frío, y la primera petición de cada entorno de ejecución sería lenta justo cuando llega un pico.
  3. La reescritura. La tienda es una aplicación WSGI con Gunicorn; llevarla a Lambda exige un adaptador o partirla en funciones, más un cambio del modelo de sesiones, de los ficheros estáticos y de la observabilidad. Es un proyecto de semanas cuyo beneficio es dudoso, justo después de terminar la migración a contenedores.

(b) El cálculo. Con 300 pedidos/hora de media y unas 8 peticiones HTTP por pedido, salen unas 2.400 peticiones/hora, es decir, 1,75 millones al mes. Suponiendo 250 ms de media y 2 GB de memoria: 1,75 M × 0,25 s × 2 GB = 875.000 GB-s. A 0,0000167 USD por GB-s son unos 14,6 USD, más 0,35 USD de invocaciones, más API Gateway: 1,75 M × 3,5 USD/millón ≈ 6,1 USD. Total ≈ 21 USD, frente a los 70 USD de Fargate arm64. Sara tiene razón en el número bruto. Pero faltan tres partidas que cambian la conclusión: RDS Proxy (unos 30 USD/mes), el coste de la reescritura (semanas de trabajo, que a coste de equipo supera con creces el ahorro anual de 588 USD) y el riesgo de latencia por arranques en frío durante el pico del viernes, que es precisamente el problema que este módulo acaba de resolver.

(c) En qué tiene razón y dónde ya se aplica. Tiene razón en el principio: no pagar por capacidad ociosa. Y ese principio ya está aplicado donde encaja: mercadofresco-generar-miniaturas se dispara por evento de S3 y no se paga entre subidas de foto; -cobrar-pago, -reservar-stock y -asignar-reparto son pasos cortos de la máquina de estados; y los trabajadores de la cola escalan hasta el mínimo fuera del pico. La arquitectura ya es híbrida por diseño: Lambda donde el trabajo es esporádico y corto, Fargate donde el proceso vive esperando peticiones.

(d) Qué mediría antes de descartarla. Tres cosas concretas: la distribución real de la latencia de la tienda por percentiles en X-Ray, para saber cuánto pesaría un arranque en frío en el p99; el número de conexiones simultáneas a Aurora en el pico del viernes, para dimensionar si RDS Proxy sería suficiente; y el coste real de Fargate durante un mes completo con arm64 y el escalado programado ya aplicado, porque la comparación se está haciendo contra un número que aún no se ha medido. Sin esos tres datos, ambas posturas son opiniones.

(e) La frase. «Tienes razón en el principio y por eso ya lo estamos aplicando: todo lo que se dispara por un evento y muere en segundos ya está en Lambda. La tienda es lo contrario —un proceso que vive esperando peticiones y mantiene caché y conexiones—, y para eso Fargate cuesta cincuenta dólares más al mes y nos ahorra una reescritura, un RDS Proxy y los arranques en frío del viernes a las siete.»

Conclusión

Las instancias han desaparecido. La tienda de MercadoFresco se ejecuta ahora sobre Fargate, y ya no existe ninguna máquina que Marta pueda listar, dimensionar, parchear o dejarse encendida.

Tienes claro qué desaparece —la AMI, el parcheo del anfitrión, el escalado del clúster, el empaquetado de tareas, la capacidad ociosa y hasta el límite de ENI por instancia— y qué sigue siendo tuyo: la imagen, la CPU y la memoria, la red y los permisos. Con la frase que ordena el resto: Fargate no elimina el trabajo de operar una aplicación, elimina el trabajo de operar un servidor. Y con el modelo de recursos real: las combinaciones válidas de CPU y memoria, los 20 GB de almacenamiento efímero ampliables a 200, y la decisión de dimensionar con el percentil 95 de Container Insights más un 30 %, porque aquí sobredimensionar se paga desde el primer segundo. Más arm64 con Graviton, que baja el servicio de 88 a 70 USD mensuales con docker buildx y una semana de prueba en preproducción.

Tienes las diferencias operativas y qué hacer con cada una: ECS Exec en lugar de SSH, auditado en CloudTrail y con la regla de que sirve para diagnosticar y no para arreglar; FireLens con Fluent Bit como sidecar donde antes había un DaemonSet, con awslogs como opción por omisión porque el sidecar cuesta CPU en cada tarea; y awsvpc como único modo de red, que ya no es una restricción sino la forma normal de trabajar. Y tienes el número que justifica el módulo entero: de 120 segundos a 35-45 hasta que una tarea nueva recibe tráfico, con el desglose por fases y lo que significa el viernes a las 19:00 cuando el tráfico pasa de 300 a 900 pedidos/hora en ocho minutos.

Tienes la migración completa: definición de tarea sin cambios porque ya declaraba FARGATE, subredes privadas snet-mercadofresco-app-a y -b sin IP pública, grupo de seguridad por tarea, y los endpoints de VPC del camino crítico —con el análisis honesto de que siete endpoints en dos AZ salen más caros que el NAT, y la decisión razonada de poner solo los cuatro que importan—. El autoescalado en tres capas: seguimiento de destino como base, y sobre todo el escalado programado del viernes, que sube el mínimo a 6 a las 16:45 con Europe/Madrid explícito y cuesta 4,9 USD al mes. Fargate Spot con el reparto base=1, weight 1:4 para los trabajadores y el criterio que decide dónde entra: solo donde la interrupción con dos minutos de aviso se reintenta sola. Las tareas programadas con EventBridge Scheduler para la carga nocturna a Redshift, con ventana flexible, reintentos y DLQ. Y el pipeline con blue/green canario de CodeDeploy sobre tg-mercadofresco-tienda y -verde, que baja la restauración de 4 minutos a menos de 1 porque revertir es mover un oyente; todo ello en veinte líneas de CDK con ApplicationLoadBalancedFargateService.

Y tienes la comparativa honesta Fargate frente a EC2 frente a Lambda, con el criterio que resuelve la mayoría de las dudas —si el proceso vive esperando peticiones, es Fargate; si se despierta ante un evento y muere, es Lambda; si necesitas el control de la máquina, es EC2— y la decisión componente a componente de MercadoFresco, en la que EC2 ya no aparece en ninguna fila.

Queda una pregunta que alguien del equipo hará esta misma semana, y que merece una respuesta seria en lugar de un encogimiento de hombros: 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 muchísima gente que sabe usarlo. ¿Nos estamos equivocando al quedarnos en ECS?

En 10-03, «Amazon EKS», se responde con datos y no con preferencias. Verás qué es Kubernetes y qué resuelve que ECS no, su vocabulario mínimo pero real, qué gestiona EKS y qué sigue siendo tuyo, las cuatro opciones de cómputo incluido Auto Mode, los manifiestos completos del despliegue de la tienda con sondas, Ingress y el AWS Load Balancer Controller, IRSA y Pod Identity como equivalentes del rol de tarea, Karpenter para los nodos, y el argumento central que casi nadie pone por escrito: el coste oculto de las actualizaciones de versión. Con la comparativa ECS frente a EKS y la decisión razonada de MercadoFresco, junto con los criterios objetivos que la cambiarían.

Curso de AWS

Módulo 1: Introducción a AWS

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados