תחילת העבודה עם פונקציות Cloud Run

1. מבוא

סקירה כללית

Cloud Run functions הוא שירות Functions-as-a-Service של Google Cloud שמבוסס על Cloud Run ועל Eventarc. השירות הזה מאפשר לכם שליטה מתקדמת יותר בביצועים ובמדרגיות, ושליטה רבה יותר בזמן הריצה של הפונקציות ובטריגרים ממעל 90 מקורות אירועים.

ב-codelab הזה נסביר איך ליצור פונקציות Cloud Run שמגיבות לקריאות HTTP, ומופעלות על ידי הודעות Pub/Sub ויומני Cloud Audit Logs.

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

אם אתם מעדיפים לא להשתמש בעדכונים אוטומטיים של תמונות בסיס, אתם יכולים להסיר את הדגל --base-image מהדוגמאות שמוצגות ב-codelab הזה.

מה תלמדו

  • סקירה כללית של פונקציות Cloud Run והסבר על השימוש בעדכונים אוטומטיים של תמונות בסיס.
  • איך כותבים פונקציה שמגיבה לקריאות HTTP.
  • איך כותבים פונקציה שמגיבה להודעות Pub/Sub.
  • איך כותבים פונקציה שמגיבה לאירועים ב-Cloud Storage.
  • איך לפצל את התנועה בין שתי גרסאות.
  • איך להימנע מהפעלות במצב התחלתי (cold start) באמצעות מספר מינימלי של מופעים.

2. הגדרה ודרישות

יצירת תיקיית בסיס

יוצרים תיקיית שורש לכל הדוגמאות.

mkdir crf-codelab
cd crf-codelab

הגדרה של משתני סביבה

מגדירים משתני סביבה שישמשו לאורך כל ה-codelab.

gcloud config set project <YOUR-PROJECT-ID>
REGION=<YOUR_REGION>

PROJECT_ID=$(gcloud config get-value project)

הפעלת ממשקי API

מפעילים את כל השירותים הנדרשים:

gcloud services enable \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  eventarc.googleapis.com \
  run.googleapis.com \
  logging.googleapis.com \
  pubsub.googleapis.com

3. פונקציית HTTP

בפונקציה הראשונה, ניצור פונקציית Node.js מאומתת שמגיבה לבקשות HTTP. נשתמש גם בערך timeout של 10 דקות כדי להראות איך פונקציה יכולה לקבל יותר זמן להגיב לבקשות HTTP.

יצירה

יוצרים תיקייה לאפליקציה ועוברים לתיקייה:

mkdir hello-http
cd hello-http

יוצרים קובץ index.js שמגיב לבקשות HTTP:

const functions = require('@google-cloud/functions-framework');

functions.http('helloWorld', (req, res) => {
  res.status(200).send('HTTP with Node.js in Cloud Run functions!');
});

יוצרים קובץ package.json כדי לציין את התלות:

{
  "name": "nodejs-run-functions-codelab",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^2.0.0"
  }
}

Deploy

פורסים את הפונקציה:

gcloud run deploy nodejs-run-function \
      --source . \
      --function helloWorld \
      --base-image nodejs22 \
      --region $REGION \
      --timeout 600 \
      --no-allow-unauthenticated

הפקודה הזו משתמשת ב-buildpacks כדי להפוך את קוד המקור של הפונקציה לקובץ אימג' בקונטיינר שמוכן לייצור.

שימו לב:

  • הדגל --source משמש כדי להנחות את Cloud Run ליצור את הפונקציה כשירות מבוסס-קונטיינר שאפשר להריץ
  • הדגל --function (חדש) משמש להגדרת נקודת הכניסה של השירות החדש לחתימת הפונקציה שרוצים להפעיל
  • הדגל --base-image (חדש) מציין את סביבת תמונת הבסיס של הפונקציה, כמו nodejs22,‏ python312,‏ go123,‏ java21,‏ dotnet8,‏ ruby33 או php83. לפרטים נוספים על תמונות בסיס ועל החבילות שכלולות בכל תמונה, אפשר לעיין במאמר תמונות בסיס של סביבות ריצה.
  • (אופציונלי) הדגל --timeout מאפשר לפונקציה להגדיר זמן קצוב ארוך יותר לתגובה לבקשות HTTP. בדוגמה הזו, נעשה שימוש ב-600 שניות כדי להדגים זמן תגובה של 10 דקות.
  • (אופציונלי) --no-allow-unauthenticated כדי למנוע הפעלה של הפונקציה על ידי הציבור

בדיקה

בודקים את הפונקציה באמצעות הפקודות הבאות:

# get the Service URL
SERVICE_URL="$(gcloud run services describe nodejs-run-function --region $REGION --format 'value(status.url)')"

# invoke the service
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

אמורה להתקבל תגובה עם ההודעה HTTP with Node.js in Cloud Run functions!.

4. פונקציית Pub/Sub

עכשיו ניצור פונקציית Python שנייה שמופעלת על ידי הודעת Pub/Sub שפורסמה בנושא ספציפי.

הגדרת טוקנים לאימות ב-Pub/Sub

אם הפעלתם את חשבון השירות של Pub/Sub ב-8 באפריל 2021 או לפני כן, צריך להקצות את התפקיד iam.serviceAccountTokenCreator לחשבון השירות של Pub/Sub:

PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member  serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
  --role roles/iam.serviceAccountTokenCreator

יצירה

יוצרים נושא Pub/Sub לשימוש בדוגמה:

TOPIC=cloud-run-functions-pubsub-topic
gcloud pubsub topics create $TOPIC

יוצרים תיקייה לאפליקציה ועוברים לתיקייה:

mkdir ../hello-pubsub
cd ../hello-pubsub

יוצרים קובץ main.py שמתעד הודעה שמכילה את מזהה CloudEvent:

import functions_framework

@functions_framework.cloud_event
def hello_pubsub(cloud_event):
   print('Pub/Sub with Python in Cloud Run functions! Id: ' + cloud_event['id'])

כדי לציין את התלות, יוצרים קובץ requirements.txt עם התוכן הבא:

functions-framework==3.*

Deploy

פורסים את הפונקציה:

gcloud run deploy python-pubsub-function \
       --source . \
       --function hello_pubsub \
       --base-image python313 \
       --region $REGION \
       --no-allow-unauthenticated

מאחזרים את מספר הפרויקט שישמש לזהות של חשבון השירות.

PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')

יצירת הטריגר

gcloud eventarc triggers create python-pubsub-function-trigger  \
    --location=$REGION \
    --destination-run-service=python-pubsub-function  \
    --destination-run-region=$REGION \
    --event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
    --transport-topic=projects/$PROJECT_ID/topics/$TOPIC \
    --service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com

בדיקה

כדי לבדוק את הפונקציה, שולחים הודעה לנושא:

gcloud pubsub topics publish $TOPIC --message="Hello World"

האירוע CloudEvent שהתקבל אמור להופיע ביומנים:

gcloud run services logs read python-pubsub-function --region $REGION --limit=10

5. פונקציה של Cloud Storage

בפונקציה הבאה, ניצור פונקציית Node.js שמגיבה לאירועים מקטגוריית Cloud Storage.

הגדרה

כדי להשתמש בפונקציות של Cloud Storage, צריך להקצות את התפקיד pubsub.publisher ב-IAM לחשבון השירות של Cloud Storage:

SERVICE_ACCOUNT=$(gsutil kms serviceaccount -p $PROJECT_NUMBER)

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT \
  --role roles/pubsub.publisher

יצירה

יוצרים תיקייה לאפליקציה ועוברים לתיקייה:

mkdir ../hello-storage
cd ../hello-storage

יוצרים קובץ index.js שמגיב לאירועים ב-Cloud Storage:

const functions = require('@google-cloud/functions-framework');

functions.cloudEvent('helloStorage', (cloudevent) => {
  console.log('Cloud Storage event with Node.js in Cloud Run functions!');
  console.log(cloudevent);
});

יוצרים קובץ package.json כדי לציין את התלות:

{
  "name": "nodejs-crf-cloud-storage",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^2.0.0"
  }
}

Deploy

קודם כול, יוצרים קטגוריה של Cloud Storage (או משתמשים בקטגוריה קיימת):

export BUCKET_NAME="gcf-storage-$PROJECT_ID"
​​export BUCKET="gs://gcf-storage-$PROJECT_ID"
gsutil mb -l $REGION $BUCKET

פורסים את הפונקציה:

gcloud run deploy nodejs-crf-cloud-storage \
 --source . \
 --base-image nodejs22 \
 --function helloStorage \
 --region $REGION \
 --no-allow-unauthenticated

אחרי פריסת הפונקציה, אפשר לראות אותה בקטע Cloud Run ב-Cloud Console.

עכשיו יוצרים את הטריגר של Eventarc.

BUCKET_REGION=$REGION

gcloud eventarc triggers create nodejs-crf-cloud-storage-trigger \
  --location=$BUCKET_REGION \
  --destination-run-service=nodejs-crf-cloud-storage \
  --destination-run-region=$REGION \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=$BUCKET_NAME" \
  --service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com

בדיקה

כדי לבדוק את הפונקציה, מעלים קובץ לקטגוריה:

echo "Hello World" > random.txt
gsutil cp random.txt $BUCKET/random.txt

האירוע CloudEvent שהתקבל אמור להופיע ביומנים:

gcloud run services logs read nodejs-crf-cloud-storage --region $REGION --limit=10

6. יומני ביקורת של Cloud

בפונקציה הבאה, ניצור פונקציית Node.js שמקבלת אירוע של Cloud Audit Log כשנוצרת מכונה וירטואלית ב-Compute Engine. בתגובה, המערכת מוסיפה תווית למכונת ה-VM החדשה שנוצרה, ומציינת את היוצר של מכונת ה-VM.

איך מזהים מכונות וירטואליות חדשות שנוצרו ב-Compute Engine

‫Compute Engine פולט 2 יומני ביקורת כשנוצרת מכונה וירטואלית.

האירוע הראשון מופק בתחילת יצירת מכונת ה-VM. השני נפלט אחרי שהמכונה הווירטואלית נוצרת.

בשדות של הפעולות ביומני הביקורת יש ערכים שונים, first: true ו-last: true. יומן הביקורת השני מכיל את כל המידע שדרוש לנו כדי לסמן מופע, ולכן נשתמש בדגל last: true כדי לזהות אותו בפונקציות של Cloud Run.

הגדרה

כדי להשתמש בפונקציות של יומן הביקורת של Cloud, צריך להפעיל את יומני הביקורת ב-Eventarc. צריך גם להשתמש בחשבון שירות עם התפקיד eventarc.eventReceiver.

  1. מפעילים את סוגי היומנים Admin Read,‏ Data Read ו-Data Write של Compute Engine API ב-Cloud Audit Logs.
  2. מקצים לחשבון השירות של Compute Engine שמוגדר כברירת מחדל את התפקיד eventarc.eventReceiver ב-IAM:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

יצירת הפונקציה

ב-Codelab הזה נעשה שימוש ב-node.js, אבל אפשר למצוא דוגמאות נוספות בכתובת https://github.com/GoogleCloudPlatform/eventarc-samples

יצירת קובץ package.json

{
  "dependencies": {
    "googleapis": "^84.0.0"
  }
}

יצירת קובץ node.js

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const { google } = require("googleapis");
var compute = google.compute("v1");

exports.labelVmCreation = async (cloudevent) => {
  const data = cloudevent.body;

  // in case an event has >1 audit log
  // make sure we respond to the last event
  if (!data.operation || !data.operation.last) {
    console.log("Operation is not last, skipping event");
    return;
  }

  // projects/dogfood-gcf-saraford/zones/us-central1-a/instances/instance-1
  var resourceName = data.protoPayload.resourceName;
  var resourceParts = resourceName.split("/");
  var project = resourceParts[1];
  var zone = resourceParts[3];
  var instanceName = resourceParts[5];
  var username = data.protoPayload.authenticationInfo.principalEmail.split("@")[0];

  console.log(`Setting label username: ${username} to instance ${instanceName} for zone ${zone}`);

  var authClient = await google.auth.getClient({
    scopes: ["https://www.googleapis.com/auth/cloud-platform"]
  });

  // per docs: When updating or adding labels in the API,
  // you need to provide the latest labels fingerprint with your request,
  // to prevent any conflicts with other requests.
  var labelFingerprint = await getInstanceLabelFingerprint(authClient, project, zone, instanceName);

  var responseStatus = await setVmLabel(
    authClient,
    labelFingerprint,
    username,
    project,
    zone,
    instanceName
  );

  // log results of setting VM label
  console.log(JSON.stringify(responseStatus, null, 2));
};

async function getInstanceLabelFingerprint(authClient, project, zone, instanceName) {
  var request = {
    project: project,
    zone: zone,
    instance: instanceName,
    auth: authClient
  };

  var response = await compute.instances.get(request);
  var labelFingerprint = response.data.labelFingerprint;
  return labelFingerprint;
}

async function setVmLabel(authClient, labelFingerprint, username, project, zone, instanceName) {
  var request = {
    project: project,
    zone: zone,
    instance: instanceName,

    resource: {
      labels: { "creator": username },
      labelFingerprint: labelFingerprint
    },

    auth: authClient
  };

  var response = await compute.instances.setLabels(request);
  return response.statusText;
}

Deploy

פורסים את הפונקציה:

gcloud run deploy gce-vm-labeler \
  --source . \
  --function labelVmCreation \
  --region $REGION \
  --no-allow-unauthenticated

עכשיו יוצרים את הטריגר. שימו לב איך הפונקציה מסננת ביומני הביקורת את הוספות של Compute Engine עם הדגל --trigger-event-filters.

gcloud eventarc triggers create gce-vm-labeler-trigger \
  --location=$REGION \
  --destination-run-service=gce-vm-labeler \
  --destination-run-region=$REGION \
  --event-filters="type=google.cloud.audit.log.v1.written,serviceName=compute.googleapis.com,methodName=v1.compute.instances.insert" \
  --service-account=$ROJECT_NUMBER-compute@developer.gserviceaccount.com

בדיקה

מגדירים את משתני הסביבה:

# if you're using europe-west1 as your region
ZONE=europe-west1-d
VM_NAME=codelab-crf-auditlog

מריצים את הפקודה הבאה כדי ליצור מכונת VM:

gcloud compute instances create $VM_NAME --zone=$ZONE --machine-type=e2-medium --image-family=debian-11  --image-project=debian-cloud

אחרי שהמכונה הווירטואלית נוצרת, התווית creator שנוספה אמורה להופיע במכונה הווירטואלית במסוף Cloud בקטע Basic information או באמצעות הפקודה הבאה:

gcloud compute instances describe $VM_NAME --zone=$ZONE

התווית אמורה להופיע בפלט כמו בדוגמה הבאה:

...
labelFingerprint: ULU6pAy2C7s=
labels:
  creator: atameldev
...

הסרת המשאבים

חשוב למחוק את מופע ה-VM. לא נשתמש בו שוב במעבדה הזו.

gcloud compute instances delete $VM_NAME --zone=$ZONE

7. פיצול תנועה

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

בשלב הזה תפרסו 2 עדכונים של פונקציה ואז תחלקו את התנועה ביניהם באופן שווה.

יצירה

יוצרים תיקייה לאפליקציה ועוברים לתיקייה:

mkdir ../traffic-splitting
cd ../traffic-splitting

יוצרים קובץ main.py עם פונקציית Python שקוראת משתנה סביבה של צבע ומחזירה את התגובה Hello World בצבע הרקע הזה:

import os

color = os.environ.get('COLOR')

def hello_world(request):
    return f'<body style="background-color:{color}"><h1>Hello World!</h1></body>'

כדי לציין את התלות, יוצרים קובץ requirements.txt עם התוכן הבא:

functions-framework==3.*

Deploy

פורסים את הגרסה הראשונה של הפונקציה עם רקע כתום:

COLOR=orange
gcloud run deploy hello-world-colors \
 --source . \
 --base-image python313 \
 --function hello_world \
 --region $REGION \
 --allow-unauthenticated \
 --update-env-vars COLOR=$COLOR

בשלב הזה, אם תבדקו את הפונקציה על ידי הצגת טריגר ה-HTTP (פלט ה-URI של פקודת הפריסה שלמעלה) בדפדפן, אמור להופיע Hello World עם רקע כתום:

36ca0c5f39cc89cf.png

פורסים את הגרסה השנייה עם רקע צהוב:

COLOR=yellow
gcloud run deploy hello-world-colors \
 --source . \
 --base-image python313 \
 --function hello_world \
 --region $REGION \
 --allow-unauthenticated \
 --update-env-vars COLOR=$COLOR

מכיוון שזו הגרסה האחרונה, אם תבדקו את הפונקציה, תראו את הערך Hello World עם רקע צהוב:

391286a08ad3cdde.png

פיצול התנועה 50-50

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

gcloud run revisions list --service hello-world-colors \
  --region $REGION --format 'value(REVISION)'

הפלט אמור להיראות כך:

hello-world-colors-00001-man
hello-world-colors-00002-wok

עכשיו, מחלקים את התנועה בין שני העדכונים האלה באופן הבא (צריך לעדכן את X-XXX בהתאם לשמות העדכונים):

gcloud run services update-traffic hello-world-colors \
  --region $REGION \
  --to-revisions hello-world-colors-0000X-XXX=50,hello-world-colors-0000X-XXX=50

בדיקה

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

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

מידע נוסף זמין במאמר בנושא החזרות, השקות הדרגתיות והעברת תנועה.

8. מינימום מכונות

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

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

יצירה

יוצרים תיקייה לאפליקציה ועוברים אליה:

mkdir ../min-instances
cd ../min-instances

יוצרים קובץ main.go. בשירות Go הזה יש פונקציה init שמשהה את הפעולה למשך 10 שניות כדי לדמות הפעלה ארוכה. יש לה גם פונקציה HelloWorld שמגיבה לקריאות HTTP:

package p

import (
        "fmt"
        "net/http"
        "time"
)

func init() {
        time.Sleep(10 * time.Second)
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Slow HTTP Go in Cloud Run functions!")
}

Deploy

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

gcloud run deploy go-slow-function \
 --source . \
 --base-image go123 \
 --function HelloWorld \
 --region $REGION \
 --no-allow-unauthenticated

בודקים את הפונקציה באמצעות הפקודה הבאה:

# get the Service URL
SERVICE_URL="$(gcloud run services describe go-slow-function --region $REGION --format 'value(status.url)')"

# invoke the service
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

בשיחה הראשונה תהיה השהיה של 10 שניות (התנעה קרה) ואז תוצג ההודעה. קריאות עוקבות אמורות לחזור באופן מיידי.

הגדרת מספר מינימלי של מופעים

כדי להימנע מההפעלה הקרה בבקשה הראשונה, צריך לפרוס מחדש את הפונקציה עם הערך 1 של האפשרות --min-instances, באופן הבא:

gcloud run deploy go-slow-function \
 --source . \
 --base-image go123 \
 --function HelloWorld \
 --region $REGION \
 --no-allow-unauthenticated \
 --min-instances 1

בדיקה

בודקים שוב את הפונקציה:

curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

העיכוב של 10 שניות לא אמור להופיע יותר בבקשה הראשונה. בעיית ההפעלה במצב התחלתי (cold start) בקריאה הראשונה (אחרי זמן רב ללא קריאה) נפתרה, בזכות המינימום של המופעים.

מידע נוסף זמין במאמר בנושא שימוש במופעים מינימליים.

9. מעולה!

כל הכבוד, סיימתם את ה-Codelab!

מה למדנו

  • סקירה כללית של פונקציות Cloud Run והסבר על השימוש בעדכונים אוטומטיים של תמונות בסיס.
  • איך כותבים פונקציה שמגיבה לקריאות HTTP.
  • איך כותבים פונקציה שמגיבה להודעות Pub/Sub.
  • איך כותבים פונקציה שמגיבה לאירועים ב-Cloud Storage.
  • איך לפצל את התנועה בין שתי גרסאות.
  • איך להימנע מהפעלות במצב התחלתי (cold start) באמצעות מספר מינימלי של מופעים.