Inhabilitar el uso de la facturación con notificaciones

En este documento se explica cómo inhabilitar automáticamente la facturación de un proyecto cuando los costes alcancen o superen el presupuesto del proyecto. Cuando inhabilitas la facturación en un proyecto, se cancelan todos los servicios de Google Cloud del proyecto, incluidos los servicios del nivel gratuito. Para obtener una respuesta más matizada a las notificaciones de presupuesto, consulta Controlar el uso de recursos con notificaciones.

Puedes limitar los costes porque tienes un importe máximo de dinero que puedes gastar en Google Cloud. En estos casos, cuando se alcance el límite de presupuesto, puede que quieras cerrar todos tus Google Cloud servicios y tu uso para dejar de incurrir en costes. Inhabilitar la facturación de un proyecto es un método eficaz para dejar de incurrir en costes en ese proyecto.

Limitaciones

  • Hay un retraso entre el momento en que se incurre en los costes y el momento en que se reciben las notificaciones de presupuesto, por lo que es posible que incurras en costes adicionales por el uso que no se haya registrado en el momento en que se detienen todos los servicios. Seguir los pasos de este ejemplo no garantiza que no vayas a superar tu presupuesto. Si tienes una cantidad de fondos limitada, define un presupuesto máximo inferior a los fondos disponibles para tener en cuenta los retrasos en la facturación.

  • No puedes inhabilitar la facturación en un proyecto que esté bloqueado en una cuenta de facturación. Para obtener más información sobre cómo bloquear y desbloquear proyectos, consulta el artículo Proteger la vinculación entre un proyecto y su cuenta de facturación.

Antes de empezar

Antes de empezar, debes completar las siguientes tareas:

  1. Habilita la API Cloud Billing
  2. Crea un presupuesto que se limite a un solo proyecto
  3. Configurar notificaciones de presupuesto programáticas

Configurar una función de Cloud Run

Para inhabilitar la facturación de Cloud en un proyecto, crea una función de Cloud Run y configúrala para que llame a la API Cloud Billing.

  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 usará 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 billing = new CloudBillingClient();
    
    exports.stopBilling = 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})`;
      }
    
      if (!PROJECT_ID) {
        return 'No project specified';
      }
    
      const billingEnabled = await _isBillingEnabled(PROJECT_NAME);
      if (billingEnabled) {
        return _disableBillingForProject(PROJECT_NAME);
      } else {
        return 'Billing already disabled';
      }
    };
    
    /**
     * Determine whether billing is enabled for a project
     * @param {string} projectName Name of project to check if billing is enabled
     * @return {bool} Whether project has billing enabled or not
     */
    const _isBillingEnabled = async projectName => {
      try {
        const [res] = await billing.getProjectBillingInfo({name: projectName});
        return res.billingEnabled;
      } catch (e) {
        console.log(
          'Unable to determine if billing is enabled on specified project, assuming billing is enabled'
        );
        return true;
      }
    };
    
    /**
     * Disable billing for a project by removing its billing account
     * @param {string} projectName Name of project disable billing on
     * @return {string} Text containing response from disabling billing
     */
    const _disableBillingForProject = async projectName => {
      const [res] = await billing.updateProjectBillingInfo({
        name: projectName,
        resource: {billingAccountName: ''}, // Disable billing
      });
      return `Billing disabled: ${JSON.stringify(res)}`;
    };

    Python

    # WARNING: The following action, if not in simulation mode, will disable billing
    # for the project, potentially stopping all services and causing outages.
    # Ensure thorough testing and understanding before enabling live deactivation.
    
    import base64
    import json
    import os
    import urllib.request
    
    from cloudevents.http.event import CloudEvent
    import functions_framework
    
    from google.api_core import exceptions
    from google.cloud import billing_v1
    from google.cloud import logging
    
    billing_client = billing_v1.CloudBillingClient()
    
    
    def get_project_id() -> str:
        """Retrieves the Google Cloud Project ID.
    
        This function first attempts to get the project ID from the
        `GOOGLE_CLOUD_PROJECT` environment variable. If the environment
        variable is not set or is None, it then attempts to retrieve the
        project ID from the Google Cloud metadata server.
    
        Returns:
            str: The Google Cloud Project ID.
    
        Raises:
            ValueError: If the project ID cannot be determined either from
                        the environment variable or the metadata server.
        """
    
        # Read the environment variable, usually set manually
        project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
        if project_id is not None:
            return project_id
    
        # Otherwise, get the `project-id`` from the Metadata server
        url = "http://metadata.google.internal/computeMetadata/v1/project/project-id"
        req = urllib.request.Request(url)
        req.add_header("Metadata-Flavor", "Google")
        project_id = urllib.request.urlopen(req).read().decode()
    
        if project_id is None:
            raise ValueError("project-id metadata not found.")
    
        return project_id
    
    
    @functions_framework.cloud_event
    def stop_billing(cloud_event: CloudEvent) -> None:
        # TODO(developer): As stoping billing is a destructive action
        # for your project, change the following constant to False
        # after you validate with a test budget.
        SIMULATE_DEACTIVATION = True
    
        PROJECT_ID = get_project_id()
        PROJECT_NAME = f"projects/{PROJECT_ID}"
    
        event_data = base64.b64decode(
            cloud_event.data["message"]["data"]
        ).decode("utf-8")
    
        event_dict = json.loads(event_data)
        cost_amount = event_dict["costAmount"]
        budget_amount = event_dict["budgetAmount"]
        print(f"Cost: {cost_amount} Budget: {budget_amount}")
    
        if cost_amount <= budget_amount:
            print("No action required. Current cost is within budget.")
            return
    
        print(f"Disabling billing for project '{PROJECT_NAME}'...")
    
        is_billing_enabled = _is_billing_enabled(PROJECT_NAME)
    
        if is_billing_enabled:
            _disable_billing_for_project(
                PROJECT_NAME,
                SIMULATE_DEACTIVATION
            )
        else:
            print("Billing is already disabled.")
    
    
    def _is_billing_enabled(project_name: str) -> bool:
        """Determine whether billing is enabled for a project.
    
        Args:
            project_name: Name of project to check if billing is enabled.
    
        Returns:
            Whether project has billing enabled or not.
        """
        try:
            print(f"Getting billing info for project '{project_name}'...")
            response = billing_client.get_project_billing_info(name=project_name)
    
            return response.billing_enabled
        except Exception as e:
            print(f'Error getting billing info: {e}')
            print(
                "Unable to determine if billing is enabled on specified project, "
                "assuming billing is enabled."
            )
    
            return True
    
    
    def _disable_billing_for_project(
        project_name: str,
        simulate_deactivation: bool,
    ) -> None:
        """Disable billing for a project by removing its billing account.
    
        Args:
            project_name: Name of project to disable billing.
            simulate_deactivation:
                If True, it won't actually disable billing.
                Useful to validate with test budgets.
        """
    
        # Log this operation in Cloud Logging
        logging_client = logging.Client()
        logger = logging_client.logger(name="disable-billing")
    
        if simulate_deactivation:
            entry_text = "Billing disabled. (Simulated)"
            print(entry_text)
            logger.log_text(entry_text, severity="CRITICAL")
            return
    
        # Find more information about `updateBillingInfo` API method here:
        # https://cloud.google.com/billing/docs/reference/rest/v1/projects/updateBillingInfo
        try:
            # To disable billing set the `billing_account_name` field to empty
            project_billing_info = billing_v1.ProjectBillingInfo(
                billing_account_name=""
            )
    
            response = billing_client.update_project_billing_info(
                name=project_name,
                project_billing_info=project_billing_info
            )
    
            entry_text = f"Billing disabled: {response}"
            print(entry_text)
            logger.log_text(entry_text, severity="CRITICAL")
        except exceptions.PermissionDenied:
            print("Failed to disable billing, check permissions.")

  4. Define el Punto de entrada en la función correcta que se va a ejecutar:

    Node.js

    Define Punto de entrada como stopBilling.

    Python

    Define Punto de entrada como stop_billing.

  5. Consulta la lista de variables de entorno definidas automáticamente para determinar si tienes que definir manualmente la variable GOOGLE_CLOUD_PROJECT en el proyecto en el que quieras inhabilitar la facturación de Cloud.

  6. 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 inhabilitar la facturación, debes conceder a la cuenta de servicio permisos para modificar los servicios del proyecto que necesite. 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

  3. Para modificar los permisos de la cuenta de facturación, en la consola, ve a la página Gestión de cuentas de facturación, añade la cuenta de servicio como principal en la cuenta de facturación de Cloud y define los permisos de cuenta de facturación adecuados. Google Cloud

    Ir a la página Gestión de cuentas de Facturación de Cloud

Consulta más información sobre cómo configurar permisos para cuentas de facturación de Cloud.

Comprobar que la facturación de Cloud está inhabilitada

Cuando el presupuesto envíe una notificación, el proyecto especificado dejará de tener una cuenta de facturación de Cloud asociada. Para asegurarte de que tu función funciona como se espera, sigue los pasos que se indican en Probar una función de Cloud Run.

Si la operación se realiza correctamente, el proyecto dejará de aparecer en la cuenta de facturación de Cloud y los recursos del proyecto se inhabilitarán, incluida la función de Cloud Run si está en el mismo proyecto.

Para seguir usando los Google Cloud recursos del proyecto, en laGoogle Cloud consola,vuelve a habilitar manualmente la facturación de Cloud en tu proyecto.

Siguientes pasos

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