Tutorial: como usar pacotes do sistema


Neste tutorial, mostramos como criar um serviço do Cloud Run personalizado que transforma um parâmetro de entrada de descrição de gráfico em um diagrama no formato de imagem PNG. Ele usa o Graphviz e é instalado como um pacote de sistema no ambiente de contêiner do serviço. O Graphviz é usado com utilitários de linha de comando para exibir solicitações.

Objetivos

  • Gravar e criar um contêiner personalizado com um Dockerfile (links em inglês)
  • Gravar, criar e implantar um serviço do Cloud Run
  • Usar o utilitário Graphviz dot para gerar diagramas
  • Testar o serviço publicando um diagrama de sintaxe DOT da coleção ou sua própria criação

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Antes de começar

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  4. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  5. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  6. Ative a API Cloud Run Admin
  7. Instale e inicialize a CLI gcloud.
  8. Atualize os componentes:
    gcloud components update

Funções exigidas

Para conseguir as permissões necessárias para concluir o tutorial, peça ao administrador para conceder a você os seguintes papéis do IAM no seu projeto:

Para mais informações sobre como conceder papéis, consulte Gerenciar acesso.

Também é possível conseguir as permissões necessárias com papéis personalizados ou outros papéis predefinidos.

Como configurar padrões do gcloud

Para configurar a gcloud com os padrões do serviço do Cloud Run, realize as etapas a seguir:

  1. Defina seu projeto padrão:

    gcloud config set project PROJECT_ID

    Substitua PROJECT_ID pelo nome do projeto que você criou para este tutorial.

  2. Configure a gcloud para a região escolhida:

    gcloud config set run/region REGION

    Substitua REGION pela região compatível do Cloud Run.

Locais do Cloud Run

O Cloud Run é regional, o que significa que a infraestrutura que executa seus serviços do Cloud Run está localizada em uma região específica e é gerenciada pelo Google para estar disponível de maneira redundante em todas as zonas da região.

Atender aos seus requisitos de latência, disponibilidade ou durabilidade são os principais fatores para selecionar a região em que seus serviços do Cloud Run são executados. Geralmente, é possível selecionar a região mais próxima de seus usuários, mas considere a localização dos outros produtos do Google Cloud usados pelo serviço do Cloud Run. O uso de produtos do Google Cloud em vários locais pode afetar a latência e o custo do serviço.

O Cloud Run está disponível nas regiões a seguir:

Sujeitas aos preços do nível 1

  • asia-east1 (Taiwan)
  • asia-northeast1 (Tóquio)
  • asia-northeast2 (Osaka)
  • europe-north1 (Finlândia) ícone de folha Baixo CO2
  • europe-southwest1 (Madrid)
  • europe-west1 (Bélgica) ícone de folha Baixo CO
  • europe-west4 (Países Baixos)
  • europe-west8 (Milão)
  • europe-west9 (Paris) ícone de folha Baixo CO2
  • me-west1 (Tel Aviv)
  • us-central1 (Iowa) ícone de folha Baixo CO2
  • us-east1 (Carolina do Sul)
  • us-east4 (Norte da Virgínia)
  • us-east5 (Columbus)
  • us-south1 (Dallas)
  • us-west1 (Oregon) ícone de folha Baixo CO2

Sujeitas aos preços do nível 2

  • africa-south1 (Johannesburgo)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seul, Coreia do Sul)
  • asia-southeast1 (Singapura)
  • asia-southeast2 (Jacarta)
  • asia-south1 (Mumbai, Índia)
  • asia-south2 (Déli, Índia)
  • australia-southeast1 (Sydney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Varsóvia, Polônia)
  • europe-west10 (Berlim)
  • europe-west12 (Turim)
  • europe-west2 (Londres, Reino Unido) ícone de folha Baixo CO2
  • europe-west3 (Frankfurt, Alemanha) ícone de folha Baixo CO2
  • europe-west6 (Zurique, Suíça) ícone de folha Baixo CO2
  • me-central1 (Doha)
  • me-central2 (Damã)
  • northamerica-northeast1 (Montreal) ícone de folha Baixo CO2
  • northamerica-northeast2 (Toronto) ícone de folha Baixo CO2
  • southamerica-east1 (São Paulo, Brasil) ícone de folha Baixo CO2
  • southamerica-west1 (Santiago, Chile) ícone de folha Baixo CO2
  • us-west2 (Los Angeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Se você já criou um serviço do Cloud Run, é possível visualizar a região no painel do Cloud Run no console do Google Cloud.

Como recuperar o exemplo de código

Para recuperar o exemplo de código para uso, siga estas etapas:

  1. Clone o repositório do aplicativo de amostra na máquina local:

    Node.js

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Python

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Go

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Java

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

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

  2. Mude para o diretório que contém o código de amostra do Cloud Run:

    Node.js

    cd nodejs-docs-samples/run/system-package/

    Python

    cd python-docs-samples/run/system-package/

    Go

    cd golang-samples/run/system_package/

    Java

    cd java-docs-samples/run/system-package/

Como visualizar a arquitetura

A arquitetura básica é assim:

Diagrama que mostra o fluxo de solicitações do usuário para o serviço da Web para o utilitário graphviz
    dot.
Para a fonte do diagrama, consulte a descrição do DOT

O usuário faz uma solicitação HTTP para o serviço Cloud Run, que executa um utilitário Graphviz para transformar a solicitação em uma imagem. Essa imagem é entregue ao usuário como a resposta HTTP.

Como entender o código

Como definir a configuração do ambiente com o Dockerfile

O Dockerfile é específico para a linguagem e o ambiente operacional base, como o Ubuntu, que o serviço usará.

O Guia de início rápido de compilação e implantação mostra vários Dockerfiles que podem ser usados como ponto de partida para criar um Dockerfile para outros serviços.

Para este serviço, um ou mais pacotes de sistema não disponíveis por padrão são requisitados.

  1. Abra o Dockerfile em um editor.

  2. Procure uma instrução Dockerfile RUN. Essa instrução permite executar comandos do shell arbitrários para modificar o ambiente. Se o Dockerfile tiver vários estágios, identificados ao encontrar várias instruções FROM, ele será encontrado no último estágio.

    Os pacotes específicos necessários e o mecanismo para instalá-los variam de acordo com o sistema operacional declarado dentro do contêiner.

    Para ver instruções sobre o sistema operacional ou sobre a imagem base, clique na guia adequada.

    Debian/Ubuntu
    RUN apt-get update -y && apt-get install -y \
      graphviz \
      && apt-get clean
    Alpine
    O Alpine requer um segundo pacote para oferecer compatibilidade com fontes.
    RUN apk --no-cache add graphviz ttf-ubuntu-font-family

    Para determinar o sistema operacional da imagem do contêiner, verifique o nome na instrução FROM ou um README associado à imagem base. Por exemplo, se você estender de node, poderá encontrar a documentação e o Dockerfile pai no Docker Hub (em inglês).

  3. Teste sua personalização criando a imagem. Use o docker build localmente ou o Cloud Build.

Como processar as solicitações recebidas

O serviço de amostra usa parâmetros da solicitação HTTP recebida para invocar uma chamada do sistema que executa o comando do utilitário dot adequado.

No manipulador HTTP abaixo, um parâmetro de entrada de descrição de gráfico é extraído da variável querystring dot.

As descrições de gráficos podem incluir caracteres que precisam ser codificados em URL (em inglês) para uso em uma querystring.

Node.js

app.get('/diagram.png', (req, res) => {
  try {
    const image = createDiagram(req.query.dot);
    res.setHeader('Content-Type', 'image/png');
    res.setHeader('Content-Length', image.length);
    res.setHeader('Cache-Control', 'public, max-age=86400');
    res.send(image);
  } catch (err) {
    console.error(`error: ${err.message}`);
    const errDetails = (err.stderr || err.message).toString();
    if (errDetails.includes('syntax')) {
      res.status(400).send(`Bad Request: ${err.message}`);
    } else {
      res.status(500).send('Internal Server Error');
    }
  }
});

Python

@app.route("/diagram.png", methods=["GET"])
def index():
    """Takes an HTTP GET request with query param dot and
    returns a png with the rendered DOT diagram in a HTTP response.
    """
    try:
        image = create_diagram(request.args.get("dot"))
        response = make_response(image)
        response.headers.set("Content-Type", "image/png")
        return response

    except Exception as e:
        print(f"error: {e}")

        # If no graphviz definition or bad graphviz def, return 400
        if "syntax" in str(e):
            return f"Bad Request: {e}", 400

        return "Internal Server Error", 500

Go


// diagramHandler renders a diagram using HTTP request parameters and the dot command.
func diagramHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		log.Printf("method not allowed: %s", r.Method)
		http.Error(w, fmt.Sprintf("HTTP Method %s Not Allowed", r.Method), http.StatusMethodNotAllowed)
		return
	}

	q := r.URL.Query()
	dot := q.Get("dot")
	if dot == "" {
		log.Print("no graphviz definition provided")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	// Cache header must be set before writing a response.
	w.Header().Set("Cache-Control", "public, max-age=86400")

	input := strings.NewReader(dot)
	if err := createDiagram(w, input); err != nil {
		log.Printf("createDiagram: %v", err)
		// Do not cache error responses.
		w.Header().Del("Cache-Control")
		if strings.Contains(err.Error(), "syntax") {
			http.Error(w, "Bad Request: DOT syntax error", http.StatusBadRequest)
		} else {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		}
	}
}

Java

get(
    "/diagram.png",
    (req, res) -> {
      InputStream image = null;
      try {
        String dot = req.queryParams("dot");
        image = createDiagram(dot);
        res.header("Content-Type", "image/png");
        res.header("Content-Length", Integer.toString(image.available()));
        res.header("Cache-Control", "public, max-age=86400");
      } catch (Exception e) {
        if (e.getMessage().contains("syntax")) {
          res.status(400);
          return String.format("Bad Request: %s", e.getMessage());
        } else {
          res.status(500);
          return "Internal Server Error";
        }
      }
      return image;
    });

Será preciso diferenciar entre erros internos do servidor e entradas inválidas do usuário. Este exemplo de serviço retorna um erro interno do servidor para todos os erros de linha de comando de ponto, a menos que a mensagem de erro contenha a string syntax, que indica um problema de entrada do usuário.

Como gerar um diagrama

A lógica central da geração de diagramas usa a ferramenta de linha de comando do dot para processar o parâmetro de entrada da descrição do gráfico em um diagrama no formato de imagem PNG.

Node.js

// Generate a diagram based on a graphviz DOT diagram description.
const createDiagram = dot => {
  if (!dot) {
    throw new Error('syntax: no graphviz definition provided');
  }

  // Adds a watermark to the dot graphic.
  const dotFlags = [
    '-Glabel="Made on Cloud Run"',
    '-Gfontsize=10',
    '-Glabeljust=right',
    '-Glabelloc=bottom',
    '-Gfontcolor=gray',
  ].join(' ');

  const image = execSync(`/usr/bin/dot ${dotFlags} -Tpng`, {
    input: dot,
  });
  return image;
};

Python

def create_diagram(dot):
    """Generates a diagram based on a graphviz DOT diagram description.

    Args:
        dot: diagram description in graphviz DOT syntax

    Returns:
        A diagram in the PNG image format.
    """
    if not dot:
        raise Exception("syntax: no graphviz definition provided")

    dot_args = [  # These args add a watermark to the dot graphic.
        "-Glabel=Made on Cloud Run",
        "-Gfontsize=10",
        "-Glabeljust=right",
        "-Glabelloc=bottom",
        "-Gfontcolor=gray",
        "-Tpng",
    ]

    # Uses local `dot` binary from Graphviz:
    # https://graphviz.gitlab.io
    image = subprocess.run(
        ["dot"] + dot_args, input=dot.encode("utf-8"), stdout=subprocess.PIPE
    ).stdout

    if not image:
        raise Exception("syntax: bad graphviz definition provided")
    return image

Go


// createDiagram generates a diagram image from the provided io.Reader written to the io.Writer.
func createDiagram(w io.Writer, r io.Reader) error {
	stderr := new(bytes.Buffer)
	args := []string{
		"-Glabel=Made on Cloud Run",
		"-Gfontsize=10",
		"-Glabeljust=right",
		"-Glabelloc=bottom",
		"-Gfontcolor=gray",
		"-Tpng",
	}
	cmd := exec.Command("/usr/bin/dot", args...)
	cmd.Stdin = r
	cmd.Stdout = w
	cmd.Stderr = stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("exec(%s) failed (%w): %s", cmd.Path, err, stderr.String())
	}

	return nil
}

Java

// Generate a diagram based on a graphviz DOT diagram description.
public static InputStream createDiagram(String dot) {
  if (dot == null || dot.isEmpty()) {
    throw new NullPointerException("syntax: no graphviz definition provided");
  }
  // Adds a watermark to the dot graphic.
  List<String> args = new ArrayList<>();
  args.add("/usr/bin/dot");
  args.add("-Glabel=\"Made on Cloud Run\"");
  args.add("-Gfontsize=10");
  args.add("-Glabeljust=right");
  args.add("-Glabelloc=bottom");
  args.add("-Gfontcolor=gray");
  args.add("-Tpng");

  StringBuilder output = new StringBuilder();
  InputStream stdout = null;
  try {
    ProcessBuilder pb = new ProcessBuilder(args);
    Process process = pb.start();
    OutputStream stdin = process.getOutputStream();
    stdout = process.getInputStream();
    // The Graphviz dot program reads from stdin.
    Writer writer = new OutputStreamWriter(stdin, "UTF-8");
    writer.write(dot);
    writer.close();
    process.waitFor();
  } catch (Exception e) {
    System.out.println(e);
  }
  return stdout;
}

Como criar um serviço seguro

Quaisquer vulnerabilidades na ferramenta dot são possíveis pontos de fragilidade do serviço da Web. É possível atenuar isso usando versões atualizadas do pacote graphviz com a recriação regular da imagem do contêiner.

Se você estende a amostra atual para aceitar a entrada do usuário como parâmetros de linha de comando, proteja-se contra ataques de injeção de comandos (em inglês). Algumas das formas de evitar ataques por injeção incluem:

  • o mapeamento de entradas para um dicionário de parâmetros compatíveis;
  • a validação de entradas para ver se correspondem a um intervalo de valores seguros conhecidos, talvez usando expressões regulares;
  • o escape de entradas para garantir que a sintaxe do shell não seja avaliada.

É possível reduzir ainda mais as possíveis vulnerabilidades ao implantar o serviço com uma conta de serviço que não tenha recebido permissões para usar os serviços do Google Cloud em vez de usar a conta padrão, que tem permissões usadas com frequência. Por esse motivo, as etapas neste tutorial criam e usam uma nova conta de serviço.

Como enviar o código

Para enviar seu código, crie com o Cloud Build, faça o upload para o Container Registry e implante no Cloud Run:

  1. Execute o comando a seguir para criar seu contêiner e publicar no Container Registry.

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz

    Em que PROJECT_ID é o ID do projeto do Google Cloud e graphviz é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem de SUCESSO contendo o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz

    Em que PROJECT_ID é o ID do projeto do Google Cloud e graphviz é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem de SUCESSO contendo o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz

    Em que PROJECT_ID é o ID do projeto do Google Cloud e graphviz é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem de SUCESSO contendo o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Java

    Esta amostra usa o Jib (em inglês) para criar imagens do Docker usando ferramentas comuns do Java. O Jib otimiza builds de contêiner sem a necessidade de um Dockerfile ou de ter o Docker (em inglês) instalado. Saiba mais sobre como criar contêineres Java com o Jib.

    1. Usando o Dockerfile, configure e crie uma imagem base com os pacotes do sistema instalados para substituir a imagem base padrão do Jib:

      # Use the Official eclipse-temurin image for a lean production stage of our multi-stage build.
      # https://hub.docker.com/_/eclipse-temurin/
      FROM eclipse-temurin:17.0.10_7-jre
      
      RUN apt-get update -y && apt-get install -y \
        graphviz \
        && apt-get clean
      gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz-base

      em que PROJECT_ID é o ID do projeto no Google Cloud.

    2. Use o auxiliar de credencial do gcloud para autorizar o Docker a enviar por push ao Container Registry.

      gcloud auth configure-docker

    3. Crie seu contêiner final com o Jib e publique no Container Registry:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.4.0</version>
        <configuration>
          <from>
            <image>gcr.io/PROJECT_ID/graphviz-base</image>
          </from>
          <to>
            <image>gcr.io/PROJECT_ID/graphviz</image>
          </to>
        </configuration>
      </plugin>
      mvn compile jib:build \
       -Dimage=gcr.io/PROJECT_ID/graphviz \
       -Djib.from.image=gcr.io/PROJECT_ID/graphviz-base

      em que PROJECT_ID é o ID do projeto no Google Cloud.

  2. Implante usando o seguinte:

    Linha de comando

    1. Crie uma nova conta de serviço. O código, incluindo os pacotes de sistema que ele usa, pode usar apenas os serviços do Google Cloud que foram concedidos a essa conta de serviço.
      gcloud iam service-accounts create SA_NAME
      Em que SA_NAME é o nome atribuído a esta conta de serviço. Se houver um erro ou uma vulnerabilidade no seu código, ele não conseguirá acessar nenhum dos outros recursos do projeto do Google Cloud.
    2. Implante o código, especificando a conta de serviço.
      gcloud run deploy graphviz-web --service-account SA_NAME@PROJECT_ID.iam.gserviceaccount.com  --image gcr.io/PROJECT_ID/graphviz
      Em que PROJECT_ID é o ID do projeto do Google Cloud, SA_NAME é o nome da conta de serviço que você criou, graphviz é o nome do contêiner acima e graphviz-web é o nome do serviço. Responda Y ao prompt "allow unauthenticated". Consulte Como gerenciar o acesso para mais detalhes sobre a autenticação baseada em IAM.
    3. Aguarde a conclusão da implantação. Isso pode levar cerca de meio minuto. Em caso de sucesso, a linha de comando exibe o URL de serviço.

    Terraform

    Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do Terraform.

    O código do Terraform a seguir cria um serviço do Cloud Run.

    resource "google_service_account" "graphviz" {
      account_id   = "graphviz"
      display_name = "GraphViz Tutorial Service Account"
    }
    
    resource "google_cloud_run_v2_service" "default" {
      name     = "graphviz-example"
      location = "us-central1"
    
      template {
        containers {
          # Replace with the URL of your graphviz image
          #   gcr.io/<YOUR_GCP_PROJECT_ID>/graphviz
          image = "us-docker.pkg.dev/cloudrun/container/hello"
        }
    
        service_account = google_service_account.graphviz.email
      }
    }

    Substitua gcr.io/cloudrun/hello por uma referência ao seu contêiner.

    O código a seguir do Terraform torna o serviço do Cloud Run público.

    # Make Cloud Run service publicly accessible
    resource "google_cloud_run_service_iam_member" "allow_unauthenticated" {
      service  = google_cloud_run_v2_service.default.name
      location = google_cloud_run_v2_service.default.location
      role     = "roles/run.invoker"
      member   = "allUsers"
    }
  3. Se você quer implantar uma atualização de código no serviço, repita as etapas anteriores. Cada implantação em um serviço cria uma nova revisão e inicia automaticamente o tráfego de serviço quando estiver pronto.

Teste

Teste seu serviço enviando solicitações HTTP POST com descrições de sintaxe DOT na carga útil da solicitação.

  1. Envie uma solicitação HTTP para o serviço.

    Copie o URL na barra de endereço do navegador e atualize [SERVICE_DOMAIN]:

    https://SERVICE_DOMAIN/diagram.png?dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }

    É possível incorporar o diagrama em uma página da Web:

    <img src="https://SERVICE_DOMAIN/diagram.png?dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }" />
  2. Abra o arquivo diagram.png resultante em qualquer aplicativo compatível com arquivos PNG, como o Chrome.

    A aparência será semelhante a esta:

    Diagrama mostrando o fluxo de estágios:
codificação > criação > implantação > execução.
    Fonte: descrição DOT

É possível explorar uma pequena coleção de descrições de diagrama prontas (em inglês).

  1. Copie o conteúdo do arquivo .dot selecionado.
  2. Envie uma solicitação HTTP para o serviço.

    Copie o URL na barra de endereço do navegador.

    https://SERVICE_DOMAIN/diagram.png?dot=SELECTED DOTFILE CONTENTS

Limpar

Se você criou um novo projeto para este tutorial, exclua o projeto. Se você usou um projeto atual e quer mantê-lo sem as alterações incluídas neste tutorial, exclua os recursos criados para o tutorial.

Como excluir o projeto

O jeito mais fácil de evitar cobranças é excluindo o projeto que você criou para o tutorial.

Para excluir o projeto:

  1. No Console do Google Cloud, acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Como excluir recursos do tutorial

  1. Exclua o serviço do Cloud Run que você implantou neste tutorial:

    gcloud run services delete SERVICE-NAME

    SERVICE-NAME é o nome escolhido do serviço.

    Também é possível excluir os serviços do Cloud Run no Console do Google Cloud.

  2. Remova a configuração da região padrão da gcloud que você adicionou durante a configuração do tutorial:

     gcloud config unset run/region
    
  3. Remova a configuração do projeto:

     gcloud config unset project
    
  4. Exclua outros recursos do Google Cloud criados neste tutorial:

    • Exclua a imagem de contêiner denominada gcr.io/<var>PROJECT_ID</var>/graphviz do Container Registry.

    • Exclua a conta de serviço <var>SA_NAME</var>.

      gcloud iam service-accounts delete SA_NAME@PROJECT_ID.iam.gserviceaccount.com

A seguir