דוגמאות לתשובות אוטומטיות של בקרת עלות

דוגמה לארכיטקטורה

תרשים שמדגים איך משתמשים בהתראות פרוגרמטיות לגבי תקציב כדי ליצור תשובות אוטומטיות של בקרת עלות.
איור 1: המחשת השימוש בהתראות תקציב לשליחת תשובות אוטומטיות של בקרת עלות, באמצעות התראות פרוגרמטיות של Pub/Sub ופונקציות Cloud Run.

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

בעזרת התראות תקציב שמבוססות על נושאי Pub/Sub תוכלו לדעת בזמן אמת מה הסטטוס של תקציב החיוב ב-Cloud, וליהנות מיכולת ההתאמה לעומס, מהגמישות ומהאמינות של תווכה ארגונית מכוונת-הודעות לענן.

במסמך הזה מופיעות דוגמאות והוראות מפורטות לניהול העלויות באופן אוטומטי באמצעות התראות תקציב עם פונקציות Cloud Run.

הגדרה של התראות תקציב

בשלב הראשון מפעילים נושא Pub/Sub לתקציב. להסבר מפורט עיינו במאמר ניהול התראות פרוגרמטיות לגבי תקציב.

אחרי שמפעילים את התראות התקציב, חשוב לזכור:

  • נושא Pub/Sub: זו נקודת הקצה שהוגדרה להתראות התקציב.
  • מזהה התקציב: זהו המזהה הייחודי של התקציב, שמצוין בכל ההתראות. אפשר למצוא את מזהה התקציב בהגדרות התקציב, בקטע Manage notifications. מזהה התקציב מוצג אחרי שבוחרים באפשרות Connect a Pub/Sub topic to this budget.

הקטע Manage notifications במסוף Google Cloud, שבו אפשר לשייך נושא Pub/Sub לתקציב. הקטע כולל את מזהה התקציב, שם הפרויקט ונושא ה-Pub/Sub.

האזנה להתראות

בשלב הבא מאזינים להתראות על ידי הרשמה לנושא ה-Pub/Sub. אם אין אף אפליקציה רשומה, ההודעות שמתפרסמות ב-Pub/Sub לא יימסרו ולא תוכלו לשחזר אותן.

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

איך יוצרים פונקציית Cloud Run

כך יוצרים פונקציית Cloud Run חדשה:

  1. נכנסים לדף Cloud Run functions במסוף Google Cloud.

    כניסה לדף Cloud Run functions

  2. לוחצים על CREATE FUNCTION ונותנים לפונקציה שם שמתייחס לתקציב.

  3. בקטע Trigger בוחרים באפשרות Pub/Sub topic.

  4. בוחרים את הנושא שהגדרתם לתקציב.

  5. מספקים את קוד המקור ואת יחסי התלות שדרושים להרצת הפונקציה.

  6. חשוב לוודא שהגדרתם את Function to execute לשם הנכון של הפונקציה.

הדף Create function בקטע Cloud Run functions במסוף Google Cloud.  הדף כולל את שם הפונקציה, את הקצאת הזיכרון, את סוג הטריגר ואת נושא Pub/Sub שהגדרתם לתקציב.

איך מתארים פונקציית Cloud Run

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

דוגמה: כשפונקציה מופעלת על ידי התראת תקציב, היא עשויה לתעד את ההתראות שהתקבלו ב-Pub/Sub, יחד עם המאפיינים והנתונים שלהן. למידע נוסף קראו את המאמר טריגרים ב-Pub/Sub.

איך רואים את האירועים של פונקציות Cloud Run

אחרי ששומרים את פונקציית Cloud Run, אפשר ללחוץ על VIEW LOGS כדי לראות את התראות התקציב שנרשמו ביומן. הפעולה הזו תציג את היומנים מההפעלות של הפונקציה.

תמונה שבה רואים איפה נמצאת האפשרות View Logs במסך, ואת רשימת האירועים של פונקציית Cloud Run במסוף Google Cloud.

איך בודקים פונקציית Cloud Run

ההתראות נשלחות ל-Pub/Sub, והאפליקציות הרשומות מקבלות את ההודעות. כדי לבדוק התראה לדוגמה ולוודא שהפונקציה פועלת כמו שצריך, צריך לפרסם הודעה ב-Pub/Sub ולהשתמש באובייקט הבא כגוף ההודעה:

{
    "budgetDisplayName": "name-of-budget",
    "alertThresholdExceeded": 1.0,
    "costAmount": 100.01,
    "costIntervalStart": "2019-01-01T00:00:00Z",
    "budgetAmount": 100.00,
    "budgetAmountType": "SPECIFIED_AMOUNT",
    "currencyCode": "USD"
}

אפשר גם להוסיף מאפיינים להודעה, כמו מספר החשבון לחיוב. למידע נוסף, תוכלו להיעזר בכל המאמרים בנושא פורמטים של התראות.

שליחת התראות ל-Slack

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

בדוגמה הזו מוסבר איך להעביר התראות לגבי תקציב ל-Slack. כך, בכל פעם שמתפרסמת התראה לגבי תקציב במערכת החיוב ב-Cloud, יש פונקציית Cloud Run שמפרסמת באמצעות בוט הודעה בערוץ Slack, בסביבת העבודה של הבוט.

איך מגדירים ערוץ והרשאות ב-Slack

בשלב הראשון יוצרים את סביבת העבודה ב-Slack ואת אסימוני המשתמשים שמשמשים את הבוטים כדי לשלוח קריאה ל-Slack API. אסימוני ה-API מנוהלים בכתובת https://api.slack.com/apps. תוכלו להיעזר בהוראות שבמאמר Bot Users באתר Slack.

הגדרת ההתראות ב-Slack.

איך כותבים פונקציית 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. כותבים את הקוד הדרוש או משתמשים בדוגמה שלמטה כדי לפרסם התראות לגבי התקציב בערוץ הצ'אט ב-Slack באמצעות Slack API.

  4. מוודאים שהפרמטרים הבאים של postMessage ב-Slack API מוגדרים בצורה נכונה:

    • אסימון הגישה ל-OAuth של משתמש הבוט
    • שם הערוץ

קוד לדוגמה

Node.js

const slack = require('slack');

// TODO(developer) replace these with your own values
const BOT_ACCESS_TOKEN =
  process.env.BOT_ACCESS_TOKEN || 'xxxx-111111111111-abcdefghidklmnopq';
const CHANNEL = process.env.SLACK_CHANNEL || 'general';

exports.notifySlack = async pubsubEvent => {
  const pubsubAttrs = pubsubEvent.attributes;
  const pubsubData = Buffer.from(pubsubEvent.data, 'base64').toString();
  const budgetNotificationText = `${JSON.stringify(
    pubsubAttrs
  )}, ${pubsubData}`;

  await slack.chat.postMessage({
    token: BOT_ACCESS_TOKEN,
    channel: CHANNEL,
    text: budgetNotificationText,
  });

  return 'Slack notification sent successfully';
};

Python

import base64
import json
import os

import slack
from slack.errors import SlackApiError

# See https://api.slack.com/docs/token-types#bot for more info
BOT_ACCESS_TOKEN = "xxxx-111111111111-abcdefghidklmnopq"
CHANNEL = "C0XXXXXX"

slack_client = slack.WebClient(token=BOT_ACCESS_TOKEN)


def notify_slack(data, context):
    pubsub_message = data

    # For more information, see
    # https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications#notification_format
    try:
        notification_attr = json.dumps(pubsub_message["attributes"])
    except KeyError:
        notification_attr = "No attributes passed in"

    try:
        notification_data = base64.b64decode(data["data"]).decode("utf-8")
    except KeyError:
        notification_data = "No data passed in"

    # This is just a quick dump of the budget data (or an empty string)
    # You can modify and format the message to meet your needs
    budget_notification_text = f"{notification_attr}, {notification_data}"

    try:
        slack_client.api_call(
            "chat.postMessage",
            json={"channel": CHANNEL, "text": budget_notification_text},
        )
    except SlackApiError:
        print("Error posting to Slack")

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

הגבלה (השבתה) של החיוב לצורך הפסקת השימוש

בדוגמה הזו מוסבר איך משביתים את החיוב ב-Cloud כדי להגביל את העלויות ולהפסיק את השימוש בפרויקט. השבתת החיוב בפרויקט תגרום לכך שכל שירותי Google Cloud בפרויקט יופסקו, כולל שירותים בתוכנית ללא תשלום.

למה להשבית את החיוב?

יכול להיות שתצטרכו להגביל את העלויות אם יש מגבלה קשיחה על ההוצאות שלכם ב-Google Cloud. המצב הזה אופייני לסטודנטים, לחוקרים ולמפתחים שעובדים בסביבות ארגז חול. במקרים כאלה כדאי להפסיק לצבור הוצאות, וכשתגיעו למגבלת התקציב תשביתו את כל שירותי Google Cloud ואת השימוש בהם.

לצורך הדוגמה שלנו, אנחנו משתמשים בפרויקט acme-backend-dev שאינו בסביבת ייצור, ועבורו אפשר להשבית את החיוב ב-Cloud באופן בטוח.

הגדרת מגבלת התקציב במסוף Google Cloud.

לפני שמתחילים ליישם את הדוגמה הזו, חשוב לוודא שביצעתם את הפעולות הבאות:

  • הפעלתם את Cloud Billing API. כדי להשבית את החיוב ב-Cloud בפרויקט, פונקציית Cloud Run צריכה להפעיל את Cloud Billing API.

  • הגדרתם תקציב כדי לעקוב אחרי העלויות בפרויקט ולהפעיל התראות לגבי תקציב.

התמונה מציגה רשימה של התראות חיוב ב-Cloud במסוף Google Cloud.

איך כותבים פונקציית Cloud Run

בשלב הבא צריך להגדיר את פונקציית Cloud Run כך שתפעיל את Cloud Billing API. כך, פונקציית Cloud Run תוכל להשבית את החיוב ב-Cloud בפרויקט לדוגמה acme-backend-dev:

  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.

  4. מגדירים את הפונקציה כך שתתבצע עד stopBilling‏ (ב-Node) או עד stop_billing‏ (ב-Python).

  5. בהתאם לסביבת זמן הריצה, יכול להיות שמשתנה הסביבה GOOGLE_CLOUD_PROJECT יוגדר באופן אוטומטי. אתם צריכים לבדוק את רשימת משתני הסביבה שמוגדרים באופן אוטומטי, כדי לקבוע אם צריך להגדיר באופן ידני את המשתנה GOOGLE_CLOUD_PROJECT כך שיכיל את הפרויקט שעבורו אתם רוצים להגביל (להשבית) את החיוב ב-Cloud.

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

import base64
import json
import os

from googleapiclient import discovery

PROJECT_ID = os.getenv("GCP_PROJECT")
PROJECT_NAME = f"projects/{PROJECT_ID}"
def stop_billing(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

    if PROJECT_ID is None:
        print("No project specified with environment variable")
        return

    billing = discovery.build(
        "cloudbilling",
        "v1",
        cache_discovery=False,
    )

    projects = billing.projects()

    billing_enabled = __is_billing_enabled(PROJECT_NAME, projects)

    if billing_enabled:
        __disable_billing_for_project(PROJECT_NAME, projects)
    else:
        print("Billing already disabled")


def __is_billing_enabled(project_name, projects):
    """
    Determine whether billing is enabled for a project
    @param {string} project_name Name of project to check if billing is enabled
    @return {bool} Whether project has billing enabled or not
    """
    try:
        res = projects.getBillingInfo(name=project_name).execute()
        return res["billingEnabled"]
    except KeyError:
        # If billingEnabled isn't part of the return, billing is not enabled
        return False
    except Exception:
        print(
            "Unable to determine if billing is enabled on specified project, assuming billing is enabled"
        )
        return True


def __disable_billing_for_project(project_name, projects):
    """
    Disable billing for a project by removing its billing account
    @param {string} project_name Name of project disable billing on
    """
    body = {"billingAccountName": ""}  # Disable billing
    try:
        res = projects.updateBillingInfo(name=project_name, body=body).execute()
        print(f"Billing disabled: {json.dumps(res)}")
    except Exception:
        print("Failed to disable billing, possibly check permissions")

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

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

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

תמונה שבה רואים איפה נמצאים הפרטים של חשבון השירות בקטע Cloud Run function במסוף Google Cloud.

אפשר לנהל את ההרשאות של האדמין לענייני חיוב בדף Billing במסוף Google Cloud.

כדי לתת לחשבון השירות הרשאות של אדמין של חשבון לחיוב, בוחרים את השם של חשבון השירות.

תמונה שמציגה איפה בוחרים את השם של חשבון השירות ואת התפקיד &#39;אדמין של חשבון לחיוב&#39; בקטע Permissions במסוף Google Cloud.

בדיקה אם החיוב ב-Cloud מושבת

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

תמונה שממחישה שהפרויקט לדוגמה לא מופיע יותר ברשימת הפרויקטים שמקושרים לחשבון לחיוב ב-Cloud. זה מאמת שהחיוב ב-Cloud מושבת בפרויקט.

אפשר להפעיל מחדש באופן ידני את החיוב ב-Cloud לפרויקט במסוף Google Cloud.

שליטה סלקטיבית בשימוש

ההגבלה (השבתה) של החיוב ב-Cloud כפי שמתואר בדוגמה הקודמת היא בינארית וסופית. הפרויקט שלכם יכול להיות באחד משני מצבים: מופעל או מושבת. כשהפרויקט מושבת, כל השירותים מופסקים ובסופו של דבר כל המשאבים נמחקים.

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

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

איך כותבים פונקציית Cloud Run

  1. תוכלו להיעזר בהוראות שבמאמר יצירת פונקציית Cloud Run כדי ליצור פונקציות חדשות. חשוב לוודא שהגדרתם את הטריגר כך שישתמש באותו נושא Pub/Sub שהוגדר לשימוש בתקציב.

  2. מוודאים שהוספתם את יחסי התלות שמתוארים במאמר הגבלה (השבתה) של החיוב לצורך הפסקת השימוש.

  3. מעתיקים את הקוד הבא לפונקציית Cloud Run.

  4. מגדירים את הפונקציה כך שתתבצע עד limitUse‏ (ב-Node) או עד limit_use‏ (ב-Python).

  5. בהתאם לסביבת זמן הריצה, יכול להיות שמשתנה הסביבה GCP_PROJECT יוגדר באופן אוטומטי. אתם צריכים לבדוק את רשימת משתני הסביבה שמוגדרים באופן אוטומטי כדי לקבוע אם צריך להגדיר באופן ידני את המשתנה GCP_PROJECT כך שיכיל את הפרויקט שבו פועלות המכונות הווירטואליות.

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

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}")

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

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

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

כשתישלח התראה לגבי התקציב, המכונות הווירטואליות של Compute Engine יופסקו.

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