Elaborazione in background con Go


Molte app devono eseguire l'elaborazione in background al di fuori del contesto di richiesta web. Questo tutorial crea un'app web che consente agli utenti di inserire testo da tradurre, quindi mostra un elenco di traduzioni precedenti. La traduzione viene eseguita in background per evitare di bloccare la richiesta dell'utente.

Il seguente diagramma illustra la procedura di richiesta di traduzione.

Diagramma dell'architettura.

Ecco la sequenza di eventi per il funzionamento dell'app tutorial:

  1. Visita la pagina web per visualizzare un elenco delle traduzioni precedenti, archiviate in Firestore.
  2. Richiedi una traduzione del testo inserendo un modulo HTML.
  3. La richiesta di traduzione è pubblicata in Pub/Sub.
  4. Viene attivata una funzione Cloud Run che ha effettuato la sottoscrizione all'argomento Pub/Sub.
  5. La funzione Cloud Run utilizza Cloud Translation per tradurre il testo.
  6. La funzione Cloud Run archivia il risultato in Firestore.

Questo tutorial è rivolto a chiunque voglia conoscere l'elaborazione in background con Google Cloud. Nessuna esperienza precedente richiesta con Pub/Sub, Firestore, App Engine o le funzioni di Cloud Run. Tuttavia, per comprendere tutto il codice, è utile avere esperienza con Go, JavaScript e HTML.

Obiettivi

  • Comprendi ed esegui il deployment di una funzione Cloud Run.
  • Comprendi ed esegui il deployment di un'app App Engine.
  • Prova l'app.

Costi

In questo documento utilizzi i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi basata sull'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud potrebbero essere idonei per una prova gratuita.

Al termine delle attività descritte in questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, consulta la sezione Pulizia.

Prima di iniziare

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Firestore, Cloud Run functions, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the Firestore, Cloud Run functions, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  8. Nella console Google Cloud, apri l'app in Cloud Shell.

    Vai a Cloud Shell

    Cloud Shell fornisce l'accesso da riga di comando alle risorse cloud direttamente dal browser. Apri Cloud Shell nel browser e fai clic su Procedi per scaricare il codice campione e passa alla directory dell'app.

  9. In Cloud Shell, configura lo strumento gcloud da utilizzare per il tuo progetto Google Cloud:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Comprensione della funzione Cloud Run

  • La funzione inizia importando diverse dipendenze, come Firestore e Traduzione. Contiene anche alcuni tipi e variabili globali.
    
    // Package background contains a Cloud Function to translate text.
    // The function listens to Pub/Sub, does the translations, and stores the
    // result in Firestore.
    package background
    
    import (
    	"context"
    	"crypto/sha512"
    	"encoding/base64"
    	"encoding/json"
    	"fmt"
    	"os"
    	"strings"
    
    	"cloud.google.com/go/firestore"
    	"cloud.google.com/go/translate"
    	"golang.org/x/text/language"
    	"google.golang.org/grpc/codes"
    	"google.golang.org/grpc/status"
    )
    
    // A Translation contains the original and translated text.
    type Translation struct {
    	Original         string `json:"original"`
    	Translated       string `json:"translated"`
    	OriginalLanguage string `json:"original_language"`
    	Language         string `json:"language"`
    }
    
    // Clients reused between function invocations.
    var (
    	translateClient *translate.Client
    	firestoreClient *firestore.Client
    )
    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See https://cloud.google.com/functions/docs/calling/pubsub.
    type PubSubMessage struct {
    	Data []byte `json:"data"`
    }
    
  • I client Firestore e Translation globali vengono inizializzati in modo da poter essere riutilizzati tra le chiamate alle funzioni. In questo modo, non devi inizializzare nuovi client per ogni chiamata di funzione, il che rallenterebbe l'esecuzione.
    
    // initializeClients creates translateClient and firestoreClient if they haven't
    // been created yet.
    func initializeClients() error {
    	projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
    	if projectID == "" {
    		return fmt.Errorf("GOOGLE_CLOUD_PROJECT must be set")
    	}
    
    	if translateClient == nil {
    		// Pre-declare err to avoid shadowing translateClient.
    		var err error
    		// Use context.Background() so the client can be reused.
    		translateClient, err = translate.NewClient(context.Background())
    		if err != nil {
    			return fmt.Errorf("translate.NewClient: %w", err)
    		}
    	}
    	if firestoreClient == nil {
    		// Pre-declare err to avoid shadowing firestoreClient.
    		var err error
    		// Use context.Background() so the client can be reused.
    		firestoreClient, err = firestore.NewClient(context.Background(), projectID)
    		if err != nil {
    			return fmt.Errorf("firestore.NewClient: %w", err)
    		}
    	}
    	return nil
    }
    
  • L'API Translation traduce la stringa nella lingua selezionata.
    
    // translateString translates text to lang, returning:
    // * the translated text,
    // * the automatically detected source language, and
    // * an error.
    func translateString(ctx context.Context, text string, lang string) (translated string, originalLang string, err error) {
    	l, err := language.Parse(lang)
    	if err != nil {
    		return "", "", fmt.Errorf("language.Parse: %w", err)
    	}
    
    	outs, err := translateClient.Translate(ctx, []string{text}, l, nil)
    	if err != nil {
    		return "", "", fmt.Errorf("Translate: %w", err)
    	}
    
    	if len(outs) < 1 {
    		return "", "", fmt.Errorf("Translate got %d translations, need at least 1", len(outs))
    	}
    
    	return outs[0].Text, outs[0].Source.String(), nil
    }
    
  • La funzione Cloud Run inizia inizializzando i client Firestore e Pub/Sub. Quindi, analizza il messaggio Pub/Sub per recuperare il testo da tradurre e la lingua di destinazione desiderata.

    Quindi, l'app assegna un nome univoco per la richiesta di traduzione, per assicurarsi non memorizza le traduzioni duplicate. Quindi, traduce in alla transazione Firestore per evitare che le esecuzioni simultanee eseguire accidentalmente la stessa traduzione due volte.

    
    // Translate translates the given message and stores the result in Firestore.
    func Translate(ctx context.Context, m PubSubMessage) error {
    	initializeClients()
    
    	t := Translation{}
    	if err := json.Unmarshal(m.Data, &t); err != nil {
    		return fmt.Errorf("json.Unmarshal: %w", err)
    	}
    
    	// Use a unique document name to prevent duplicate translations.
    	key := fmt.Sprintf("%s/%s", t.Language, t.Original)
    	sum := sha512.Sum512([]byte(key))
    	// Base64 encode the sum to make a nice string. The [:] converts the byte
    	// array to a byte slice.
    	docName := base64.StdEncoding.EncodeToString(sum[:])
    	// The document name cannot contain "/".
    	docName = strings.Replace(docName, "/", "-", -1)
    	ref := firestoreClient.Collection("translations").Doc(docName)
    
    	// Run in a transation to prevent concurrent duplicate translations.
    	err := firestoreClient.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
    		doc, err := tx.Get(ref)
    		if err != nil && status.Code(err) != codes.NotFound {
    			return fmt.Errorf("Get: %w", err)
    		}
    		// Do nothing if the document already exists.
    		if doc.Exists() {
    			return nil
    		}
    
    		translated, originalLang, err := translateString(ctx, t.Original, t.Language)
    		if err != nil {
    			return fmt.Errorf("translateString: %w", err)
    		}
    		t.Translated = translated
    		t.OriginalLanguage = originalLang
    
    		if err := tx.Set(ref, t); err != nil {
    			return fmt.Errorf("Set: %w", err)
    		}
    		return nil
    	})
    
    	if err != nil {
    		return fmt.Errorf("RunTransaction: %w", err)
    	}
    	return nil
    }
    

Deployment della funzione Cloud Run

  • In Cloud Shell, nella stessa directory del file translate.go, esegui il deployment la funzione Cloud Run con un trigger Pub/Sub:

    gcloud functions deploy Translate --runtime go111 \
    --trigger-topic=translate --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_GOOGLE_CLOUD_PROJECT

    dove YOUR_GOOGLE_CLOUD_PROJECT è l'ID progetto Google Cloud.

Comprensione dell'app

L'app web è composta da due componenti principali:

  • Un server HTTP Go per gestire le richieste web. Il server ha i seguenti due endpoint:
    • /: elenca tutte le traduzioni esistenti e mostra un modulo che gli utenti possono inviare per richiedere nuove traduzioni.
    • /request-translation: gli invii dei moduli vengono inviati a questo endpoint, che pubblica la richiesta in Pub/Sub per la traduzione asincrona.
  • Un modello HTML che viene compilato con le traduzioni esistenti dal server Go.

Il server HTTP

  • Nella directory index, main.go inizia configurando l'app e registrando gli handler HTTP:

    
    // Command index is an HTTP app that displays all previous translations
    // (stored in Firestore) and has a form to request new translations. On form
    // submission, the request is sent to Pub/Sub to be processed in the background.
    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"html/template"
    	"log"
    	"net/http"
    	"os"
    	"path/filepath"
    
    	"cloud.google.com/go/firestore"
    	"cloud.google.com/go/pubsub"
    	"github.com/GoogleCloudPlatform/golang-samples/getting-started/background"
    )
    
    // topicName is the Pub/Sub topic to publish requests to. The Cloud Function to
    // process translation requests should be subscribed to this topic.
    const topicName = "translate"
    
    // An app holds the clients and parsed templates that can be reused between
    // requests.
    type app struct {
    	pubsubClient    *pubsub.Client
    	pubsubTopic     *pubsub.Topic
    	firestoreClient *firestore.Client
    	tmpl            *template.Template
    }
    
    func main() {
    	projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
    	if projectID == "" {
    		log.Fatalf("GOOGLE_CLOUD_PROJECT must be set")
    	}
    
    	a, err := newApp(projectID, "index")
    	if err != nil {
    		log.Fatalf("newApp: %v", err)
    	}
    
    	http.HandleFunc("/", a.index)
    	http.HandleFunc("/request-translation", a.requestTranslation)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    
    	log.Printf("Listening on localhost:%v", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // newApp creates a new app.
    func newApp(projectID, templateDir string) (*app, error) {
    	ctx := context.Background()
    
    	pubsubClient, err := pubsub.NewClient(ctx, projectID)
    	if err != nil {
    		return nil, fmt.Errorf("pubsub.NewClient: %w", err)
    	}
    
    	pubsubTopic := pubsubClient.Topic(topicName)
    
    	firestoreClient, err := firestore.NewClient(ctx, projectID)
    	if err != nil {
    		return nil, fmt.Errorf("firestore.NewClient: %w", err)
    	}
    
    	// Template referenced relative to the module/app root.
    	tmpl, err := template.ParseFiles(filepath.Join(templateDir, "index.html"))
    	if err != nil {
    		return nil, fmt.Errorf("template.New: %w", err)
    	}
    
    	return &app{
    		pubsubClient: pubsubClient,
    		pubsubTopic:  pubsubTopic,
    
    		firestoreClient: firestoreClient,
    		tmpl:            tmpl,
    	}, nil
    }
    
  • L'handler dell'indice (/) recupera tutte le traduzioni esistenti da Firestore e compila un modello HTML con l'elenco:

    
    // index lists the current translations.
    func (a *app) index(w http.ResponseWriter, r *http.Request) {
    	docs, err := a.firestoreClient.Collection("translations").Documents(r.Context()).GetAll()
    	if err != nil {
    		log.Printf("GetAll: %v", err)
    		http.Error(w, fmt.Sprintf("Error getting translations: %v", err), http.StatusInternalServerError)
    		return
    	}
    
    	var translations []background.Translation
    	for _, d := range docs {
    		t := background.Translation{}
    		if err := d.DataTo(&t); err != nil {
    			log.Printf("DataTo: %v", err)
    			http.Error(w, "Error reading translations", http.StatusInternalServerError)
    			return
    		}
    		translations = append(translations, t)
    	}
    
    	if err := a.tmpl.Execute(w, translations); err != nil {
    		log.Printf("tmpl.Execute: %v", err)
    		http.Error(w, "Error writing response", http.StatusInternalServerError)
    		return
    	}
    }
    
  • Per richiedere nuove traduzioni, è necessario inviare un modulo HTML. Il gestore della traduzione della richiesta, registrato in /request-translation, analizza l'invio del modulo, convalida la richiesta e pubblica un messaggio in Pub/Sub:

    
    // requestTranslation parses the request, validates it, and sends it to Pub/Sub.
    func (a *app) requestTranslation(w http.ResponseWriter, r *http.Request) {
    	if err := r.ParseForm(); err != nil {
    		log.Printf("ParseForm: %v", err)
    		http.Error(w, "Bad request", http.StatusBadRequest)
    		return
    	}
    	v := r.PostFormValue("v")
    	if v == "" {
    		log.Printf("Empty value")
    		http.Error(w, "Empty value", http.StatusBadRequest)
    		return
    	}
    	acceptableLanguages := map[string]bool{
    		"de": true,
    		"en": true,
    		"es": true,
    		"fr": true,
    		"ja": true,
    		"sw": true,
    	}
    	lang := r.PostFormValue("lang")
    	if !acceptableLanguages[lang] {
    		log.Printf("Unsupported language: %v", lang)
    		http.Error(w, fmt.Sprintf("Unsupported language: %v", lang), http.StatusBadRequest)
    		return
    	}
    
    	log.Printf("Translation requested: %q -> %s", v, lang)
    
    	t := background.Translation{
    		Original: v,
    		Language: lang,
    	}
    	msg, err := json.Marshal(t)
    	if err != nil {
    		log.Printf("json.Marshal: %v", err)
    		http.Error(w, "Error requesting translation", http.StatusInternalServerError)
    		return
    	}
    
    	res := a.pubsubTopic.Publish(r.Context(), &pubsub.Message{Data: msg})
    	if _, err := res.Get(r.Context()); err != nil {
    		log.Printf("Publish.Get: %v", err)
    		http.Error(w, "Error requesting translation", http.StatusInternalServerError)
    		return
    	}
    }
    

Il modello HTML

Il modello HTML è la base per la pagina HTML mostrata all'utente, in modo che possa vedere le traduzioni precedenti e richiederne di nuove. Il modello viene compilato con i Server HTTP con l'elenco delle traduzioni esistenti.

  • L'elemento <head> del modello HTML include metadati, fogli di stile e JavaScript per la pagina:
    <html>
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Translations</title>
    
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
        <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script>
            $(document).ready(function() {
                $("#translate-form").submit(function(e) {
                    e.preventDefault();
                    // Get value, make sure it's not empty.
                    if ($("#v").val() == "") {
                        return;
                    }
                    $.ajax({
                        type: "POST",
                        url: "/request-translation",
                        data: $(this).serialize(),
                        success: function(data) {
                            // Show snackbar.
                            console.log(data);
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--red-100");
                            $("#snackbar").addClass("mdl-color--green-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation requested'
                            });
                        },
                        error: function(data) {
                            // Show snackbar.
                            console.log("Error requesting translation");
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--green-100");
                            $("#snackbar").addClass("mdl-color--red-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation request failed'
                            });
                        }
                    });
                });
            });
        </script>
        <style>
            .lang {
                width: 50px;
            }
            .translate-form {
                display: inline;
            }
        </style>
    </head>

    Viene visualizzata la pagina Material Design Lite (MDL) gli asset CSS e JavaScript. MDL ti consente di aggiungere un aspetto di Material Design ai tuoi siti web.

    La pagina utilizza JQuery per attendere il completamento del caricamento del documento e impostare un gestore di invio del modulo. Ogni volta che il modulo di richiesta di traduzione viene inviato, la pagina esegue un modulo minimo per controllare che il valore non sia vuoto, quindi invia una query all'endpoint /request-translation.

    Infine, viene visualizzata una barra snack MDL per indicare se la richiesta è andata a buon fine o se si è verificato un errore.

  • Il corpo HTML della pagina utilizza un parametro Layout MDL e molti Componenti SDL per visualizzare un elenco di traduzioni e un modulo per richiedere traduzioni aggiuntive:
    <body>
        <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
            <header class="mdl-layout__header">
                <div class="mdl-layout__header-row">
                    <!-- Title -->
                    <span class="mdl-layout-title">Translate with Background Processing</span>
                </div>
            </header>
            <main class="mdl-layout__content">
                <div class="page-content">
                    <div class="mdl-grid">
                    <div class="mdl-cell mdl-cell--1-col"></div>
                        <div class="mdl-cell mdl-cell--3-col">
                            <form id="translate-form" class="translate-form">
                                <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                                    <input class="mdl-textfield__input" type="text" id="v" name="v">
                                    <label class="mdl-textfield__label" for="v">Text to translate...</label>
                                </div>
                                <select class="mdl-textfield__input lang" name="lang">
                                    <option value="de">de</option>
                                    <option value="en">en</option>
                                    <option value="es">es</option>
                                    <option value="fr">fr</option>
                                    <option value="ja">ja</option>
                                    <option value="sw">sw</option>
                                </select>
                                <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent" type="submit"
                                    name="submit">Submit</button>
                            </form>
                        </div>
                        <div class="mdl-cell mdl-cell--8-col">
                            <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
                                <thead>
                                    <tr>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Original</strong></th>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Translation</strong></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {{range .}}
                                    <tr>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--primary">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ .OriginalLanguage }} </span>
                                            </span>
                                        {{ .Original }}
                                        </td>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--accent">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ .Language }} </span>
                                            </span>
                                            {{ .Translated }}
                                        </td>
                                    </tr>
                                    {{end}}
                                </tbody>
                            </table>
                            <br/>
                            <button class="mdl-button mdl-js-button mdl-button--raised" type="button" onClick="window.location.reload();">
                                Refresh
                            </button>
                        </div>
                    </div>
                </div>
                <div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar" id="snackbar">
                    <div class="mdl-snackbar__text mdl-color-text--black"></div>
                    <button type="button" class="mdl-snackbar__action"></button>
                </div>
            </main>
        </div>
    </body>
    
    </html>

Creazione dell'app

  • Prima di provare a eseguire il deployment dell'app web, compilala per assicurarti che venga compilata e che tutte le dipendenze funzionino.
    go build -o start ./index
    

    La build è riuscita se non è stato stampato nulla e se è stato eseguito un file start è stato creato.

Deployment dell'app web

Puoi utilizzare l'ambiente standard di App Engine per creare ed eseguire il deployment di un'app che funziona in modo affidabile anche se sottoposta a un carico elevato e con grandi quantità di dati.

Questo tutorial utilizza l'ambiente standard di App Engine per eseguire il deployment del frontend HTTP.

app.yaml configura l'app App Engine:

runtime: go111
main: index
  • Nella stessa directory del file app.yaml, esegui il deployment dell'app in Ambiente standard di App Engine:
    gcloud app deploy

Test dell'app

Dopo aver eseguito il deployment della funzione Cloud Run e dell'app App Engine, prova richiedere una traduzione.

  1. Per visualizzare l'app nel browser,inserisci il seguente URL:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    Sostituisci quanto segue:

    È presente una pagina con un elenco vuoto di traduzioni e un modulo per richiedere nuove traduzioni.

  2. Nel campo Testo da tradurre, inserisci il testo da tradurre, ad esempio Hello, World.
  3. Seleziona dall'elenco a discesa la lingua in cui vuoi tradurre il testo.
  4. Fai clic su Invia.
  5. Per aggiornare la pagina, fai clic su Aggiorna. . Nell'elenco delle traduzioni è presente una nuova riga. Se non vedi una traduzione, attendi ancora qualche istante secondi e riprova. Se continui a non vedere una traduzione, consulta la sezione successiva sul debug dell'app.

Eseguire il debug dell'app

Se non riesci a connetterti all'app App Engine o non vedi nuovi elementi traduzioni, controlla quanto segue:

  1. Verifica che i comandi di deployment di gcloud siano stati completati correttamente e non abbiano generato errori. Se si sono verificati errori, correggili e prova il deployment della funzione Cloud Run e l'app App Engine.
  2. Nella console Google Cloud, vai alla pagina Visualizzatore log.

    Vai alla pagina Visualizzatore log
    1. Nell'elenco a discesa Risorse selezionate di recente, fai clic su GAE Application (Applicazione GAE), quindi fai clic su All module_id. Viene visualizzato un elenco richieste da quando hai visitato la tua app. Se non vedi l'elenco di richieste, conferma di aver selezionato Tutti i_modulo_id dall'elenco a discesa. Se nella console Google Cloud vengono visualizzati messaggi di errore, verifica che il codice dell'app corrisponda al codice riportato nella sezione relativa alla comprensione dell'app.
    2. Nell'elenco a discesa Risorse selezionate di recente, fai clic su funzione Cloud Functions Functions, quindi fai clic su Nome tutte le funzioni. Viene visualizzata una funzione elencata per ogni traduzione richiesta. In caso contrario, verifica che La funzione Cloud Run e l'app App Engine utilizzano lo stesso Argomento Pub/Sub:

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

Elimina il progetto Google Cloud

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Elimina l'istanza di App Engine

  1. In the Google Cloud console, go to the Versions page for App Engine.

    Go to Versions

  2. Select the checkbox for the non-default app version that you want to delete.
  3. Per eliminare la versione dell'app, fai clic su Elimina.

Elimina la funzione Cloud Run

  • Elimina la funzione Cloud Run creata in questo tutorial:
    gcloud functions delete Translate

Passaggi successivi