Como proteger modelos de ML e propriedade intelectual usando o espaço confidencial

1. Visão geral

O Confidential Space é um ambiente seguro para colaboração entre várias partes. Este codelab demonstra como o Espaço confidencial pode ser usado para proteger propriedade intelectual confidencial, como modelos de machine learning.

Neste codelab, você vai usar o Confidential Space para permitir que uma empresa compartilhe seu modelo reservado de machine learning de forma segura com outra empresa que gostaria de usá-lo. Especificamente, a Empresa Primus tem um modelo de machine learning que só seria lançado para cargas de trabalho em execução no Confidential Space, permitindo que a Primus mantivesse o controle total sobre a propriedade intelectual. A empresa Secundus será a operadora da carga de trabalho e executará a carga de trabalho de machine learning em um Espaço confidencial. O Secundus vai carregar esse modelo e executar uma inferência usando dados de amostra de propriedade da Secundus.

Aqui Primus é o autor da carga de trabalho que cria o código da carga de trabalho e um colaborador que deseja proteger sua propriedade intelectual do operador de carga de trabalho não confiável, Secundus. Secundus é o operador de carga de trabalho da carga de trabalho de machine learning.

5a86c47d935da998.jpeg

O que você vai aprender

  • Como configurar um ambiente em que uma parte possa compartilhar o modelo de ML próprio com outra sem perder o controle sobre a propriedade intelectual.

O que é necessário

Funções envolvidas em uma configuração do Confidential Space

Neste codelab, a empresa Primus será o proprietário do recurso e o autor da carga de trabalho, que será responsável pelo seguinte:

  1. Como configurar os recursos de nuvem necessários com um modelo de machine learning
  2. Como escrever o código da carga de trabalho
  3. Como publicar a imagem da carga de trabalho
  4. Como configurar a política de pool de Identidade da carga de trabalho para proteger o modelo de ML contra um operador não confiável

A Secundus Company será o operador e será responsável por:

  1. Como configurar os recursos de nuvem necessários para armazenar as imagens de amostra usadas pela carga de trabalho e os resultados
  2. Como executar a carga de trabalho de ML no Confidential Space usando o modelo fornecido pelo Primus

Como funciona o Confidential Space

Quando você executa a carga de trabalho no Confidential Space, o seguinte processo ocorre usando os recursos configurados:

  1. A carga de trabalho solicita um token de acesso geral do Google para o $PRIMUS_SERVICEACCOUNT do pool de Identidade da carga de trabalho. Ele oferece um token de serviço do Verificador de atestados com declarações de carga de trabalho e ambiente.
  2. Se as declarações de medição de carga de trabalho no token do serviço do verificador de atestados corresponderem à condição do atributo no WIP, ele retornará o token de acesso para $PRIMUS_SERVICEACCOUNT..
  3. A carga de trabalho usa o token de acesso da conta de serviço associado a $PRIMUS_SERVICEACCOUNT para acessar o modelo de machine learning armazenado no bucket $PRIMUS_INPUT_STORAGE_BUCKET.
  4. A carga de trabalho executa uma operação nos dados de propriedade da Secundus, que é operada e executada pela Secundus no projeto.
  5. A carga de trabalho usa a conta de serviço $WORKLOAD_SERVICEACCOUNT para gravar os resultados dessa operação no bucket $SECUNDUS_RESULT_STORAGE_BUCKET.

2. Configurar recursos do Cloud

Antes de começar

  • Clone este repositório com o comando abaixo para acessar os scripts necessários que são usados como parte deste codelab.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  • Mude o diretório deste codelab.
cd confidential-space/codelabs/ml_model_protection/scripts
  • Verifique se você definiu as variáveis de ambiente do projeto necessárias, conforme mostrado abaixo. Para mais informações sobre como configurar um projeto do GCP, consulte este codelab. Consulte este link para ver detalhes sobre como recuperar o ID do projeto e como ele é diferente do nome e do número do projeto.
export PRIMUS_PROJECT_ID=<GCP project id of Primus>
export SECUNDUS_PROJECT_ID=<GCP project id of Secundus>
  • Ative o faturamento dos projetos.
  • Ativar a API Confidential Computing e as APIs seguintes nos dois projetos.
gcloud services enable \
    cloudapis.googleapis.com \
    cloudresourcemanager.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    containerregistry.googleapis.com \
    iam.googleapis.com \
    confidentialcomputing.googleapis.com
  • Atribua valores às variáveis para os nomes de recursos especificados acima usando o comando a seguir. Com essas variáveis, é possível personalizar os nomes dos recursos conforme necessário e também usar os que já foram criados. (por exemplo, export PRIMUS_INPUT_STORAGE_BUCKET='my-input-bucket')
  1. É possível definir as variáveis a seguir com nomes de recursos da nuvem atuais no projeto do Primus. Se a variável for definida, o recurso de nuvem correspondente do projeto do Primus será usado. Se a variável não for definida, o nome do recurso de nuvem será gerado com base no nome do projeto e um novo recurso de nuvem será criado com esse nome. Veja a seguir as variáveis compatíveis com os nomes de recursos:

$PRIMUS_INPUT_STORAGE_BUCKET

O bucket que armazena o modelo de machine learning do Primus.

$PRIMUS_WORKLOAD_IDENTITY_POOL

O pool de identidade (WIP) da carga de trabalho do Primus que valida declarações.

$PRIMUS_WIP_PROVIDER

O provedor do pool de identidade da carga de trabalho do Primus, que inclui a condição de autorização a ser usada para tokens assinados pelo serviço Verificador de atestados.

$PRIMUS_SERVICE_ACCOUNT

Conta de serviço do Primus que o $PRIMUS_WORKLOAD_IDENTITY_POOL usa para acessar os recursos protegidos (modelo de ML neste codelab). Nesta etapa, ele tem permissão para ler o modelo de machine learning armazenado no bucket $PRIMUS_INPUT_STORAGE_BUCKET.

$PRIMUS_ARTIFACT_REPOSITORY

O repositório de artefatos para onde a imagem do Docker da carga de trabalho será enviada.

  1. É possível definir as variáveis a seguir com nomes de recursos da nuvem atuais no projeto do Secundus. Se a variável for definida, o recurso de nuvem correspondente do projeto Secundus será usado. Se a variável não for definida, o nome do recurso de nuvem será gerado com base no nome do projeto e um novo recurso de nuvem será criado com esse nome. Veja a seguir as variáveis compatíveis com os nomes de recursos:

$SECUNDUS_INPUT_STORAGE_BUCKET

O bucket que armazena as imagens de amostra que a Secundus quer classificar usando o modelo fornecido pela Primus.

$SECUNDUS_RESULT_STORAGE_BUCKET

O bucket que armazena os resultados da carga de trabalho.

$WORKLOAD_IMAGE_NAME

O nome da imagem do contêiner da carga de trabalho.

$WORKLOAD_IMAGE_TAG

A tag da imagem do contêiner da carga de trabalho.

$WORKLOAD_SERVICE_ACCOUNT

A conta de serviço que tem permissão para acessar a VM confidencial que executa a carga de trabalho.

  • Você vai precisar de determinadas permissões para esses dois projetos. Consulte este guia sobre como conceder papéis do IAM usando o console do GCP:
  • Para o $PRIMUS_PROJECT_ID, você vai precisar de administrador do Storage, administrador do Artifact Registry, administrador da conta de serviço e administrador de pool de Identidade da carga de trabalho do IAM.
  • Para o $SECUNDUS_PROJECT_ID, você vai precisar de: administrador do Compute, administrador do Storage, administrador da conta de serviço, administrador de pool de Identidade da carga de trabalho do IAM e administrador de segurança (opcional).
  • Execute o script a seguir para definir os nomes de variáveis restantes como valores baseados no ID do projeto para nomes de recursos.
source config_env.sh

Configurar os recursos da empresa Primus

Como parte desta etapa, você vai configurar os recursos de nuvem necessários para o Primus. Execute o script a seguir para configurar os recursos do Primus. Os seguintes recursos serão criados como parte da execução do script:

  • Bucket do Cloud Storage ($PRIMUS_INPUT_STORAGE_BUCKET) para armazenar o modelo de machine learning do Primus.
  • O pool de identidade da carga de trabalho ($PRIMUS_WORKLOAD_IDENTITY_POOL) é usado para validar declarações com base nas condições de atributos configuradas no provedor.
  • Conta de serviço ($PRIMUS_SERVICEACCOUNT) anexada ao pool de identidade da carga de trabalho ($PRIMUS_WORKLOAD_IDENTITY_POOL) mencionado acima com acesso do IAM para ler dados do bucket do Cloud Storage (usando o papel objectViewer) e conectar essa conta de serviço ao pool de identidade da carga de trabalho (usando o papel roles/iam.workloadIdentityUser).

Como parte da configuração dos recursos da nuvem, vamos usar um modelo do TensorFlow. É possível salvar todo o modelo, incluindo a arquitetura, os pesos e a configuração de treinamento do modelo, em um arquivo ZIP. Para os fins deste codelab, usaremos o modelo MobileNet V1 treinado no conjunto de dados do ImageNet encontrado aqui.

./setup_primus_company_resources.sh

O script acima vai configurar o recurso de nuvem. Agora vamos fazer o download e publicar o modelo no bucket do Cloud Storage criado pelo script.

  1. Faça o download do modelo pré-treinado aqui.
  2. Após o download, renomeie o arquivo tar salvo como model.tar.gz.
  3. Publique o arquivo model.tar.gz no bucket do Cloud Storage usando o seguinte comando do diretório que contém o arquivo model.tar.gz.
gsutil cp model.tar.gz gs://${PRIMUS_INPUT_STORAGE_BUCKET}/

Configurar os recursos da Secundus Company

Como parte desta etapa, você vai configurar os recursos de nuvem necessários para o Secundus. Execute o script a seguir para configurar os recursos do Secundus. Como parte dessas etapas, os seguintes recursos serão criados:

  • Bucket do Cloud Storage ($SECUNDUS_INPUT_STORAGE_BUCKET) para armazenar as imagens de amostra para executar inferências pela Secundus.
  • Bucket do Cloud Storage ($SECUNDUS_RESULT_STORAGE_BUCKET) para armazenar o resultado da execução da carga de trabalho de ML pela Secundus.

Algumas imagens de exemplo estão disponíveis aqui para este codelab.

./setup_secundus_company_resources.sh

3. Criar carga de trabalho

Criar conta de serviço da carga de trabalho

Agora você vai criar uma conta de serviço para a carga de trabalho com os papéis e as permissões necessários. Execute o script a seguir para criar uma conta de serviço de carga de trabalho no projeto do Secundus. Essa conta de serviço seria usada pela VM que executa a carga de trabalho de ML.

A conta de serviço da carga de trabalho ($WORKLOAD_SERVICEACCOUNT) terá os seguintes papéis:

  • confidentialcomputing.workloadUser para receber um token de atestado
  • logging.logWriter para gravar registros no Cloud Logging.
  • objectViewer para ler dados do bucket do Cloud Storage $SECUNDUS_INPUT_STORAGE_BUCKET.
  • objectUser para gravar o resultado da carga de trabalho no bucket do Cloud Storage $SECUNDUS_RESULT_STORAGE_BUCKET.
./create_workload_service_account.sh

Criar carga de trabalho

Como parte desta etapa, você criará uma imagem Docker de carga de trabalho. A carga de trabalho seria de autoria do Primus. A carga de trabalho usada neste codelab é um código Python de machine learning que acessa o modelo de ML armazenado no bucket de armazenamento do Primus e executa inferências com as imagens de amostra armazenadas em um bucket de armazenamento.

O modelo de machine learning armazenado no bucket de armazenamento do Primus só pode ser acessado pelas cargas de trabalho que atendem às condições de atributo necessárias. Essas condições de atributos são descritas com mais detalhes na próxima seção sobre autorização da carga de trabalho.

Este é o método run_inference() da carga de trabalho que será criada e usada neste codelab. Acesse o código completo da carga de trabalho aqui.

def run_inference(image_path, model):
  try:
    # Read and preprocess the image
    image = tf.image.decode_image(tf.io.read_file(image_path), channels=3)
    image = tf.image.resize(image, (128, 128))
    image = tf.image.convert_image_dtype(image, tf.float32)
    image = tf.expand_dims(image, axis=0)

    # Get predictions from the model
    predictions = model(image)
    predicted_class = np.argmax(predictions)

    top_k = 5
    top_indices = np.argsort(predictions[0])[-top_k:][::-1]

    # Convert top_indices to a TensorFlow tensor
    top_indices_tensor = tf.convert_to_tensor(top_indices, dtype=tf.int32)

    # Use TensorFlow tensor for indexing
    top_scores = tf.gather(predictions[0], top_indices_tensor)

    return {
        "predicted_class": int(predicted_class),
        "top_k_predictions": [
            {"class_index": int(idx), "score": float(score)}
            for idx, score in zip(top_indices, top_scores)
        ],
    }
  except Exception as e:
    return {"error": str(e)}

Execute o script a seguir para criar uma carga de trabalho em que as seguintes etapas estão sendo realizadas:

  • Crie o Artifact Registry($PRIMUS_ARTIFACT_REGISTRY) de propriedade do Primus.
  • Atualize o código da carga de trabalho com os nomes dos recursos obrigatórios.
  • Criar a carga de trabalho de ML e criar um Dockerfile para gerar uma imagem Docker do código da carga de trabalho. Confira aqui o Dockerfile usado neste codelab.
  • Crie e publique a imagem Docker no Artifact Registry ($PRIMUS_ARTIFACT_REGISTRY) de propriedade do Primus.
  • Conceder permissão de leitura a $WORKLOAD_SERVICEACCOUNT para $PRIMUS_ARTIFACT_REGISTRY. Isso é necessário para que o contêiner da carga de trabalho extraia a imagem do Docker da carga de trabalho do Artifact Registry.
./create_workload.sh

Além disso, as cargas de trabalho podem ser codificadas para garantir que estejam carregando a versão esperada do modelo de machine learning, verificando o hash ou a assinatura do modelo antes de usá-lo. A vantagem de tais verificações adicionais é que elas garantem a integridade do modelo de machine learning. Com isso, o operador de carga de trabalho também precisa atualizar a imagem da carga de trabalho ou os parâmetros dela quando se espera que ela use versões diferentes do modelo de ML.

4. Autorizar e executar carga de trabalho

Autorizar carga de trabalho

A Primus quer autorizar as cargas de trabalho a acessar o modelo de machine learning com base nos atributos dos seguintes recursos:

  • O quê: código verificado
  • Onde: um ambiente seguro
  • Quem: um operador confiável

O Primus usa a federação de identidade da carga de trabalho para aplicar uma política de acesso com base nesses requisitos. A federação de identidade da carga de trabalho permite especificar condições de atributos. Essas condições restringem quais identidades podem ser autenticadas com o pool de identidade da carga de trabalho (WIP). É possível adicionar o serviço Verificador de atestados ao WIP como um provedor de pool de identidade da carga de trabalho para apresentar medições e aplicar a política.

O pool de Identidade da carga de trabalho já foi criado como parte da etapa de configuração dos recursos da nuvem. Agora o Primus vai criar um novo provedor de pool de identidades de carga de trabalho do OIDC. O --attribute-condition especificado autoriza o acesso ao contêiner de carga de trabalho. Ela requer:

  • O quê: o último $WORKLOAD_IMAGE_NAME enviado ao repositório $PRIMUS_ARTIFACT_REPOSITORY.
  • Onde: o ambiente de execução confiável do Confidential Space é executado na imagem de VM do Confidential Space totalmente compatível.
  • Quem: conta de serviço $WORKLOAD_SERVICE_ACCOUNT da Secundus.
export WORKLOAD_IMAGE_DIGEST=$(docker images digests ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_ARTIFACT_REPOSITORY}/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}| awk 'NR>1{ print $3 }')
gcloud config set project $PRIMUS_PROJECT_ID
gcloud iam workload-identity-pools providers create-oidc $PRIMUS_WIP_PROVIDER \
  --location="global" \
  --workload-identity-pool="$PRIMUS_WORKLOAD_IDENTITY_POOL" \
  --issuer-uri="https://confidentialcomputing.googleapis.com/" \
  --allowed-audiences="https://sts.googleapis.com" \
  --attribute-mapping="google.subject='assertion.sub'" \
  --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' && 
'STABLE' in assertion.submods.confidential_space.support_attributes && 
assertion.submods.container.image_digest == '${WORKLOAD_IMAGE_DIGEST}' &&
 assertion.submods.container.image_reference == '${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG' && 
'$WORKLOAD_SERVICEACCOUNT@$SECUNDUS_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts"

Executar carga de trabalho

Como parte desta etapa, vamos executar a carga de trabalho na VM do Confidential Space. Os argumentos TEE obrigatórios são transmitidos usando a sinalização de metadados. Os argumentos do contêiner de carga de trabalho são transmitidos usando "tee-cmd" da sinalização. O resultado da execução da carga de trabalho será publicado em $SECUNDUS_RESULT_STORAGE_BUCKET.

gcloud config set project $SECUNDUS_PROJECT_ID
gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=TERMINATE \
 --scopes=cloud-platform --zone=${SECUNDUS_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
 --service-account=${WORKLOAD_SERVICEACCOUNT}@${SECUNDUS_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata  ^~^tee-image-reference=${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_ARTIFACT_REPOSITORY}/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}

Ver resultados

Depois que a carga de trabalho for concluída, o resultado da carga de trabalho de ML será publicado em $SECUNDUS_RESULT_STORAGE_BUCKET.

gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result

Confira alguns exemplos de como podem ser os resultados da inferência em imagens de amostra:

Image: sample_image_1.jpeg, Response: {'predicted_class': 531, 'top_k_predictions': [{'class_index': 531, 'score': 12.08437442779541}, {'class_index': 812, 'score': 10.269512176513672}, {'class_index': 557, 'score': 9.202644348144531}, {'class_index': 782, 'score': 9.08737564086914}, {'class_index': 828, 'score': 8.912498474121094}]}

Image: sample_image_2.jpeg, Response: {'predicted_class': 905, 'top_k_predictions': [{'class_index': 905, 'score': 9.53619384765625}, {'class_index': 557, 'score': 7.928380966186523}, {'class_index': 783, 'score': 7.70129919052124}, {'class_index': 531, 'score': 7.611623287200928}, {'class_index': 906, 'score': 7.021416187286377}]}

Image: sample_image_3.jpeg, Response: {'predicted_class': 905, 'top_k_predictions': [{'class_index': 905, 'score': 6.09878396987915}, {'class_index': 447, 'score': 5.992854118347168}, {'class_index': 444, 'score': 5.9582319259643555}, {'class_index': 816, 'score': 5.502010345458984}, {'class_index': 796, 'score': 5.450454235076904}]}

Você verá uma entrada nos resultados para cada imagem de amostra em um bucket de armazenamento do Secundus. Essa entrada incluirá duas informações importantes:

  • Índice da classe prevista:é um índice numérico que representa a classe a que o modelo prevê que a imagem pertence.
  • Top_k_predictions::fornece até k previsões para a imagem, classificadas da maior para a menor. O valor de k está definido como 5 neste codelab, mas é possível ajustá-lo no código da carga de trabalho para receber mais ou menos previsões.

Para traduzir o índice da classe em um nome legível por humanos, consulte a lista de rótulos disponíveis aqui. Por exemplo, se você vir um índice de classe de 2, ele corresponde ao rótulo de classe "tench" na lista de marcadores.

Neste codelab, demonstramos que um modelo de propriedade do Primus é liberado apenas para a carga de trabalho em execução em um TEE. O Secundus executa a carga de trabalho de ML em um TEE, e essa carga de trabalho pode consumir o modelo de propriedade da Primus, enquanto a Primus mantém o controle total sobre o modelo.

Executar carga de trabalho não autorizada

O Secundus altera a imagem da carga de trabalho extraindo uma imagem de carga de trabalho diferente do próprio repositório de artefatos, que não é autorizado pelo Primus. O pool de Identidade da carga de trabalho do Primus autorizou apenas a imagem da carga de trabalho ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG.

Executar novamente a carga de trabalho

Quando o Secundus tentar executar a carga de trabalho original com essa nova imagem, ocorrerá uma falha. Para ver o erro, exclua o arquivo de resultados originais e a instância da VM e tente executar a carga de trabalho novamente.

Verifique se há uma nova imagem do Docker publicada no Artifact Registry do Secundus (como us-docker.pkg.dev/${SECUNDUS_PROJECT_ID}/custom-image/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}) e se a conta de serviço da carga de trabalho ($WORKLOAD_SERVICEACCOUNT) concedeu permissão ao leitor do Artifact Registry para ler essa nova imagem da carga de trabalho. Isso garante que a carga de trabalho não seja encerrada antes que a política de WIP do Primus rejeite o token apresentado pela carga de trabalho.

Excluir o arquivo de resultados atual e a instância de VM

  1. Defina o projeto como $SECUNDUS_PROJECT_ID.
gcloud config set project $SECUNDUS_PROJECT_ID
  1. Exclua o arquivo de resultados.
gsutil rm gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result
  1. Exclua a instância de VM confidencial.
gcloud compute instances delete ${WORKLOAD_VM}

Execute a carga de trabalho não autorizada:

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=TERMINATE \
 --scopes=cloud-platform --zone=${SECUNDUS_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \ 
--service-account=${WORKLOAD_SERVICE_ACCOUNT}@${SECUNDUS_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata  ^~^tee-image-reference=us-docker.pkg.dev/${SECUNDUS_PROJECT_ID}/custom-image/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}

Ver erro

Em vez dos resultados da carga de trabalho, será exibido um erro (The given credential is rejected by the attribute condition).

gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result

5. Limpeza

Confira aqui o script que pode ser usado para limpar os recursos criados neste codelab. Como parte dessa limpeza, os seguintes recursos serão excluídos:

  • Bucket de armazenamento de entrada do Primus ($PRIMUS_INPUT_STORAGE_BUCKET).
  • Conta de serviço do Primus ($PRIMUS_SERVICEACCOUNT).
  • Repositório de artefatos do Primus ($PRIMUS_ARTIFACT_REPOSITORY).
  • Pool de identidade da carga de trabalho do Primus ($PRIMUS_WORKLOAD_IDENTITY_POOL).
  • Conta de serviço de carga de trabalho da Secundus ($WORKLOAD_SERVICEACCOUNT).
  • Bucket de armazenamento de entrada de Secundus ($SECUNDUS_INPUT_STORAGE_BUCKET).
  • Instâncias de computação da carga de trabalho.
  • Bucket de armazenamento de resultados de Secundus ($SECUNDUS_RESULT_STORAGE_BUCKET).
$ ./cleanup.sh

Quando terminar de explorar, considere excluir seu projeto.

  • Acesse o console do Cloud Platform.
  • Selecione o projeto que você quer encerrar e clique em "Excluir". Na parte superior: isso programa a exclusão do projeto

Qual é a próxima etapa?

Confira alguns destes codelabs parecidos: