Purgas rápidas de Fastly con funciones de Cloud Functions de GCP

Los clientes suelen utilizar almacenes estáticos de objetos (p. ej., Cloud Storage, de Google) como backends para los servicios de Fastly. Al ser estables las condiciones dentro del cubo de almacenamiento la mayor parte del tiempo, Fastly tiene capacidad para almacenar en caché durante largos periodos. Esto está muy bien desde el punto de vista del rendimiento, sí. Pero cuando hay cambios, ¿cómo lograr que los usuarios finales los vean con rapidez?

Esta cuestión afecta, por lo general, al contenido que no está sometido a cambios frecuentes. La solución más recurrida es definir un TTL de caché muy corto en la red de distribución de contenidos (CDN), o bien purgar de forma manual toda la caché de la CDN. Sin embargo, tales procedimientos tienen sus consecuencias: si haces un cambio en tu sitio web, es posible que los usuarios se pasen horas, incluso días, viendo contenido antiguo hasta que la caché de la CDN se actualice con el contenido más reciente.

Por suerte, Fastly ofrece Instant Purge, que purga toda tu caché, archivos concretos e incluso grupos de archivos que compartan etiquetas (que denominamos «claves suplentes»). Cloud Storage de Google ofrece eventos que permiten activar Cloud Functions, producto de GCP que ejecuta bajo demanda lógica que no tiene estado asignado. Si combinamos estas funciones, podemos purgar la plataforma de edge cloud de Fastly al instante y de manera selectiva. ¿Con qué finalidad? Garantizar que las actualizaciones de contenidos sean visibles de inmediato para los usuarios, aun cuando los objetos tengan una vida útil de caché muy larga.

En esta entrada del blog, te voy a mostrar cómo vincular ambas funciones. Además, te voy a ayudar a configurar tu propio backend de GCS de tal forma que Fastly se purgue cuando hagas cambios en tu contenido.

Ten en cuenta que los pasos que vamos a describir aquí podrían conllevar que Google te cobre gastos adicionales.

Cómo funciona

Nuestro objetivo es conseguir un flujo como el siguiente:

  1. Un usuario solicita un recurso de tu sitio web, Fastly lo captura desde tu backend de GCS y lo almacena en la memoria caché durante largo tiempo (lo habitual es un año) en el edge.

  2. Actualizas el recurso cargando una nueva versión en GCS.

  3. Se activa un evento de GCS, lo cual ejecuta una función de Cloud Functions.

  4. La función envía una petición a la API de Fastly en la que se dan instrucciones a Fastly para que invalide el objeto.

  5. Otro usuario final hace la misma petición, Fastly ignora el objeto en la caché y vuelve a capturarlo en GCS.

Purging fastly when your GCS bucket contents changes

Conviene remarcar que la petición de purga mostrada en el paso 4 no suele borrar el recurso de las cachés de Fastly, sino que lo marca como obsoleto. Esto resulta muy práctico porque te podría interesar utilizar las directivas de caché stale-while-revalidate o stale-if-error. Si no es así, recuerda que marcar un objeto como obsoleto equivale a eliminarlo.

Introducción

Voy a presuponer que ya has contratado un servicio de Fastly; que tienes una cuenta de Google Cloud; que trabajas en un proyecto que contiene un cubo de GCS, donde tienes tus archivos; y que el cubo:

Para ligar estos componentes, vas a tener que habilitar la API de Cloud Functions. Probablemente te convenga instalar el SDK de Cloud en el sistema y asegurarte de que se actualice para poder ejecutar los comandos gsutil de la CLI; sin embargo, si lo prefieres, también podrás utilizar los botones de la consola de GCP.

Un requisito importante es que deshabilites el almacenamiento en caché integrado que viene con GCS. Para ello, define los metadatos de objeto al cargar el objeto en el cubo:

gsutil -m \
       -h "Cache-Control: public, max-age=0" \
       cp -r ~/[PATH_TO_CONTENT]/* gs://[BUCKET_NAME]

Además, podrás utilizar el comando setmeta para actualizar los metadatos de objeto respecto a objetos que ya hayas cargado.

Si tienes pensado configurar el cubo para este tutorial, podrías detenerte unos instantes para probar si puedes acceder al archivo de muestra a través del protocolo HTTP:

curl -i "https://[BUCKET_NAME].storage.googleapis.com/[FILE_PATH]"

Deberías comprobar los encabezados HTTP de la respuesta junto con los contenidos del archivo que cargaste. En concreto, fíjate en content-type y cache-control; si no son correctos, corrígelos mediante los metadatos de objeto descritos con anterioridad. Si recibes una respuesta del tipo «403 Forbidden», verifica si tu cubo es accesible públicamente.

Establecimiento de un TTL constante para Fastly

Puesto que todos los objetos que Fastly haya almacenado en caché se invalidan si sufren cambios, la vida útil de caché de todos los objetos puede ser la misma y se le puede asignar una duración muy larga. Si aún no has configurado tu servicio de Fastly para que funcione de este modo, puedes establecer un TTL constante siguiendo estos pasos:

  1. En la sección Configuration de la consola de Fastly, haz clic en Clone para crear una versión editable de tu servicio.

  2. En la barra lateral, selecciona Settings.

  3. Haz clic en Create your first cache setting.

  4. En Name, escribe un nombre adecuado, como «Set TTL to a year» (Definir TTL a un año).

  5. Define TTL (seconds) en 31536000 (son los segundos que tiene un año).

  6. Haz clic en Create.

  7. Haz clic en Activate para desplegar la configuración actualizada en el servicio de Fastly.

Creación de la función de Cloud Functions

Llegados a este punto, vamos a configurar la función de Cloud Functions.Aunque se puede escribir el código correspondiente en tu equipo local y desplegarlo mediante el SDK de Cloud de Google, de momento nos limitaremos a utilizar la IU. En la consola de Cloud Functions, haz clic en create function.

Escoge un nombre (como «fastly-purge»), y define el valor de trigger como «Cloud Storage» y el de event como «Finalize/create» (tendrás que navegar y seleccionar el cubo de GCS que contiene tus archivos).

Escribe el código siguiente con el editor insertado:

const fetch = require('node-fetch');
const FASTLY_PUBLIC_BASEURL = "https://www.example.com";
exports.fastlyPurge = async (obj, context) => {
  const baseUrl = FASTLY_PUBLIC_BASEURL.replace(/\/+$/, '');
  const fileName = obj.name.replace(/^\/+/, '');
  const completeObjectUrl = `${baseUrl}/${fileName}`;
  const resp = await fetch(completeObjectUrl, { method: 'PURGE'})
  if (!resp.ok) throw new Error('Unexpected status ' + resp.status);
  const data = await resp.json();
  console.log(`Job complete for ${fileName}, purge ID ${data.id}`);
};

La única dependencia que vamos a utilizar en este caso es el módulo node-fetch, de modo que hacer peticiones HTTP sea un poco más sencillo y reconocible. Cambia a la pestaña package.json y añádela como dependencia:

"dependencies": {
  "@google-cloud/storage": "^1.6.0",
  "node-fetch": "^2.6.0"
}

Por último, establece function to invoke a fastlyPurge y guarda la información.

GCP tarda alrededor de un minuto en cargar y activar tu función. ¡Y listo! Cada vez que cambies cualquier archivo en tu cubo de GCS, GCP envía a Fastly una notificación para eliminar de nuestros servidores las copias de esos archivos almacenadas en caché. A 31 de diciembre de 2019, este proceso tarda una media de 150 ms en completarse para toda la edge cloud.

Pruebas

Comprueba si Fastly almacena el objeto en caché ejecutando la siguiente petición en repetidas ocasiones:

curl -is "https://your.fastly.domain/file/path" 2>&1 | grep -i "x-cache:"

De este modo, verás el encabezado X-Cache de la respuesta HTTP, que debería mostrar de forma sistemática HIT o HIT-CLUSTER (y quizás MISS al principio):

x-cache: MISS
x-cache: HIT
x-cache: HIT
x-cache: HIT

Ahora introduce un cambio en el archivo y vuelve a cargarlo en el cubo. Podrías hacerlo con gsutil (o incluso, si lo deseas, con la IU del explorador del cubo):

gsutil -m -h "Content-Type:text/css" -h "Cache-Control:public, max-age=0" cp -r ~/path-to-content/* gs://[BUCKET_NAME]

Vuelve a ejecutar el comando cURL para solicitar de nuevo el objeto a Fastly. La primera vez que lo hagas, quizás experimentes un tiempo de respuesta un poco más largo y veas valores de MISS o MISS-CLUSTER para x-cache.

Sin embargo, lo más reseñable es que el archivo ha cambiado y refleja el cambio que hayas cargado en el cubo (elimina el grep «|» del comando cURL para ver el contenido de la respuesta).

Significado del código

La función se invoca con los argumentos obj y context;el primero es un objeto de Cloud Storage, y el segundo proporciona metadatos sobre el evento. La única propiedad de los argumentos que nos interesa es obj.name, que contiene el nombre del objeto que se ha creado, actualizado o eliminado en el cubo de GCS. También tenemos que conocer el dominio al que se entrega el objeto en tu servicio de Fastly; así, podremos construir una URL completa dirigida al objeto que tenga sentido para Fastly:

const baseUrl = FASTLY_PUBLIC_BASEURL.replace(/\/+$/, '');
const fileName = obj.name.replace(/^\/+/, '');
const completeObjectUrl = `${baseUrl}/${fileName}`;

Las purgas de URL concretas son caso aparte en la API de Fastly. ¿Por qué? Porque la petición se envía a la URL que quieras purgar, no a un punto de conexión del tipo «api.fastly.com». Enviamos la petición ayudándonos de la biblioteca node-fetch con el método HTTP PURGE y notificamos el identificador de purga que Fastly haya devuelto. Si necesitas asistencia del equipo de soporte técnico de Fastly, menciona ese identificador para agilizar el proceso.

const resp = await fetch(completeObjectUrl, { method: 'PURGE'})
const data = await resp.json();
console.log(`Job complete for ${fileName}, purge ID ${data.id}`);

Resolución de problemas

Si el archivo que Fastly entregó no cambia una vez que lo editas y lo cargas en GCS, a continuación te damos algunas ideas para resolver problemas:

¿Se cargó de manera correcta?

Solicita el objeto directamente al cubo, eludiendo a Fastly, para comprobar si el cambio se ha producido en GCS:

curl -i "https://[BUCKET_NAME].storage.googleapis.com/[PATH]"

En caso negativo, el problema podría tener que ver con que el archivo actualizado se ha insertado en el cubo o con que el almacenamiento en caché esté integrado en GCS. En caso afirmativo, el problema podría estar relacionado con la función de Cloud Functions que creaste.

¿Se ejecutó la función de Cloud Functions?

Repasa tus registros de Stackdriver para determinar si la función se activó de manera correcta:

  1. Accede a la consola de Google Cloud Functions.

  2. Haz clic en el nombre de la función que creaste.

  3. En la parte superior de la página, haz clic en View logs.

  4. Verifica la marca de fecha y hora de la entrada más reciente del registro y cotéjala con la hora a la que cargaste tu cambio en el cubo.

¿Funcionó la petición de la API?

Si los registros indican que hubo actividad atribuible a la función cuando actualizaste el archivo en el cubo, echa un vistazo al contenido de estos para confirmar si Fastly confirmó la petición de purga. Lo normal es que vieras este mensaje:

Function execution started
Job complete for sample.css, purge ID 17953-1516801745-2111226
Function execution took 683 ms, finished with status: 'ok'

Si no aparece ese mensaje, es posible que se haya registrado un error en su lugar. Si necesitas ayuda, podrás ponerte en contacto con nosotros en el foro comunitario o por correo electrónico en la dirección support@fastly.com.

Siguientes pasos

¡Enhorabuena! Has conseguido que un cubo de GCS distribuya contenido a través de Fastly e invalide archivos al instante en caso de que estos sufran cambios. ¿Qué más se puede hacer?

  • Distribuir un sitio web estático entero, en vez de activos concretos. Tu solución funciona a la perfección con activos; para todo un sitio web, vas a necesitar un nombre de archivo para el índice del directorio predeterminado (p. ej., «index.html») al que distribuir peticiones de directorio y un archivo al que distribuir errores del tipo «404 Not Found». Para ello, tienes dos opciones: asigna un dominio personalizado a tu cubo de GCS (Google Cloud Platform) o escribe código complementario en VCL (Fastly).

  • Autenticar el acceso a GCS. No brindes acceso público de lectura a tu cubo. En su lugar, crea un usuario de IAM, asígnale el rol Storage Object Viewer y sigue las instrucciones que hemos dispuesto para generar tokens por petición para GCS.

  • Eliminar todos los encabezados x-goog-. Google agrega muchos encabezados a la respuesta HTTP que se recibe de Cloud Platform. Para eliminarlos, tienes dos alternativas: 1) asignar un dominio personalizado a tu cubo, lo cual activa el alojamiento estático de sitios web en GCS; o 2) configurar tu servicio de Fastly de modo que los suprima.

  • Autenticar las purgas. En la actualidad, cualquiera puede enviar una petición HTTP a tu dominio público de Fastly destinada a depurar un elemento de la memoria caché. Si esto te preocupa, quizá te interese autenticar tu función de Cloud Functions de tal forma que solo esta pueda enviar señales de purga. Para ello, crea un token de la API de Fastly que tenga capacidad de purga respecto a tu servicio, habilita las purgas autenticadas y modifica el código de tu función de modo que envíe un encabezado Fastly-Key con el valor definido al token de API que creaste.

  • Expresar toda la organización en forma de código. ¿Te has planteado utilizar la herramienta Terraform, de Hashicorp? Te permite capturar la configuración de Google Cloud Platform y de Fastly en un archivo que puedas guardar con fines de control de versiones.

Hemos profundizado mucho, pero la idea principal es esta: solo se necesita una línea de código en una sencilla función de Cloud Functions para lograr que Fastly conozca los cambios operados en tus cubos de GCS. Esta integración funciona como la seda y puede ser decisiva para el rendimiento y la eficacia de tu sitio web por dos motivos: acorta el tiempo de presentación de cara a tus usuarios finales y rebaja el consumo de datos que te cobra Google en la factura.

Andrew Betts
Head of Developer Relations
Fecha de publicación:

9 min de lectura

Comparte esta entrada
Andrew Betts
Head of Developer Relations

Andrew Bett es Head of Developer Relations en Fastly y colabora con desarrolladores de todo el mundo para contribuir a que la web sea más rápida, segura, fiable y manejable. Fundó una consultora web que acabó adquiriendo el Financial Times, dirigió un equipo que creó la pionera aplicación web HTML5 del FT y fundó la división Labs dentro de este rotativo. Además, es miembro electo del Technical Architecture Group del W3C, comité compuesto por nueve personas que proporcionan orientación sobre el desarrollo de internet.

¿List@ para empezar?

Ponte en contacto o crea una cuenta.