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 recursos de Cloud Storage. Detener solo algunos recursos reduce tus costos sin inhabilitar por completo tu entorno.

En el siguiente ejemplo, el proyecto ejecuta una investigación con varias máquinas virtuales (VMs) de Compute Engine y almacena los resultados en buckets de Cloud Storage. Con las notificaciones de presupuesto como activador, después de que se exceda el presupuesto, esta función de Cloud Run cierra todas las instancias de Compute Engine, pero no afecta 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 Cloud Run Function

  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 <[email protected]>",
      "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 otorgar permisos de cuenta de servicio a cualquier servicio en el proyecto que necesite modificar. Para ello, completa los siguientes pasos:

  1. Para identificar la cuenta de servicio correcta, visualiza los detalles de tu 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 establecer los permisos correspondientes.

    Ve a la página IAM

Prueba que las instancias se detengan

Para asegurarte de que tu función funcione según lo esperado, sigue los pasos que se indican en Cómo probar una función de Cloud Run.

Si la operación se realiza correctamente, se detendrán tus 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: