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, todos los servicios se detienen y todos los recursos se eliminan. Si necesitas una respuesta más matizada, puedes controlar los recursos de forma selectiva. Por ejemplo, puedes detener algunos recursos de Compute Engine y dejar intactos los recursos de Cloud Storage. Si detienes solo algunos recursos, reducirás los costes sin inhabilitar por completo tu entorno.

En el siguiente ejemplo, el proyecto lleva a cabo una investigación con varias máquinas virtuales (VMs) de Compute Engine y almacena los resultados en segmentos de Cloud Storage. Esta función de Cloud Run usa las notificaciones de presupuesto como activador. Cuando se supera el presupuesto, se cierran todas las instancias de Compute Engine, pero no se ven afectados los resultados almacenados.

Antes de empezar

Antes de empezar, debes completar las siguientes tareas:

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

Configurar una función de Cloud Run

  1. Completa los pasos que se indican en Crear una función de Cloud Run. Asegúrate de que el Tipo de activador sea el mismo tema de Pub/Sub que utilizará tu presupuesto.
  2. Añade 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. Define el Punto de entrada en la función correcta que se va a ejecutar:

    Node.js

    Define Punto de entrada como limitUse.

    Python

    Define Punto de entrada como limit_use.

  5. Consulta la lista de variables de entorno definidas automáticamente y determina si tienes que definir manualmente la variable GCP_PROJECT en el proyecto que ejecuta las máquinas virtuales.

  6. Defina 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 DESPLEGAR.

Configurar 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 conceder permisos a la cuenta de servicio para que pueda modificar los servicios del proyecto. Para ello, sigue estos pasos:

  1. Identifica la cuenta de servicio correcta consultando los detalles de tu función de Cloud Run. La cuenta de servicio se muestra en la parte inferior de la página.
  2. Ve a la página Gestión de identidades y accesos de la consola Google Cloud para definir los permisos adecuados.

    Ir a la página de gestión de identidades y accesos

Comprobar que las instancias se han detenido

Para asegurarte de que tu función funciona correctamente, sigue los pasos que se indican en Probar una función de Cloud Run.

Si se completa correctamente, las máquinas virtuales de Compute Engine de la consola Google Cloud se detendrán.

Siguientes pasos

Consulta otros ejemplos de notificaciones programáticas para saber cómo hacer lo siguiente: