Disattivare l'utilizzo della fatturazione con le notifiche

Questo documento spiega come disattivare automaticamente la fatturazione per un progetto quando i costi raggiungono o superano il budget del progetto. Quando disattivi la fatturazione per un progetto, interrompi tutti i Google Cloud servizi nel progetto, inclusi i servizi del livello senza costi. Per una risposta più sfumata alle notifiche di budget, consulta Controllare l'utilizzo delle risorse con le notifiche.

Potresti limitare i costi perché hai un importo massimo di denaro che puoi spendere Google Cloud. In questi casi, quando viene raggiunto il limite di budget, potresti voler arrestare tutti i servizi Google Cloud e l'utilizzo per interrompere l'accumulo di costi. La disattivazione della fatturazione per il progetto è un metodo efficiente per interrompere l'accumulo di costi nel progetto.

Limitazioni

  • Esiste un ritardo tra l'accumulo di costi e la ricezione delle notifiche di budget, pertanto potresti sostenere costi aggiuntivi per l'utilizzo che non è arrivato al momento dell'interruzione di tutti i servizi. Seguire i passaggi di questo esempio non garantisce che non spenderai più del tuo budget. Se hai una quantità limitata di fondi, imposta il budget massimo al di sotto dei fondi disponibili per tenere conto dei ritardi di fatturazione.

  • Non puoi disattivare la fatturazione per un progetto bloccato a un account di fatturazione. Per saperne di più sul blocco e sullo sblocco dei progetti, consulta Proteggere il collegamento tra un progetto e il relativo account di fatturazione.

Script di esempio

Puoi utilizzare il seguente script per eseguire tutti i passaggi di questo tutorial in un unico script. Basta copiare questo script in un file Bash, modificare i parametri utente vicino alla parte superiore del file ed eseguire lo script.

Fai clic per espandere lo script


#!/bin/bash

# This script combines all the steps from the following documentation so they
# can be executed in a single command:
# https://docs.cloud.google.com/billing/docs/how-to/disable-billing-with-notifications#functions_cap_billing_dependencies-nodejs

# Step-by-step instructions:
# A) Edit the parameters under step 1 (Set User Parameters) below and then
#    copy the contents of this file to your clipboard.
# B) Open Cloud Shell (or any bash terminal with gcloud installed) via the 
#    terminal icon in the top right of http://console.cloud.google.com
# C) Paste the contents into a new Bash file which you can do with the 
#    following commands:
#    type "vi ./billing_caps.sh" to create a new file and open it for editing
#    press 'i' to enter insert mode
#    paste the contents of this file into the terminal (right-click + "paste")
#    press 'ESC' to exit insert mode
#    type ":wq" to save and exit
#    type "chmod +x billing_caps.sh" to make the script executable
# C) Execute the script with the command "./billing_caps.sh"
# D) Monitor the terminal output for any errors that might require you to 
#    repeat one or more of the above steps. If no errors occur then you're done!

# 1. Set User Parameters
PROJECT_ID="your-project-id"
REGION="us-central1"
TOPIC_ID="billing-alerts-topic"
BILLING_ACCOUNT_ID="your-billing-account-id"
BUDGET_AMOUNT="100" # Example: $100

# 2. Enable Required APIs
gcloud services enable billingbudgets.googleapis.com \
    cloudbilling.googleapis.com \
    cloudbuild.googleapis.com \
    cloudfunctions.googleapis.com \
    eventarc.googleapis.com \
    run.googleapis.com \
    pubsub.googleapis.com \
    artifactregistry.googleapis.com \
    --project="${PROJECT_ID}"

# 3. Create Pub/Sub Topic
gcloud pubsub topics create "${TOPIC_ID}" --project="${PROJECT_ID}"

# 4. Create Source Files for the Function
mkdir -p billing_function
cat <<'EOF' > billing_function/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": ">=18.0.0"
  },
  "author": "Ace Nassri ",
  "license": "Apache-2.0",
  "dependencies": {
    "@google-cloud/billing": "^4.0.0"
  },
  "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"
  }
}
EOF

cat <<'EOF' > billing_function/index.js
const {CloudBillingClient} = require('@google-cloud/billing');

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)}`;
};
EOF

# 5. Deploy the Cloud Run Function
# This uses the Gen 2 runtime as suggested by current standards
gcloud functions deploy stop-billing-function \
    --gen2 \
    --runtime=nodejs24 \
    --region="${REGION}" \
    --trigger-topic="${TOPIC_ID}" \
    --entry-point=stopBilling \
    --set-env-vars GOOGLE_CLOUD_PROJECT="${PROJECT_ID}" \
    --source=./billing_function \
    --project="${PROJECT_ID}"

# 6. Configure Service Account Permissions
# Get the service account associated with the function
SERVICE_ACCOUNT=$(gcloud functions describe stop-billing-function --region="${REGION}" --format="value(serviceConfig.serviceAccountEmail)" --project="${PROJECT_ID}")

# Grant the service account the "Billing Account Administrator" role on the
# billing account.
# Note: You must have 'Billing Account Administrator' rights to run this
gcloud billing accounts add-iam-policy-binding "${BILLING_ACCOUNT_ID}" \
    --member="serviceAccount:${SERVICE_ACCOUNT}" \
    --role="roles/billing.admin"

# 7. Create the Budget and Link to Topic
gcloud billing budgets create \
    --billing-account="${BILLING_ACCOUNT_ID}" \
    --display-name="Budget for ${PROJECT_ID}" \
    --budget-amount="${BUDGET_AMOUNT}" \
    --threshold-rule=percent=100 \
    --notifications-rule-pubsub-topic="projects/${PROJECT_ID}/topics/${TOPIC_ID}"

echo "Setup complete. Billing will be disabled if costs exceed ${BUDGET_AMOUNT}."

Prima di iniziare

Prima di iniziare, devi completare le seguenti attività:

  1. Abilitare l'API Cloud Billing
  2. Creare un budget con ambito a un singolo progetto
  3. Configurare le notifiche di budget programmatiche

Configurare una funzione Cloud Run

Per disattivare la fatturazione Cloud per un progetto, crea una funzione Cloud Run e configurala per chiamare l'API Cloud Billing.

  1. Completa i passaggi descritti in Creare una funzione Cloud Run. Assicurati che il Tipo di trigger sia impostato sullo stesso argomento Pub/Sub che verrà utilizzato dal budget.
  2. Aggiungi le seguenti dipendenze:

    Node.js

    Copia quanto segue nel file 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": ">=18.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",
        "@slack/web-api": "^7.15.0",
        "google-auth-library": "^9.0.0",
        "googleapis": "^143.0.0"
      },
      "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 quanto segue nel file requirements.txt:

    functions-framework==3.*
    google-cloud-billing==1.16.2
    google-cloud-logging==3.12.1
    

  3. Copia il seguente codice nella funzione 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. Imposta il punto di ingresso sulla funzione corretta da eseguire:

    Node.js

    Imposta il punto di ingresso su stopBilling.

    Python

    Imposta il punto di ingresso su stop_billing.

  5. Esamina l'elenco delle variabili di ambiente impostate automaticamente per determinare se devi impostare manualmente la GOOGLE_CLOUD_PROJECT variabile sul progetto per cui vuoi disattivare la fatturazione Cloud.

  6. Fai clic su ESEGUI IL DEPLOYMENT.

Configura le autorizzazioni degli account di servizio

La funzione Cloud Run viene eseguita come un account di servizio creato automaticamente. Per disattivare la fatturazione, devi concedere al account di servizio le autorizzazioni per tutti i servizi del progetto che deve modificare completando i seguenti passaggi:

  1. Identifica il account di servizio corretto visualizzando i dettagli della funzione Cloud Run. Il account di servizio è elencato nella parte inferiore della pagina.
  2. Vai alla pagina IAM nella Google Cloud console per impostare le autorizzazioni appropriate.

    Vai alla pagina IAM

  3. Per modificare le autorizzazioni dell'account di fatturazione, nella Google Cloud console vai a la pagina Gestione account di fatturazione, aggiungi il account di servizio come entità nell'account di fatturazione Cloud e imposta le autorizzazioni appropriate per l'account di fatturazione.

    Vai alla pagina Gestione account in Fatturazione Cloud

Scopri di più su come configurare le autorizzazioni per gli account di fatturazione Cloud.

Verifica che la fatturazione Cloud sia disattivata

Quando il budget invia una notifica, il progetto specificato non avrà più un account di fatturazione Cloud associato. Per assicurarti che la funzione funzioni come previsto, segui i passaggi descritti in Testare una funzione Cloud Run.

Se l'operazione va a buon fine, il progetto non è più visibile nell'account di fatturazione Cloud e le risorse del progetto sono disattivate, inclusa la funzione Cloud Run se si trova nello stesso progetto.

Per continuare a utilizzare Google Cloud le risorse del progetto, nella Google Cloud console, riattiva manualmente la fatturazione Cloud per il progetto.

Passaggi successivi

Esamina altri esempi di notifiche programmatiche per scoprire come eseguire le seguenti operazioni: