En la lección 02-03 hicimos un cálculo que dejó a Marta en silencio: de la factura mensual de S3 del catálogo de fotos, el 97 % no era almacenamiento, era transferencia de salida. Cada vez que un cliente de Sevilla abre la ficha del tomate rama, la foto viaja íntegra desde un centro de datos de Irlanda hasta su móvil, y MercadoFresco paga por cada byte. Si el cliente recarga la página, se paga otra vez. Si diez mil clientes miran la misma foto, se paga diez mil veces por el mismo fichero.

Y ahora, tras montar el balanceador en 03-03, hay un segundo cobro por GB: el ALB también factura por el tráfico que procesa.

Amazon CloudFront es la red de distribución de contenido (CDN) de AWS: unos 700 puntos de presencia repartidos por el mundo que guardan una copia de tu contenido cerca de quien lo pide. El cliente de Sevilla recibe la foto desde Madrid en vez de desde Dublín; la petición no llega ni a S3 ni al ALB; y la transferencia de salida de CloudFront tiene una capa gratuita permanente de 1 TB al mes que cubre entera la demanda actual de MercadoFresco.

En esta lección Marta pone CloudFront delante de todo y ataca directamente el problema del coste, sin perder de vista lo otro que aporta una CDN y que a menudo importa más: latencia.

Contenido

  1. Qué es una CDN y cómo funciona el reparto desde el borde
  2. Aciertos y fallos de caché
  3. Distribuciones y orígenes
  4. Grupos de origen y conmutación por error
  5. Comportamientos de caché y patrones de ruta
  6. La clave de caché: políticas de caché y de solicitud de origen
  7. TTL: mínimo, máximo y por defecto
  8. Compresión, HTTP/2 y HTTP/3
  9. Origin Access Control: cerrar el bucket al mundo
  10. Invalidaciones frente a versionado de nombres de fichero
  11. Dominios propios y la trampa del certificado en us-east-1
  12. Contenido dinámico a través de CloudFront hacia el ALB
  13. CloudFront Functions y Lambda@Edge
  14. Registros, métricas y tasa de aciertos
  15. Clases de precio y el cálculo de ahorro de MercadoFresco
  16. Creación por CLI, invalidación y limpieza

Qué es una CDN y cómo funciona el reparto desde el borde

Una CDN (Content Delivery Network) es una red de servidores caché distribuidos geográficamente que se sitúan entre el usuario y el servidor de origen. CloudFront tiene tres niveles:

Nivel Cuántos Qué hace
Edge locations ~700 en más de 100 ciudades Atienden al usuario y guardan el contenido más popular
Regional edge caches ~13, más grandes Segundo nivel de caché: si falla la edge, se pregunta aquí antes que al origen
Origen Tu bucket S3, tu ALB, o cualquier servidor HTTP La fuente de la verdad

Las edge locations más cercanas a los clientes de MercadoFresco son Madrid y Marsella, con salida también por Londres y París. Un cliente de Sevilla que hoy espera a que la foto cruce 2.000 km hasta Dublín pasará a recibirla desde Madrid, a 400 km.

La ganancia de latencia es la mitad del argumento y no debe subestimarse: una tienda que carga medio segundo más lento pierde conversiones medibles. La otra mitad es el dinero, y la vemos con números al final de la lección.

Aciertos y fallos de caché

Todo el funcionamiento de CloudFront se reduce a este diagrama:

sequenceDiagram
    participant C as Cliente<br/>(Sevilla)
    participant E as Edge location<br/>(Madrid)
    participant R as Regional edge cache<br/>(Frankfurt)
    participant O as Origen<br/>mercadofresco-catalogo-fotos

    Note over C,O: FALLO DE CACHÉ (primera petición)
    C->>E: GET /productos/tomate-rama.jpg
    E->>E: ¿Lo tengo? No (Miss)
    E->>R: GET al segundo nivel
    R->>R: ¿Lo tengo? No (Miss)
    R->>O: GET al origen
    O-->>R: 200 + imagen (transferencia GRATIS a CloudFront)
    R-->>E: imagen (se guarda)
    E-->>C: imagen (se guarda) · ~350 ms

    Note over C,O: ACIERTO DE CACHÉ (peticiones siguientes)
    C->>E: GET /productos/tomate-rama.jpg
    E->>E: ¿Lo tengo? Sí (Hit)
    E-->>C: imagen desde Madrid · ~25 ms

Tres cosas que este diagrama enseña y conviene fijar:

  1. Solo el primer usuario paga la espera larga. Los siguientes reciben la respuesta desde el borde.
  2. La transferencia del origen a CloudFront es gratuita cuando el origen es S3 (y también desde un ALB o EC2 en la misma cuenta). Esto es la clave económica de toda la lección.
  3. Existe un segundo nivel. Aunque la edge de Madrid no lo tenga, quizá lo tenga la regional edge cache, y el origen se ahorra la petición igualmente. Esto mejora mucho la tasa de aciertos real frente a lo que uno calcularía a ojo.

Cada respuesta lleva la cabecera X-Cache, que dice exactamente qué ocurrió:

Valor de X-Cache Significado
Hit from cloudfront Servido desde la edge, sin tocar el origen
Miss from cloudfront No estaba: se pidió al origen
RefreshHit from cloudfront Estaba caducado, el origen respondió 304 Not Modified
Error from cloudfront El origen falló

Distribuciones y orígenes

Una distribución es la unidad de configuración de CloudFront. Al crearla, AWS asigna un nombre de dominio propio, del tipo d111111abcdef8.cloudfront.net, y despliega la configuración en todas las edge locations en unos minutos.

Dentro de una distribución se declaran uno o varios orígenes. MercadoFresco necesita dos:

Origen (Id) Tipo Dominio Qué sirve
origen-fotos-s3 S3 con OAC mercadofresco-catalogo-fotos.s3.eu-west-1.amazonaws.com Fotos de productos y miniaturas
origen-tienda-alb Personalizado alb-mercadofresco-tienda-1234567890.eu-west-1.elb.amazonaws.com HTML, API, todo lo dinámico

La diferencia entre los dos tipos de origen no es cosmética:

Origen S3 Origen personalizado (ALB, EC2, servidor externo)
Protocolo hacia el origen Interno de AWS HTTP o HTTPS, configurable
Acceso restringido Origin Access Control (OAC) Cabecera secreta + grupo de seguridad
Reenvío de cabeceras Limitado Completo
Métodos permitidos Solo GET, HEAD, OPTIONS Todos, incluidos POST y PUT
Tiempos de espera Fijos Ajustables (OriginReadTimeout)

Un matiz que confunde: si configuras un bucket de S3 como sitio web estático (el modo que vimos en 02-03, con su propio dominio .s3-website-), CloudFront lo trata como origen personalizado, no como origen S3, y entonces no puedes usar OAC. Para MercadoFresco usamos el endpoint REST de S3 con OAC, que es lo correcto.

Grupos de origen y conmutación por error

Un grupo de origen empareja dos orígenes: uno primario y uno secundario. Si el primario devuelve uno de los códigos de error configurados, CloudFront reintenta automáticamente contra el secundario, de forma transparente para el cliente.

{
  "Quantity": 1,
  "Items": [
    {
      "Id": "grupo-fotos-con-respaldo",
      "FailoverCriteria": {
        "StatusCodes": { "Quantity": 4, "Items": [500, 502, 503, 504] }
      },
      "Members": {
        "Quantity": 2,
        "Items": [
          { "OriginId": "origen-fotos-s3" },
          { "OriginId": "origen-fotos-s3-respaldo" }
        ]
      }
    }
  ]
}

Solo funciona con GET, HEAD y OPTIONS, y los criterios pueden incluir además 403 y 404. Para MercadoFresco tiene sentido el día que se replique el catálogo a un bucket de otra región; hoy es una configuración preparada, no activa. La conmutación por error a nivel de DNS, que es un mecanismo distinto y complementario, se estudia en la lección 03-05.

Comportamientos de caché y patrones de ruta

Un comportamiento de caché (cache behavior) asocia un patrón de ruta con un origen y con un conjunto de reglas. Es el equivalente a las reglas del ALB, pero en el borde.

MercadoFresco define cuatro:

Prec. Patrón Origen Métodos Política de caché Por qué
0 /productos/* origen-fotos-s3 GET, HEAD Caché larga (1 año) Las fotos no cambian: el nombre lleva versión
1 /miniaturas/* origen-fotos-s3 GET, HEAD Caché larga (1 año) Generadas por Lambda, inmutables
2 /api/* origen-tienda-alb Todos CachingDisabled Datos de pedidos: nunca se cachean
(defecto) * origen-tienda-alb GET, HEAD, OPTIONS Caché corta (60 s) HTML del catálogo

Reglas de evaluación, muy parecidas a las del ALB:

  • Los comportamientos se evalúan por orden de precedencia y gana el primero que coincida.
  • El comportamiento por defecto (*) se evalúa siempre el último, sin excepción.
  • Los patrones admiten * y ?, y distinguen mayúsculas de minúsculas.

El comportamiento de /api/* merece atención: hay que permitir todos los métodos HTTP (GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE) porque si no, un cliente que confirme un pedido con POST recibiría un 403 de CloudFront. Y hay que desactivar la caché explícitamente: cachear una respuesta de /api/pedidos/4711 y servírsela a otro cliente sería una fuga de datos grave.

La clave de caché: políticas de caché y de solicitud de origen

Esta sección es la que separa una CDN que ahorra dinero de una que no sirve para nada.

La clave de caché es el conjunto de datos con el que CloudFront identifica un objeto guardado. Por defecto es solo la ruta de la URL. Pero se le pueden añadir cabeceras, cookies y cadenas de consulta. Y aquí está la trampa:

Cada valor distinto de cualquier elemento de la clave de caché crea una entrada distinta en la caché.

Un ejemplo aritmético que lo deja claro. Supón que incluyes en la clave de caché la cabecera User-Agent, que tiene miles de valores distintos:

Elementos en la clave de caché Entradas para UNA foto Tasa de aciertos
Solo la ruta 1 ~95 %
Ruta + Accept-Encoding 2 (gzip, br) ~93 %
Ruta + CloudFront-Viewer-Country ~30 ~70 %
Ruta + User-Agent miles ~5 %
Ruta + todas las cookies Prácticamente una por usuario ~0 %

Con User-Agent en la clave, CloudFront guarda una copia distinta de la misma foto para cada versión de cada navegador. Casi todas las peticiones son fallos, todas van al origen, y has pagado por una CDN que no cachea nada. Es el error más caro y más frecuente de CloudFront.

Para distinguir bien, AWS separa dos políticas:

Política de caché Política de solicitud de origen
Qué controla Qué forma la clave de caché Qué se reenvía al origen
Efecto en la caché Cada valor crea una entrada nueva Ninguno
Cuándo usarla Solo si el contenido cambia según ese valor Si el origen necesita ese dato para funcionar

El caso que aclara la diferencia: el origen quiere registrar el país del visitante en sus logs. Si pones CloudFront-Viewer-Country en la política de caché, multiplicas las entradas por 30. Si lo pones en la política de solicitud de origen, el origen recibe el dato en cada fallo de caché y la caché sigue teniendo una sola entrada. La segunda es la correcta.

Las políticas gestionadas por AWS cubren casi todo:

Política gestionada Clave de caché Cuándo
CachingOptimized Solo la ruta Contenido estático: las fotos de MercadoFresco
CachingOptimizedForUncompressedObjects Solo la ruta, sin compresión Ficheros ya comprimidos (JPEG, vídeo)
CachingDisabled No se cachea nada /api/*
Elemental-MediaPackage Específica de vídeo Streaming

Y una política propia, cuando de verdad hace falta variar por idioma:

{
  "Name": "pol-cache-catalogo-mercadofresco",
  "Comment": "HTML del catalogo: varia por idioma y por pagina",
  "DefaultTTL": 60,
  "MinTTL": 0,
  "MaxTTL": 300,
  "ParametersInCacheKeyAndForwardedToOrigin": {
    "EnableAcceptEncodingGzip": true,
    "EnableAcceptEncodingBrotli": true,
    "HeadersConfig": {
      "HeaderBehavior": "whitelist",
      "Headers": { "Quantity": 1, "Items": ["Accept-Language"] }
    },
    "CookiesConfig": { "CookieBehavior": "none" },
    "QueryStringsConfig": {
      "QueryStringBehavior": "whitelist",
      "QueryStrings": { "Quantity": 2, "Items": ["pagina", "categoria"] }
    }
  }
}

Decisiones de esta política, una a una:

  • Accept-Language en lista blanca: el catálogo se sirve en español y en catalán, así que cambia el contenido. Multiplica las entradas por 2, no por miles.
  • CookieBehavior: none: las cookies de sesión no cambian el HTML del catálogo. Incluirlas destruiría la caché por completo.
  • Solo dos parámetros de consulta en lista blanca: pagina y categoria sí cambian el contenido. Los parámetros de campañas de marketing (utm_source, utm_medium…) no, y si se incluyeran, cada enlace de cada campaña sería una entrada de caché distinta del mismo HTML.
  • EnableAcceptEncodingGzip y Brotli: CloudFront normaliza la cabecera y guarda las variantes comprimidas de forma inteligente, sin que cuenten como valores arbitrarios.

TTL: mínimo, máximo y por defecto

El TTL (Time To Live) es cuánto tiempo guarda CloudFront un objeto antes de volver a preguntar. Hay tres valores y su interacción con las cabeceras del origen despista:

Parámetro Qué hace
MinTTL Tiempo mínimo que se guarda, aunque el origen pida menos
DefaultTTL Tiempo que se usa si el origen no envía Cache-Control
MaxTTL Tiempo máximo, aunque el origen pida más

La lógica completa:

  1. Si el origen no envía Cache-Control ni Expires → se aplica DefaultTTL.
  2. Si el origen envía Cache-Control: max-age=N → se usa N, acotado entre MinTTL y MaxTTL.
  3. Cache-Control: no-store o private → no se cachea (salvo que MinTTL sea mayor que 0, en cuyo caso se cachea de todos modos: una fuente clásica de sorpresas).

Los valores de MercadoFresco:

Contenido MinTTL DefaultTTL MaxTTL Cache-Control del origen
Fotos /productos/* 0 31.536.000 (1 año) 31.536.000 public, max-age=31536000, immutable
Miniaturas /miniaturas/* 0 31.536.000 31.536.000 public, max-age=31536000, immutable
HTML del catálogo 0 60 300 public, max-age=60
/api/* 0 0 0 no-store

Un año de caché para las fotos suena temerario hasta que se entiende la técnica del versionado de nombres, que vemos en dos apartados. Lo correcto es poner la cabecera en el objeto de S3 al subirlo, porque ahí también la aprovechan los navegadores:

aws s3 cp foto-nueva.jpg s3://mercadofresco-catalogo-fotos/productos/tomate-rama-v3.jpg \
  --profile mercadofresco-dev --region eu-west-1 \
  --cache-control "public, max-age=31536000, immutable" \
  --content-type "image/jpeg"

immutable es una directiva que le dice al navegador que ni siquiera pregunte si ha cambiado. Elimina incluso las peticiones de revalidación 304.

Compresión, HTTP/2 y HTTP/3

Tres ajustes que se activan con una casilla y reducen bytes y latencia:

Compresión automática. Con Compress: true, CloudFront comprime con Gzip o Brotli las respuestas de tipos de contenido comprimibles (HTML, CSS, JavaScript, JSON, SVG) cuando el cliente lo admite, siempre que el objeto pese entre 1 KB y 10 MB. Brotli reduce un 15-20 % más que Gzip. Advertencia: no comprime JPEG, PNG ni MP4, que ya están comprimidos; intentarlo solo gastaría CPU.

HTTP/2 y HTTP/3. Se activan por distribución:

Protocolo Ventaja Estado
HTTP/1.1 Universal Compatibilidad
HTTP/2 Multiplexado: muchas peticiones en una conexión Por defecto
HTTP/3 (QUIC) Sobre UDP; sobrevive al cambio de red y arranca más rápido Hay que activarlo

HTTP/3 importa especialmente para MercadoFresco: buena parte de sus clientes navegan desde el móvil, y en el móvil las conexiones cambian de antena y de wifi constantemente. QUIC mantiene la sesión cuando eso pasa; TCP tiene que reconectar.

# En la configuración de la distribución
"HttpVersion": "http2and3"

Origin Access Control: cerrar el bucket al mundo

Hoy mercadofresco-catalogo-fotos tiene una política que permite s3:GetObject público bajo productos/. Eso significa que cualquiera puede saltarse CloudFront y descargar directamente desde S3, y MercadoFresco paga la transferencia cara. Además, hace posible el hotlinking: que otra web enlace las fotos de MercadoFresco y le cargue la factura.

Origin Access Control (OAC) resuelve las dos cosas: hace que solo CloudFront pueda leer del bucket, firmando cada petición al origen con SigV4.

OAC sustituye al antiguo OAI (Origin Access Identity). Si encuentras documentación con OAI, está desactualizada: OAI no soporta el cifrado con KMS (nuestra clave alias/mercadofresco-datos), no funciona en todas las regiones y no admite POST/PUT. Para todo lo nuevo, OAC.

Creación del control de acceso:

OAC_ID=$(aws cloudfront create-origin-access-control \
  --profile mercadofresco-dev \
  --origin-access-control-config '{
    "Name": "oac-mercadofresco-catalogo",
    "Description": "Acceso de CloudFront al bucket de fotos de MercadoFresco",
    "SigningProtocol": "sigv4",
    "SigningBehavior": "always",
    "OriginAccessControlOriginType": "s3"
  }' \
  --query 'OriginAccessControl.Id' --output text)

Y la política del bucket, que es la pieza esencial:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SoloCloudFrontPuedeLeerLasFotos",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": [
        "arn:aws:s3:::mercadofresco-catalogo-fotos/productos/*",
        "arn:aws:s3:::mercadofresco-catalogo-fotos/miniaturas/*"
      ],
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E2QWERTY123ABC"
        }
      }
    },
    {
      "Sid": "DenegarTodoLoQueNoVayaCifrado",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::mercadofresco-catalogo-fotos",
        "arn:aws:s3:::mercadofresco-catalogo-fotos/*"
      ],
      "Condition": {
        "Bool": { "aws:SecureTransport": "false" }
      }
    }
  ]
}

Lo importante de esta política, cláusula a cláusula:

  • El principal es el servicio cloudfront.amazonaws.com, no una identidad de usuario.
  • La condición AWS:SourceArn es la parte crítica: sin ella, cualquier distribución de CloudFront de cualquier cuenta de AWS podría leer el bucket. Restringe el permiso a la distribución concreta E2QWERTY123ABC de la cuenta 111122223333.
  • Solo se concede s3:GetObject, y solo bajo los dos prefijos del catálogo. CloudFront no puede listar el bucket ni escribir nada.
  • La segunda cláusula, con Deny y aws:SecureTransport: false, rechaza cualquier acceso por HTTP sin cifrar. Es una buena práctica que conviene tener en todo bucket.

El resultado, comparando el antes y el después:

flowchart LR
    subgraph ANTES["ANTES: bucket con lectura pública"]
        C1["Cliente"] -->|"paga transferencia cara"| S1[("mercadofresco-catalogo-fotos")]
        C2["Web ajena<br/>(hotlinking)"] -->|"y también paga MercadoFresco"| S1
    end

    subgraph DESPUES["DESPUÉS: bucket cerrado con OAC"]
        C3["Cliente"] --> CF["CloudFront<br/>E2QWERTY123ABC"]
        CF -->|"firma SigV4<br/>transferencia gratuita"| S2[("mercadofresco-catalogo-fotos")]
        C4["Acceso directo a S3"] -.->|"403 Forbidden"| S2
    end

Después hay que quitar la antigua política pública y comprobar que el bloqueo de acceso público (visto en 02-03) sigue activo:

aws s3api put-bucket-policy --profile mercadofresco-dev --region eu-west-1 \
  --bucket mercadofresco-catalogo-fotos \
  --policy file://politica-oac-catalogo.json

# Verificación: el acceso directo debe fallar ahora con 403
curl -s -o /dev/null -w "S3 directo: %{http_code}\n" \
  https://mercadofresco-catalogo-fotos.s3.eu-west-1.amazonaws.com/productos/tomate-rama-v3.jpg

curl -s -o /dev/null -w "Por CloudFront: %{http_code}\n" \
  https://d111111abcdef8.cloudfront.net/productos/tomate-rama-v3.jpg

El resultado esperado es 403 en el primero y 200 en el segundo. Ese par de comandos es la verificación completa de que OAC está bien montado.

Un detalle práctico: las fotos están cifradas con la clave KMS alias/mercadofresco-datos (02-03). La política de esa clave debe permitir kms:Decrypt al servicio de CloudFront; si no, todas las peticiones devolverán 403 y el mensaje no será muy claro. KMS y sus políticas de clave se estudian en 04-02.

Invalidaciones frente a versionado de nombres de fichero

Luis sustituye la foto del tomate por una mejor y la sube con el mismo nombre. CloudFront sigue sirviendo la antigua durante un año, porque así lo dice el TTL. Hay dos formas de resolverlo, y una es claramente mejor.

Opción A: invalidación. Se le dice a CloudFront que olvide un objeto en todas las edge locations.

aws cloudfront create-invalidation --profile mercadofresco-dev \
  --distribution-id E2QWERTY123ABC \
  --paths "/productos/tomate-rama.jpg"

# Invalidar todo (lento y caro; solo para emergencias)
aws cloudfront create-invalidation --profile mercadofresco-dev \
  --distribution-id E2QWERTY123ABC --paths "/*"

Opción B: versionado del nombre. Cada versión de la foto se sube con un nombre distinto: tomate-rama-v1.jpg, tomate-rama-v2.jpg, tomate-rama-v3.jpg. El HTML apunta a la última, y como la URL es nueva, es un fallo de caché limpio. La antigua caduca sola.

Invalidación Versionado del nombre
Tiempo hasta ver el cambio 1-5 minutos (propagar a 700 edges) Inmediato
Coste 1.000 rutas gratis al mes; después 0,005 USD por ruta Cero
Vuelta atrás Imposible: lo viejo se ha borrado Cambiar el HTML a v2
Objetos ya descargados por navegadores Siguen siendo los viejos Se piden de nuevo, URL distinta
Riesgo en un despliegue Ventana de inconsistencia Ninguna
Escalabilidad Se satura con despliegues frecuentes Ilimitada

La cuarta fila es la decisiva y casi nadie la ve venir: la invalidación limpia la caché de CloudFront, pero no la del navegador del cliente. Si el navegador guardó la foto con max-age=31536000, seguirá mostrando la vieja durante un año pase lo que pase. El versionado del nombre resuelve también eso, porque la URL es distinta.

Por eso MercadoFresco adopta el versionado, y las invalidaciones quedan reservadas para emergencias —una foto subida por error, un precio equivocado en el HTML cacheado.

Dominios propios y la trampa del certificado en us-east-1

Por defecto la distribución responde en d111111abcdef8.cloudfront.net. Para usar cdn.mercadofresco.example hacen falta dos cosas: declararlo como nombre de dominio alternativo (CNAME) y aportar un certificado TLS que lo cubra.

⚠️ La trampa clásica de CloudFront

El certificado de ACM para CloudFront debe estar SIEMPRE en la región us-east-1 (Norte de Virginia), sea cual sea la región donde vivan tus recursos.

Esto es así porque CloudFront es un servicio global y su plano de control está en us-east-1 (lo vimos al hablar de servicios globales, regionales y zonales en 01-03). Un certificado emitido en eu-west-1 es perfectamente válido para el ALB, pero CloudFront ni siquiera lo verá en la lista desplegable, y el mensaje de error no explica por qué.

Consecuencia práctica para MercadoFresco: hay que pedir dos certificados para el mismo dominio.

# Certificado para el ALB: en la región del ALB
aws acm request-certificate --profile mercadofresco-dev --region eu-west-1 \
  --domain-name mercadofresco.example \
  --subject-alternative-names "*.mercadofresco.example" \
  --validation-method DNS

# Certificado para CloudFront: OBLIGATORIAMENTE en us-east-1
aws acm request-certificate --profile mercadofresco-dev --region us-east-1 \
  --domain-name mercadofresco.example \
  --subject-alternative-names "*.mercadofresco.example" \
  --validation-method DNS

Ambos son gratuitos y ambos se renuevan solos. Ambos quedan en PENDING_VALIDATION hasta que se creen los registros CNAME de validación, cosa que haremos en la lección 03-05.

Además, toda distribución debería forzar HTTPS:

Ajuste Valor Efecto
ViewerProtocolPolicy redirect-to-https Quien entre por HTTP recibe un 301 a HTTPS
MinimumProtocolVersion TLSv1.2_2021 No se aceptan versiones antiguas de TLS
OriginProtocolPolicy (ALB) https-only El tramo CloudFront→ALB también va cifrado

Contenido dinámico a través de CloudFront hacia el ALB

Una CDN sirve para más que ficheros estáticos. Pasar también el HTML y la API por CloudFront aporta cosas incluso cuando no se cachea nada:

Ventaja Por qué ocurre
Conexión TLS más cercana El saludo TLS se completa en Madrid, no en Dublín: se ahorran varios viajes de ida y vuelta
Conexiones persistentes al origen CloudFront mantiene conexiones abiertas con el ALB y las reutiliza
Red troncal de AWS De la edge al origen se viaja por la red privada de AWS, más estable que internet
Un único punto para WAF y Shield La protección se aplica en el borde, antes de llegar a la VPC — 04-04 y 04-05
Compresión y HTTP/3 gratis Aunque el origen no los soporte
Menos tráfico procesado por el ALB Todo lo cacheable deja de contar como LCU

Cuándo no compensa: si la aplicación es interna y todos los usuarios están en la misma región que el origen, CloudFront añade un salto sin ganancia. Para MercadoFresco, con clientes por toda España y planes de abrir en Portugal, compensa claramente.

El único cuidado: para el comportamiento /api/* hay que permitir todos los métodos y reenviar al origen las cabeceras que la aplicación necesita (Authorization, Host, Content-Type) mediante la política de solicitud de origen AllViewerExceptHostHeader, sin meterlas en la clave de caché.

CloudFront Functions y Lambda@Edge

A veces hace falta ejecutar lógica en el borde: reescribir una URL, añadir cabeceras de seguridad, redirigir por país. CloudFront ofrece dos mecanismos muy distintos.

CloudFront Functions Lambda@Edge
Dónde se ejecuta En la edge location (las ~700) En las regional edge caches (~13)
Lenguaje JavaScript (ECMAScript 5.1 estricto) Node.js o Python
Tiempo máximo < 1 ms 5 s (viewer) / 30 s (origin)
Memoria 2 MB 128-10.240 MB
Acceso a la red No
Acceso al cuerpo de la petición No
Puede llamar a otros servicios de AWS No
Eventos Solicitud/respuesta de visor Solicitud/respuesta de visor y de origen
Coste 0,10 USD por millón 0,60 USD por millón + tiempo de cómputo
Caso típico Reescritura de URL, cabeceras, redirecciones simples Autenticación, llamadas a bases de datos, transformación de imágenes

La regla: si cabe en CloudFront Functions, va en CloudFront Functions. Es seis veces más barato, se ejecuta en más sitios y es órdenes de magnitud más rápido.

Ejemplo real de MercadoFresco: añadir cabeceras de seguridad a todas las respuestas y normalizar las URL del catálogo.

// funcion-cabeceras-seguridad.js
// Evento: viewer-response (se ejecuta justo antes de devolver la respuesta al cliente)
function handler(event) {
    var respuesta = event.response;
    var cabeceras = respuesta.headers;

    // Fuerza HTTPS durante un año en todos los subdominios
    cabeceras['strict-transport-security'] = {
        value: 'max-age=31536000; includeSubDomains; preload'
    };
    // Impide que el navegador adivine el tipo de contenido
    cabeceras['x-content-type-options'] = { value: 'nosniff' };
    // Impide que la tienda se cargue dentro de un iframe ajeno (clickjacking)
    cabeceras['x-frame-options'] = { value: 'DENY' };
    // No envía la URL completa como referente a sitios externos
    cabeceras['referrer-policy'] = { value: 'strict-origin-when-cross-origin' };
    // Desactiva APIs del navegador que la tienda no usa
    cabeceras['permissions-policy'] = {
        value: 'geolocation=(), microphone=(), camera=()'
    };

    return respuesta;
}

Y la reescritura de URL, en el evento de solicitud de visor:

// funcion-normalizar-url.js
// Evento: viewer-request (antes de mirar la caché)
function handler(event) {
    var peticion = event.request;
    var uri = peticion.uri;

    // /categoria/verduras  ->  /categoria/verduras/index.html
    if (uri.endsWith('/')) {
        peticion.uri = uri + 'index.html';
    } else if (!uri.includes('.')) {
        peticion.uri = uri + '/index.html';
    }

    // Elimina los parámetros de campaña de la clave de caché.
    // Sin esto, cada enlace de cada campaña de marketing sería una entrada distinta
    // de la MISMA página, y la tasa de aciertos se hundiría.
    var qs = peticion.querystring;
    ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'fbclid', 'gclid']
        .forEach(function (p) { delete qs[p]; });

    return peticion;
}

Ese segundo ejemplo tiene efecto económico directo: es una función de cuatro líneas útiles que recupera la tasa de aciertos que las campañas de Sara estaban destruyendo sin que nadie lo supiera.

Detalle importante sobre el momento de ejecución: en viewer-request la función se ejecuta antes de consultar la caché, así que la modificación de la URI afecta a la clave de caché. Si se ejecutara en origin-request, solo se lanzaría en los fallos de caché y no cambiaría nada.

# Publicar y asociar la función
aws cloudfront create-function --profile mercadofresco-dev \
  --name mercadofresco-normalizar-url \
  --function-config '{"Comment":"Normaliza URL y limpia parametros de campana","Runtime":"cloudfront-js-2.0"}' \
  --function-code fileb://funcion-normalizar-url.js

aws cloudfront publish-function --profile mercadofresco-dev \
  --name mercadofresco-normalizar-url --if-match "$ETAG"

Registros, métricas y tasa de aciertos

Registros estándar. CloudFront vuelca en S3 una línea por petición, con el resultado de caché incluido:

# Se configuran dentro de la distribución
"Logging": {
  "Enabled": true,
  "IncludeCookies": false,
  "Bucket": "mercadofresco-registros-web.s3.amazonaws.com",
  "Prefix": "cloudfront/"
}

Métricas en CloudWatch (siempre en us-east-1, por ser un servicio global):

Métrica Qué mide Objetivo en MercadoFresco
Requests Peticiones totales
CacheHitRate % servido desde caché > 90 % en fotos
OriginLatency Tiempo de respuesta del origen < 300 ms
4xxErrorRate Errores de cliente < 1 %
5xxErrorRate Errores de servidor < 0,1 %
TotalErrorRate Suma de ambos < 1 %
BytesDownloaded Bytes servidos Base del cálculo de coste

CacheHitRate es la métrica que hay que vigilar: si baja, el ahorro desaparece y el origen se carga. Las causas más frecuentes de una tasa baja son las que ya hemos visto: demasiados elementos en la clave de caché, TTL demasiado corto o Cache-Control mal puesto en el origen.

aws cloudwatch get-metric-statistics --profile mercadofresco-dev --region us-east-1 \
  --namespace AWS/CloudFront --metric-name CacheHitRate \
  --dimensions Name=DistributionId,Value=E2QWERTY123ABC Name=Region,Value=Global \
  --start-time "$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 86400 --statistics Average \
  --query 'sort_by(Datapoints,&Timestamp)[].{Dia:Timestamp,Aciertos:Average}' --output table

Los paneles y las alarmas sobre estas métricas se construyen en la lección 05-01.

Clases de precio y el cálculo de ahorro de MercadoFresco

Las clases de precio limitan desde qué edge locations se sirve, a cambio de un precio menor:

Clase Regiones incluidas Coste relativo Para MercadoFresco
PriceClass_All Todas, incluidas Sudamérica, Australia, India Más caro Innecesario hoy
PriceClass_200 Todas menos Sudamérica, Australia y Nueva Zelanda Intermedio Innecesario hoy
PriceClass_100 Norteamérica, Europa e Israel Más barato La elección correcta

Los clientes de MercadoFresco están en España y, próximamente, en Portugal: ambos en Europa. Con PriceClass_100 se paga menos y la experiencia de los clientes reales es idéntica. Quien esté fuera seguirá siendo atendido, solo que desde una edge más lejana.

El cálculo, con los números de 02-03

Datos de partida del catálogo de MercadoFresco:

  • 40 GB de fotos y miniaturas en S3.
  • 1.200.000 visualizaciones de foto al mes.
  • Peso medio por foto servida: 250 KB → 300 GB de transferencia mensual.

Situación actual, sirviendo directamente desde S3:

Concepto Cálculo Coste
Almacenamiento 40 GB × 0,023 USD/GB 0,92 USD
Peticiones GET 1.200.000 × 0,0004 USD/1.000 0,48 USD
Transferencia de salida 300 GB × 0,09 USD/GB 27,00 USD
Total 28,40 USD

Y ahí está el 97 % de 02-03: 27,00 ÷ 28,40 = 95 %, que con el tráfico de los meses de campaña supera el 97 %.

Con CloudFront delante, suponiendo un 90 % de aciertos de caché:

Concepto Cálculo Coste
Almacenamiento en S3 40 GB × 0,023 USD/GB 0,92 USD
Transferencia S3 → CloudFront Gratuita siempre 0,00 USD
Peticiones GET a S3 (solo el 10 % de fallos) 120.000 × 0,0004/1.000 0,05 USD
Transferencia de salida de CloudFront 300 GB, dentro del 1 TB gratuito permanente 0,00 USD
Peticiones HTTPS de CloudFront 1.200.000, dentro de los 10 M gratuitos 0,00 USD
Total 0,97 USD

Ahorro: 27,43 USD al mes, un 96,6 %. Y además las fotos se sirven en unos 25 ms en vez de 350 ms.

La capa gratuita permanente de CloudFront (1 TB de salida y 10 millones de peticiones al mes, sin caducidad, no confundir con la capa gratuita de 12 meses de 01-01) es lo que hace que el resultado sea tan rotundo. Conviene ver también qué pasa cuando MercadoFresco crezca diez veces:

Escenario Solo S3 Con CloudFront Ahorro
Hoy: 300 GB, 1,2 M peticiones 28,40 USD 0,97 USD 96,6 %
×10: 3 TB, 12 M peticiones 277 USD 176 USD 36,5 %
×50: 15 TB, 60 M peticiones 1.383 USD ~880 USD 36 %

El ahorro porcentual baja al crecer, porque la capa gratuita deja de ser significativa, pero el ahorro absoluto sube. Y a esa cifra hay que sumarle lo que se deja de pagar en LCU del ALB por el tráfico que ya no lo atraviesa, más la conversión que se gana por servir cinco veces más rápido.

Creación por CLI, invalidación y limpieza

La configuración de una distribución es un JSON largo. Lo escribimos en un fichero:

{
  "CallerReference": "mercadofresco-catalogo-2026-08-02",
  "Comment": "CDN del catalogo y la tienda de MercadoFresco",
  "Enabled": true,
  "PriceClass": "PriceClass_100",
  "HttpVersion": "http2and3",
  "IsIPV6Enabled": true,
  "DefaultRootObject": "index.html",
  "Origins": {
    "Quantity": 2,
    "Items": [
      {
        "Id": "origen-fotos-s3",
        "DomainName": "mercadofresco-catalogo-fotos.s3.eu-west-1.amazonaws.com",
        "OriginAccessControlId": "E1OACMERCADOFRESCO",
        "S3OriginConfig": { "OriginAccessIdentity": "" }
      },
      {
        "Id": "origen-tienda-alb",
        "DomainName": "alb-mercadofresco-tienda-1234567890.eu-west-1.elb.amazonaws.com",
        "CustomOriginConfig": {
          "HTTPPort": 80,
          "HTTPSPort": 443,
          "OriginProtocolPolicy": "https-only",
          "OriginSslProtocols": { "Quantity": 1, "Items": ["TLSv1.2"] },
          "OriginReadTimeout": 30,
          "OriginKeepaliveTimeout": 5
        }
      }
    ]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "origen-tienda-alb",
    "ViewerProtocolPolicy": "redirect-to-https",
    "AllowedMethods": {
      "Quantity": 3,
      "Items": ["GET", "HEAD", "OPTIONS"],
      "CachedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] }
    },
    "Compress": true,
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "OriginRequestPolicyId": "b689b0a8-53d0-40ab-baf2-68738e2966ac"
  },
  "CacheBehaviors": {
    "Quantity": 2,
    "Items": [
      {
        "PathPattern": "/productos/*",
        "TargetOriginId": "origen-fotos-s3",
        "ViewerProtocolPolicy": "redirect-to-https",
        "AllowedMethods": {
          "Quantity": 2,
          "Items": ["GET", "HEAD"],
          "CachedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] }
        },
        "Compress": false,
        "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
      },
      {
        "PathPattern": "/api/*",
        "TargetOriginId": "origen-tienda-alb",
        "ViewerProtocolPolicy": "https-only",
        "AllowedMethods": {
          "Quantity": 7,
          "Items": ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"],
          "CachedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] }
        },
        "Compress": true,
        "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
      }
    ]
  },
  "Logging": {
    "Enabled": true,
    "IncludeCookies": false,
    "Bucket": "mercadofresco-registros-web.s3.amazonaws.com",
    "Prefix": "cloudfront/"
  }
}

Los identificadores largos son políticas gestionadas por AWS, iguales en todas las cuentas: 658327ea-... es CachingOptimized, 4135ea2d-... es CachingDisabled y b689b0a8-... es la política de solicitud de origen AllViewer.

# Crear la distribución
DIST_ID=$(aws cloudfront create-distribution --profile mercadofresco-dev \
  --distribution-config file://distribucion-mercadofresco.json \
  --query 'Distribution.Id' --output text)

# Etiquetarla (CloudFront usa un comando aparte, con ARN)
aws cloudfront tag-resource --profile mercadofresco-dev \
  --resource "arn:aws:cloudfront::111122223333:distribution/$DIST_ID" \
  --tags 'Items=[{Key=Proyecto,Value=mercadofresco},{Key=Entorno,Value=produccion},
                 {Key=Componente,Value=catalogo},{Key=Propietario,Value=marta},
                 {Key=CentroCoste,Value=marketing}]'

# El despliegue a las edge locations tarda unos minutos
aws cloudfront wait distribution-deployed --profile mercadofresco-dev --id "$DIST_ID"

aws cloudfront get-distribution --profile mercadofresco-dev --id "$DIST_ID" \
  --query 'Distribution.DomainName' --output text

Nota sobre el perfil: CloudFront es global, así que sus comandos no llevan --region; la CLI usa us-east-1 internamente.

Invalidación y seguimiento:

INV_ID=$(aws cloudfront create-invalidation --profile mercadofresco-dev \
  --distribution-id "$DIST_ID" --paths "/productos/tomate-rama.jpg" \
  --query 'Invalidation.Id' --output text)

aws cloudfront get-invalidation --profile mercadofresco-dev \
  --distribution-id "$DIST_ID" --id "$INV_ID" --query 'Invalidation.Status'

⚠️ Coste y limpieza

CloudFront no cobra por tener una distribución: solo por transferencia y peticiones, y las primeras 1 TB y 10 M al mes son gratuitas de forma permanente. Es, con diferencia, el servicio más barato de este módulo, y una distribución de prácticas con tráfico de laboratorio cuesta cero.

Lo único que puede sorprender son las invalidaciones: 1.000 rutas gratis al mes y 0,005 USD por ruta después. Invalidar /* cuenta como una sola ruta, así que ni eso es caro; lo malo es invalidar 5.000 ficheros uno a uno en cada despliegue.

Para borrarla hay que desactivarla primero y esperar a que se despliegue el cambio:

ETAG=$(aws cloudfront get-distribution-config --profile mercadofresco-dev \\
  --id "$DIST_ID" --query 'ETag' --output text)
# Editar el JSON poniendo "Enabled": false y volver a aplicarlo
aws cloudfront update-distribution --profile mercadofresco-dev --id "$DIST_ID" \\
  --distribution-config file://distribucion-desactivada.json --if-match "$ETAG"
aws cloudfront wait distribution-deployed --profile mercadofresco-dev --id "$DIST_ID"
aws cloudfront delete-distribution --profile mercadofresco-dev --id "$DIST_ID" --if-match "$ETAG_NUEVO"

Y si borras la distribución, acuérdate de restaurar el acceso al bucket, o las fotos dejarán de servirse por completo.

Una mención final: delante de CloudFront se pueden colocar AWS Shield, que protege frente a ataques de denegación de servicio (lección 04-04), y AWS WAF, que filtra peticiones maliciosas por reglas y por tasa (lección 04-05). Ambos se aplican en el borde, antes de que el tráfico llegue a la VPC, y ese es precisamente el motivo por el que conviene que todo el tráfico de MercadoFresco, estático y dinámico, pase por CloudFront.

Errores Comunes y Consejos

Meter demasiado en la clave de caché. Es el error caro. User-Agent o todas las cookies hunden la tasa de aciertos a casi cero y pagas una CDN que no cachea. Si el origen necesita un dato pero el contenido no cambia con él, va en la política de solicitud de origen, no en la de caché.

Pedir el certificado de CloudFront en eu-west-1. Debe estar en us-east-1. Ni siquiera aparecerá en la lista y perderás media hora buscando el motivo.

Invalidar en cada despliegue en vez de versionar los nombres. Es más lento, cuesta dinero, no limpia la caché de los navegadores y no permite volver atrás. Versiona el nombre del fichero.

Olvidar AWS:SourceArn en la política del OAC. Sin esa condición, cualquier distribución de CloudFront del mundo puede leer tu bucket. Es un fallo de seguridad real y silencioso.

Cachear /api/*. Servir a un cliente la respuesta cacheada del pedido de otro es una fuga de datos. Usa CachingDisabled y permite todos los métodos HTTP.

Poner TTL largos sin Cache-Control en el origen. El TTL de CloudFront no llega al navegador. Si quieres que el cliente también cachee, la cabecera tiene que estar en el objeto de S3.

No revisar CacheHitRate. Una distribución con un 20 % de aciertos está mal configurada y nadie se entera hasta que llega la factura. Míralo cada semana.

Dejar los parámetros de campaña en la clave de caché. Cada utm_source distinto crea una entrada nueva del mismo HTML. Límpialos con una CloudFront Function en viewer-request.

Usar Lambda@Edge para algo que resuelve una CloudFront Function. Seis veces más caro, menos puntos de ejecución y mucha más latencia, para nada.

Consejo de oro: después de cada cambio de configuración, comprueba la cabecera X-Cache con curl -I dos veces seguidas. La primera debe decir Miss; la segunda, Hit. Si la segunda sigue diciendo Miss, hay algo en la clave de caché que no debería estar.

Ejercicios

Ejercicio 1: diseñar los comportamientos de caché de una tienda completa

MercadoFresco añade tres tipos de contenido nuevos: /static/* (CSS y JavaScript con nombre versionado), /pedidos/* (páginas HTML personalizadas por usuario autenticado) y /buscar (búsqueda con parámetro q, cuyos resultados son iguales para todos los usuarios). Diseña el comportamiento de caché de cada uno: patrón, origen, métodos, política de caché, TTL y justificación.

Ejercicio 2: diagnosticar una tasa de aciertos del 12 %

MercadoFresco lleva un mes con CloudFront y CacheHitRate está en el 12 %. La factura de S3 no ha bajado. Enumera al menos cinco causas posibles, el comando o la comprobación que confirma cada una, y la corrección.

Ejercicio 3: calcular el punto de equilibrio

Sara quiere saber a partir de qué volumen de tráfico CloudFront deja de salir «gratis» y cuánto se ahorraría si MercadoFresco abriera en Portugal y Francia y el tráfico se duplicara. Calcula el coste mensual de servir 2 TB con 4 millones de peticiones, con y sin CloudFront, y explica qué cambia cuando se supera la capa gratuita.

Soluciones

Solución 1

Patrón Origen Métodos Política de caché TTL Justificación
/static/* origen-fotos-s3 o el ALB GET, HEAD CachingOptimized + Compress: true Min 0 / Def 1 año / Max 1 año El nombre lleva versión (app.a3f9c1.js), así que el contenido es inmutable. La compresión es clave: CSS y JS se reducen un 70-80 % con Brotli
/pedidos/* origen-tienda-alb Todos CachingDisabled 0 / 0 / 0 Contenido personalizado por usuario autenticado. Cachearlo mostraría el pedido de un cliente a otro. Además hay que reenviar Authorization y las cookies de sesión con la política de solicitud de origen AllViewer, sin meterlas en la clave de caché (que aquí no existe)
/buscar origen-tienda-alb GET, HEAD Política propia: clave = ruta + parámetro q únicamente Min 0 / Def 300 / Max 3600 El resultado es igual para todos, así que se puede cachear: la búsqueda de «tomate» la hacen cientos de clientes. Solo q en la clave; ni cookies ni utm_*. Cinco minutos es suficiente para absorber el pico sin mostrar precios obsoletos

El caso más instructivo es /buscar: es contenido dinámico que se puede cachear porque no depende del usuario. La distinción correcta no es «estático frente a dinámico», sino «igual para todos frente a personalizado». Una búsqueda popular servida desde caché ahorra una consulta a RDS por cada acierto, lo cual descarga la base de datos además de la red.

Precedencia: /static/*, /pedidos/* y /buscar antes que el comportamiento por defecto *. El orden entre ellos es indiferente porque los patrones no se solapan.

Solución 2

Causa 1: elementos innecesarios en la clave de caché. La más probable con diferencia.

aws cloudfront get-cache-policy --profile mercadofresco-dev --id "$POLICY_ID" \
  --query 'CachePolicy.CachePolicyConfig.ParametersInCacheKeyAndForwardedToOrigin'

Buscar HeaderBehavior: whitelist con cabeceras muy variables, CookieBehavior: all o QueryStringBehavior: all. Corrección: pasar todo lo que el origen necesite pero que no cambie el contenido a la política de solicitud de origen.

Causa 2: parámetros de campaña. Sara ha lanzado una campaña y cada correo lleva un utm_content distinto. Se ve en los registros de acceso, con miles de URL casi idénticas. Corrección: la CloudFront Function de limpieza de parámetros.

Causa 3: el origen envía Cache-Control: no-cache o max-age=0.

curl -I https://mercadofresco-catalogo-fotos.s3.eu-west-1.amazonaws.com/productos/tomate-rama-v3.jpg

Si no aparece Cache-Control, el objeto se subió sin la cabecera. Corrección: volver a subir con --cache-control, o aws s3 cp sobre sí mismo con --metadata-directive REPLACE.

Causa 4: TTL demasiado corto. Un DefaultTTL de 60 s en las fotos hace que la caché caduque constantemente. Se ve en get-cache-policy. Corrección: 1 año para contenido versionado.

Causa 5: tráfico muy repartido geográficamente con contenido poco popular. Cada edge location tiene su propia caché; si un objeto se pide una vez al mes desde cada ciudad, siempre será un fallo. Se confirma cruzando los registros de acceso por x-edge-location. Corrección: PriceClass_100 concentra el tráfico en menos edges y mejora la tasa de aciertos además de abaratar.

Causa 6, menos evidente: el comportamiento por defecto * está capturando las fotos porque el patrón /productos/* está mal escrito (por ejemplo, productos/* sin la barra inicial). Se comprueba con X-Cache y con el x-edge-result-type de los registros.

Solución 3

Sin CloudFront, 2 TB (2.048 GB) y 4 M de peticiones desde S3:

Concepto Cálculo Coste
Transferencia de salida 2.048 GB × 0,09 USD/GB 184,32 USD
Peticiones GET 4.000.000 × 0,0004/1.000 1,60 USD
Almacenamiento 40 GB × 0,023 0,92 USD
Total 186,84 USD

Con CloudFront (PriceClass_100, 90 % de aciertos):

Concepto Cálculo Coste
Transferencia de CloudFront Primer 1 TB gratis; los otros 1.024 GB × 0,085 87,04 USD
Peticiones HTTPS 4 M, dentro de los 10 M gratuitos 0,00 USD
Transferencia S3 → CloudFront Gratuita 0,00 USD
Peticiones GET a S3 (10 % de fallos) 400.000 × 0,0004/1.000 0,16 USD
Almacenamiento 40 GB × 0,023 0,92 USD
Total 88,12 USD

Ahorro: 98,72 USD al mes, un 53 %.

Qué cambia al superar la capa gratuita. Por debajo de 1 TB, CloudFront es esencialmente gratis y el ahorro roza el 100 %. Por encima, el ahorro pasa a depender de dos factores estructurales que se mantienen siempre:

  1. La transferencia del origen a CloudFront es gratuita, así que S3 deja de facturar salida por completo, sea cual sea el volumen.
  2. El precio por GB de CloudFront es menor que el de S3 (0,085 frente a 0,09 USD/GB en Europa) y además baja por tramos con el volumen: a partir de 10 TB, 50 TB y 150 TB el precio cae escalonadamente, mientras que el de S3 es prácticamente plano.

El punto de equilibrio, por tanto, no existe: CloudFront es más barato que S3 directo en cualquier volumen, y la única razón para no ponerlo sería que el contenido fuera imposible de cachear y estuviera consumido por un único cliente en la misma región. Ese no es el caso de una tienda.

Conclusión

MercadoFresco ha atacado el problema del coste por su raíz. Sabes qué es una CDN, cómo funcionan los tres niveles de CloudFront —edge locations, regional edge caches y origen— y qué ocurre exactamente en un acierto y en un fallo de caché, incluido el dato que lo cambia todo: la transferencia del origen a CloudFront es gratuita. Sabes leer la cabecera X-Cache para saber en qué situación estás.

Has configurado una distribución con dos orígenes —el bucket mercadofresco-catalogo-fotos y el alb-mercadofresco-tienda de la lección anterior—, conoces los grupos de origen para conmutar por error, y has repartido el tráfico en comportamientos de caché por patrón de ruta, con la regla de que gana el primero que coincide y el * va siempre el último. Sobre todo, dominas el concepto que decide si una CDN sirve para algo: la clave de caché. Sabes que cada valor distinto de cada elemento crea una entrada nueva, que meter User-Agent o todas las cookies hunde la tasa de aciertos al 5 %, y que la distinción correcta es entre la política de caché (lo que forma la clave) y la política de solicitud de origen (lo que el origen necesita recibir sin fragmentar la caché). Sabes cómo interactúan MinTTL, DefaultTTL y MaxTTL con el Cache-Control del origen, y por qué la cabecera debe ponerse en el objeto de S3.

Has cerrado el bucket con Origin Access Control, con una política que autoriza al servicio cloudfront.amazonaws.com únicamente desde la distribución concreta gracias a la condición AWS:SourceArn —sin la cual cualquier distribución del mundo podría leerlo—, y sabes que OAC sustituye al antiguo OAI. Has comprobado con dos curl que el acceso directo devuelve 403 y el de CloudFront 200. Sabes por qué el versionado de nombres de fichero es mejor que las invalidaciones: es inmediato, gratuito, reversible y limpia también la caché del navegador. Conoces la trampa del certificado en us-east-1 y sabes que hacen falta dos certificados, uno por región. Has visto cuándo compensa pasar también el contenido dinámico por CloudFront, y has distinguido CloudFront Functions de Lambda@Edge, escribiendo dos funciones reales: las cabeceras de seguridad y la limpieza de parámetros de campaña que recupera la tasa de aciertos que las campañas de Sara estaban destruyendo.

Y las cuentas: con PriceClass_100, la capa gratuita permanente de 1 TB y un 90 % de aciertos, el coste mensual del catálogo pasa de 28,40 USD a 0,97 USD, un 96,6 % menos, y las fotos se sirven en 25 ms en lugar de 350 ms. El problema del 97 % de transferencia de salida detectado en 02-03 está resuelto, y el ALB de 03-03 solo procesa ya lo que de verdad no se puede cachear.

Falta una sola cosa, y es la que hace que nada de esto sea visible para un cliente real: el dominio. Ahora mismo la tienda responde en d111111abcdef8.cloudfront.net y el balanceador en alb-mercadofresco-tienda-1234567890.eu-west-1.elb.amazonaws.com. Nadie va a teclear eso. Además, los dos certificados de ACM que has pedido siguen en PENDING_VALIDATION esperando unos registros DNS que aún no existen. En la lección 03-05, «Route 53», montaremos el DNS de MercadoFresco: validaremos los certificados, aprenderemos qué son los registros de alias y por qué son la única forma de apuntar el vértice mercadofresco.example a CloudFront, repasaremos las políticas de enrutamiento —ponderada para el canario del 10 %, geolocalización para abrir en Portugal, conmutación por error hacia una página de mantenimiento— y cerraremos el módulo con la arquitectura de red completa.

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