Ciao a tutti, Jules Martin qui, di nuovo su agntmax.com!
Oggi voglio parlare di qualcosa che mi preoccupa, e probabilmente anche molti di voi, da circa un anno: l’aumento progressivo dei costi dell’infrastruttura cloud, in particolare quando si tratta delle funzioni senza server. Siamo stati tutti sedotti dal sogno del “paga per ciò che usi”, e per molto tempo, sembrava essere una realtà. Ma ultimamente, ho visto le fatture salire, a volte in modo incomprensibile, anche quando i modelli di traffico sembravano stabili. È come se fossimo stati erosi dalla stessa flessibilità che abbiamo adottato. Quindi, esploriamo qualcosa di molto specifico e attuale: Dominare il Mostro Senza Server: Svelare e Ridurre i Costi Nascosti di AWS Lambda.
Il mio percorso in questo campo è iniziato circa sei mesi fa. Abbiamo un microservizio centrale che gestisce l’autenticazione degli utenti e la gestione delle sessioni. È costruito quasi interamente su AWS Lambda, API Gateway, DynamoDB e Cognito. Per lungo tempo, i costi erano perfettamente prevedibili. Poi, l’estate scorsa, la nostra fattura AWS per questo servizio specifico è aumentata di circa il 15%. Nessuna nuova funzionalità, nessun picco di traffico significativo. All’inizio ho attribuito questo a una fluttuazione stagionale o a un piccolo bug che non avevo ancora scoperto. Ma quando la fattura del mese successivo è arrivata ancora più alta, ho capito che dovevo indagare. Non era solo un incidente isolato; era una tendenza, e ci stava costando realmente dei soldi.
L’Illusione dei Livelli “Gratuiti” e la Realtà delle Invocazioni “Minime”
Uno dei principali punti di vendita delle soluzioni senza server, soprattutto per le startup o i piccoli team, è il generoso livello gratuito. Ed è generoso! Un milione di invocazioni gratuite al mese per Lambda, oltre a una quantità significativa di tempo di calcolo. Il problema è che man mano che la tua applicazione cresce, queste invocazioni “gratuite” scompaiono più velocemente di una fetta di pizza durante un meetup tech. Ciò che viene spesso trascurato è il volume considerevole di invocazioni minime, apparentemente irrilevanti, che si accumulano. Pensa ai task cron, ai controlli di stato interni o persino ai meccanismi di ripetizione di altri servizi. Ognuna di queste invocazioni conta.
La mia indagine sul nostro servizio di autenticazione ha rivelato esattamente questo. Avevamo una funzione Lambda, chiamiamola auth-token-refresher, progettata per aggiornare periodicamente i token di servizio interni. Era programmata per essere eseguita ogni cinque minuti. Sembra innocua, vero? 288 invocazioni al giorno. Moltiplicalo per 30 giorni, e ottieni 8.640 invocazioni al mese. Aggiungi i nostri ambienti di sviluppo, staging e produzione, e all’improvviso sono più di 25.000 invocazioni solo per un piccolo compito di manutenzione. Avevamo una dozzina di tali funzioni. All’improvviso, le nostre invocazioni “minime” non erano più così minime.
Trovare i Colpevoli: Le Metriche CloudWatch sono i Tuoi Migliori Amici
Il primo passo per domare questa bestia è sapere dove vanno i tuoi soldi. AWS CloudWatch è indispensabile qui. Non limitarti a guardare il dashboard di fatturazione a un livello elevato; esplora le metriche specifiche per le tue funzioni Lambda.
Ecco su cosa mi sono concentrato:
- Invocazioni: Questa è la metrica più semplice. Contatori di invocazioni elevati per funzioni che non gestiscono il traffico utente diretto sono segnali d’allerta immediati.
- Durata: Quanto tempo impiega ciascuna invocazione? Durate più lunghe significano costi di calcolo più elevati.
- Utilizzo della Memoria: Hai sovraprovisionato la memoria per le tue funzioni? Paga per ciò che allocchi, non per ciò che usi.
- Indice di Errore: Tassi di errore elevati possono portare a ripetizioni, il che significa più invocazioni e cicli di calcolo sprecati.
Per il nostro auth-token-refresher, ho esaminato la sua metrica `Invocazioni`. Infatti, girava come un orologio, ogni cinque minuti. La durata era minima, solo circa 50ms. Ma il volume considerevole contribuiva al nostro costo globale di invocazione.
Esempio Pratico 1: Consolidare e Pianificare in Modo più Intelligente
La soluzione per auth-token-refresher e per molte altre funzioni di manutenzione simili era sorprendentemente semplice: la consolidazione. Invece di avere funzioni Lambda individuali attivate da eventi CloudWatch (o EventBridge al giorno d’oggi) in orari separati, ho creato una sola funzione Lambda chiamata “Maintenance Runner”.
Questo “Maintenance Runner” è attivato da una sola regola di evento CloudWatch, diciamo, una volta all’ora. All’interno di questo runner, ho un semplice dispatcher che controlla l’ora attuale e esegue i compiti necessari. Ad esempio:
import os
import datetime
def lambda_handler(event, context):
current_hour = datetime.datetime.now().hour
current_minute = datetime.datetime.now().minute
# Compito 1: Aggiornare il token di auth (si eseguiva ogni 5 minuti)
if current_minute % 10 == 0: # Esegui ora ogni 10 minuti
print("Eseguendo l'aggiornamento del token di auth...")
# Chiama la logica reale per aggiornare il token o un'altra funzione interna
refresh_auth_token()
# Compito 2: Pulire i log vecchi (si eseguiva ogni ora)
if current_hour % 1 == 0 and current_minute == 0: # Esegui all'inizio dell'ora
print("Eseguendo la pulizia dei log...")
cleanup_old_logs()
# Compito 3: Controllare lo stato del servizio esterno (si eseguiva ogni 30 minuti)
if current_minute == 0 or current_minute == 30:
print("Controllando lo stato del servizio esterno...")
check_external_service()
return {
'statusCode': 200,
'body': 'Compiti di manutenzione eseguiti.'
}
def refresh_auth_token():
# ... logica reale di aggiornamento del token ...
pass
def cleanup_old_logs():
# ... logica reale di pulizia dei log ...
pass
def check_external_service():
# ... logica reale di verifica del servizio esterno ...
pass
Questo semplice cambiamento ha immediatamente ridotto il numero di invocazioni per questi compiti di manutenzione da centinaia di migliaia al mese a poche migliaia. I risparmi erano tangibili, non solo in termini di invocazioni Lambda, ma anche per quanto riguarda l’ingestione dei log CloudWatch e le chiamate a API Gateway (se una di queste chiamate era esposta tramite API Gateway).
Il Trappola della Sovraprovisionamento della Memoria
Questo rappresenta un altro fattore di costo sottile che è spesso trascurato. Quando crei una funzione Lambda, allochi una certa quantità di memoria (ad esempio, 128Mo, 256Mo, 512Mo). Paga per questa memoria allocata, indipendentemente da quanta ne utilizzi effettivamente la tua funzione. Inoltre, la potenza della CPU aumenta proporzionalmente all’allocazione di memoria. Quindi, se allochi 1 Go di memoria per un semplice script Python che ha bisogno solo di 128 Mo, non stai solo pagando troppo per la memoria; stai anche pagando troppo per i cicli CPU di cui non ha bisogno.
Ho imparato questo a mie spese con una funzione Lambda di elaborazione dati che era inizialmente configurata con 1 Go di memoria “giusto per sicurezza”. Quando ho guardato le sue metriche CloudWatch per l’utilizzo della memoria, rimaneva costantemente al di sotto dei 200 Mo, anche durante i picchi di carico. Fondamentalmente stavamo pagando per 800 Mo di RAM inutilizzata e il corrispondente incremento della CPU.
Esempio Pratico 2: Ottimizzare l’Allocazione della Memoria con Lambda Power Tuning
Determinare manualmente la configurazione di memoria ottimale può essere noioso. Devi distribuire, testare, monitorare, regolare e ripetere. Fortunatamente, c’è uno strumento open source eccellente chiamato AWS Lambda Power Tuning (sviluppato da Alex Casalboni di AWS) che semplifica questo processo.
È un’applicazione senza server che ti aiuta a visualizzare e identificare la configurazione di memoria ottimale per le tue funzioni Lambda in base ai costi e alle prestazioni. La distribuisci sul tuo account AWS e poi puoi usarla per testare le tue funzioni.
Ecco come funziona generalmente:
- Distribuisci lo strumento Power Tuning tramite il Serverless Application Repository o SAM.
- Invoca una macchina di stato (creata dallo strumento) con l’ARN della tua funzione Lambda e un payload.
- La macchina di stato invoca la tua Lambda più volte con diverse configurazioni di memoria (ad esempio, 128 Mo, 256 Mo, 512 Mo, 1024 Mo, ecc.).
- Analizza quindi i log di esecuzione e fornisce una visualizzazione che mostra i compromessi tra costi e velocità per ciascuna configurazione di memoria.
Per la mia funzione Lambda di elaborazione dei dati, il test con il Power Tuner ha mostrato che 256 MB era il giusto compromesso per il costo, con una degradazione delle prestazioni trascurabile rispetto a 1 GB. Abbiamo immediatamente ridotto l’allocazione di memoria a 256 MB, il che ha portato a una riduzione del 75% dei costi di calcolo per questa funzione specifica. Non è stato un colpo unico; ho da allora fatto in modo che fosse una pratica standard far passare nuove funzioni o quelle rivalutate tramite questo strumento.
Per utilizzarlo, dopo il deployment, inizieresti tipicamente la macchina a stati con qualcosa del genere (regolando l’ARN e il payload):
aws stepfunctions start-execution \
--state-machine-arn "arn:aws:states:REGION:ACCOUNT_ID:stateMachine:powerTuningStateMachine" \
--input '{ "lambdaARN": "arn:aws:lambda:REGION:ACCOUNT_ID:function:YOUR_FUNCTION_NAME", "num": 100, "payload": {}, "parallel": 5 }'
Il risultato fornisce un grafico chiaro, mostrando esattamente dove i tuoi costi e la tua velocità si incrociano per una prestazione ottimale. È un cambiamento significativo per l’ottimizzazione dei costi.
La Verbosità dei Log e i Démarrages à Froid
Due altri ambiti che ti sorprendono spesso sono la verbosità dei log e i démarrages à froid. I log di CloudWatch non sono gratis. Ogni riga stampata dalla tua funzione Lambda viene ingestita e memorizzata, e tu paghi per questo. Sebbene un buon log sia cruciale per il debug, una registrazione troppo verbosa (ad esempio, stampare oggetti interi o ripetere inutilmente messaggi di stato) può gonfiare rapidamente la tua fattura per i log di CloudWatch.
Ho trovato alcune funzioni che registravano il corpo completo della richiesta HTTP a ogni invocazione. Sebbene questo sia utile per lo sviluppo iniziale, in produzione, non era altro che rumore e costo. Un rapido aggiustamento per registrare solo i metadati essenziali (ID richiesta, codice di stato, endpoint) ha ridotto significativamente la nostra ingestione di log.
I démarrages à froid, sebbene non siano un “costo” diretto nello stesso modo, influenzano l’esperienza utente e possono indirettamente comportare più retry o tempi di fatturazione più lunghi se la tua funzione deve aspettare risorse. Sebbene AWS abbia fatto progressi significativi per ridurre i tempi di démarrages à froid, ottimizzare le dimensioni del pacchetto della tua funzione ed evitare una logica di inizializzazione complessa al di fuori del gestore può ancora fare la differenza. Per le funzioni critiche sensibili alla latenza, la concorrenza provisionata è un’opzione, ma sappi che paghi per questa concorrenza allocata anche quando è inattiva.
Esempio Pratico 3: Registrazione Intelligente e Variabili d’Ambiente
Per la registrazione, la soluzione più semplice è spesso la migliore. Usa variabili d’ambiente per controllare i livelli di registrazione. In Python, ad esempio, puoi fare così:
import os
import logging
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(level=LOG_LEVEL)
logger = logging.getLogger()
def lambda_handler(event, context):
logger.debug("Questo è un messaggio di debug, visibile solo se LOG_LEVEL è DEBUG")
logger.info("Elaborazione dell'evento: %s", event.get('request_id'))
try:
# ... logica della funzione ...
logger.debug("Fatto di elaborare per request_id: %s", event.get('request_id'))
return {
'statusCode': 200,
'body': 'Successo'
}
except Exception as e:
logger.error("Errore durante l'elaborazione di request_id %s: %s", event.get('request_id'), str(e), exc_info=True)
return {
'statusCode': 500,
'body': 'Errore'
}
Impostando LOG_LEVEL su INFO in produzione e DEBUG in sviluppo/staging, puoi ridurre notevolmente la tua fattura per CloudWatch Logs senza sacrificare l’osservabilità quando ne hai bisogno.
Un altro consiglio è prestare attenzione a ciò che viene inizializzato al di fuori del gestore. Qualsiasi codice direttamente nel contesto globale della tua funzione Lambda verrà eseguito durante il démarrage à froid. Se hai operazioni costose come il pooling di connessioni al database o importazioni di grandi librerie, considera di rimandarle fino a quando non sono realmente necessarie nel gestore, oppure assicurati che siano efficientemente memorizzate nella cache per le invocazioni calde successive.
Azioni Concrete per la Tua Crociata ai Costi Serverless
Bene, abbiamo coperto un bel po’ di cose. Ecco un riepilogo delle azioni pratiche che puoi intraprendere subito per cominciare a ridurre questi costi Lambda insidiosi:
- Monitora incessantemente: Non limitarti a dare un’occhiata alla tua fattura AWS complessiva. Esplora le metriche di CloudWatch per le Invocazioni, la Durata e l’Utilizzo della Memoria per ogni funzione Lambda. Configura allarmi per picchi imprevisti.
- Consolida i cron job: Se hai molte piccole funzioni Lambda programmate, considera di raggrupparle in un unico “Manutentore” che distribuisce i compiti secondo un calendario meno frequente. Questo riduce significativamente il numero di invocazioni.
- Ottimizza l’allocazione della memoria: Usa strumenti come AWS Lambda Power Tuning per trovare l’impostazione di memoria ottimale per le tue funzioni. Non limitarti a indovinare e sovraprovisionare. Non dimenticare che più memoria significa più CPU, e paghi per entrambi.
- Controlla la verbosità dei log: Implementa livelli di registrazione controllati da variabili d’ambiente (ad esempio,
INFOper la produzione,DEBUGper lo sviluppo). Evita di registrare l’intero corpo delle richieste o uno stato interno eccessivo in produzione. La tua fattura per CloudWatch Logs te ne sarà grata. - Esamina le funzioni non utilizzate: Audita periodicamente le tue funzioni Lambda. Ci sono vecchie funzioni, sperimentali o obsolete, ancora attive e che comportano costi? Eliminale!
- Monitora la dimensione dei pacchetti: Pacchetti di distribuzione più piccoli significano démarrages à froid più rapidi e meno costi di archiviazione. Includi solo le dipendenze necessarie.
- Comprendi il tuo modello di prezzo: Rileggi la pagina di pricing di Lambda. Comprendi come vengono addebitate le invocazioni, le GB-secondi e il trasferimento dati. La conoscenza è potere, soprattutto quando si tratta del tuo portafoglio.
Dominare il mostro serverless non significa evitarlo; si tratta di essere intelligenti e intenzionali nel nostro utilizzo. La flessibilità e la scalabilità sono inestimabili, ma senza la giusta vigilanza, questi “piccoli” costi possono accumularsi e rappresentare una parte significativa del tuo budget. Vai avanti, monitora, ottimizza e risparmia!
Questo è tutto per me oggi. Fammi sapere nei commenti se hai altri suggerimenti o trucchi per l’ottimizzazione dei costi Lambda!
🕒 Published: