Ottimizzare le applicazioni Python per Cloud Run

Questa guida descrive le ottimizzazioni per i servizi Cloud Runscritti nel linguaggio di programmazione Python, insieme a informazioni di contesto per aiutarti a comprendere i compromessi coinvolti in alcune delle ottimizzazioni. Le informazioni riportate in questa pagina integrano i suggerimenti generali per l'ottimizzazione, che si applicano anche a Python.

Molte delle best practice e delle ottimizzazioni di queste applicazioni web tradizionali basate su Python ruotano attorno a:

  • Gestione delle richieste in parallelo (I/O basate su thread e non bloccanti)
  • Riduci la latenza di risposta utilizzando il pooling delle connessioni e raggruppando le funzioni non critiche, ad esempio l'invio di tracce e metriche alle attività in background.

Ottimizza l'immagine container

Ottimizzando l'immagine del contenitore, puoi ridurre i tempi di caricamento e di avvio. Puoi ottimizzare l'immagine:

  • Inserire nel container solo ciò di cui l'app ha bisogno in fase di esecuzione
  • Ottimizzazione del server WSGI

Inserisci nel container solo ciò di cui la tua app ha bisogno in fase di runtime

Valuta quali componenti sono inclusi nel contenitore e se sono necessari per l'esecuzione del servizio. Esistono diversi modi per ridurre al minimo l'immagine del contenitore:

  • Utilizza un'immagine di base più piccola
  • Spostare i file di grandi dimensioni all'esterno del contenitore

Utilizza un'immagine di base più piccola

Docker Hub fornisce una serie di immagini di base Python ufficiali che puoi utilizzare, se scegli di non installare Python dal codice sorgente all'interno dei container. Sono basati sul sistema operativo Debian.

Se utilizzi l'immagine python di Docker Hub, valuta la possibilità di utilizzare la versione slim. Queste immagini sono di dimensioni inferiori perché non includono una serie di pacchetti che potrebbero essere utilizzati per creare, ad esempio, le ruote, il che potrebbe non essere necessario per la tua applicazione. Ad esempio, l'immagine Python include il compilatore GNU C, il preprocessore e le utilità di base.

Per identificare i dieci pacchetti più grandi in un'immagine di base, puoi eseguire il seguente 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à. Tieni presente che queste immagini potrebbero non includere gli elementi necessari per creare le ruote dall'origine.

Puoi aggiungere nuovamente pacchetti specifici aggiungendo una riga RUN apt install al tuo Dockerfile. Scopri di più sull'utilizzo dei pacchetti di sistema in Cloud Run.

Sono disponibili anche opzioni per i container non basati su Debian. L'opzione python:alpine potrebbe comportare un contenitore molto più piccolo, ma molti pacchetti Python potrebbero non avere i wheel precompilati che supportano i sistemi basati su Alpine. L'assistenza sta migliorando (consulta PEP-656), ma continua a essere diversificata. Puoi anche valutare la possibilità di utilizzare distroless base image, che non contiene gestori pacchetti, shell o altri programmi.

Spostare i file di grandi dimensioni all'esterno del contenitore

I file di grandi dimensioni, come gli asset multimediali e così via, non devono essere inclusi nel contenitore di base.

Google Cloud offre più opzioni di hosting, come Cloud Storage, per archiviare questi elementi di grandi dimensioni. Sposta gli asset di grandi dimensioni in questi servizi, quindi fai riferimento a questi asset dalla tua applicazione in fase di esecuzione.

Ottimizzare il server WSGI

Python ha standardizzato il modo in cui le applicazioni possono interagire con i server web tramite l'implementazione dello standard WSGI, PEP-3333. Uno dei server WSGI più comuni è gunicorn, che viene utilizzato in gran parte della documentazione di esempio.

Ottimizza gunicorn

Aggiungi il seguente 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 di thread in base all'applicazione. Ad esempio, prova a utilizzare un numero di worker uguale ai core disponibili e assicurati che ci sia un miglioramento delle prestazioni, quindi modifica il numero di thread. L'impostazione di un numero eccessivo di thread o worker può avere un impatto negativo, ad esempio una latenza di avvio a freddo più lunga, una maggiore quantità di memoria utilizzata, 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 del contenitore come sana 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 dell'ascolto. In questo modo puoi:

  • Identificare bug di runtime gravi al momento del deployment
  • Risparmiare risorse di memoria

Prima di aggiungerlo, devi considerare cosa viene precaricato dalla tua applicazione.

Altri server WSGI

Non è necessario utilizzare gunicorn per eseguire Python in container. Puoi utilizzare qualsiasi server web WSGI o ASGI, a condizione che il contenitore rimanga in ascolto sulla porta HTTP $PORT, come da contratto di runtime del contenitore.

Le alternative più comuni sono uwsgi, uvicorn e waitress.

Ad esempio, dato un file denominato main.py contenente l'oggetto app, le seguenti invocazioni 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

Possono essere aggiunti come riga CMD exec in un file Dockerfile o come voce web: in Procfile quando si utilizzano i buildpack di Google Cloud.

Ottimizzare le applicazioni

Nel codice del servizio Cloud Run, puoi anche ottimizzare per tempi di avvio e utilizzo della memoria più rapidi.

Riduci i thread

Puoi ottimizzare la memoria riducendo il numero di thread, utilizzando strategie reattive non bloccanti ed evitando le attività in background. Evita inoltre 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 la CPU del servizio Cloud Run in modo che sia sempre allocata, in modo da poter eseguire attività in background al di fuori delle richieste e avere comunque accesso alla CPU.

Riduci le attività di avvio

Le applicazioni web in Python possono avere molte attività da completare durante l'avvio, ad esempio il precaricamento dei dati, il riscaldamento della cache, l'impostazione di pool di connessioni e così via. Queste attività, se eseguite in sequenza, possono essere lente. Tuttavia, se vuoi che vengano eseguiti in parallelo, devi aumentare il numero di core della CPU.

Al momento Cloud Run invia una richiesta di un utente reale per attivare un'istanza di avvio a freddo. Gli utenti che hanno una richiesta assegnata a un'istanza appena avviata potrebbero riscontrare ritardi lunghi. Al momento Cloud Run non dispone di un controllo di "idoneità" per evitare di inviare richieste a applicazioni non pronte.

Passaggi successivi

Per altri suggerimenti, consulta