通知でリソース使用量を制御する

このドキュメントでは、予算通知を使用してリソース使用量を選択的に制御する方法について説明します。

プロジェクトで課金を無効にすると、すべてのサービスが停止し、最終的にすべてのリソースが削除されます。よりきめ細かなレスポンスが必要な場合は、リソースを選択的に制御できます。たとえば、Cloud Storage リソースはそのままにして、一部の Compute Engine リソースを停止できます。一部のリソースのみを停止すると、環境を完全に無効にすることなく費用を削減できます。

次の例では、プロジェクトは複数の Compute Engine 仮想マシン(VM)を使用して研究を実行し、結果を Cloud Storage バケットに保存します。予算通知をトリガーとして使用し、予算を超えると、この Cloud Run 関数はすべての Compute Engine インスタンスをシャットダウンしますが、保存された結果には影響しません。

始める前に

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

  1. Cloud Billing API を有効にする
  2. 予算の作成
  3. プログラムによる予算通知を設定する

Cloud Run 関数を設定する

  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 instancesClient = new InstancesClient();
    const ZONE = 'us-central1-a';
    
    exports.limitUse = 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})`;
      }
    
      const instanceNames = await _listRunningInstances(PROJECT_ID, ZONE);
      if (!instanceNames.length) {
        return 'No running instances were found.';
      }
    
      await _stopInstances(PROJECT_ID, ZONE, instanceNames);
      return `${instanceNames.length} instance(s) stopped successfully.`;
    };
    
    /**
     * @return {Promise} Array of names of running instances
     */
    const _listRunningInstances = async (projectId, zone) => {
      const [instances] = await instancesClient.list({
        project: projectId,
        zone: zone,
      });
      return instances
        .filter(item => item.status === 'RUNNING')
        .map(item => item.name);
    };
    
    /**
     * @param {Array} instanceNames Names of instance to stop
     * @return {Promise} Response from stopping instances
     */
    const _stopInstances = async (projectId, zone, instanceNames) => {
      await Promise.all(
        instanceNames.map(instanceName => {
          return instancesClient
            .stop({
              project: projectId,
              zone: zone,
              instance: instanceName,
            })
            .then(() => {
              console.log(`Instance stopped successfully: ${instanceName}`);
            });
        })
      );
    };

    Python

    import base64
    import json
    import os
    
    from googleapiclient import discovery
    
    PROJECT_ID = os.getenv("GCP_PROJECT")
    PROJECT_NAME = f"projects/{PROJECT_ID}"
    ZONE = "us-west1-b"
    
    
    def limit_use(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
    
        compute = discovery.build(
            "compute",
            "v1",
            cache_discovery=False,
        )
        instances = compute.instances()
    
        instance_names = __list_running_instances(PROJECT_ID, ZONE, instances)
        __stop_instances(PROJECT_ID, ZONE, instance_names, instances)
    
    
    def __list_running_instances(project_id, zone, instances):
        """
        @param {string} project_id ID of project that contains instances to stop
        @param {string} zone Zone that contains instances to stop
        @return {Promise} Array of names of running instances
        """
        res = instances.list(project=project_id, zone=zone).execute()
    
        if "items" not in res:
            return []
    
        items = res["items"]
        running_names = [i["name"] for i in items if i["status"] == "RUNNING"]
        return running_names
    
    
    def __stop_instances(project_id, zone, instance_names, instances):
        """
        @param {string} project_id ID of project that contains instances to stop
        @param {string} zone Zone that contains instances to stop
        @param {Array} instance_names Names of instance to stop
        @return {Promise} Response from stopping instances
        """
        if not len(instance_names):
            print("No running instances were found.")
            return
    
        for name in instance_names:
            instances.stop(project=project_id, zone=zone, instance=name).execute()
            print(f"Instance stopped successfully: {name}")
    
    

  4. [エントリ ポイント] を、実行する正しい関数に設定します。

    Node.js

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

    Python

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

  5. 自動的に設定される環境変数のリストを確認し、仮想マシンを実行するプロジェクトに GCP_PROJECT 変数を手動で設定する必要があるかどうかを判断します。

  6. ZONE パラメータを設定します。このパラメータは、予算を超えたときにインスタンスが停止されるゾーンです。

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

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

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

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

    IAM ページに移動

インスタンスが停止していることをテストする

関数が想定どおりに動作することを確認するには、Cloud Run 関数をテストするの手順に沿って操作します。

成功すると、Google Cloud コンソールの Compute Engine VM が停止します。

次のステップ

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