通知で課金の使用状況を無効にする

このドキュメントでは、費用がプロジェクトの予算に達したときにプロジェクトの課金を自動的に無効にする方法について説明します。プロジェクトで課金を無効にすると、無料枠サービスを含め、プロジェクト内のすべての Google Cloud サービスが停止します。より細かいレスポンスを予算通知に設定するには、通知でリソース使用量を制御するをご覧ください。

Google Cloudに支出できる金額に上限があるため、費用を制限している場合があります。このような場合、予算の上限に達したら、すべての Google Cloud サービスと使用を停止して費用の発生を停止したいと考えるかもしれません。プロジェクトで課金を無効にすると、そのプロジェクトで費用が発生しないようにできます。

制限事項

  • 費用が発生してから予算通知を受け取るまでに時間差があるため、すべてのサービスが停止された時点で確認されていなかった使用により追加費用が発生することがあります。この例の手順に沿ったとしても、予算を超過しないという保証はありません。資金に限りがある場合は、請求の遅延を考慮して、使用可能な資金より低い最大予算を設定してください。

  • 請求先アカウントに固定されているプロジェクトの請求を無効にすることはできません。プロジェクトの固定と固定解除の詳細については、プロジェクトと請求先アカウント間のリンクを保護するをご覧ください。

始める前に

始める前に、次のタスクを完了する必要があります。

  1. Cloud Billing API を有効にする
  2. 1 つのプロジェクトを範囲とする予算を作成する
  3. プログラムによる予算通知を設定する

Cloud Run functions の関数を設定する

プロジェクトの Cloud Billing を無効にするには、Cloud Run functions の関数を作成し、Cloud Billing API を呼び出すように構成します。

  1. Cloud Run functions の関数を作成するの手順を行います。[トリガーのタイプ] が、予算で使用する同じ 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 functions の関数にコピーします。

    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. [エントリ ポイント] を、実行する正しい関数に設定します。

    Node.js

    [エントリ ポイント] を stopBilling に設定します。

    Python

    [エントリ ポイント] を stop_billing に設定します。

  5. 自動的に設定される環境変数のリストを確認し、Cloud Billing を無効にするプロジェクトに GOOGLE_CLOUD_PROJECT 変数を手動で設定する必要があるかどうかを判断します。

  6. [デプロイ] をクリックします。

サービス アカウント権限を構成する

Cloud Run functions の関数は、自動的に作成されたサービス アカウントとして実行されます。課金を無効にするには、次の手順で、変更が必要なプロジェクトのサービスにサービス アカウントの権限を付与する必要があります。

  1. Cloud Run functions の関数の詳細を表示して、正しいサービス アカウントを特定します。サービス アカウントはページの下部に表示されます。
  2. Google Cloud コンソールの [IAM] ページに移動して、適切な権限を設定します。

    IAM ページに移動

  3. 請求先アカウントの権限を変更するには、 Google Cloud コンソールで請求の [アカウント管理] ページに移動し、サービス アカウントを Cloud 請求先アカウントのプリンシパルとして追加して、適切な請求先アカウントの権限を設定します。

    Cloud Billing の [アカウント管理] ページに移動

詳細については、Cloud 請求先アカウントの権限を構成する方法をご覧ください。

Cloud Billing が無効であることをテストする

予算による通知が送信されると、指定したプロジェクトからそこに関連付けられている Cloud 請求先アカウントが削除されます。関数が想定どおりに動作することを確認するには、Cloud Run 関数をテストするの手順に沿って操作します。

成功すると、プロジェクトは Cloud 請求先アカウントに表示されなくなり、同じプロジェクト内にある Cloud Run functions の関数を含めプロジェクト内のリソースが無効になります。

プロジェクトの Google Cloud リソースを引き続き使用するには、Google Cloud コンソールでプロジェクトの Cloud Billing を手動で再度有効にします

次のステップ

他のプログラムによる通知の例を確認し、以下の方法を学びます。