Controlar o uso de recursos com notificações

Este documento explica como usar as notificações de orçamento para controlar seletivamente o uso de recursos.

Quando você desativa o faturamento em um projeto, todos os serviços são interrompidos e todos os recursos são excluídos. Se você precisar de uma resposta com mais detalhes, controle seletivamente os recursos. Por exemplo, é possível interromper alguns recursos do Compute Engine e deixar os recursos do Cloud Storage intatos. A interrupção de apenas alguns recursos reduz os custos sem desativar completamente o ambiente.

No exemplo a seguir, o projeto realiza pesquisas com várias máquinas virtuais (VMs) do Compute Engine e armazena os resultados em buckets do Cloud Storage. Usando as notificações de orçamento como acionador, depois que o orçamento for excedido, essa função do Cloud Run encerra todas as instâncias do Compute Engine, mas não afeta os resultados armazenados.

Antes de começar

Antes de começar, faça o seguinte:

  1. Ativar a API Cloud Billing
  2. Criar um orçamento
  3. Configurar notificações de orçamento programático

Configurar uma função do Cloud Run

  1. Siga as etapas em Criar uma função do Cloud Run. Defina o tipo de gatilho como o mesmo tópico do Pub/Sub que o orçamento vai usar.
  2. Adicione as seguintes dependências:

    Node.js

    Copie o seguinte para o arquivo package.json:

    {
      "name": "cloud-functions-billing",
      "private": "true",
      "version": "0.0.1",
      "description": "Examples of integrating Cloud Functions with billing",
      "main": "index.js",
      "engines": {
        "node": ">=16.0.0"
      },
      "scripts": {
        "compute-test": "c8 mocha -p -j 2 test/periodic.test.js --timeout=600000",
        "test": "c8 mocha -p -j 2 test/index.test.js --timeout=5000 --exit"
      },
      "author": "Ace Nassri <anassri@google.com>",
      "license": "Apache-2.0",
      "dependencies": {
        "@google-cloud/billing": "^4.0.0",
        "@google-cloud/compute": "^4.0.0",
        "google-auth-library": "^9.0.0",
        "googleapis": "^143.0.0",
        "slack": "^11.0.1"
      },
      "devDependencies": {
        "@google-cloud/functions-framework": "^3.0.0",
        "c8": "^10.0.0",
        "gaxios": "^6.0.0",
        "mocha": "^10.0.0",
        "promise-retry": "^2.0.0",
        "proxyquire": "^2.1.0",
        "sinon": "^18.0.0",
        "wait-port": "^1.0.4"
      }
    }
    

    Python

    Copie o seguinte para o arquivo requirements.txt:

    slackclient==2.9.4
    google-api-python-client==2.131.0
    

  3. Copie o código abaixo na função do Cloud Run:

    Node.js

    const {CloudBillingClient} = require('@google-cloud/billing');
    const {InstancesClient} = require('@google-cloud/compute');
    
    const PROJECT_ID = process.env.GOOGLE_CLOUD_PROJECT;
    const PROJECT_NAME = `projects/${PROJECT_ID}`;
    const instancesClient = new InstancesClient();
    const ZONE = 'us-central1-a';
    
    exports.limitUse = async pubsubEvent => {
      const pubsubData = JSON.parse(
        Buffer.from(pubsubEvent.data, 'base64').toString()
      );
      if (pubsubData.costAmount <= pubsubData.budgetAmount) {
        return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
      }
    
      const instanceNames = await _listRunningInstances(PROJECT_ID, ZONE);
      if (!instanceNames.length) {
        return 'No running instances were found.';
      }
    
      await _stopInstances(PROJECT_ID, ZONE, instanceNames);
      return `${instanceNames.length} instance(s) stopped successfully.`;
    };
    
    /**
     * @return {Promise} Array of names of running instances
     */
    const _listRunningInstances = async (projectId, zone) => {
      const [instances] = await instancesClient.list({
        project: projectId,
        zone: zone,
      });
      return instances
        .filter(item => item.status === 'RUNNING')
        .map(item => item.name);
    };
    
    /**
     * @param {Array} instanceNames Names of instance to stop
     * @return {Promise} Response from stopping instances
     */
    const _stopInstances = async (projectId, zone, instanceNames) => {
      await Promise.all(
        instanceNames.map(instanceName => {
          return instancesClient
            .stop({
              project: projectId,
              zone: zone,
              instance: instanceName,
            })
            .then(() => {
              console.log(`Instance stopped successfully: ${instanceName}`);
            });
        })
      );
    };

    Python

    import base64
    import json
    import os
    
    from googleapiclient import discovery
    
    PROJECT_ID = os.getenv("GCP_PROJECT")
    PROJECT_NAME = f"projects/{PROJECT_ID}"
    ZONE = "us-west1-b"
    
    
    def limit_use(data, context):
        pubsub_data = base64.b64decode(data["data"]).decode("utf-8")
        pubsub_json = json.loads(pubsub_data)
        cost_amount = pubsub_json["costAmount"]
        budget_amount = pubsub_json["budgetAmount"]
        if cost_amount <= budget_amount:
            print(f"No action necessary. (Current cost: {cost_amount})")
            return
    
        compute = discovery.build(
            "compute",
            "v1",
            cache_discovery=False,
        )
        instances = compute.instances()
    
        instance_names = __list_running_instances(PROJECT_ID, ZONE, instances)
        __stop_instances(PROJECT_ID, ZONE, instance_names, instances)
    
    
    def __list_running_instances(project_id, zone, instances):
        """
        @param {string} project_id ID of project that contains instances to stop
        @param {string} zone Zone that contains instances to stop
        @return {Promise} Array of names of running instances
        """
        res = instances.list(project=project_id, zone=zone).execute()
    
        if "items" not in res:
            return []
    
        items = res["items"]
        running_names = [i["name"] for i in items if i["status"] == "RUNNING"]
        return running_names
    
    
    def __stop_instances(project_id, zone, instance_names, instances):
        """
        @param {string} project_id ID of project that contains instances to stop
        @param {string} zone Zone that contains instances to stop
        @param {Array} instance_names Names of instance to stop
        @return {Promise} Response from stopping instances
        """
        if not len(instance_names):
            print("No running instances were found.")
            return
    
        for name in instance_names:
            instances.stop(project=project_id, zone=zone, instance=name).execute()
            print(f"Instance stopped successfully: {name}")
    
    

  4. Defina o Ponto de entrada como a função correta a ser executada:

    Node.js

    Defina o Ponto de entrada como limitUse.

    Python

    Defina o Ponto de entrada como limit_use.

  5. Revise a lista de variáveis de ambiente definidas automaticamente e determine se você precisa definir manualmente a variável GCP_PROJECT para o projeto que executa as máquinas virtuais.

  6. Defina o parâmetro ZONE. Esse parâmetro é a zona em que as instâncias são interrompidas quando o orçamento é excedido.

  7. Clique em IMPLANTAR.

Configurar permissões da conta de serviço

Sua função do Cloud Run é executada como uma conta de serviço criada automaticamente. Para controlar o uso, você precisa conceder à conta de serviço permissões para os serviços do projeto que ela precisa modificar, seguindo estas etapas:

  1. Identifique a conta de serviço correta conferindo os detalhes da sua função do Cloud Run. A conta de serviço é listada na parte de baixo da página.
  2. Acesse a página IAM no console do Google Cloud para definir as permissões apropriadas.

    Acessar a página do IAM

Testar se as instâncias foram interrompidas

Para garantir que a função funcione conforme o esperado, siga as etapas em Testar uma função do Cloud Run.

Se for bem-sucedido, as VMs do Compute Engine no console do Google Cloud serão interrompidas.

A seguir

Confira outros exemplos de notificações programáticas para saber como fazer o seguinte: