Questa guida fornisce le best practice per la progettazione, l'implementazione, il test e il deployment di un servizio Cloud Run. Per altri suggerimenti, consulta Eseguire la migrazione di un servizio esistente.
Scrivere servizi efficaci
Questa sezione descrive le best practice generali per la progettazione e l'implementazione di un servizio Cloud Run.
Attività in background
Le attività in background sono tutto ciò che accade dopo la consegna della risposta HTTP. Per determinare se nel tuo servizio sono presenti attività in background non immediatamente evidenti, controlla i log per verificare se sono presenti voci registrate dopo la voce relativa alla richiesta HTTP.
Configurare la fatturazione basata sulle istanze per utilizzare le attività in background
Se vuoi supportare le attività in background nel tuo servizio Cloud Run, imposta la fatturazione basata sulle istanze in modo da poter eseguire le attività in background al di fuori delle richieste e avere comunque accesso alla CPU.
Evitare le attività in background se si utilizza la fatturazione basata sulle richieste
Se devi impostare la fatturazione basata sulle richieste per il tuo servizio, quando il servizio Cloud Run termina la gestione di una richiesta, l'accesso dell'istanza alla CPU verrà disattivato o limitato. Se utilizzi questo tipo di fatturazione, non devi avviare thread o routine in background che vengono eseguiti al di fuori dell'ambito dei gestori delle richieste.
Controlla il codice per assicurarti che tutte le operazioni asincrone vengano completate prima di consegnare la risposta.
L'esecuzione di thread in background con la fatturazione basata sulle richieste abilitata può comportare un comportamento imprevisto, perché qualsiasi richiesta successiva alla stessa istanza container riprende qualsiasi attività in background sospesa.
Eliminare i file temporanei
Nell'ambiente Cloud Run, lo spazio di archiviazione su disco è un filesystem in memoria. I file scritti su disco consumano la memoria altrimenti disponibile per il tuo servizio e possono persistere tra le chiamate. Se non elimini questi file, alla fine potresti riscontrare un errore di memoria insufficiente e tempi di avvio del container lenti.
Segnalare gli errori
Gestisci tutte le eccezioni e non lasciare che il servizio si arresti in modo anomalo in caso di errori. Un arresto anomalo comporta un avvio lento del container mentre il traffico viene messo in coda per un'istanza di sostituzione.
Consulta la guida di Error Reporting per informazioni su come segnalare correttamente gli errori.
Ottimizzare le prestazioni
Questa sezione descrive le best practice per ottimizzare le prestazioni.
Avviare rapidamente i container
Poiché le istanze vengono scalate in base alle esigenze, il loro tempo di avvio influisce sulla latenza del servizio. Cloud Run disaccoppia l'avvio dell'istanza e l'elaborazione delle richieste, quindi in alcuni casi una richiesta deve attendere l'avvio di una nuova istanza prima di essere elaborata. Questo accade di solito quando un servizio viene scalato da zero.
La routine di avvio è costituita da:
- Download dell'immagine container (utilizzando la tecnologia di streaming delle immagini container di Cloud Run)
- Avvio del container eseguendo il comando entrypoint.
- Attesa dell'avvio dell'ascolto del container sulla porta configurata.
L'ottimizzazione per la velocità di avvio del container riduce al minimo la latenza di elaborazione delle richieste.
Utilizzare il boosting della CPU all'avvio per ridurre la latenza di avvio
Puoi abilitare il boosting della CPU all'avvio per aumentare temporaneamente l'allocazione della CPU durante l'avvio dell'istanza al fine di ridurre la latenza di avvio.
Utilizzare le istanze minime per ridurre i tempi di avvio del container
Puoi configurare le istanze minime e la concorrenza per ridurre al minimo i tempi di avvio del container. Ad esempio, l'utilizzo di un'istanza minima di 1 significa che il servizio è pronto a ricevere fino al numero di richieste in parallelo configurate per il servizio senza dover avviare una nuova istanza. Quando utilizzi le istanze minime, evita di utilizzare le uscite di sistema che arrestano un'istanza e potrebbero aumentare gli avvii a freddo.
Tieni presente che una richiesta in attesa dell'avvio di un'istanza verrà mantenuta in attesa in una coda come segue:
Le richieste rimarranno in attesa per un massimo di 3, 5 volte il tempo di avvio medio delle istanze container di questo servizio o per 10 secondi, a seconda di quale valore è maggiore.
Utilizzare le dipendenze in modo strategico
Se utilizzi un linguaggio dinamico con librerie dipendenti, ad esempio l'importazione di moduli in Node.js, il tempo di caricamento di questi moduli si aggiunge alla latenza di avvio.
Riduci la latenza di avvio nei seguenti modi:
- Riduci al minimo il numero e le dimensioni delle dipendenze per creare un servizio snello.
- Carica in modo lazy il codice utilizzato di rado, se il linguaggio lo supporta.
- Utilizza le ottimizzazioni del caricamento del codice, ad esempio l'ottimizzazione dell'autoloader del compositore di PHP .
Utilizzare le variabili globali
In Cloud Run, non puoi presupporre che lo stato del servizio venga mantenuto tra le richieste. Tuttavia, Cloud Run riutilizza le singole istanze per gestire il traffico continuo, quindi puoi dichiarare una variabile nell'ambito globale per consentire il riutilizzo del suo valore nelle chiamate successive. Non è possibile sapere in anticipo se una singola richiesta usufruirà di questo riutilizzo.
Puoi anche memorizzare nella cache gli oggetti in memoria se la loro ricreazione è costosa per ogni richiesta di servizio. Lo spostamento di questa logica dalla logica della richiesta all'ambito globale comporta un miglioramento delle prestazioni.
Node.js
Python
Go
Java
Eseguire l'inizializzazione lazy delle variabili globali
L'inizializzazione delle variabili globali avviene sempre durante l'avvio, il che aumenta il tempo di avvio del container. Utilizza l'inizializzazione lazy per gli oggetti utilizzati di rado per posticipare il costo del tempo e ridurre i tempi di avvio del container.
Uno svantaggio dell'inizializzazione lazy è l'aumento della latenza per le prime richieste alle nuove istanze. Questo può causare uno scale up eccessivo e la perdita di richieste quando esegui il deployment di una nuova revisione di un servizio che gestisce attivamente molte richieste.
Node.js
Python
Go
Java
Utilizzare un ambiente di esecuzione diverso
Potresti riscontrare tempi di avvio più rapidi utilizzando un ambiente di esecuzione diverso.
Ottimizzare la concorrenza
Le istanze Cloud Run possono gestire più richieste contemporaneamente, "in parallelo", fino a una concorrenza massima configurabile.
Cloud Run regola automaticamente la concorrenza fino al massimo configurato.
La concorrenza massima predefinita di 80 è adatta a molte immagini container. Tuttavia, devi:
- Ridurre il valore se il container non è in grado di elaborare molte richieste in parallelo.
- Aumentare il valore se il container è in grado di gestire un volume elevato di richieste.
Ottimizzare la concorrenza per il servizio
Il numero di richieste in parallelo che ogni istanza può gestire può essere limitato dallo stack tecnologico e dall'utilizzo di risorse condivise come variabili e connessioni di database.
Per ottimizzare il servizio per la massima concorrenza stabile:
- Ottimizza le prestazioni del servizio.
- Imposta il livello previsto di supporto della concorrenza in qualsiasi configurazione della concorrenza a livello di codice. Non tutti gli stack tecnologici richiedono questa impostazione.
- Esegui il deployment del servizio.
- Imposta la concorrenza di Cloud Run per il servizio uguale o inferiore a qualsiasi configurazione a livello di codice. Se non esiste una configurazione a livello di codice, utilizza la concorrenza prevista.
- Utilizza strumenti di test di carico che supportano una concorrenza configurabile. Devi verificare che il servizio rimanga stabile con il carico e la concorrenza previsti.
- Se il servizio non funziona correttamente, vai al passaggio 1 per migliorarlo o al passaggio 2 per ridurre la concorrenza. Se il servizio funziona correttamente, torna al passaggio 2 e aumenta la concorrenza.
Continua a eseguire iterazioni finché non trovi la concorrenza stabile massima.
Allineare la memoria alla concorrenza
Ogni richiesta gestita dal servizio richiede una certa quantità di memoria aggiuntiva. Pertanto, quando aumenti o diminuisci la concorrenza, assicurati di regolare anche il limite di memoria.
Evitare lo stato globale modificabile
Se vuoi sfruttare lo stato globale modificabile in un contesto di concorrenza, esegui passaggi aggiuntivi nel codice per assicurarti che questa operazione venga eseguita in modo sicuro. Riduci al minimo la contesa limitando le variabili globali all'inizializzazione una tantum e al riutilizzo come descritto sopra in Prestazioni.
Se utilizzi variabili globali modificabili in un servizio che gestisce più richieste contemporaneamente, assicurati di utilizzare blocchi o mutex per evitare condizioni di competizione.
Compromessi tra velocità effettiva, latenza e costi
La regolazione dell'impostazione del numero massimo di richieste in parallelo può aiutarti a bilanciare il compromesso tra velocità effettiva, latenza e costi per il tuo servizio.
In generale, un'impostazione del numero massimo di richieste in parallelo inferiore comporta una latenza inferiore e una velocità effettiva inferiore per istanza. Con un numero massimo di richieste in parallelo inferiore, un numero inferiore di richieste compete per le risorse all'interno di ogni istanza e ogni richiesta ottiene prestazioni migliori. Tuttavia, poiché ogni istanza può gestire un numero inferiore di richieste contemporaneamente, la velocità effettiva per istanza è inferiore e il servizio ha bisogno di più istanze per gestire lo stesso traffico.
Al contrario, un'impostazione del numero massimo di richieste in parallelo superiore comporta in genere una latenza maggiore e una velocità effettiva maggiore per istanza. Le richieste potrebbero dover attendere l'accesso a risorse come CPU, GPU e larghezza di banda della memoria all'interno dell'istanza, il che comporta un aumento della latenza. Tuttavia, ogni istanza può elaborare più richieste contemporaneamente, in modo che il servizio abbia bisogno di meno istanze in totale per elaborare lo stesso traffico.
Considerazioni sui costi
I prezzi di Cloud Run sono per il tempo di istanza. Se imposti la fatturazione basata sulle istanze, il tempo di istanza è la durata totale di ogni istanza. Se imposti la fatturazione basata sulle richieste, il tempo di istanza è il tempo che ogni istanza trascorre elaborando almeno una richiesta.
L'impatto del numero massimo di richieste in parallelo sulla fatturazione dipende dal pattern di traffico. La riduzione del numero massimo di richieste in parallelo può comportare una fattura inferiore se l'impostazione inferiore comporta
- Latenza ridotta
- Istanze che completano il lavoro più velocemente
- Istanze che si arrestano più velocemente anche se sono necessarie più istanze totali
Tuttavia, è possibile anche il contrario: la riduzione del numero massimo di richieste in parallelo può aumentare la fatturazione se l'aumento del numero di istanze non è compensato dalla riduzione del tempo di esecuzione di ogni istanza, a causa del miglioramento della latenza.
Il modo migliore per ottimizzare la fatturazione è eseguire test di carico
utilizzando diverse impostazioni del numero massimo di richieste in parallelo per identificare l'impostazione
che comporta il tempo di istanza fatturabile più basso, come mostrato nella metrica di monitoraggio
container/billable_instance_time.
Sicurezza dei container
Molte pratiche di sicurezza software di uso generale si applicano ai servizi containerizzati. Esistono alcune pratiche specifiche per i container o che si allineano alla filosofia e all'architettura dei container.
Per migliorare la sicurezza dei container:
Utilizza immagini di base sicure e gestite attivamente, come le immagini di base di Google o le immagini ufficiali di Docker Hub.
Applica gli aggiornamenti di sicurezza ai tuoi servizi ricreando regolarmente le immagini container ed eseguendo nuovamente il deployment dei servizi.
Includi nel container solo ciò che è necessario per eseguire il servizio. Codice, pacchetti e strumenti aggiuntivi sono potenziali vulnerabilità di sicurezza. Per l'impatto sulle prestazioni correlato, vedi sopra .
Implementa un processo di compilazione deterministico che includa versioni specifiche di software e librerie. In questo modo, il codice non verificato non viene incluso nel container.
Imposta l'esecuzione del container come utente diverso da
rootcon l' istruzioneUSERdel Dockerfile. Alcune immagini container potrebbero avere già un utente specifico configurato.Impedisci l'utilizzo delle funzionalità di anteprima utilizzando policy dell'organizzazione personalizzate.
Automatizzare l'analisi della sicurezza
Abilita l'analisi delle vulnerabilità per l'analisi della sicurezza delle immagini container archiviate in Artifact Registry.
Creare immagini container minime
Le immagini container di grandi dimensioni aumentano probabilmente le vulnerabilità di sicurezza perché contengono più di quanto necessario per il codice.
Grazie alla tecnologia di streaming delle immagini container di Cloud Run, le dimensioni dell'immagine container non influiscono sui tempi di avvio del container o sul tempo di elaborazione delle richieste. Le dimensioni dell'immagine container non vengono conteggiate ai fini della memoria disponibile del container.
Per creare un container minimo, valuta la possibilità di utilizzare un'immagine di base snella, ad esempio:
Ubuntu è più grande, ma è un'immagine di base di uso comune con un ambiente server out-of-the-box più completo.
Se il tuo servizio ha un processo di compilazione con molti strumenti, valuta la possibilità di utilizzare build a più fasi per mantenere il container leggero in fase di runtime.
Queste risorse forniscono ulteriori informazioni sulla creazione di immagini container snelle:
- Best practice di Kubernetes: come e perché creare immagini container di piccole dimensioni
- 7 best practice per la creazione di container