Inhabilita 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 costos alcanzan o superan el presupuesto del proyecto. Cuando inhabilitas la facturación en un proyecto, finalizas todos los servicios Google Cloud del proyecto, incluidos los servicios del nivel gratuito. Para obtener una respuesta más detallada a las notificaciones de presupuesto, consulta Controla el uso de recursos con notificaciones.

Es posible que limites los costos porque tienes un importe máximo de dinero que puedes invertir en Google Cloud. En estos casos, cuando se alcance el límite de tu presupuesto, es posible que quieras cerrar todos tus servicios Google Cloud y el uso para dejar de generar costos. Inhabilitar la facturación de tu proyecto es un método eficiente para dejar de generar costos en él.

Limitaciones

  • Existe un retraso entre el momento en que se generan los costos y el momento en que recibes las notificaciones de presupuesto, por lo que es posible que se generen costos adicionales por un uso que no haya llegado cuando se detuvieron todos los servicios. Seguir los pasos de este ejemplo no garantiza que no excedas tu presupuesto. Si tienes una cantidad limitada de fondos, establece el presupuesto máximo por debajo de los fondos disponibles para tener en cuenta los retrasos de facturación.

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

Antes de comenzar

Antes de comenzar, debes completar las siguientes tareas:

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

Configura una Cloud Run Function

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 de Cloud Billing.

  1. Completa los pasos que se describen en Crea una función de Cloud Run. Asegúrate de que el Tipo de activador esté configurado con 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 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. Establece el punto de entrada en la función correcta para ejecutar:

    Node.js

    Establece el Punto de entrada en stopBilling.

    Python

    Establece el Punto de entrada en stop_billing.

  5. Revisa la lista de variables de entorno que se configuran automáticamente para determinar si necesitas configurar de forma manual la variable GOOGLE_CLOUD_PROJECT en el proyecto en el que deseas inhabilitar la Facturación de Cloud.

  6. 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 inhabilitar la facturación, 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

  3. Para modificar los permisos de la cuenta de facturación, en la consola de Google Cloud , ve a la página Administración de cuentas de facturación, agrega la cuenta de servicio como principal en la cuenta de Facturación de Cloud y establece los permisos de cuenta de facturación adecuados.

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

Obtén más información para configurar permisos para las cuentas de Facturación de Cloud.

Prueba que la Facturación de Cloud esté inhabilitada

Cuando el presupuesto envía una notificación, el proyecto especificado ya no tendrá una cuenta de facturación de Cloud asociada. Para asegurarte de que tu función funcione según lo esperado, sigue los pasos que se describen en Cómo probar una función de Cloud Run.

Si la operación se realiza correctamente, el proyecto ya no se verá en la cuenta de facturación de Cloud y los recursos del proyecto estarán inhabilitados, 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 la Facturación de Cloud de forma manual para tu proyecto.

¿Qué sigue?

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