En este artículo te explico por qué conviene redimensionar las imágenes de tu sitio y cómo aprovechar Google Cloud Functions junto con GCP Load Balancer y Cloud CDN para servir imágenes redimensionadas al vuelo, mejorar el rendimiento de tu sitio, ahorrar en costos de almacenamiento y reducir los tiempos de carga.

Imagen generada con ChatGPT 5
Casi todos los sitios web grandes redimensionan las fotos que muestran, y lo hacen por tres razones principales:
- Descargar imágenes pesadas dispara el consumo de ancho de banda y los costos de transferencia de datos.
- Reducir el tamaño de las imágenes vía HTML obliga al navegador del cliente a descargar la imagen completa y luego gastar tiempo de CPU redimensionándola, con lo que se ralentiza la carga del sitio.
- Sin redimensionamiento al vuelo, tendrías que almacenar varios tamaños de la misma imagen para las versiones de Desktop, Mobile, Tablet y email (newsletter) de tu sitio. Y como los costos de almacenamiento en la nube parten desde $0.02/GB al mes, la cuenta puede salir cara.
Para profundizar en optimización de imágenes, revisa el libro de Steve Souders, Even Faster Web Sites , capítulo 10.
Usar Google Cloud Content Delivery Network (CDN) nos permite ofrecer tiempos de carga más rápidos para nuestro contenido estático, ya que los objetos se cachean en la red edge, distribuida globalmente.

La solución de este artículo se apoya en los siguientes servicios:
- Google Cloud Storage — para almacenar las imágenes en su tamaño original.
- Cloud Function — para redimensionar los objetos al vuelo.
- Google Cloud Load Balancer con Cloud CDN habilitado — los usuarios accederán al load balancer para obtener las imágenes.
Creamos un GCP Cloud Load Balancer (LB) y lo configuramos para invocar una Cloud Function. La función toma una imagen desde Google Cloud Storage (GCS), la redimensiona, y el LB responde con la imagen redimensionada y la guarda en el edge para futuras solicitudes.

Cómo desplegarlo
Creamos un bucket en Google Cloud Storage (GCS) y subimos todos los objetos (imágenes) en su tamaño original.

Le ponemos un nombre al bucket, configuramos una ubicación regional y hacemos clic en el botón Create:

Subimos una imagen, por ejemplo, nasdaq.jpg (del edificio del Nasdaq en Times Square, que tomé en 2019) a este bucket en su tamaño original, 6.3 MB.

El siguiente paso es crear una función. Entra a la consola de Cloud Functions y haz clic en Write a function.

Si es la primera vez que usas Cloud Functions, puede aparecer el mensaje "Cloud Functions API is enabled". Esto significa que las funcionalidades de Cloud Functions se están habilitando en tu proyecto sin costo adicional.
En este paso configuramos la función:
- Usamos el editor inline.
- Le ponemos un nombre a la función.
- Elegimos la región — debe ser la misma que la del GCS.
- Definimos el runtime. En este demo trabajamos con Python.
- Y desmarcamos la autenticación IAM para que el LB pueda invocar la función.

Ahora haz clic en el botón Create; si es la primera vez que usas servicios de GCP, es posible que tengas que habilitar algunas APIs adicionales:

en este ejemplo necesitamos habilitar Cloud Build para construir el contenedor que ejecuta la función.
Ahora viene la parte divertida: hay que escribir el código que toma el archivo desde GCS, lo redimensiona y devuelve el archivo resultante.
Queríamos ver si todo esto se podía hacer enteramente con ChatGPT, así que hice unas cuantas iteraciones con el LLM con los siguientes prompts:
"Write Python code to resize jpg png images to x,y based on the string provided."
"Let's assume it's google cloud function that pass the params as query string and the file needs to be loaded from gcs and the result should be sent to a load balancer with the appropriate mime type."
"What should I put in requirements.txt for this code?"
"Now I'm getting 'Error processing image: module 'PIL.Image' has no attribute 'ANTIALIAS''"
El resultado es el siguiente código. En la línea 9, configura el bucket que vamos a usar:
Este es el archivo requirements.txt para configurar Cloud Functions, donde se indican las librerías de Python que se usarán para construir el contenedor:
Cuando actualizamos el código en main.py y las librerías en requirements.txt, aparece la advertencia: "The specified function (entry point) might not be present in your source code. Please ensure the entry point in your code matches the input field."
Esto pasa porque la función llama a resize_image, mientras que el nombre por defecto de la función en Cloud Functions es hello_http:

Cambiamos el Function entry point a resize_image y hacemos clic en Save and redeploy.
Cloud Functions arma el contenedor de nuestro código. Puede tardar un par de minutos, y podemos seguir el estado del build en la parte superior del dashboard:

Nota: Cloud Function invoca una función. La función utiliza la Compute Engine Default Service Account, que le da acceso a todos los buckets de GCS del proyecto. No lo abordaremos en este artículo, pero como buena práctica conviene usar una service account dedicada con el principio de mínimo privilegio.
El siguiente paso es crear un Load Balancer. Busca "Load Balancer" en la consola, entra al dashboard de Load Balancer y haz clic en Create load balancer:

Vamos a crear un Application Load Balancer Global y Public Facing. En el tipo de load balancer, elige Application Load Balancer (HTTP/HTTPS):

Queremos que los usuarios de todo el mundo puedan acceder al load balancer, así que elegimos Public Facing (external):

Elegimos el load balancer Global para aprovechar la funcionalidad de Google Cloud CDN, que nos permite cachear la imagen redimensionada en el servidor edge (sin costo adicional).

Después elegimos la última generación de Load Balancer y hacemos clic en Next y luego en Create.

Para simplificar esta etapa, creamos un endpoint frontend HTTP para el Load Balancer.
Asigna un nombre al Load Balancer y a la IP del frontend.

Haz clic en Backend Configuration, y en el cuadro Backend services & backend buckets, haz clic en Create a backend service.

El backend service le indica al Load Balancer a qué recurso debe enviar la solicitud; en nuestra configuración es una Cloud Function.
Asigna un nombre y una descripción al backend service, y cambia el Backend type a Serverless network endpoint group.

Baja hasta el área de Backends. En New backend, haz clic en Serverless network endpoint groups y luego en Create Serverless network endpoint group.

Ponle un nombre al Endpoint y elige la región donde creaste la Cloud Function.

Si nunca has invocado la función, aparece este error:

Entra al enlace del error y habilita la Cloud Function API:

Vuelve a la página del Serverless network endpoint, ponle un nombre al endpoint, indica la región donde está la función y elige Cloud Run (puede resultar confuso, pero las Cloud Functions ahora se llaman Cloud Run Functions y usan la misma tecnología subyacente).
Selecciona la función y haz clic en Create.

En la página del backend podemos configurar los ajustes de caché de Cloud CDN:
Podemos configurar el CDN para cachear la respuesta durante un tiempo determinado (1 hora, según la línea 54 del script) o por una duración mayor.
Quiero destacar que el CDN usa una warm cache, es decir, solo el contenido solicitado con frecuencia se almacena en la caché del CDN. Si configuras una caché de 1 año pero no recibes solicitudes frecuentes, después de un tiempo el CDN volverá a traer el contenido desde el origen (la función).

Fíjate en la funcionalidad de Logging (marcada con la flecha arriba). Es excelente para depurar y monitorear el uso, pero también es costosa ($512 por 1 TB de logs).
Si generas muchas solicitudes, puedes configurar el Sample rate en 0.01 para registrar 1 de cada 100 solicitudes.
Bajemos hasta Security:
Por defecto, Google habilita Cloud Armor, el Web Application Firewall (WAF), para nuestro backend. Pero como tiene costo adicional y esto es un demo, lo desactivamos haciendo clic en Cloud Armor backend security policy y cambiándolo a None.

El siguiente paso es hacer clic en Create, y en el dashboard de Load Balancer, hacer clic en Create de nuevo.
Nota: Crear y actualizar un Load Balancer en GCP puede tardar hasta 15 minutos en propagarse.
Cuando el load balancer ya esté listo, haz clic en su nombre y verás la IP que se generó:

Accedemos a esa IP e indicamos el nombre de la imagen junto con el alto y el ancho:
http://34.49.21.153/?image=nasdaq.jpg&size=500x600
El resultado: en lugar de un archivo de 6.5 MB, recibimos una imagen redimensionada mucho más liviana, de 63 KB.

Mi trabajo es acompañar a los clientes en el uso de la nube. Descubre lo que podemos hacer por ti en doit.com/services