Désactiver l'utilisation de la facturation avec les notifications

Ce document explique comment désactiver automatiquement la facturation sur un projet lorsque vos coûts atteignent ou dépassent votre budget de projet. Lorsque vous désactivez la facturation sur un projet, vous mettez fin à tous les Google Cloud services du projet, y compris les services du niveau sans frais. Pour une réponse plus nuancée aux notifications de budget, consultez Contrôler l'utilisation des ressources avec des notifications.

Vous pouvez limiter les coûts si vous disposez d'un montant maximal à dépenser sur Google Cloud. Dans ce cas, lorsque vous atteignez votre limite budgétaire vous pouvez être amené à arrêter tous vos Google Cloud services et leur utilisation pour ne plus engager de coûts. La désactivation de la facturation sur votre projet est un moyen efficace de ne plus engager de coûts dans ce projet.

Limites

  • Il existe un délai entre le moment où vous engagez des coûts et celui où vous recevez des notifications de budget. Par conséquent, des coûts supplémentaires peuvent vous être facturés pour une utilisation n'ayant pas encore été faite au moment où tous les services sont arrêtés. En suivant les étapes de cet exemple, vous n'avez aucune garantie de ne pas dépasser votre budget. Si vos fonds sont limités, définissez votre budget maximal en dessous de vos fonds disponibles afin de tenir compte des délais de facturation.

  • Vous ne pouvez pas désactiver la facturation sur un projet associé à un compte de facturation. Pour en savoir plus sur le verrouillage et le déverrouillage des projets, consultez Sécuriser l'association entre un projet et son compte de facturation.

Exemple de script

Vous pouvez utiliser le script suivant pour effectuer toutes les étapes de ce tutoriel dans un seul script. Il vous suffit de copier ce script dans un fichier Bash, de modifier les paramètres utilisateur en haut du fichier, puis d'exécuter le script.

Cliquez pour développer le 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}."

Avant de commencer

Avant de commencer, vous devez effectuer les tâches suivantes :

  1. Activer l'API Cloud Billing
  2. Créer un budget limité à un seul projet
  3. Configurer des notifications de budget automatisées

Configurer une fonction Cloud Run

Pour désactiver Cloud Billing pour un projet, créez une fonction Cloud Run et configurez-la pour qu'elle appelle l'API Cloud Billing.

  1. Suivez les étapes décrites dans Créer une fonction Cloud Run. Assurez-vous que le Type de déclencheur est défini sur le même sujet Pub/Sub que celui que votre budget utilisera.
  2. Ajoutez les dépendances suivantes :

    Node.js

    Copiez le code suivant dans votre fichier 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

    Copiez le code suivant dans votre fichier requirements.txt :

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

  3. Copiez le code suivant dans votre fonction 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. Définissez le Point d'entrée sur la fonction correcte à exécuter :

    Node.js

    Définissez le Point d'entrée sur stopBilling.

    Python

    Définissez le Point d'entrée sur stop_billing.

  5. Consultez la liste des variables d'environnement définies automatiquement pour déterminer si vous devez définir manuellement la GOOGLE_CLOUD_PROJECT variable sur le projet pour lequel vous souhaitez désactiver Cloud Billing.

  6. Cliquez sur DÉPLOYER.

Configurer les autorisations du compte de service

Votre fonction Cloud Run s'exécute en tant qu' un compte de service créé automatiquement. Pour désactiver la facturation, vous devez accorder au compte de service des autorisations sur tous les services du projet qu'il doit modifier en procédant comme suit :

  1. Identifiez le bon compte de service en consultant les informations de votre fonction Cloud Run. Le compte de service est répertorié au bas de la page.
  2. Accédez à la page IAM dans la Google Cloud console pour définir les autorisations appropriées.

    Accéder à la page IAM

  3. Pour modifier les autorisations du compte de facturation, dans la Google Cloud console, accédez à la page Gestion des comptes de facturation, ajoutez le compte de service en tant que principal sur le compte de facturation Cloud, puis définissez les autorisations appropriées pour le compte de facturation.

    Accéder à la page "Gestion des comptes" dans Cloud Billing

Découvrez comment configurer les autorisations pour les comptes Cloud Billing.

Vérifier que Cloud Billing est désactivé

Lorsque le budget envoie une notification, le projet spécifié n'est plus associé à un compte de facturation Cloud. Pour vous assurer que votre fonction fonctionne comme prévu, suivez les étapes décrites dans Tester une fonction Cloud Run.

Si l'opération réussit, le projet n'est plus visible sous le compte Cloud Billing et les ressources du projet sont désactivées, y compris la fonction Cloud Run si elle se trouve dans ce projet.

Pour continuer à utiliser Google Cloud les ressources du projet, dans la Google Cloud console, réactivez manuellement Cloud Billing pour votre projet.

Étape suivante

Consultez d'autres exemples de notifications automatisées pour découvrir comment effectuer les opérations suivantes :