Il tuo gestionale carica un CSV ogni notte e qualcuno deve elaborarlo. Oppure un webhook di Stripe manda un evento e devi aggiornare il database in tempo reale. La soluzione classica? Un server sempre acceso, che paghi anche quando non fa nulla.
Noi, di Meteora Web, lavoriamo con aziende che hanno processi batch o eventi asincroni da gestire. Cloud Functions di Google Cloud Platform è lo strumento che usiamo quando serve codice che gira solo quando serve, si scala da zero a centinaia di richieste e costa pochi centesimi al mese. In questa guida vediamo come scrivere, deployare e monitorare funzioni serverless con Python e Node.js, con codice funzionante e ragionamenti sui costi.
Perché serverless? Il punto di vista economico
Prima di scrivere una riga di codice, partiamo dal problema reale: il cliente ha un processo che va eseguito ogni volta che succede qualcosa. Un server dedicato costa decine di euro al mese anche se esegue un solo job al giorno. Con Cloud Functions paghi solo il tempo di esecuzione e le invocazioni. Con la quota gratuita (2 milioni di invocazioni/mese, 400.000 GB-sec) puoi gestire molte PMI senza spendere un euro.
Noi lo vediamo tutti i giorni: un e-commerce che genera report notturni, un sistema di prenotazioni che invia email di conferma, un backend che processa immagini caricate dagli utenti. In tutti questi casi, una funzione serverless è più economica, più facile da mantenere e scala automaticamente.
Cloud Functions: cosa sono e quando usarle
Cloud Functions è il prodotto serverless di Google Cloud. Scrivi una funzione in Python, Node.js, Go, Java, Ruby, PHP o .NET, la deployi con un comando, e Google si occupa di runtime, scalabilità, aggiornamenti di sicurezza. Puoi triggerarla da eventi (upload su Cloud Storage, messaggi su Pub/Sub, modifiche al Firestore) o via HTTP (API leggera).
Sponsored Protocol
Il nostro stack preferito: Python per elaborazione dati (pandas, numpy) e Node.js per API veloci e webhook. Ma la logica è identica.
Quando NON usare Cloud Functions
Una funzione ha un timeout massimo di 60 minuti (con 2nd gen) e risorse limitate (fino a 16 GB RAM, 4 vCPU). Se hai un job che deve durare ore o consumare centinaia di GB di RAM, meglio Cloud Run o Compute Engine. E se devi rispondere a centinaia di richieste concorrenti con latenza ultra-bassa, un servizio always-on può essere più adatto.
Scrivere la prima funzione: Python
Creiamo una funzione HTTP che accetta una richiesta POST con un JSON, lo processa e restituisce un risultato. Esempio reale: un webhook che riceve un ordine e lo salva su Firestore.
# main.py
import functions_framework
from google.cloud import firestore
@functions_framework.http
def process_order(request):
"""Riceve ordine da webhook e lo salva su Firestore"""
request_json = request.get_json(silent=True)
if not request_json or 'order_id' not in request_json:
return {'error': 'Missing order_id'}, 400
db = firestore.Client()
doc_ref = db.collection('orders').document(request_json['order_id'])
doc_ref.set(request_json)
return {'status': 'ok', 'id': request_json['order_id']}, 200
Per deployare:
gcloud functions deploy process-order \
--runtime python312 \
--trigger-http \
--allow-unauthenticated \
--region europe-west1
Attenzione: `--allow-unauthenticated` va usato solo per test o webhook pubblici. In produzione, proteggi con API Key o Identity-Aware Proxy.
Sponsored Protocol
Gestire le dipendenze
Le dipendenze Python vanno elencate in requirements.txt:
functions-framework==3.*
google-cloud-firestore==2.*
Il framework functions-framework serve per test locali. Non serve in produzione, ma lo includiamo per coerenza.
Scrivere la prima funzione: Node.js
Stessa logica in JavaScript. Usiamo il framework @google-cloud/functions-framework.
// index.js
const functions = require('@google-cloud/functions-framework');
const {Firestore} = require('@google-cloud/firestore');
functions.http('processOrder', async (req, res) => {
if (req.method !== 'POST') {
res.status(405).send('Method Not Allowed');
return;
}
const { order_id, ...data } = req.body;
if (!order_id) {
res.status(400).json({ error: 'Missing order_id' });
return;
}
const db = new Firestore();
const docRef = db.collection('orders').doc(order_id);
await docRef.set(data);
res.status(200).json({ status: 'ok', id: order_id });
});
package.json:
{
"dependencies": {
"@google-cloud/functions-framework": "^3.0.0",
"@google-cloud/firestore": "^7.0.0"
}
}
Deploy:
gcloud functions deploy process-order \
--runtime nodejs20 \
--trigger-http \
--allow-unauthenticated \
--region europe-west1
Trigger da Cloud Storage (Python)
Scenario: un file CSV viene caricato su un bucket, la funzione lo legge e lo importa in BigQuery.
# import_csv.py
from google.cloud import storage, bigquery
import functions_framework
@functions_framework.cloud_event
def import_csv_from_bucket(cloud_event):
data = cloud_event.data
bucket_name = data['bucket']
file_name = data['name']
if not file_name.endswith('.csv'):
print(f"Ignored {file_name} - not a CSV")
return
client = bigquery.Client()
uri = f"gs://{bucket_name}/{file_name}"
dataset_id = "my_dataset"
table_id = "imported_data"
job_config = bigquery.LoadJobConfig(
source_format=bigquery.SourceFormat.CSV,
skip_leading_rows=1,
autodetect=True,
write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE
)
load_job = client.load_table_from_uri(uri, f"{dataset_id}.{table_id}", job_config=job_config)
load_job.result() # wait
print(f"Loaded {file_name} into {dataset_id}.{table_id}")
Deploy con trigger su bucket:
Sponsored Protocol
gcloud functions deploy import-csv \
--runtime python312 \
--trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
--trigger-event-filters="bucket=my-data-bucket" \
--region europe-west1
Test locale e debug (senza spendere un centesimo)
Prima di deployare, testa localmente. Per Python:
pip install functions-framework
functions-framework --target=process_order --port=8080
curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d '{"order_id":"123"}'
Per Node.js:
npx @google-cloud/functions-framework --target=processOrder --port=8080
curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d '{"order_id":"123"}'
Puoi usare Docker per simulare l'ambiente esatto, ma per il 90% dei casi il framework locale basta.
Sponsored Protocol
Variabili d'ambiente e segreti
Non scrivere mai credenziali nel codice. Usa le variabili d'ambiente o Secret Manager.
gcloud functions deploy my-function \
--set-env-vars DATABASE_URL=postgres://... \
--set-secrets MY_API_KEY=my-secret:latest \
--region europe-west1
Nel codice Python accedi con os.environ.get('DATABASE_URL'). I segreti sono montati come file in /etc/secrets o accessibili via libreria.
Costi e ottimizzazione
Una funzione HTTP con 128 MB di RAM che impiega 200 ms per risposta costa circa $0.000002 per invocazione (dopo la quota gratuita). Per un e-commerce con 10.000 ordini/mese parliamo di 2 centesimi. Ma attenzione:
- Cold start: la prima invocazione dopo un periodo di inattività può impiegare 1-2 secondi. Per ridurlo, mantieni un minimo di istanze (min-instances) o usa runtime più leggeri (Node.js è più veloce di Python nella partenza).
- Timeout: imposta un timeout adeguato (es. 30 secondi per un webhook, 10 minuti per un job batch). Valori troppo alti aumentano il rischio di costi imprevisti se la funzione va in loop.
- Memoria: usa la minima RAM necessaria. Python con pandas può necessitare 512 MB, ma una semplice API funziona con 128 MB. Ogni GB aggiuntivo costa circa $0.000006 per 100 ms.
Noi, di Meteora Web, consigliamo sempre di attivare i budget alert su GCP per ricevere notifiche se i costi superano una soglia. Un piccolo job sbagliato può generare migliaia di invocazioni in minuti.
Errori comuni e soluzioni
- Timeout scaduto: la funzione ha un limite di 60 minuti (2nd gen) o 9 minuti (1st gen). Per job più lunghi usa Cloud Run o Workflows.
- Dipendenze mancanti: in Python, se usi librerie native (pandas, numpy), assicurati che siano incluse nel requirements.txt e compatibili con l'ambiente (molte sono pre-installate).
- Connessioni non chiuse: apri e chiudi sempre le connessioni a database o API. Le connessioni aperte possono esaurire le risorse. Usa
withstatement in Python ofinallyin JS. - Eventi duplicati: Cloud Functions garantisce at-least-once delivery. La tua funzione deve essere idempotente (es. upsert invece di insert).
Monitoraggio e logging
Non deployare alla cieca. Usa Cloud Logging per vedere gli errori in tempo reale. Puoi visualizzare i log direttamente nella console GCP o con gcloud functions logs read. Imposta metriche personalizzate per monitorare il successo (es. contare ordini processati).
Sponsored Protocol
Per Python, usa il logger standard di Python. Per Node.js, console.log funziona. Google Cloud Logging cattura automaticamente stdout e stderr.
In sintesi — cosa fare adesso
- Identifica un processo che esegui periodicamente o su evento (webhook, upload, job notturno).
- Scrivi una funzione di test locale usando il framework di riferimento (Python o Node.js).
- Deploya con gcloud functions deploy, verificando prima con
--no-gen2(usa 1st gen per test) e poi passa alla 2nd gen per produzione. - Proteggi la funzione: per trigger HTTP usa API Key o Cloud Endpoints; per trigger eventi imposta i permessi IAM corretti.
- Attiva budget alert e monitora i log per le prime 24 ore.
Se vuoi approfondire l'ecosistema GCP, leggi la nostra Pillar Guide su Google Cloud Platform. Lì trovi il quadro completo: Cloud Run, GKE, Cloud Storage e come integrare Cloud Functions in architetture reali.