Controllare l'utilizzo delle risorse con le notifiche

Questo documento spiega come utilizzare le notifiche del budget per controllare in modo selettivo l'utilizzo delle risorse.

Quando disattivi la fatturazione in un progetto, tutti i servizi vengono interrotti e tutte le risorse vengono eliminate. Se hai bisogno di una risposta più sfumata, puoi controllare le risorse in modo selettivo. Ad esempio, puoi interrompere alcune risorse Compute Engine lasciando invariate le risorse Cloud Storage. L'interruzione di alcune risorse riduce i costi senza disattivare completamente l'ambiente.

Nell'esempio seguente, il progetto esegue ricerche con una serie di VM (macchine virtuali) Compute Engine e memorizza i risultati nei bucket Cloud Storage. Utilizzando le notifiche del budget come attivatore, dopo il superamento del budget, questa funzione Cloud Run arresta tutte le istanze Compute Engine, ma non influisce sui risultati archiviati.

Prima di iniziare

Prima di iniziare, devi completare le seguenti attività:

  1. Abilita l'API Cloud Billing
  2. Creare un budget
  3. Configurare le notifiche relative al budget programmatico

Configurare una funzione Cloud Run

  1. Completa i passaggi descritti in Creare una funzione Cloud Run. Assicurati di impostare il Tipo di trigger sullo stesso argomento Pub/Sub usato dal budget.
  2. Aggiungi le seguenti dipendenze:

    Node.js

    Copia quanto segue nel file 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

    Copia quanto segue nel file requirements.txt:

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

  3. Copia il seguente codice nella funzione 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. Imposta il punto di ingresso sulla funzione corretta da eseguire:

    Node.js

    Imposta Punto di ingresso su limitUse.

    Python

    Imposta Punto di ingresso su limit_use.

  5. Esamina l'elenco delle variabili di ambiente impostate automaticamente e determina se devi impostare manualmente la variabile GCP_PROJECT per il progetto che esegue le macchine virtuali.

  6. Imposta il parametro ZONE. Questo parametro indica la zona in cui le istanze vengono interrotte quando il budget viene superato.

  7. Fai clic su ESEGUI IL ROLLOUT.

Configura le autorizzazioni degli account di servizio

La funzione Cloud Run viene eseguita come account di servizio creato automaticamente. Per controllare l'utilizzo, devi concedere all'account di servizio le autorizzazioni per tutti i servizi del progetto che deve modificare completando i seguenti passaggi:

  1. Identifica l'account di servizio corretto visualizzando i dettagli della funzione Cloud Run. L'account di servizio è elencato nella parte inferiore della pagina.
  2. Vai alla pagina IAM nella console Google Cloud per impostare le autorizzazioni appropriate.

    Vai alla pagina IAM

Verifica che le istanze siano arrestate

Per assicurarti che la funzione funzioni come previsto, segui i passaggi descritti in Testare una funzione Cloud Run.

In caso di esito positivo, le VM Compute Engine nella console Google Cloud vengono arrestate.

Passaggi successivi

Esamina altri esempi di notifiche programmatiche per scoprire come svolgere le seguenti operazioni: