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
- Qué es una CDN y cómo funciona el reparto desde el borde
- Aciertos y fallos de caché
- Distribuciones y orígenes
- Grupos de origen y conmutación por error
- Comportamientos de caché y patrones de ruta
- La clave de caché: políticas de caché y de solicitud de origen
- TTL: mínimo, máximo y por defecto
- Compresión, HTTP/2 y HTTP/3
- Origin Access Control: cerrar el bucket al mundo
- Invalidaciones frente a versionado de nombres de fichero
- Dominios propios y la trampa del certificado en
us-east-1 - Contenido dinámico a través de CloudFront hacia el ALB
- CloudFront Functions y Lambda@Edge
- Registros, métricas y tasa de aciertos
- Clases de precio y el cálculo de ahorro de MercadoFresco
- 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:
- Solo el primer usuario paga la espera larga. Los siguientes reciben la respuesta desde el borde.
- 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.
- 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-Languageen lista blanca: el catálogo se sirve en español y en catalán, así que sí 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:
paginaycategoriasí 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. EnableAcceptEncodingGzipyBrotli: 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:
- Si el origen no envía
Cache-ControlniExpires→ se aplicaDefaultTTL. - Si el origen envía
Cache-Control: max-age=N→ se usaN, acotado entreMinTTLyMaxTTL. Cache-Control: no-storeoprivate→ no se cachea (salvo queMinTTLsea 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.
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 admitePOST/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:SourceArnes 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 concretaE2QWERTY123ABCde la cuenta111122223333. - 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
Denyyaws: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.jpgEl 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 eneu-west-1es 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 DNSAmbos 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 | Sí |
| Acceso al cuerpo de la petición | No | Sí |
| Puede llamar a otros servicios de AWS | No | Sí |
| 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 tableLos 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 textNota 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 sí 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.jpgSi 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:
- La transferencia del origen a CloudFront es gratuita, así que S3 deja de facturar salida por completo, sea cual sea el volumen.
- 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
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
