Menonaktifkan penggunaan penagihan dengan notifikasi

Dokumen ini menjelaskan cara menonaktifkan penagihan secara otomatis pada project saat biaya Anda memenuhi atau melebihi anggaran project. Jika Anda menonaktifkan penagihan pada project, semua Google Cloud layanan dalam project akan dihentikan, termasuk layanan Paket Gratis. Untuk respons yang lebih mendalam terhadap notifikasi anggaran, lihat Mengontrol penggunaan resource dengan notifikasi.

Anda mungkin membatasi biaya karena memiliki jumlah uang maksimum yang dapat Anda belanjakan Google Cloud. Dalam kasus ini, saat batas anggaran tercapai, Anda mungkin bersedia menghentikan semua layanan Google Cloud dan penggunaan untuk menghentikan biaya. Menonaktifkan penagihan pada project adalah metode yang efisien untuk menghentikan biaya dalam project tersebut.

Batasan

  • Ada jeda antara timbulnya biaya dan diterimanya notifikasi anggaran, sehingga Anda mungkin dikenai biaya tambahan untuk penggunaan yang belum muncul pada saat semua layanan dihentikan. Mengikuti langkah-langkah dalam contoh ini tidak menjamin bahwa Anda tidak akan membelanjakan lebih dari anggaran. Jika Anda memiliki dana terbatas, tetapkan anggaran maksimum di bawah dana yang tersedia untuk memperhitungkan keterlambatan penagihan.

  • Anda tidak dapat menonaktifkan penagihan pada project yang dikunci ke akun penagihan. Untuk mempelajari lebih lanjut cara mengunci dan membuka kunci project, lihat Mengamankan link antara project dan akun penagihannya.

Contoh skrip

Anda dapat menggunakan skrip berikut untuk melakukan semua langkah dalam tutorial ini dalam satu skrip. Cukup salin skrip ini ke file Bash, edit parameter pengguna di dekat bagian atas file, dan jalankan skrip.

Klik untuk meluaskan skrip


#!/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}."

Sebelum memulai

Sebelum memulai, Anda harus menyelesaikan tugas berikut:

  1. Mengaktifkan Cloud Billing API
  2. Membuat anggaran yang menjadi cakupan satu project
  3. Menyiapkan notifikasi anggaran terprogram

Menyiapkan Cloud Run Function

Untuk menonaktifkan Penagihan Cloud untuk project, buat Cloud Run Function dan konfigurasikan untuk memanggil Cloud Billing API.

  1. Selesaikan langkah-langkah di Membuat Cloud Run Function. Pastikan Trigger type ditetapkan ke topik Pub/Sub yang sama dengan yang akan digunakan oleh anggaran Anda.
  2. Tambahkan dependensi berikut:

    Node.js

    Salin kode berikut ke file package.json Anda:

    {
      "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

    Salin kode berikut ke file requirements.txt Anda:

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

  3. Salin kode berikut ke fungsi Cloud Run Anda:

    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. Tetapkan Entry point ke fungsi yang benar untuk dieksekusi:

    Node.js

    Tetapkan Entry point ke stopBilling.

    Python

    Tetapkan Entry point ke stop_billing.

  5. Tinjau daftar variabel lingkungan yang ditetapkan secara otomatis untuk menentukan apakah Anda perlu menetapkan variabel GOOGLE_CLOUD_PROJECT secara manual ke project yang Penagihan Cloud-nya ingin Anda nonaktifkan.

  6. Klik DEPLOY.

Mengonfigurasi izin akun layanan

Cloud Run Function Anda berjalan sebagai akun layanan yang dibuat secara otomatis . Untuk menonaktifkan penagihan, Anda perlu memberikan izin akun layanan ke layanan apa pun di project yang perlu diubahnya dengan menyelesaikan langkah-langkah berikut:

  1. Identifikasi akun layanan yang benar dengan melihat detail Cloud Run Function Anda. Akun layanan tercantum di bagian bawah halaman.
  2. Buka halaman IAM di Google Cloud konsol untuk menetapkan izin yang sesuai.

    Buka halaman IAM

  3. Untuk mengubah izin akun penagihan, di Google Cloud konsol, buka halaman Pengelolaan akun Penagihan, tambahkan akun layanan sebagai prinsip di akun Penagihan Cloud, dan tetapkan izin akun penagihan yang sesuai.

    Buka halaman Pengelolaan akun di Penagihan Cloud

Pelajari lebih lanjut cara mengonfigurasi izin untuk akun Penagihan Cloud.

Menguji bahwa Penagihan Cloud dinonaktifkan

Saat anggaran mengirimkan notifikasi, project yang ditentukan tidak akan lagi memiliki akun Penagihan Cloud yang terkait dengannya. Untuk memastikan fungsi Anda berfungsi seperti yang diharapkan, ikuti langkah-langkah di Menguji Cloud Run Function.

Jika berhasil, project tidak akan terlihat lagi di akun Penagihan Cloud dan resource dalam project dinonaktifkan, termasuk Cloud Run Function jika berada dalam project yang sama.

Untuk terus menggunakan Google Cloud resource dalam project, di Google Cloud konsol, aktifkan kembali Penagihan Cloud untuk project Anda.

Langkah berikutnya

Lihat contoh notifikasi terprogram lainnya untuk mempelajari cara melakukan hal berikut: