Questa guida descrive le ottimizzazioni per i servizi Cloud Run scritti nel linguaggio di programmazione Python, insieme a informazioni di base per aiutarti a comprendere i compromessi coinvolti in alcune delle ottimizzazioni. Le informazioni contenute in questa pagina integrano i suggerimenti generali per l'ottimizzazione, che si applicano anche a Python.
Molte delle best practice e delle ottimizzazioni nelle comuni applicazioni web Python riguardano:
- Gestione delle richieste simultanee (I/O basato su thread e non bloccante)
- Riduzione della latenza di risposta mediante il pooling delle connessioni e il batching di funzioni non critiche, ad esempio l'invio di tracce e metriche alle attività in background.
Ottimizzare l'immagine container
Ottimizza l'immagine container per ridurre i tempi di caricamento e avvio utilizzando questi metodi:
- Ridurre al minimo i file caricati all'avvio
- Ottimizzare il server WSGI
Ridurre al minimo i file caricati all'avvio
Per ottimizzare il tempo di avvio, carica solo i file necessari all'avvio e riducine le dimensioni. Per i file di grandi dimensioni, valuta le seguenti opzioni:
Archivia file di grandi dimensioni, come i modelli di AI, nel container per un accesso più rapido. Valuta la possibilità di caricare questi file dopo l'avvio o in fase di runtime.
Valuta la possibilità di configurare i montaggi dei volumi Cloud Storage per i file di grandi dimensioni non critici all'avvio, come gli asset multimediali.
Importa solo i sottomoduli richiesti da eventuali dipendenze pesanti o importa i moduli quando sono necessari nel codice, anziché caricarli all'avvio dell'applicazione.
Ottimizzare il server WSGI
Python ha standardizzato il modo in cui le applicazioni possono interagire con i server web
mediante l'implementazione dello standard WSGI,
PEP-3333. Uno dei server WSGI più comuni è gunicorn
, utilizzato in gran parte della documentazione di esempio.
Ottimizza gunicorn
Aggiungi il seguente codice CMD
a Dockerfile
per ottimizzare l'invocazione di
gunicorn
:
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
Se stai valutando la possibilità di modificare queste impostazioni, regola il numero di worker e thread in base all'applicazione. Ad esempio, prova a utilizzare un numero di worker pari ai core disponibili e assicurati che ci sia un miglioramento delle prestazioni, poi regola il numero di thread. L'impostazione di un numero eccessivo di worker o thread può avere un impatto negativo, ad esempio una latenza di avvio a freddo più lunga, un maggiore consumo di memoria, un numero inferiore di richieste al secondo e così via.
Per impostazione predefinita, gunicorn
genera worker e ascolta sulla porta specificata all'avvio, anche prima di valutare il codice dell'applicazione. In questo caso, devi configurare probe di avvio personalizzati per il tuo servizio, poiché il probe di avvio predefinito di Cloud Run contrassegna immediatamente un'istanza di container come integra non appena inizia ad ascoltare su $PORT
.
Se vuoi modificare questo comportamento, puoi richiamare gunicorn
con l'impostazione
--preload
per valutare il codice dell'applicazione prima di ascoltare. In questo modo puoi:
- Identificare bug di runtime gravi al momento del deployment
- Risparmiare risorse di memoria
Prima di aggiungere questa funzionalità, valuta il precaricamento della tua applicazione.
Altri server WSGI
Non sei limitato all'utilizzo di gunicorn
per l'esecuzione di Python nei container.
Puoi utilizzare qualsiasi server web WSGI o ASGI, a condizione che il container sia in ascolto sulla porta HTTP $PORT
, come previsto dal contratto di runtime del container.
Le alternative più comuni includono uwsgi
,
uvicorn
e waitress
.
Ad esempio, dato un file denominato main.py
contenente l'oggetto app
, le seguenti chiamate avviano un server WSGI:
# uwsgi: pip install pyuwsgi
uwsgi --http :$PORT -s /tmp/app.sock --manage-script-name --mount /app=main:app
# uvicorn: pip install uvicorn
uvicorn --port $PORT --host 0.0.0.0 main:app
# waitress: pip install waitress
waitress-serve --port $PORT main:app
Questi possono essere aggiunti come riga CMD exec
in un Dockerfile
o come voce web:
in Procfile
quando utilizzi
i buildpack di Google Cloud.
Ottimizzare le applicazioni
Nel codice del servizio Cloud Run, puoi anche ottimizzare i tempi di avvio e l'utilizzo della memoria.
Ridurre i thread
Puoi ottimizzare la memoria riducendo il numero di thread, utilizzando strategie reattive non bloccanti ed evitando attività in background. Evita anche di scrivere nel file system, come indicato nella pagina dei suggerimenti generali.
Se vuoi supportare le attività in background nel tuo servizio Cloud Run, imposta il servizio Cloud Run sulla fatturazione basata sulle istanze in modo da poter eseguire le attività in background al di fuori delle richieste e avere comunque accesso alla CPU.
Ridurre le attività di avvio
Le applicazioni web basate su Python possono avere molte attività da completare durante l'avvio, come il precaricamento dei dati, il riscaldamento della cache e la creazione di pool di connessioni. Se eseguite in sequenza, queste attività possono essere lente. Tuttavia, se vuoi che vengano eseguiti in parallelo, aumenta il numero di core della CPU.
Cloud Run invia una richiesta di un utente reale per attivare un'istanza di avvio a freddo. Gli utenti a cui è stata assegnata una richiesta a un'istanza appena avviata potrebbero riscontrare lunghi ritardi.
Migliora la sicurezza con immagini base slimline
Per migliorare la sicurezza della tua applicazione, utilizza un'immagine di base slimline con meno pacchetti e librerie.
Se scegli di non installare Python dall'origine all'interno dei container, utilizza un'immagine di base Python ufficiale da Docker Hub. Queste immagini si basano sul sistema operativo Debian.
Se utilizzi l'immagine python
di Docker Hub, valuta la possibilità di utilizzare la versione slim
. Queste immagini sono più piccole perché non includono una serie di
pacchetti che verrebbero utilizzati per creare ruote, cosa che potresti non dover fare
per la tua applicazione. L'immagine python
include il compilatore, il preprocessor e le utilità di base GNU C.
Per identificare i dieci pacchetti più grandi in un'immagine di base, esegui questo comando:
DOCKER_IMAGE=python # or python:slim
docker run --rm ${DOCKER_IMAGE} dpkg-query -Wf '${Installed-Size}\t${Package}\t${Description}\n' | sort -n | tail -n10 | column -t -s $'\t'
Poiché questi pacchetti di basso livello sono meno numerosi, le immagini basate su slim
offrono anche
una superficie di attacco inferiore per le potenziali vulnerabilità. Alcune di queste immagini
potrebbero non includere gli elementi necessari per creare ruote dall'origine.
Puoi aggiungere di nuovo pacchetti specifici aggiungendo una riga RUN apt install
al tuo
Dockerfile. Per ulteriori informazioni, consulta Utilizzo dei pacchetti di sistema in Cloud Run.
Sono disponibili anche opzioni per i container non basati su Debian. L'opzione python:alpine
potrebbe generare un container molto più piccolo, ma molti pacchetti Python potrebbero non
avere ruote precompilate che supportano i sistemi basati su Alpine. Il supporto sta migliorando
(vedi PEP-656), ma continua a variare.
Potresti anche usare
distroless base image
, che non contiene gestori di pacchetti, shell o altri programmi.
Utilizza la variabile di ambiente PYTHONUNBUFFERED
per la registrazione
Per visualizzare i log non memorizzati nel buffer della tua applicazione Python, imposta la variabile di ambiente PYTHONUNBUFFERED
. Quando imposti questa variabile, i dati stdout
e
stderr
sono immediatamente visibili nei log dei container, anziché essere
memorizzati in un buffer finché non si accumula una certa quantità di dati o il flusso
viene chiuso.
Passaggi successivi
Per altri suggerimenti, vedi