透過通知控管資源用量

本文說明如何使用預算通知,有選擇性地控管資源用量。

停用專案的計費功能後,所有服務都會停止運作,且最終會刪除所有資源。如果需要更精細的回應,則可選擇性地控制資源。舉例來說,您可以停止部分 Compute Engine 資源,但保留 Cloud Storage 資源。只停止部分資源可降低費用,而不必完全停用環境。

在下列範例中,專案使用了多台 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

    將「Entry point」(進入點) 設為 limitUse

    Python

    將「Entry point」(進入點) 設為 limit_use

  5. 查看自動設定的環境變數清單,判斷是否需要手動將 GCP_PROJECT 變數設為執行虛擬機器的專案。

  6. 設定 ZONE 參數。這個參數是執行個體在超出預算時停止的區域。

  7. 按一下「部署」

設定服務帳戶權限

Cloud Run 函式會以自動建立的服務帳戶執行。如要控制用量,請完成下列步驟,將服務帳戶權限授予給專案中需要修改的服務:

  1. 如要識別正確的服務帳戶,請查看 Cloud Run 函式的詳細資料。服務帳戶會列在頁面底部。
  2. 前往 Google Cloud 控制台的「IAM」IAM頁面,設定適當的權限。

    前往「IAM」頁面

測試執行個體是否已停止

如要確保函式正常運作,請按照「測試 Cloud Run 函式」一文中的步驟操作。

如果成功, Google Cloud 控制台中的 Compute Engine VM 就會停止。

後續步驟

請參閱其他程式輔助通知範例,瞭解如何執行下列操作: