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

1. Visão geral

O Espaço confidencial oferece um ambiente seguro para colaboração entre várias partes. Este codelab demonstra como o Espaço confidencial pode ser usado para proteger propriedades intelectuais sensíveis, como modelos de machine learning.

Neste codelab, você vai usar o Espaço confidencial para permitir que uma empresa compartilhe com segurança o modelo de machine learning proprietário com outra empresa que gostaria de usar o modelo. Especificamente, a Empresa Primus tem um modelo de aprendizado de máquina que só seria liberado para uma carga de trabalho executada no Confidential Space, permitindo que a Primus mantenha o controle total sobre a propriedade intelectual. A empresa Secundus será a operadora da carga de trabalho e vai 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 pertencentes ao Secundus.

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

5a86c47d935da998.jpeg

O que você vai aprender

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

O que é necessário

Papéis envolvidos na configuração do Confidential Space

Neste codelab, a Company Primus será a proprietária do recurso e o autor da carga de trabalho, que será responsável por:

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

A Secundus Company será a operadora e será responsável por:

  1. Configurar os recursos de nuvem necessários para armazenar 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 o Confidential Space funciona

Ao executar a carga de trabalho no Confidential Space, o processo a seguir é realizado usando os recursos configurados:

  1. A carga de trabalho solicita um token de acesso geral do Google para o $PRIMUS_SERVICEACCOUNT do pool de identidades da carga de trabalho. Ele oferece um token de serviço do verificador de atestado 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 de atestado do verificador corresponderem à condição de 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 aprendizado de máquina armazenado no bucket $PRIMUS_INPUT_STORAGE_BUCKET.
  4. A carga de trabalho realiza uma operação nos dados de propriedade da Secundus e é 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 usando o comando abaixo para receber 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 para este 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 saber 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 seus projetos.
  • Ative a API Confidential Computing e as APIs a seguir para os 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 dos nomes de recursos especificados acima usando o comando a seguir. Essas variáveis permitem personalizar os nomes dos recursos conforme necessário e usar recursos existentes, se já criados. (por exemplo, export PRIMUS_INPUT_STORAGE_BUCKET='my-input-bucket')
  1. É possível definir as variáveis a seguir com nomes de recursos do Cloud no projeto Primus. Se a variável for definida, o recurso de nuvem correspondente do projeto Primus será usado. Se a variável não for definida, o nome do recurso da nuvem será gerado a partir do nome do projeto, e um novo recurso da nuvem será criado com esse nome. Confira a seguir as variáveis compatíveis para nomes de recursos:

$PRIMUS_INPUT_STORAGE_BUCKET

O bucket que armazena o modelo de aprendizado de máquina do Primus.

$PRIMUS_WORKLOAD_IDENTITY_POOL

O pool de identidade da carga de trabalho (WIP, na sigla em inglês) do Primus que valida as reivindicações.

$PRIMUS_WIP_PROVIDER

O provedor de 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 Attestation Verifier.

$PRIMUS_SERVICE_ACCOUNT

Conta de serviço Primus que $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 do Cloud atuais no projeto 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 da nuvem será gerado com base no nome do projeto, e um novo recurso da nuvem será criado com esse nome. Confira a seguir as variáveis compatíveis para nomes de recursos:

$SECUNDUS_INPUT_STORAGE_BUCKET

O bucket que armazena as imagens de amostra que Secundus quer classificar usando o modelo fornecido por 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 algumas 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ê precisa de administrador de armazenamento, 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ê precisa de administrador do Compute, administrador do Storage, administrador da conta de serviço, administrador do pool de Identidade da carga de trabalho do IAM e administrador de segurança (opcional).
  • Execute o script abaixo para definir os nomes das variáveis restantes como valores com base no ID do projeto para nomes de recursos.
source config_env.sh

Configurar recursos da Primus Company

Nesta etapa, você vai configurar os recursos de nuvem necessários para o Primus. Execute o script abaixo 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.
  • Pool de identidade da carga de trabalho ($PRIMUS_WORKLOAD_IDENTITY_POOL) 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 de armazenamento em nuvem (usando o papel objectViewer) e para 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 na nuvem, vamos usar um modelo do TensorFlow. Podemos salvar o modelo inteiro, incluindo a arquitetura, os pesos e a configuração de treinamento, em um arquivo ZIP. Para este codelab, vamos usar o modelo MobileNet V1 treinado no conjunto de dados ImageNet aqui.

./setup_primus_company_resources.sh

O script mencionado 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. Depois de fazer o download, renomeie o arquivo tar para model.tar.gz.
  3. Publique o arquivo model.tar.gz no bucket do Cloud Storage usando o comando a seguir no diretório que contém o arquivo model.tar.gz.
gsutil cp model.tar.gz gs://${PRIMUS_INPUT_STORAGE_BUCKET}/

Configurar os recursos da empresa Secundus

Nesta etapa, você vai configurar os recursos de nuvem necessários para o Secundus. Execute o script abaixo 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 pelo Secundus.
  • Bucket do Cloud Storage ($SECUNDUS_RESULT_STORAGE_BUCKET) para armazenar o resultado da execução da carga de trabalho de ML pelo Secundus.

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

./setup_secundus_company_resources.sh

3. Criar carga de trabalho

Criar uma conta de serviço de carga de trabalho

Agora, você vai criar uma conta de serviço para a carga de trabalho com as funções e permissões necessárias. Execute o script abaixo para criar uma conta de serviço de carga de trabalho no projeto Secundus. Essa conta de serviço seria usada pela VM que executa a carga de trabalho de ML.

Essa conta de serviço de carga de trabalho ($WORKLOAD_SERVICEACCOUNT) terá as seguintes funções:

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

Criar carga de trabalho

Nesta etapa, você vai criar uma imagem Docker de carga de trabalho. A carga de trabalho seria criada por 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 aprendizado de máquina armazenado no bucket de armazenamento do Primus só seria acessível pelas cargas de trabalho que atendem às condições de atributo necessárias. Essas condições de atributo são descritas em mais detalhes na próxima seção sobre como autorizar a carga de trabalho.

Este é o método run_inference() da carga de trabalho que será criado e usado neste codelab. Confira 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 abaixo para criar uma carga de trabalho em que as etapas a seguir estão sendo realizadas:

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

Além disso, os workloads podem ser codificados para garantir que estão carregando a versão esperada do modelo de aprendizado de máquina verificando o hash ou a assinatura do modelo antes de usá-lo. A vantagem dessas verificações adicionais é que elas garantem a integridade do modelo de aprendizado de máquina. Com isso, o operador de carga de trabalho também precisa atualizar a imagem da carga de trabalho ou os parâmetros dela quando a carga de trabalho precisa usar versões diferentes do modelo de ML.

4. Autorizar e executar a carga de trabalho

Autorizar a carga de trabalho

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

  • O que é: 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 atributo. Essas condições restringem quais identidades podem ser autenticadas com o pool de identidade da carga de trabalho (WIP, na sigla em inglês). É possível adicionar o serviço de verificação 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 anteriormente como parte da etapa de configuração dos recursos da nuvem. Agora, o Primus vai criar um novo provedor de pool de identidade 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 $WORKLOAD_IMAGE_NAME mais recente foi enviado por upload para o repositório $PRIMUS_ARTIFACT_REPOSITORY.
  • Onde: o ambiente de execução confiável do Confidential Space está sendo executado na imagem da VM do Confidential Space com suporte total.
  • Quem: conta de serviço $WORKLOAD_SERVICE_ACCOUNT do Secundus.
export WORKLOAD_IMAGE_DIGEST=$(gcloud artifacts docker images describe ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG  --format="value(image_summary.digest)" --project ${PRIMUS_PROJECT_ID})
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

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

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --project=${SECUNDUS_PROJECT_ID} \
 --maintenance-policy=MIGRATE \
 --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 dela será publicado em $SECUNDUS_RESULT_STORAGE_BUCKET.

gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result

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

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

Para cada imagem de amostra em um bucket de armazenamento do Secundus, você vai encontrar uma entrada nos resultados. Essa entrada vai incluir duas informações importantes:

  • Índice de predicted_class:é 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 mais para a menos provável. O valor de k é definido como 5 neste codelab, mas você pode ajustá-lo no código da carga de trabalho para receber mais ou menos previsões.

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

Neste codelab, demonstramos que um modelo de propriedade da Primus só é liberado para a carga de trabalho executada 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 do Primus, que mantém o controle total sobre o modelo.

Executar carga de trabalho não autorizada

O Secundus muda a imagem da carga de trabalho extraindo uma imagem 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 a carga de trabalho novamente

Quando o Secundus tentar executar a carga de trabalho original com essa nova imagem, ele vai falhar. Para conferir o erro, exclua o arquivo de resultados original 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 ao leitor do Artifact Registry permissão 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 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} --zone=${SECUNDUS_PROJECT_ZONE}

Execute a carga de trabalho não autorizada:

gcloud compute instances create ${WORKLOAD_VM} \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --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=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, você vê um erro (The given credential is rejected by the attribute condition).

gsutil cat gs://$SECUNDUS_RESULT_STORAGE_BUCKET/result

5. Limpeza

Este é o script que pode ser usado para limpar os recursos criados como parte deste 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 Primus ($PRIMUS_SERVICEACCOUNT).
  • Repositório de artefatos do Primus ($PRIMUS_ARTIFACT_REPOSITORY).
  • Pool de Identidade da carga de trabalho Primus ($PRIMUS_WORKLOAD_IDENTITY_POOL).
  • Conta de serviço de carga de trabalho do Secundus ($WORKLOAD_SERVICEACCOUNT).
  • Bucket de armazenamento de entrada do Secundus ($SECUNDUS_INPUT_STORAGE_BUCKET).
  • Instâncias de computação de carga de trabalho.
  • Bucket de armazenamento de resultados do Secundus ($SECUNDUS_RESULT_STORAGE_BUCKET).
$ ./cleanup.sh

Se você já tiver terminado 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 de cima. Isso vai programar o projeto para exclusão.

Qual é a próxima etapa?

Confira alguns desses codelabs semelhantes: