Suggerimenti generali per lo sviluppo

Questa guida fornisce le best practice per la progettazione, l'implementazione, i test eseguendo il deployment di un servizio Cloud Run. Per altri suggerimenti, vedi Migrazione di un servizio esistente.

Scrivere servizi efficaci

Questa sezione descrive le best practice generali per la progettazione e l'implementazione di un dal servizio Cloud Run.

Attività in background

Per attività in background si intende tutto ciò che accade dopo che la risposta HTTP è stata sono recapitate. Per determinare se nel servizio è presente attività in background che non sia immediatamente evidente, controlla nei log la presenza di eventuali registrato dopo la voce della richiesta HTTP.

Configura la CPU da allocare sempre per l'utilizzo di attività in background

Se vuoi supportare attività in background in Cloud Run di servizio, imposta la CPU del servizio Cloud Run in modo che sempre allocati, eseguire attività in background al di fuori delle richieste e continuare ad avere accesso alla CPU.

Evita attività in background se la CPU viene allocata solo durante l'elaborazione delle richieste

Se devi impostare il servizio su alloca la CPU solo durante l'elaborazione delle richieste, quando il servizio Cloud Run termina la gestione di una richiesta, l'accesso dell'istanza alla CPU verrà disabilitato o gravemente limitato. Non devi avviare routine o thread in background eseguiti all'esterno l'ambito dei gestori delle richieste se utilizzi questo tipo di allocazione della CPU.

Rivedi il codice per assicurarti che tutte le operazioni asincrone vengano completate prima di consegnare la risposta.

L'esecuzione di thread in background con questo tipo di allocazione della CPU può comportare un comportamento imprevisto perché ogni richiesta successiva allo stesso container l'istanza riprende qualsiasi attività in background sospesa.

Eliminare i file temporanei

Nell'ambiente Cloud Run, l'archiviazione su disco è un file system in memoria. I file scritti sul disco consumano memoria altrimenti disponibile per il tuo servizio, e può persistere tra una chiamata e l'altra. Se non elimini questi file, prima o poi genererà un errore di memoria insufficiente e un successivo avvio a freddo.

Segnala errori

Gestisci tutte le eccezioni e non permettere che il servizio abbia un arresto anomalo in caso di errori. Un incidente conduce a un avvio a freddo mentre il traffico è in coda per un'istanza sostitutiva.

Per informazioni su come eseguire la segnalazione degli errori, consulta la guida alla segnalazione degli errori come segnalare correttamente gli errori.

Ottimizzazione del rendimento

Questa sezione descrive le best practice per l'ottimizzazione del rendimento.

Avvia rapidamente i container

Poiché le istanze vengono scalate secondo necessità, il tempo di avvio influisce sulla latenza del tuo servizio. Sebbene Cloud Run disaccoppia l'avvio e l'elaborazione delle richieste, può accadere che una richiesta debba attendere l'avvio di una nuova istanza dall'elaborazione dei dati, in particolare quando si scala da zero. Questa operazione è chiamata "avvio a freddo".

La routine di avvio è costituita da:

  • Download dell'immagine container (utilizzando l'immagine container di Cloud Run) tecnologia di streaming)
  • avvia il container eseguendo l'entrypoint .
  • In attesa che il container inizi ad ascoltare sulla porta configurata.

L'ottimizzazione per la velocità di avvio dei container riduce al minimo la latenza di elaborazione delle richieste.

Usa il boost CPU all'avvio per ridurre la latenza di avvio

Puoi abilitare il booster della CPU all'avvio per aumentare temporaneamente l'allocazione della CPU durante l'avvio dell'istanza in modo da per ridurre la latenza di avvio.

Utilizza il numero minimo di istanze per ridurre gli avvii a freddo

Puoi configurare un minimo di istanze contemporaneità per ridurre al minimo gli avvii a freddo. Per Ad esempio, se utilizzi il numero minimo di istanze pari a 1, il servizio è pronto a ricevere fino al numero di richieste in parallelo configurate per il servizio senza dover avviare una nuova istanza.

Tieni presente che una richiesta in attesa dell'avvio di un'istanza rimarrà in attesa in un inserisci la coda nel seguente modo:

  • Se vengono avviate nuove istanze, ad esempio durante uno scale out, le richieste almeno il tempo di avvio medio delle istanze di container di questo servizio. Sono inclusi i casi in cui la richiesta avvia uno scale out, ad esempio durante la scalabilità partendo da zero.
  • Se il tempo di avvio è inferiore a 10 secondi, le richieste scadranno per un massimo di 10 secondi.
  • Se non sono presenti istanze in fase di avvio e la richiesta non avviano uno scale out, le richieste pendono per un massimo di per 10 secondi.

Usa le dipendenze con oculatezza

Se usi un linguaggio dinamico con librerie dipendenti, ad esempio importando 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 la dimensione delle dipendenze per creare un servizio snello.
  • Carica lentamente il codice utilizzato di rado, se il tuo linguaggio lo supporta.
  • Usa ottimizzazioni del caricamento del codice come PHP ottimizzazione del caricatore automatico del compositore.

Utilizza le variabili globali

In Cloud Run, non puoi presumere che lo stato del servizio sia mantenuto tra richieste. Tuttavia, Cloud Run riutilizza le singole istanze per gestire il traffico continuo, quindi puoi dichiarare una variabile in ambito globale il suo valore affinché venga riutilizzato nelle chiamate successive. Eventuali richieste individuali beneficio di questo riutilizzo non può essere noto in anticipo.

Puoi anche memorizzare nella cache gli oggetti se sono costosi da ricreare richiesta di servizio. Lo spostamento dalla logica di richiesta all'ambito globale prestazioni migliori.

Node.js

const functions = require('@google-cloud/functions-framework');

// TODO(developer): Define your own computations
const {lightComputation, heavyComputation} = require('./computations');

// Global (instance-wide) scope
// This computation runs once (at instance cold-start)
const instanceVar = heavyComputation();

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
functions.http('scopeDemo', (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
});

Python

import time

import functions_framework


# Placeholder
def heavy_computation():
    return time.time()


# Placeholder
def light_computation():
    return time.time()


# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()


@functions_framework.http
def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # Per-function scope
    # This computation runs every time this function is called
    function_var = light_computation()
    return f"Instance: {instance_var}; function: {function_var}"

Vai


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
	functions.HTTP("ScopeDemo", ScopeDemo)
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class Scopes implements HttpFunction {
  // Global (instance-wide) scope
  // This computation runs at instance cold-start.
  // Warning: Class variables used in functions code must be thread-safe.
  private static final int INSTANCE_VAR = heavyComputation();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Per-function scope
    // This computation runs every time this function is called
    int functionVar = lightComputation();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Instance: %s; function: %s", INSTANCE_VAR, functionVar);
  }

  private static int lightComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).sum();
  }

  private static int heavyComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

Esegui l'inizializzazione lazy delle variabili globali

L'inizializzazione delle variabili globali avviene sempre durante l'avvio, che aumenta l'avvio a freddo. Usa l'inizializzazione lazy per gli oggetti usati raramente per posticipare il costo in termini di tempo e ridurre i tempi di avvio a freddo.

Node.js

const functions = require('@google-cloud/functions-framework');

// Always initialized (at cold-start)
const nonLazyGlobal = fileWideComputation();

// Declared at cold-start, but only initialized if/when the function executes
let lazyGlobal;

/**
 * HTTP function that uses lazy-initialized globals
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
functions.http('lazyGlobals', (req, res) => {
  // This value is initialized only if (and when) the function is called
  lazyGlobal = lazyGlobal || functionSpecificComputation();

  res.send(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`);
});

Python

import functions_framework

# Always initialized (at cold-start)
non_lazy_global = file_wide_computation()

# Declared at cold-start, but only initialized if/when the function executes
lazy_global = None


@functions_framework.http
def lazy_globals(request):
    """
    HTTP Cloud Function that uses lazily-initialized globals.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    global lazy_global, non_lazy_global

    # This value is initialized only if (and when) the function is called
    if not lazy_global:
        lazy_global = function_specific_computation()

    return f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}."

Vai


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"context"
	"log"
	"net/http"
	"sync"

	"cloud.google.com/go/storage"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

// client is lazily initialized by LazyGlobal.
var client *storage.Client
var clientOnce sync.Once

func init() {
	functions.HTTP("LazyGlobal", LazyGlobal)
}

// LazyGlobal is an example of lazily initializing a Google Cloud Storage client.
func LazyGlobal(w http.ResponseWriter, r *http.Request) {
	// You may wish to add different checks to see if the client is needed for
	// this request.
	clientOnce.Do(func() {
		// Pre-declare an err variable to avoid shadowing client.
		var err error
		client, err = storage.NewClient(context.Background())
		if err != nil {
			http.Error(w, "Internal error", http.StatusInternalServerError)
			log.Printf("storage.NewClient: %v", err)
			return
		}
	})
	// Use client.
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class LazyFields implements HttpFunction {
  // Always initialized (at cold-start)
  // Warning: Class variables used in Servlet classes must be thread-safe,
  // or else might introduce race conditions in your code.
  private static final int NON_LAZY_GLOBAL = fileWideComputation();

  // Declared at cold-start, but only initialized if/when the function executes
  // Uses the "initialization-on-demand holder" idiom
  // More information: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
  private static class LazyGlobalHolder {
    // Making the default constructor private prohibits instantiation of this class
    private LazyGlobalHolder() {}

    // This value is initialized only if (and when) the getLazyGlobal() function below is called
    private static final Integer INSTANCE = functionSpecificComputation();

    private static Integer getInstance() {
      return LazyGlobalHolder.INSTANCE;
    }
  }

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    Integer lazyGlobal = LazyGlobalHolder.getInstance();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Lazy global: %s; non-lazy global: %s%n", lazyGlobal, NON_LAZY_GLOBAL);
  }

  private static int functionSpecificComputation() {
    int[] numbers = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
    return Arrays.stream(numbers).sum();
  }

  private static int fileWideComputation() {
    int[] numbers = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

Utilizza un ambiente di esecuzione diverso

Potresti riscontrare tempi di avvio più rapidi utilizzando una un ambiente di esecuzione diverso.

Ottimizza la contemporaneità

Le istanze Cloud Run possono gestire più richieste contemporaneamente, "contemporaneamente", fino a una contemporaneità massima configurabile. È diverso da Cloud Functions, che utilizza concurrency = 1.

Cloud Run regola automaticamente la contemporaneità in base al valore massimo.

La contemporaneità massima predefinita di 80 è per molte immagini container. Tuttavia, devi:

  • Riducilo se il container non è in grado di elaborare molte richieste in parallelo.
  • Aumentalo se il container è in grado di gestire un grande volume di richieste.

Ottimizza la contemporaneità per il tuo servizio

Il numero di richieste in parallelo che ogni istanza può gestire può essere limitati dallo stack tecnologico e dall'uso di risorse condivise, come e connessioni ai database.

Per ottimizzare il tuo servizio per ottenere la massima contemporaneità stabile:

  1. Ottimizza le prestazioni dei tuoi servizi.
  2. Imposta il livello previsto di supporto della contemporaneità in qualsiasi contemporaneità a livello di codice configurazione. Non tutti gli stack tecnologici richiedono questa impostazione.
  3. Esegui il deployment del servizio.
  4. Imposta la contemporaneità di Cloud Run per il tuo servizio su un valore uguale o inferiore a qualsiasi configurazione a livello di codice. Se non esiste una configurazione a livello di codice, utilizza il metodo della contemporaneità prevista.
  5. Utilizzare i test di carico che supportano una contemporaneità configurabile. Devi confermare che il tuo servizio rimane stabile con il carico e la contemporaneità previsti.
  6. Se il servizio non funziona bene, vai al passaggio 1 per migliorare il servizio o al passaggio 2 per e ridurre la contemporaneità. Se il servizio funziona correttamente, torna al passaggio 2 e aumenta la contemporaneità.

Continua a eseguire l'iterazione finché non trovi la contemporaneità stabile massima.

Abbina la memoria alla contemporaneità

Ogni richiesta gestita dal tuo servizio richiede una certa quantità di memoria aggiuntiva. Perciò, quando regoli la contemporaneità in alto o in basso, assicurati di regolare la memoria limite.

Evita stato globale modificabile

Se vuoi sfruttare lo stato globale modificabile in un contesto simultaneo, prendi in considerazione passaggi nel codice per assicurarti che questa operazione venga eseguita in sicurezza. Ridurre al minimo il conflitto limitando l'inizializzazione una tantum e il riutilizzo delle variabili globali, come descritto sopra Prestazioni.

Se utilizzi variabili globali modificabili in un servizio che gestisce più richieste contemporaneamente, assicurati di usare blocchi o mutex per prevenire le racecondition.

Sicurezza dei container

Molte pratiche di sicurezza del software per uso generico si applicano alle applicazioni containerizzate. Esistono alcune pratiche specifiche dei container o che sono in linea con la filosofia e l'architettura dei container.

Per migliorare la sicurezza dei container:

  • Usa immagini di base protette e gestite attivamente, come quelle di Google immagini di base o dalle immagini ufficiali di Docker Hub.

  • Applica gli aggiornamenti della sicurezza ai tuoi servizi ricreando regolarmente il container ed eseguire nuovamente il deployment dei servizi.

  • Includi nel container solo ciò che è necessario all'esecuzione del tuo servizio. Extra codice, pacchetti e strumenti sono potenziali vulnerabilità di sicurezza. Vedi sopra per il relativo impatto sul rendimento.

  • Implementare un processo di compilazione deterministico. che includa versioni specifiche di librerie e software. In questo modo, le schede non verificate di includere codice nel tuo container.

  • Imposta il contenitore in modo che venga eseguito come utente diverso da root con il Istruzione Dockerfile USER. Per alcune immagini container potrebbe essere già configurato un utente specifico.

Automatizzare l'analisi della sicurezza

Attiva 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ù elementi di quelli necessari al codice.

Grazie alla tecnologia di flussi di immagini container di Cloud Run, le dimensioni dell'immagine container non influisce sull'avvio a freddo o sul tempo di elaborazione della richiesta. Inoltre, le dimensioni dell'immagine container non vengono conteggiate ai fini della memoria disponibile. del container.

Per creare un container minimo, valuta l'utilizzo di un'immagine di base magra come:

Ubuntu ha dimensioni più grandi, ma è una suite con un ambiente server pronto all'uso più completo.

Se il tuo servizio ha un processo di compilazione che utilizza molti strumenti, valuta la possibilità di creazioni multifase per mantenere il container leggero in fase di esecuzione.

Queste risorse forniscono ulteriori informazioni sulla creazione di immagini container lean: