In questo articolo spiego perché conviene ridimensionare le immagini sul proprio sito e come sfruttare Google Cloud Functions insieme a GCP Load Balancer e Cloud CDN per servire immagini ridimensionate on-the-fly, migliorare le performance del sito, risparmiare sullo spazio di storage e ridurre i tempi di caricamento.

Immagine generata con ChatGPT 5
Quasi tutti i grandi siti web ridimensionano le foto che pubblicano, e lo fanno per tre motivi principali:
- Scaricare immagini di grandi dimensioni si traduce in un elevato consumo di banda e in costi di trasferimento dati altrettanto elevati.
- Ridimensionare le immagini via HTML costringe il browser del client a scaricare l'intero file e a impiegare CPU per ridurlo, rallentando il caricamento del sito.
- Senza ridimensionamento on-the-fly bisogna conservare versioni diverse della stessa immagine per Desktop, Mobile, Tablet ed email (newsletter): considerando che lo storage in cloud parte da 0,02 $/GB al mese, il conto può diventare salato.
Per approfondire l'ottimizzazione delle immagini, dia un'occhiata al libro di Steve Souders Even Faster Web Sites, capitolo 10.
Sfruttare Google Cloud Content Delivery Network (CDN) ci permette di servire i contenuti statici con tempi di caricamento più rapidi, perché gli oggetti vengono memorizzati nella cache della rete edge distribuita a livello globale.

La soluzione descritta in questo articolo si basa sui seguenti servizi:
- Google Cloud Storage — per archiviare le immagini in dimensione originale.
- Cloud Function — per ridimensionare gli oggetti on-the-fly.
- Google Cloud Load Balancer con Cloud CDN abilitato — è il punto di accesso da cui gli utenti recuperano le immagini.
Creiamo un GCP Cloud Load Balancer (LB) e lo configuriamo perché invochi una Cloud Function: la funzione preleva un'immagine da Google Cloud Storage (GCS) e la ridimensiona, poi il LB restituisce l'immagine ridimensionata salvandola sull'edge per le richieste successive.

Come effettuare il deploy
Creiamo un bucket Google Cloud Storage (GCS) e carichiamo tutti gli oggetti (immagini) nelle dimensioni originali.

Assegniamo un nome al bucket, scegliamo una location regionale e clicchiamo sul pulsante Create:

Nel bucket abbiamo caricato un'immagine, ad esempio nasdaq.jpg (l'edificio del Nasdaq a Times Square, scattata nel 2019), nella sua dimensione originale di 6,3 MB.

Il passo successivo è creare una funzione. Apriamo la console di Cloud Functions e clicchiamo su Write a function.

Se è la prima volta che usa Cloud Functions, potrebbe comparire il messaggio "Cloud Functions API is enabled": significa che le funzionalità di Cloud Functions vengono attivate sul progetto senza costi aggiuntivi.
A questo punto configuriamo la funzione:
- Usiamo l'editor inline.
- Diamo un nome alla funzione.
- Scegliamo la regione: deve essere la stessa del bucket GCS.
- Impostiamo il runtime. In questa demo usiamo Python.
- Disattiviamo l'autenticazione IAM, in modo che il LB possa invocare la funzione.

Ora clicchiamo sul pulsante Create: se è la prima volta che si usano i servizi GCP, potrebbe essere necessario abilitare qualche API in più.

In questo esempio dobbiamo abilitare Cloud Build per costruire il container che esegue la funzione.
Ora arriva la parte divertente: scrivere il codice che preleva il file da GCS, lo ridimensiona e restituisce il file ridimensionato.
Volevamo capire se l'intera operazione potesse essere svolta da ChatGPT, così ho fatto qualche iterazione con l'LLM utilizzando questi prompt:
"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''"
Il risultato è il codice qui sotto. Alla riga 9 va indicato il bucket che usiamo:
Questo è il file requirements.txt con cui diciamo a Cloud Functions quali librerie Python utilizzare per costruire il container:
Quando aggiorniamo il codice in main.py e le librerie in requirements.txt, compare l'avviso "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."
Il motivo è che la funzione richiama resize_image, mentre il nome di default in Cloud Functions è hello_http:

Cambiamo il Function entry point in resize_image e clicchiamo su Save and redeploy.
Cloud Functions costruisce il container con il nostro codice. L'operazione può richiedere un paio di minuti e lo stato della build è visibile nella parte alta del dashboard:

Nota: Cloud Function invoca una funzione che utilizza il Compute Engine Default Service Account, il quale le concede l'accesso a tutti i bucket GCS del progetto. Non approfondiremo l'argomento in questo articolo, ma come best practice è preferibile utilizzare un service account dedicato basato sul principio del minimo privilegio.
Il passo successivo è creare un Load Balancer. Cerchiamo "Load Balancer" nella search console, apriamo il dashboard del Load Balancer e clicchiamo su Create load balancer:

Vogliamo creare un Application Load Balancer Global e Public Facing. Tra i tipi di load balancer scegliamo quindi Application Load Balancer (HTTP/HTTPS):

Vogliamo che gli utenti di tutto il mondo possano raggiungerlo, perciò selezioniamo Public Facing (external):

Abbiamo scelto il load balancer Global per sfruttare Google Cloud CDN, che permette di mettere in cache l'immagine ridimensionata sull'edge server (senza costi aggiuntivi).

Quindi selezioniamo l'ultima generazione di Load Balancer e clicchiamo su Next e Create.

Per semplificare questa fase, creiamo un endpoint frontend HTTP per il Load Balancer.
Assegniamo un nome al Load Balancer e all'IP frontend.

Clicchiamo su Backend Configuration e, nel riquadro Backend services & backend buckets, su Create a backend service.

Il backend service indica al Load Balancer a quale risorsa inoltrare la richiesta; nella nostra configurazione si tratta di una Cloud Function.
Diamo un nome e una descrizione al backend service e cambiamo il Backend type in Serverless network endpoint group.

Scorriamo fino alla sezione Backends. Sotto New backend clicchiamo su Serverless network endpoint groups e poi su Create Serverless network endpoint group.

Indichiamo il nome dell'Endpoint e selezioniamo la regione in cui è stata creata la Cloud Function.

Se non ha mai invocato la funzione prima d'ora, comparirà questo errore:

Apra il link nel messaggio di errore e abiliti la Cloud Function API:

Torni alla pagina Serverless network endpoint, imposti un nome per l'endpoint, la regione in cui si trova la funzione e scelga Cloud Run (può sembrare poco intuitivo, ma le Cloud Functions oggi si chiamano Cloud Run Functions e si basano sulla stessa tecnologia sottostante).
Selezioni la funzione e clicchi su Create.

Nella pagina del backend possiamo configurare le impostazioni della cache di Cloud CDN:
Possiamo dire alla CDN di tenere la risposta in cache per la durata indicata (1 ora, dalla riga 54 dello script) o anche più a lungo.
Va però sottolineato che la CDN usa una warm cache: nella cache vengono mantenuti solo i contenuti richiesti di frequente. Anche impostando una cache di 1 anno, in assenza di richieste regolari è prevedibile che dopo un po' la CDN debba recuperare nuovamente il contenuto dall'origine (la funzione).

Si noti la funzionalità di Logging (indicata dalla freccia nell'immagine sopra). È utilissima per il debug e per tracciare l'utilizzo, ma è anche cara (512 $ per 1 TB di log).
Se genera molte richieste, può impostare il Sample rate a 0.01 per registrare una richiesta ogni 100.
Scorriamo fino a Security:
Per impostazione predefinita Google abilita Cloud Armor, il Web Application Firewall (WAF), sul nostro backend. Trattandosi di una demo e dato che comporta costi aggiuntivi, lo disabilitiamo: clicchiamo su Cloud Armor backend security policy e impostiamo il valore su None.

Il passo successivo è cliccare su Create e, nel dashboard del Load Balancer, di nuovo su Create.
Nota: la creazione e l'aggiornamento di un Load Balancer in GCP possono richiedere fino a 15 minuti per la propagazione.
Quando il load balancer è pronto, clicchi sul suo nome per vedere l'IP generato:

Apriamo questo IP indicando il nome dell'immagine, l'altezza e la larghezza:
http://34.49.21.153/?image=nasdaq.jpg&size=500x600
Il risultato: invece di un file da 6,5 MB otteniamo un'immagine ridimensionata da appena 63 KB.

Il mio lavoro è guidare i clienti nell'uso del cloud. Scopra cosa possiamo fare per Lei su doit.com/services