Mengontrol penggunaan resource dengan notifikasi

Dokumen ini menjelaskan cara menggunakan notifikasi anggaran untuk mengontrol penggunaan resource secara selektif.

Saat Anda menonaktifkan penagihan di project, semua layanan akan berhenti dan semua resource pada akhirnya akan dihapus. Jika memerlukan respons yang lebih terperinci, Anda dapat mengontrol resource secara selektif. Misalnya, Anda dapat menghentikan beberapa resource Compute Engine sekaligus membiarkan resource Cloud Storage tetap utuh. Menghentikan hanya beberapa resource akan mengurangi biaya tanpa menonaktifkan lingkungan Anda sepenuhnya.

Dalam contoh berikut, project menjalankan riset dengan sejumlah VM Compute Engine dan menyimpan hasilnya di bucket Cloud Storage. Dengan menggunakan notifikasi anggaran sebagai pemicu, setelah anggaran terlampaui, fungsi Cloud Run ini akan menonaktifkan semua instance Compute Engine, tetapi tidak memengaruhi hasil yang disimpan.

Sebelum memulai

Sebelum memulai, Anda harus menyelesaikan tugas-tugas berikut:

  1. Mengaktifkan Cloud Billing API
  2. Buat Anggaran
  3. Menyiapkan notifikasi anggaran terprogram

Menyiapkan fungsi Cloud Run

  1. Selesaikan langkah-langkah di Membuat fungsi Cloud Run. Pastikan Anda menetapkan Jenis pemicu ke topik Pub/Sub yang sama dengan yang akan digunakan 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": ">=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

    Salin kode berikut ke file requirements.txt Anda:

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

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

    Node.js

    Tetapkan Entry point ke limitUse.

    Python

    Tetapkan Entry point ke limit_use.

  5. Tinjau daftar variabel lingkungan yang ditetapkan secara otomatis dan tentukan apakah Anda perlu menetapkan variabel GCP_PROJECT secara manual ke project yang menjalankan virtual machine.

  6. Tetapkan parameter ZONE. Parameter ini adalah zona tempat instance dihentikan saat anggaran terlampaui.

  7. Klik DEPLOY.

Mengonfigurasi izin akun layanan

Fungsi Cloud Run Anda berjalan sebagai akun layanan yang dibuat secara otomatis. Untuk mengontrol penggunaan, Anda perlu memberikan izin akun layanan ke layanan apa pun di project yang perlu diubah dengan menyelesaikan langkah-langkah berikut:

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

    Buka halaman IAM

Menguji apakah instance dihentikan

Untuk memastikan fungsi Anda berfungsi seperti yang diharapkan, ikuti langkah-langkah di Menguji fungsi Cloud Run.

Jika berhasil, VM Compute Engine Anda di konsol Google Cloud akan dihentikan.

Langkah berikutnya

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