Processamento em segundo plano com Go

Muitas apps precisam de fazer processamento em segundo plano fora do contexto de um pedido Web. Este tutorial cria uma app Web que permite aos utilizadores introduzir texto para traduzir e, em seguida, apresenta uma lista de traduções anteriores. A tradução é feita num processo em segundo plano para evitar o bloqueio do pedido do utilizador.

O diagrama seguinte ilustra o processo de pedido de tradução.

Diagrama de arquitetura.

Veja a sequência de eventos que explica como funciona a app de tutoriais:

  1. Visite a página Web para ver uma lista de traduções anteriores, armazenadas no Firestore.
  2. Peça uma tradução de texto introduzindo um formulário HTML.
  3. O pedido de tradução é publicado no Pub/Sub.
  4. É acionada uma função do Cloud Run subscrita nesse tópico do Pub/Sub.
  5. A função do Cloud Run usa o Cloud Translation para traduzir o texto.
  6. A função do Cloud Run armazena o resultado no Firestore.

Este tutorial destina-se a qualquer pessoa interessada em saber mais sobre o processamento em segundo plano com o Google Cloud. Não é necessária experiência prévia com as funções do Pub/Sub, Firestore, App Engine ou Cloud Run. No entanto, para compreender todo o código, é útil ter alguma experiência com Go, JavaScript e HTML.

Objetivos

  • Compreender e implementar uma função do Cloud Run.
  • Compreenda e implemente uma app do App Engine.
  • Experimente a app.

Custos

Neste documento, usa os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custos com base na sua utilização projetada, use a calculadora de preços.

Os novos Google Cloud utilizadores podem ser elegíveis para uma avaliação gratuita.

Quando terminar as tarefas descritas neste documento, pode evitar a faturação contínua eliminando os recursos que criou. Para mais informações, consulte o artigo Limpe.

Antes de começar

  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.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

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

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

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

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

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

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. Na Google Cloud consola, abra a app no Cloud Shell.

    Aceda ao Cloud Shell

    O Cloud Shell dá acesso à linha de comandos aos recursos da nuvem diretamente a partir do navegador. Abra o Cloud Shell no navegador e clique em Continuar para transferir o código de exemplo e mudar para o diretório da app.

  9. No Cloud Shell, configure a ferramenta gcloud para usar o seu projeto Google Cloud :
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Compreender a função do Cloud Run

  • A função começa por importar várias dependências, como o Firestore e a tradução. Também tem algumas variáveis e tipos globais.
    
    // 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"`
    }
    
  • Os clientes globais do Firestore e do Translation são inicializados para que possam ser reutilizados entre invocações de funções. Desta forma, não tem de inicializar novos clientes para cada invocação de função, o que atrasaria a execução.
    
    // 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
    }
    
  • A API Translation traduz a string para o idioma que selecionou.
    
    // 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
    }
    
  • A função do Cloud Run começa por inicializar os clientes do Firestore e do Pub/Sub. Em seguida, analisa a mensagem do Pub/Sub para obter o texto a traduzir e o idioma de destino pretendido.

    Em seguida, a app apresenta um nome exclusivo para o pedido de tradução para garantir que não armazena traduções duplicadas. Em seguida, traduz numa transação do Firestore para garantir que as execuções simultâneas não executam acidentalmente a mesma tradução duas vezes.

    
    // 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
    }
    

Implementar a função do Cloud Run

  • No Cloud Shell, no mesmo diretório que o ficheiro translate.go, implemente a função do Cloud Run com um acionador do Pub/Sub:

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

    onde YOUR_GOOGLE_CLOUD_PROJECT é o ID do seu Google Cloud projeto.

Compreender a app

Existem dois componentes principais para a app Web:

  • Um servidor HTTP Go para processar pedidos Web. O servidor tem os seguintes dois pontos finais:
    • /: lista todas as traduções existentes e mostra um formulário que os utilizadores podem enviar para pedir novas traduções.
    • /request-translation: os envios de formulários são enviados para este ponto final, que publica o pedido no Pub/Sub para ser traduzido de forma assíncrona.
  • Um modelo HTML preenchido com as traduções existentes pelo servidor Go.

O servidor HTTP

  • No diretório index, o main.go começa por configurar a app e registar controladores 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
    }
    
  • O controlador de índice (/) obtém todas as traduções existentes do Firestore e preenche um modelo HTML com a lista:

    
    // 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
    	}
    }
    
  • As novas traduções são pedidas através do envio de um formulário HTML. O controlador de tradução de pedidos, registado em /request-translation, analisa o envio do formulário, valida o pedido e publica uma mensagem no 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
    	}
    }
    

O modelo HTML

O modelo HTML é a base da página HTML apresentada ao utilizador para que possa ver traduções anteriores e pedir novas. O modelo é preenchido pelo servidor HTTP com a lista de traduções existentes.

  • O elemento <head> do modelo HTML inclui metadados, folhas de estilos e JavaScript para a página:
    <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>

    A página extrai recursos de CSS e JavaScript do Material Design Lite (MDL). O MDL permite-lhe adicionar um aspeto do Material Design aos seus Websites.

    A página usa JQuery para aguardar a conclusão do carregamento do documento e definir um controlador de envio de formulários. Sempre que o formulário de tradução de pedidos é enviado, a página faz uma validação mínima do formulário para verificar se o valor não está vazio e, em seguida, envia um pedido assíncrono para o ponto final /request-translation.

    Por último, é apresentada uma barra de mensagens do MDL para indicar se o pedido foi bem-sucedido ou se ocorreu um erro.

  • O corpo HTML da página usa um esquema MDL e vários componentes MDL para apresentar uma lista de traduções e um formulário para pedir traduções adicionais:
    <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>

Criar a app

  • Antes de tentar implementar a app Web, crie a app para se certificar de que é compilada e que todas as dependências estão a funcionar.
    go build -o start ./index
    

    A compilação foi bem-sucedida se não tiver sido impresso nada e tiver sido criado um ficheiro start.

Implementar a app Web

Pode usar o ambiente padrão do App Engine para criar e implementar uma app que é executada de forma fiável sob carga elevada e com grandes quantidades de dados.

Este tutorial usa o ambiente padrão do App Engine para implementar o front-end HTTP.

O app.yaml configura a app do App Engine:

runtime: go111
main: index
  • No mesmo diretório que o ficheiro app.yaml, implemente a sua app no ambiente padrão do App Engine:
    gcloud app deploy

Testar a app

Depois de implementar a função do Cloud Run e a app do App Engine, experimente pedir uma tradução.

  1. Para ver a app no navegador,introduza o seguinte URL:

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

    Substitua o seguinte:

    Existe uma página com uma lista vazia de traduções e um formulário para pedir novas traduções.

  2. No campo Texto a traduzir, introduza algum texto para traduzir, por exemplo, Hello, World.
  3. Selecione um idioma na lista pendente para o qual quer traduzir o texto.
  4. Clique em Enviar.
  5. Para atualizar a página, clique em Atualizar . É apresentada uma nova linha na lista de traduções. Se não vir uma tradução, aguarde mais alguns segundos e tente novamente. Se ainda não vir uma tradução, consulte a secção seguinte sobre a depuração da app.

Depurar a app

Se não conseguir estabelecer ligação à sua app do App Engine ou não vir novas traduções, verifique o seguinte:

  1. Verifique se os comandos de implementação gcloud foram concluídos com êxito e não apresentaram erros. Se existirem erros, corrija-os e tente implementar a função do Cloud Run e a app do App Engine novamente.
  2. Na Google Cloud consola, aceda à página do visualizador de registos.

    Aceda à página do visualizador de registos
    1. Na lista pendente Recursos selecionados recentemente, clique em Aplicação GAE e, de seguida, clique em Todos os module_id. É apresentada uma lista de pedidos de quando visitou a sua app. Se não vir uma lista de pedidos, confirme que selecionou Todos os module_id na lista pendente. Se vir mensagens de erro impressas na Google Cloud consola, verifique se o código da sua app corresponde ao código na secção sobre a compreensão da app.
    2. Na lista pendente Recursos selecionados recentemente, clique em Função da nuvem e, de seguida, clique em Nome de todas as funções. É apresentada uma função para cada tradução pedida. Caso contrário, verifique se a função do Cloud Run e a app App Engine estão a usar o mesmo tópico do Pub/Sub:
      • No ficheiro background/index/main.go, verifique se a constante topicName é "translate".
      • Quando implementar a função do Cloud Run, certifique-se de que inclui a flag --trigger-topic=translate.

Limpar

Para evitar incorrer em custos na sua conta do Google Cloud pelos recursos usados neste tutorial, elimine o projeto que contém os recursos ou mantenha o projeto e elimine os recursos individuais.

Elimine o Google Cloud projeto

  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.

Elimine a instância do 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. Para eliminar a versão da app, clique em Eliminar.

Elimine a função do Cloud Run

  • Elimine a função do Cloud Run que criou neste tutorial:
    gcloud functions delete Translate

O que se segue?