מבוא לתזמור ללא שרת (serverless) עם Workflows

1. מבוא

c9b0cc839df0bb8f.png

אפשר להשתמש בתהליכי עבודה כדי ליצור תהליכי עבודה ללא שרת (serverless) שמקשרים יחד סדרה של משימות ללא שרת (serverless), לפי סדר ההגדרה. ההגדרה הזו מאפשרת לשלב את העוצמה של ממשקי ה-API של Google Cloud, מוצרים ללא שרת (serverless) כמו Cloud Functions ו-Cloud Run, וקריאות לממשקי API חיצוניים כדי ליצור אפליקציות גמישות ללא שרת (serverless).

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

ב-Codelab הזה תלמדו איך לחבר בין שירותים שונים של Google Cloud וממשקי API חיצוניים של HTTP באמצעות Workflows. באופן ספציפי יותר, חיבור לתהליך העבודה שני שירותי Cloud Functions ציבוריים, שירות Cloud Run פרטי אחד ו-API חיצוני של HTTP.

מה תלמדו

  • העקרונות הבסיסיים של תהליכי עבודה.
  • איך מקשרים פונקציות ציבוריות של Cloud Functions עם Workflows.
  • איך לחבר שירותים פרטיים של Cloud Run ל-Workflows.
  • איך לחבר ממשקי API חיצוניים של HTTP ל-Workflows.

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

הגדרת סביבה בקצב עצמאי

  1. נכנסים למסוף Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. (אם עדיין אין לכם חשבון Gmail או G Suite, עליכם ליצור חשבון).

H_hgylo4zxOllHaAbPKJ7VyqCKPDUnDhkr-BsBIFBsrB6TYSisg6LX-uqmMhh4sXUy_hoa2Qv87C2nFmkg-QAcCiZZp0qtpf6VPaNEEfP_iqt29KVLD-gklBWugQVeOWsFnJmNjHDw

dcCPqfBIwNO4R-0fNQLUC4aYXOOZhKhjUnakFLZJGeziw2ikOxGjGkCHDwN5x5kCbPFB8fiOzZnX-GfuzQ8Ox-UU15BwHirkVPR_0RJwl0oXrhqZmMIvZMa_uwHugBJIdx5-bZ6Z8Q

jgLzVCxk93d6E2bbonzATKA4jFZReoQ-fORxZZLEi5C3D-ubnv6nL-eP-iyh7qAsWyq_nyzzuEoPFD1wFOFZOe4FWhPBJjUDncnTxTImT3Ts9TM54f4nPpsAp52O0y3Cb19IceAEgQ

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

  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים של Google Cloud.

מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. חשוב לבצע את כל ההוראות בקטע 'ניקוי' שמסביר איך להשבית משאבים כדי שלא תצברו חיובים מעבר למדריך הזה. משתמשים חדשים ב-Google Cloud זכאים להשתתף בתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

הפעלת Cloud Shell

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

ממסוף GCP, לוחצים על הסמל של Cloud Shell בסרגל הכלים שבפינה השמאלית העליונה:

STgwiN06Y0s_gL7i9bTed8duc9tWOIaFw0z_4QOjc-jeOmuH2TBK8l4udei56CKPLoM_i1yEF6pn5Ga88eniJQoEh8cAiTH79gWUHJdKOw0oiBZfBpOdcEOl6p29i4mvPe_A6UMJBQ

נדרשים רק כמה דקות כדי להקצות את הסביבה ולהתחבר אליה. בסיום התהליך, אתם אמורים לראות משהו כזה:

r6WRHJDzL-GdB5VDxMWa67_cQxRR_x_xCG5xdt9Nilfuwe9fTGAwM9XSZbNPWvDSFtrZ7DDecKqR5_pIq2IJJ9puAMkC3Kt4JbN9jfMX3gAwTNHNqFmqOJ-3iIX5HSePO4dNVZUkNA

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

3. סקירה כללית של תהליכי עבודה

היסודות

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

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

הפעלת שירותים

ב-Codelab הזה, יתבצע חיבור של שירותי Cloud Functions, שירותי Cloud Run ו-Workflows. תשתמשו גם ב-Cloud Build וב-Cloud Storage במהלך פיתוח השירותים.

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

gcloud services enable \
  cloudfunctions.googleapis.com \
  run.googleapis.com \
  workflows.googleapis.com \
  cloudbuild.googleapis.com \
  storage.googleapis.com

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

4. פריסת הפונקציה הראשונה של Cloud Functions

הפונקציה הראשונה היא מחולל מספרים אקראיים ב-Python.

יוצרים ספרייה שבה מופיע קוד הפונקציה ועוברים אליה:

mkdir ~/randomgen
cd ~/randomgen

יוצרים בספרייה קובץ main.py עם התוכן הבא:

import random, json
from flask import jsonify

def randomgen(request):
    randomNum = random.randint(1,100)
    output = {"random":randomNum}
    return jsonify(output)

כשהיא מקבלת בקשת HTTP, הפונקציה הזו יוצרת מספר אקראי בין 1 ל-100 ומחזירה אל המתקשר בפורמט JSON.

הפונקציה מסתמכת על Flask לעיבוד HTTP, ואנחנו צריכים להוסיף זאת כתלות. יחסי התלות ב-Python מנוהלים באמצעות PIP ומבוטאים בקובץ מטא-נתונים שנקרא requirements.txt.

יוצרים קובץ requirements.txt באותה ספרייה עם התוכן הבא:

flask>=1.0.2

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

gcloud functions deploy randomgen \
    --runtime python37 \
    --trigger-http \
    --allow-unauthenticated

אחרי שהפונקציה פרוסה, כתובת ה-URL של הפונקציה מופיעה בנכס httpsTrigger.url שמוצגת במסוף או מוצגת באמצעות הפקודה gcloud functions describe.

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

curl $(gcloud functions describe randomgen --format='value(httpsTrigger.url)')

הפונקציה מוכנה לתהליך העבודה.

5. פריסת הפונקציה השנייה של Cloud Functions

הפונקציה השנייה היא מכפיל. היא מכפילה את הקלט שהתקבל ב-2.

יוצרים ספרייה שבה מופיע קוד הפונקציה ועוברים אליה:

mkdir ~/multiply
cd ~/multiply

יוצרים בספרייה קובץ main.py עם התוכן הבא:

import random, json
from flask import jsonify

def multiply(request):
    request_json = request.get_json()
    output = {"multiplied":2*request_json['input']}
    return jsonify(output)

כשהיא מקבלת בקשת HTTP, הפונקציה מחלצת את input מגוף ה-JSON, מכפילה אותו ב-2 ומחזירה אותו בפורמט JSON חזרה למבצע הקריאה.

יוצרים את אותו קובץ requirements.txt באותה ספרייה עם התכנים הבאים:

flask>=1.0.2

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

gcloud functions deploy multiply \
    --runtime python37 \
    --trigger-http \
    --allow-unauthenticated

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

curl $(gcloud functions describe multiply --format='value(httpsTrigger.url)') \
-X POST \
-H "content-type: application/json" \
-d '{"input": 5}'

הפונקציה מוכנה לתהליך העבודה.

6. חיבור שתי פונקציות של Cloud Functions

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

יצירת קובץ workflow.yaml עם התכנים הבאים.

- randomgenFunction:
    call: http.get
    args:
        url: https://<region>-<project-id>.cloudfunctions.net/randomgen
    result: randomgenResult
- multiplyFunction:
    call: http.post
    args:
        url: https://<region>-<project-id>.cloudfunctions.net/multiply
        body:
            input: ${randomgenResult.body.random}
    result: multiplyResult
- returnResult:
    return: ${multiplyResult}

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

פורסים את תהליך העבודה הראשון:

gcloud workflows deploy workflow --source=workflow.yaml

מפעילים את תהליך העבודה הראשון:

gcloud workflows execute workflow

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

gcloud workflows executions describe <your-execution-id> --workflow workflow

הפלט יכלול את result ואת state:

result: '{"body":{"multiplied":108},"code":200 ... } 

...
state: SUCCEEDED

7. חיבור ממשק API חיצוני של HTTP

בשלב הבא, תחבר את math.js בתור שירות חיצוני בתהליך העבודה.

ב-math.js, אפשר להעריך ביטויים מתמטיים כמו בדוגמה הבאה:

curl https://api.mathjs.org/v4/?'expr=log(56)'

הפעם תשתמשו ב-Cloud Console כדי לעדכן את תהליך העבודה שלנו. מוצאים את Workflows במסוף Google Cloud:

7608a7991b33bbb0.png

מאתרים את תהליך העבודה ולוחצים על הכרטיסייה Definition:

f3c8c4d3ffa49b1b.png

צריך לערוך את ההגדרה של תהליך העבודה ולכלול קריאה אל math.js.

- randomgenFunction:
    call: http.get
    args:
        url: https://<region>-<project-id>.cloudfunctions.net/randomgen
    result: randomgenResult
- multiplyFunction:
    call: http.post
    args:
        url: https://<region>-<project-id>.cloudfunctions.net/multiply
        body:
            input: ${randomgenResult.body.random}
    result: multiplyResult
- logFunction:
    call: http.get
    args:
        url: https://api.mathjs.org/v4/
        query:
            expr: ${"log(" + string(multiplyResult.body.multiplied) + ")"}
    result: logResult
- returnResult:
    return: ${logResult}

תהליך העבודה מזין עכשיו את הפלט של פונקציית ההכפלה לקריאה של פונקציית יומן ב-math.js.

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

b40c76ee43a1ce65.png

שימו לב לקוד הסטטוס 200 ול-body עם הפלט של פונקציית היומן.

שילבת שירות חיצוני בתהליך העבודה שלנו, ממש מגניב!

8. פריסת שירות של Cloud Run

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

השירות Cloud Run מחזיר את הערך math.floor של המספר שהועבר.

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

mkdir ~/floor
cd ~/floor

יוצרים בספרייה קובץ app.py עם התוכן הבא:

import json
import logging
import os
import math

from flask import Flask, request

app = Flask(__name__)

@app.route('/', methods=['POST'])
def handle_post():
    content = json.loads(request.data)
    input = float(content['input'])
    return f"{math.floor(input)}", 200

if __name__ != '__main__':
    # Redirect Flask logs to Gunicorn logs
    gunicorn_logger = logging.getLogger('gunicorn.error')
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)
    app.logger.info('Service started...')
else:
    app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

ב-Cloud Run המערכת פורסת קונטיינרים, לכן צריך Dockerfile, והקונטיינר צריך להיות מקושר למשתנה env של 0.0.0.0 ו-PORT, ולכן נוצר הקוד שלמעלה.

כשהיא מקבלת בקשת HTTP, הפונקציה הזו מחלצת את input מגוף ה-JSON, קוראת ל-mat.floor ומחזירה את התוצאה חזרה למבצע הקריאה.

באותה ספרייה, יוצרים את קובץ ה-Dockerfile הבא:

# Use an official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim

# Install production dependencies.
RUN pip install Flask gunicorn

# Copy local code to the container image.
WORKDIR /app
COPY . .

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind 0.0.0.0:8080 --workers 1 --threads 8 app:app

יוצרים את מאגר התגים:

export SERVICE_NAME=floor
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/${SERVICE_NAME}

לאחר יצירת הקונטיינר, פורסים אותו ב-Cloud Run. שימו לב לדגל no-allow-unauthenticated. כך אפשר לוודא שהשירות מקבל רק קריאות מאומתות:

gcloud run deploy ${SERVICE_NAME} \
  --image gcr.io/${GOOGLE_CLOUD_PROJECT}/${SERVICE_NAME} \
  --platform managed \
  --no-allow-unauthenticated

לאחר הפריסה, השירות מוכן לתהליך העבודה.

9. חיבור שירות Cloud Run

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

export SERVICE_ACCOUNT=workflows-sa
gcloud iam service-accounts create ${SERVICE_ACCOUNT}

מקצים לחשבון השירות את התפקיד run.invoker. כך חשבון השירות יוכל לקרוא לשירותי Cloud Run מאומתים:

gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
    --member "serviceAccount:${SERVICE_ACCOUNT}@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
    --role "roles/run.invoker"

עדכון, הגדרת תהליך העבודה ב-workflow.yaml כך שתכלול את שירות Cloud Run. שימו לב איך כוללים גם את השדה auth, כדי לוודא ש-Workflows מעביר את אסימון האימות בקריאות שלו לשירות Cloud Run:

- randomgenFunction:
    call: http.get
    args:
        url: https://<region>-<project-id>.cloudfunctions.net/randomgen
    result: randomgenResult
- multiplyFunction:
    call: http.post
    args:
        url: https://<region>-<project-id>.cloudfunctions.net/multiply
        body:
            input: ${randomgenResult.body.random}
    result: multiplyResult
- logFunction:
    call: http.get
    args:
        url: https://api.mathjs.org/v4/
        query:
            expr: ${"log(" + string(multiplyResult.body.multiplied) + ")"}
    result: logResult
- floorFunction:
    call: http.post
    args:
        url: https://floor-<random-hash>.run.app
        auth:
            type: OIDC
        body:
            input: ${logResult.body}
    result: floorResult
- returnResult:
    return: ${floorResult}

עדכון תהליך העבודה. המועד הזה עובר בחשבון השירות:

gcloud workflows deploy workflow \
    --source=workflow.yaml \
    --service-account=${SERVICE_ACCOUNT}@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com

מפעילים את תהליך העבודה:

gcloud workflows execute workflow

בעוד כמה שניות ניתן לבדוק את ביצוע תהליך העבודה כדי לראות את התוצאה:

gcloud workflows executions describe <your-execution-id> --workflow workflow

הפלט יכלול מספר שלם: result ו-state:

result: '{"body":"5","code":200 ... } 

...
state: SUCCEEDED

10. מעולה!

ברכות על השלמת ה-Codelab.

אילו נושאים דיברנו?

  • העקרונות הבסיסיים של תהליכי עבודה.
  • איך מקשרים פונקציות ציבוריות של Cloud Functions עם Workflows.
  • איך לחבר שירותים פרטיים של Cloud Run ל-Workflows.
  • איך לחבר ממשקי API חיצוניים של HTTP ל-Workflows.