停用带通知的结算使用情况

本文档介绍了如何在费用达到或超过项目预算时自动停用项目的结算功能。停用项目结算功能后,您将终止该项目中的所有 Google Cloud 服务,包括免费层级服务。如需对预算通知做出更细致的响应,请参阅使用通知控制资源使用情况

您可能会因为 Google Cloud的相关开支存在上限,而需要设置费用上限。在这些情况下,当达到预算上限时,您可能想要关停所有服务并停止使用,以免继续产生费用。 Google Cloud 停用项目的结算功能是一种有效的方法,可让您停止为该项目产生费用。

限制

  • 从产生费用到您收到预算通知是有延迟的,因此在停止所有服务时,尚未达到的使用量可能会产生一些额外的费用。按照此示例中的步骤操作并不能保证您的支出不会超出预算。如果您的资金有限,请将预算设置为低于可用资金的额度,以应对结算延迟情况。

  • 您无法为已与结算账号锁定的项目停用结算功能。如需详细了解如何锁定和解锁项目,请参阅保护项目与其结算账号之间的关联性

准备工作

在开始之前,您必须完成以下任务:

  1. 启用 Cloud Billing API
  2. 创建范围限定为单个项目的预算
  3. 设置程序化预算通知

设置 Cloud Run 函数

如需为项目停用 Cloud Billing,请创建一个 Cloud Run 函数并将其配置为调用 Cloud Billing API。

  1. 完成创建 Cloud Run 函数中的步骤。确保将触发器类型设置为预算将使用的同一 Pub/Sub 主题。
  2. 添加以下依赖项:

    Node.js

    将以下内容复制到 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

    将以下内容复制到 requirements.txt 文件中:

    slackclient==2.9.4
    google-api-python-client==2.131.0
    

  3. 将以下代码复制到您的 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

    import base64
    import json
    import os
    
    from googleapiclient import discovery
    
    PROJECT_ID = os.getenv("GCP_PROJECT")
    PROJECT_NAME = f"projects/{PROJECT_ID}"
    def stop_billing(data, context):
        pubsub_data = base64.b64decode(data["data"]).decode("utf-8")
        pubsub_json = json.loads(pubsub_data)
        cost_amount = pubsub_json["costAmount"]
        budget_amount = pubsub_json["budgetAmount"]
        if cost_amount <= budget_amount:
            print(f"No action necessary. (Current cost: {cost_amount})")
            return
    
        if PROJECT_ID is None:
            print("No project specified with environment variable")
            return
    
        billing = discovery.build(
            "cloudbilling",
            "v1",
            cache_discovery=False,
        )
    
        projects = billing.projects()
    
        billing_enabled = __is_billing_enabled(PROJECT_NAME, projects)
    
        if billing_enabled:
            __disable_billing_for_project(PROJECT_NAME, projects)
        else:
            print("Billing already disabled")
    
    
    def __is_billing_enabled(project_name, projects):
        """
        Determine whether billing is enabled for a project
        @param {string} project_name Name of project to check if billing is enabled
        @return {bool} Whether project has billing enabled or not
        """
        try:
            res = projects.getBillingInfo(name=project_name).execute()
            return res["billingEnabled"]
        except KeyError:
            # If billingEnabled isn't part of the return, billing is not enabled
            return False
        except Exception:
            print(
                "Unable to determine if billing is enabled on specified project, assuming billing is enabled"
            )
            return True
    
    
    def __disable_billing_for_project(project_name, projects):
        """
        Disable billing for a project by removing its billing account
        @param {string} project_name Name of project disable billing on
        """
        body = {"billingAccountName": ""}  # Disable billing
        try:
            res = projects.updateBillingInfo(name=project_name, body=body).execute()
            print(f"Billing disabled: {json.dumps(res)}")
        except Exception:
            print("Failed to disable billing, possibly check permissions")
    
    

  4. 入口点设置为要执行的正确函数:

    Node.js

    入口点设置为 stopBilling

    Python

    入口点设置为 stop_billing

  5. 查看自动设置的环境变量列表,确定是否需要手动将 GOOGLE_CLOUD_PROJECT 变量设为您要为其停用 Cloud Billing 的项目。

  6. 点击部署

配置服务账号权限

Cloud Run 函数作为自动创建的服务账号运行。如需停用结算功能,您需要完成以下步骤,将服务账号权限授予项目中需要修改的任何服务:

  1. 查看 Cloud Run 函数的详细信息,确定正确的服务账号。服务账号显示在页面底部。
  2. 前往 Google Cloud 控制台中的 IAM 页面,以设置相应权限。

    转到 IAM 页面

  3. 如需修改结算账号权限,请在 Google Cloud 控制台中前往“结算”账号管理页面,将服务账号添加为 Cloud Billing 账号的负责人,然后设置适当的结算账号权限

    前往 Cloud Billing 中的“账号管理”页面

详细了解如何为 Cloud Billing 账号配置权限

测试 Cloud Billing 是否已停用

当预算发出通知时,指定的项目将不再与 Cloud Billing 账号相关联。为确保您的函数能按预期运行,请按照测试 Cloud Run 函数中的步骤操作。

如果成功,Cloud Billing 账号下将不再显示此项目,且此项目中的资源(包括同一项目中的 Cloud Run 函数)已停用。

如需继续使用 Google Cloud 项目中的资源,请在 Google Cloud 控制台中为项目手动重新启用 Cloud Billing

后续步骤

查看其他程序化通知示例,了解如何执行以下操作: