Utilizzo di Spanner con le funzioni di Cloud Run (1ª generazione)


Obiettivi

scrivi, esegui il deployment e attiva Funzione HTTP Cloud Run che accede Spanner.

Costi

Questo documento utilizza le funzioni di Spanner e Cloud Run, che sono componenti fatturabili di Google Cloud.

  • Per informazioni sul costo dell'utilizzo di Spanner, consulta la pagina Prezzi di Spanner.

  • Per informazioni sul costo dell'utilizzo delle funzioni di Cloud Run, incluse le funzionalità di Cloud Functions, consulta i prezzi di Cloud Run Functions.

Prima di iniziare

  1. Questo documento presuppone che tu abbia un'istanza Spanner denominata test-instance e un database denominato example-db che utilizza la musica applicazione schema. Per istruzioni sulla creazione di un'istanza e un database con l'applicazione Music schema, consulta la Guida rapida all'utilizzo della console oppure i tutorial per iniziare a usare Go, Java, Node.js oppure Python.

  2. Abilitare le funzioni Cloud Run e le API Cloud Build.

    Abilita le API

  3. Installa e inizializza gcloud CLI.

    Se hai già installato gcloud CLI, aggiornalo eseguendo il seguente comando:

    gcloud components update
    
  4. Prepara l'ambiente di sviluppo:

Prepara l'applicazione

  1. Clona il repository dell'app di esempio nella tua macchina locale:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    In alternativa, puoi scarica l'esempio come file ZIP ed estrarlo.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    In alternativa, puoi scarica l'esempio come file ZIP ed estrarlo.

    Vai

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    In alternativa, puoi scarica l'esempio come file ZIP ed estrarlo.

  2. Passa alla directory che contiene l'esempio delle funzioni Cloud Run per accedere a Spanner:

    Node.js

    cd nodejs-docs-samples/functions/spanner/

    Python

    cd python-docs-samples/functions/spanner/

    Vai

    cd golang-samples/functions/spanner/

    Java

    cd java-docs-samples/functions/spanner/
  3. Dai un'occhiata al codice campione:

    Node.js

    // Imports the Google Cloud client library
    const {Spanner} = require('@google-cloud/spanner');
    
    // Imports the functions framework to register your HTTP function
    const functions = require('@google-cloud/functions-framework');
    
    // Instantiates a client
    const spanner = new Spanner();
    
    // Your Cloud Spanner instance ID
    const instanceId = 'test-instance';
    
    // Your Cloud Spanner database ID
    const databaseId = 'example-db';
    
    /**
     * HTTP Cloud Function.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    functions.http('spannerQuickstart', async (req, res) => {
      // Gets a reference to a Cloud Spanner instance and database
      const instance = spanner.instance(instanceId);
      const database = instance.database(databaseId);
    
      // The query to execute
      const query = {
        sql: 'SELECT * FROM Albums',
      };
    
      // Execute the query
      try {
        const results = await database.run(query);
        const rows = results[0].map(row => row.toJSON());
        rows.forEach(row => {
          res.write(
            `SingerId: ${row.SingerId}, ` +
              `AlbumId: ${row.AlbumId}, ` +
              `AlbumTitle: ${row.AlbumTitle}\n`
          );
        });
        res.status(200).end();
      } catch (err) {
        res.status(500).send(`Error querying Spanner: ${err}`);
      }
    });

    Python

    import functions_framework
    from google.cloud import spanner
    
    instance_id = "test-instance"
    database_id = "example-db"
    
    client = spanner.Client()
    instance = client.instance(instance_id)
    database = instance.database(database_id)
    
    
    @functions_framework.http
    def spanner_read_data(request):
        query = "SELECT * FROM Albums"
    
        outputs = []
        with database.snapshot() as snapshot:
            results = snapshot.execute_sql(query)
    
            for row in results:
                output = "SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row)
                outputs.append(output)
    
        return "\n".join(outputs)
    
    

    Vai

    
    // Package spanner contains an example of using Spanner from a Cloud Function.
    package spanner
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"net/http"
    	"sync"
    
    	"cloud.google.com/go/spanner"
    	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
    	"google.golang.org/api/iterator"
    )
    
    // client is a global Spanner client, to avoid initializing a new client for
    // every request.
    var client *spanner.Client
    var clientOnce sync.Once
    
    // db is the name of the database to query.
    var db = "projects/my-project/instances/my-instance/databases/example-db"
    
    func init() {
    	functions.HTTP("HelloSpanner", HelloSpanner)
    }
    
    // HelloSpanner is an example of querying Spanner from a Cloud Function.
    func HelloSpanner(w http.ResponseWriter, r *http.Request) {
    	clientOnce.Do(func() {
    		// Declare a separate err variable to avoid shadowing client.
    		var err error
    		client, err = spanner.NewClient(context.Background(), db)
    		if err != nil {
    			http.Error(w, "Error initializing database", http.StatusInternalServerError)
    			log.Printf("spanner.NewClient: %v", err)
    			return
    		}
    	})
    
    	fmt.Fprintln(w, "Albums:")
    	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
    	iter := client.Single().Query(r.Context(), stmt)
    	defer iter.Stop()
    	for {
    		row, err := iter.Next()
    		if err == iterator.Done {
    			return
    		}
    		if err != nil {
    			http.Error(w, "Error querying database", http.StatusInternalServerError)
    			log.Printf("iter.Next: %v", err)
    			return
    		}
    		var singerID, albumID int64
    		var albumTitle string
    		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
    			http.Error(w, "Error parsing database response", http.StatusInternalServerError)
    			log.Printf("row.Columns: %v", err)
    			return
    		}
    		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
    	}
    }
    

    Java

    import com.google.api.client.http.HttpStatusCodes;
    import com.google.cloud.functions.HttpFunction;
    import com.google.cloud.functions.HttpRequest;
    import com.google.cloud.functions.HttpResponse;
    import com.google.cloud.spanner.DatabaseClient;
    import com.google.cloud.spanner.DatabaseId;
    import com.google.cloud.spanner.LazySpannerInitializer;
    import com.google.cloud.spanner.ResultSet;
    import com.google.cloud.spanner.SpannerException;
    import com.google.cloud.spanner.SpannerOptions;
    import com.google.cloud.spanner.Statement;
    import com.google.common.annotations.VisibleForTesting;
    import com.google.common.base.MoreObjects;
    import java.io.PrintWriter;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    // HelloSpanner is an example of querying Spanner from a Cloud Function.
    public class HelloSpanner implements HttpFunction {
      private static final Logger logger = Logger.getLogger(HelloSpanner.class.getName());
    
      // TODO<developer>: Set these environment variables.
      private static final String SPANNER_INSTANCE_ID =
          MoreObjects.firstNonNull(System.getenv("SPANNER_INSTANCE"), "my-instance");
      private static final String SPANNER_DATABASE_ID =
          MoreObjects.firstNonNull(System.getenv("SPANNER_DATABASE"), "example-db");
    
      private static final DatabaseId databaseId =
          DatabaseId.of(
              SpannerOptions.getDefaultProjectId(),
              SPANNER_INSTANCE_ID,
              SPANNER_DATABASE_ID);
    
      // The LazySpannerInitializer instance is shared across all instances of the HelloSpanner class.
      // It will create a Spanner instance the first time one is requested, and continue to return that
      // instance for all subsequent requests.
      private static final LazySpannerInitializer SPANNER_INITIALIZER = new LazySpannerInitializer();
    
      @VisibleForTesting
      DatabaseClient getClient() throws Throwable {
        return SPANNER_INITIALIZER.get().getDatabaseClient(databaseId);
      }
    
      @Override
      public void service(HttpRequest request, HttpResponse response) throws Exception {
        var writer = new PrintWriter(response.getWriter());
        try {
          DatabaseClient client = getClient();
          try (ResultSet rs =
              client
                  .singleUse()
                  .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
            writer.printf("Albums:%n");
            while (rs.next()) {
              writer.printf(
                  "%d %d %s%n",
                  rs.getLong("SingerId"), rs.getLong("AlbumId"), rs.getString("AlbumTitle"));
            }
          } catch (SpannerException e) {
            writer.printf("Error querying database: %s%n", e.getMessage());
            response.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, e.getMessage());
          }
        } catch (Throwable t) {
          logger.log(Level.SEVERE, "Spanner example failed", t);
          writer.printf("Error setting up Spanner: %s%n", t.getMessage());
          response.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, t.getMessage());
        }
      }
    }

    La funzione invia una query SQL per recuperare tutti i dati Albums dal database. La funzione viene eseguita quando invii una richiesta HTTP all'endpoint della funzione.

esegui il deployment della funzione

Per eseguire il deployment della funzione con un trigger HTTP, esegui questo comando nella directory spanner:

Node.js

gcloud functions deploy get \
--runtime nodejs20 --trigger-http

Utilizza il flag --runtime per specificare l'ID runtime di una versione di Node.js supportata per eseguire la funzione.

Python

gcloud functions deploy spanner_read_data \
--runtime python312 --trigger-http

Utilizza la --runtime per specificare l'ID runtime di un versione Python supportata per l'esecuzione la tua funzione.

Vai

gcloud functions deploy HelloSpanner \
--runtime go121 --trigger-http

Utilizza la --runtime per specificare l'ID runtime di un versione Go supportata per l'esecuzione la tua funzione.

Java

gcloud functions deploy java-spanner-function \
--entry-point functions.HelloSpanner \
--runtime java17 \
--memory 512MB --trigger-http

Utilizza la --runtime per specificare l'ID runtime di un versione Java supportata per l'esecuzione la tua funzione.

Il deployment delle funzioni potrebbe richiedere fino a due minuti.

Osserva il valore url restituito al termine del deployment della funzione. Potrai da utilizzare quando attivi la funzione.

Puoi visualizzare le funzioni di cui hai eseguito il deployment nella nella pagina Funzioni di Cloud Run nella console Google Cloud. In questa pagina puoi anche creare e modificare le funzioni, nonché visualizzarne dettagli e diagnostica.

Attiva la funzione

Effettua una richiesta HTTP alla tua funzione:

Node.js

curl "https://REGION-PROJECT_ID.cloudfunctions.net/get" 

Python

curl "https://REGION-PROJECT_ID.cloudfunctions.net/spanner_read_data" 

Vai

curl "https://REGION-PROJECT_ID.cloudfunctions.net/HelloSpanner" 

Java

curl "https://REGION-PROJECT_ID.cloudfunctions.net/java-spanner-function" 

dove REGION e PROJECT_ID deve corrispondere ai valori visibili nel terminale quando la funzione del deployment. Dovresti vedere un output che mostra i risultati Query SQL, supponendo che tu abbia seguito un corso introduttivo tutorial e ha compilato il database:

SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold Your Peace
SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go
SingerId: 2, AlbumId: 1, AlbumTitle: Green
SingerId: 2, AlbumId: 3, AlbumTitle: Terrified
SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk

Puoi anche visitare l'URL della funzione nel browser per visualizzare il risultato della query SQL.

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi aggiuntivi per Risorse delle funzioni Spanner e Cloud Run utilizzate in questo documento, procedi nel seguente modo:

  1. Elimina l'istanza:

    gcloud spanner instances delete test-instance
    
  2. Elimina la funzione di cui hai eseguito il deployment:

    Node.js

    gcloud functions delete get 

    Python

    gcloud functions delete spanner_read_data 

    Vai

    gcloud functions delete HelloSpanner 

    Java

    gcloud functions delete java-spanner-function 

Passaggi successivi