שליטה בשימוש במשאבים באמצעות התראות

במאמר הזה נסביר איך להשתמש בהתראות לגבי תקציבים בשביל שליטה סלקטיבית בשימוש במשאבים.

כשמשביתים את החיוב בפרויקט, כל השירותים מופסקים, ובסופו של דבר כל המשאבים נמחקים. אם אתם לא רוצים להגיב בצורה כל כך גורפת, אתם יכולים לשלוט במשאבים באופן סלקטיבי. לדוגמה, אתם יכולים להפסיק כמה משאבי Compute Engine בלי שתהיה לכך השפה על משאבי Cloud Storage. השבתה של חלק מהמשאבים מצמצמת את העלויות בלי להשבית את הסביבה לחלוטין.

בדוגמה שכאן, בפרויקט מופעל מחקר בכמה מכונות וירטואליות (VM) של Compute Engine, והתוצאות מאוחסנות בקטגוריות של Cloud Storage. כשמשתמשים בהתראות לגבי תקציב בתור טריגר, פונקציית Cloud Run הזאת משביתה את כל המכונות של Compute Engine אחרי חריגה מהתקציב, אבל לא לפעולה הזאת אין השפעה על התוצאות שמאוחסנות.

לפני שמתחילים

לפני שמתחילים צריך לבצע את המשימות האלה:

  1. הפעלת Cloud Billing API
  2. יצירת תקציב
  3. יצירת התראות פרוגרמטיות לגבי תקציבים

הגדרת פונקציית Cloud Run

  1. אתם יכולים להיעזר בהוראות שבמאמר יצירה של פונקציית Cloud Run. חשוב לוודא שמגדירים את Trigger type לנושא 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 <[email protected]>",
      "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. מגדירים את Entry point לפונקציה הנכונה להרצה:

    Node.js

    מגדירים את Entry point ל-limitUse.

    Python

    מגדירים את Entry point ל-limit_use.

  5. בודקים את רשימת משתני הסביבה שמוגדרים אוטומטית כדי להחליט אם צריכים להגדיר ידנית את המשתנה GCP_PROJECT לפרויקט שהמכונות הווירטואליות רצות בו.

  6. מגדירים את הפרמטר ZONE. הפרמטר הזה הוא התחום (zone) שבו המכונות מופסקות כשיש חריגה מהתקציב.

  7. לוחצים על DEPLOY.

הגדרת הרשאות לחשבון שירות

פונקציית Cloud Run פועלת בתור חשבון שירות שנוצר אוטומטית. כדי לשלוט בשימוש, צריך לתת לחשבון השירות הרשאות לכל השירותים בפרויקט שהוא אמור לשנות. כדי לעשות את זה, מבצעים את הפעולות האלה:

  1. כדי לזהות את חשבון השירות הנכון, אתם יכולים לבדוק את הפרטים של פונקציית Cloud Run. חשבון השירות מופיע בחלק התחתון של הדף.
  2. נכנסים לדף IAM במסוף Google Cloud כדי להגדיר את ההרשאות המתאימות.

    כניסה לדף IAM

בדיקה שהמכונות הופסקו

כדי לוודא שהפונקציה פועלת כמו שצריך, מבצעים את הפעולות של בדיקת פונקציית Cloud Run.

אם הפעולה מתבצעת, ה-VM של Compute Engine במסוף Google Cloud מופסקות.

המאמרים הבאים

דוגמאות נוספות להתראות פרוגרמטיות, שיעזרו לכם ללמוד איך מבצעים את הפעולות הבאות: