Cómo controlar el uso de recursos con notificaciones

En este documento, se explica cómo usar las notificaciones de presupuesto para controlar de forma selectiva el uso de recursos.

Cuando inhabilitas la facturación en un proyecto, se detienen todos los servicios y, con el tiempo, se borran todos los recursos. Si necesitas una respuesta más matizada, puedes controlar los recursos de manera selectiva. Por ejemplo, puedes detener algunos recursos de Compute Engine y dejar intactos los de Cloud Storage. Detener solo algunos recursos reduce los costos sin inhabilitar por completo el entorno.

En el siguiente ejemplo, el proyecto ejecuta una investigación con varias máquinas virtuales (VM) de Compute Engine y almacena los resultados en buckets de Cloud Storage. Si usas las notificaciones de presupuesto como activador, después de que se exceda el presupuesto, esta función de Cloud Run cerrará todas las instancias de Compute Engine, pero no afectará los resultados almacenados.

Antes de comenzar

Antes de comenzar, debes completar las siguientes tareas:

  1. Habilita la API de Cloud Billing
  2. Crea un presupuesto
  3. Configura notificaciones de presupuesto programáticas

Configura una función de Cloud Run

  1. Completa los pasos que se describen en Crea una función de Cloud Run. Asegúrate de configurar el tipo de activador en el mismo tema de Pub/Sub que usará tu presupuesto.
  2. Agrega las siguientes dependencias:

    Node.js

    Copia lo siguiente en tu archivo 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 lo siguiente en tu archivo requirements.txt:

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

  3. Copia el siguiente código en tu función de 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. Establece el Punto de entrada en la función correcta para ejecutar:

    Node.js

    Establece el Punto de entrada en limitUse.

    Python

    Establece el Punto de entrada en limit_use.

  5. Revisa la lista de variables de entorno que se configuran automáticamente y determina si necesitas configurar de forma manual la variable GCP_PROJECT para el proyecto que ejecuta las máquinas virtuales.

  6. Establece el parámetro ZONE. Este parámetro es la zona en la que se detienen las instancias cuando se supera el presupuesto.

  7. Haz clic en IMPLEMENTAR.

Configura los permisos de la cuenta de servicio

Tu función de Cloud Run se ejecuta como una cuenta de servicio creada automáticamente. Para controlar el uso, debes otorgarle permisos a la cuenta de servicio para cualquier servicio del proyecto que necesite modificar. Para ello, completa los siguientes pasos:

  1. Para identificar la cuenta de servicio correcta, visualiza los detalles de la función de Cloud Run. La cuenta de servicio se encuentra en la parte inferior de la página.
  2. Ve a la página IAM en la consola de Google Cloud para configurar los permisos correspondientes.

    Ve a la página IAM

Prueba que las instancias se detengan

Para asegurarte de que tu función funcione como se espera, sigue los pasos que se indican en Cómo probar una función de Cloud Run.

Si se realiza correctamente, se detendrán las VMs de Compute Engine en la consola de Google Cloud.

¿Qué sigue?

Revisa otros ejemplos de notificaciones programáticas para aprender a hacer lo siguiente: