מעבר מ-App Engine Blobstore ל-Cloud Storage (מודול 16)

1. סקירה כללית

סדרת ה-codelabs של Serverless Migration Station (מדריכים מעשיים בקצב אישי) והסרטונים הקשורים נועדו לעזור למפתחים של Google Cloud ללא שרת (serverless) לשדרג את האפליקציות שלהם, על ידי הדרכה בתהליך של העברה אחת או יותר, בעיקר משירותים מדור קודם. כך האפליקציות שלכם יהיו ניידות יותר, ויהיו לכם יותר אפשרויות וגמישות. תוכלו לשלב מגוון רחב יותר של מוצרי Cloud ולגשת אליהם, ולשדרג בקלות רבה יותר לגרסאות חדשות יותר של שפות. הסדרה מתמקדת בהתחלה במשתמשים הראשונים של Cloud, בעיקר במפתחים של App Engine (סביבת ברירת המחדל), אבל היא מספיק רחבה כדי לכלול פלטפורמות אחרות ללא שרת (serverless) כמו Cloud Functions ו-Cloud Run, או פלטפורמות אחרות אם רלוונטי.

ב-Codelab הזה תלמדו איך לעבור מ-App Engine Blobstore ל-Cloud Storage. יש גם העברות משתמעות מ:

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

כאן אפשר להבין איך

  • הוספת השימוש ב-API או בספרייה של App Engine Blobstore
  • אחסון העלאות של משתמשים בשירות Blobstore
  • הכנה לשלב הבא במעבר ל-Cloud Storage

מה תצטרכו

סקר

איך תשתמשו במדריך הזה?

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

איך היית מדרג את הניסיון שלך עם Python?

מתחילים בינוניים מתקדמים

איך היית מדרג/ת את חוויית השימוש שלך בשירותי Google Cloud?

מתחילים רמת ביניים מתקדמים

2. רקע

ה-Codelab הזה מתחיל באפליקציה לדוגמה מיחידת לימוד 15, ומדגים איך לעבור מ-Blobstore (ו-NDB) ל-Cloud Storage (ו-Cloud NDB). תהליך ההעברה כולל החלפה של יחסי תלות בחבילות השירות מדור קודם של App Engine, שמאפשרות להעביר את האפליקציות לפלטפורמה אחרת ללא שרת (serverless) ב-Cloud או לפלטפורמת אירוח אחרת, אם רוצים.

ההעברה הזו דורשת קצת יותר מאמץ בהשוואה להעברות האחרות בסדרה הזו. ל-Blobstore יש יחסי תלות ב-webapp framework המקורי, ולכן האפליקציה לדוגמה משתמשת ב-webapp2 framework במקום ב-Flask. במדריך הזה נתמקד בהעברות ל-Cloud Storage, ל-Cloud NDB, ל-Flask ול-Python 3.

האפליקציה עדיין רושמת "ביקורים" של משתמשי קצה ומציגה את עשרת הביקורים האחרונים, אבל ב-Codelab הקודם (מודול 15) נוספה פונקציונליות חדשה כדי להתאים לשימוש ב-Blobstore: האפליקציה מבקשת ממשתמשי הקצה להעלות פריט מידע שנוצר בתהליך הפיתוח (קובץ) שתואם ל "ביקור" שלהם. המשתמשים יכולים לעשות את זה או לבחור באפשרות "דילוג" כדי לבטל את ההצטרפות. לא משנה מה המשתמש יבחר, בדף הבא יוצג אותו פלט כמו בגרסאות הקודמות של האפליקציה, עם הביקורים האחרונים. עוד דבר אחד: בביקורים עם פריטי מידע שקשורים אליהם יש קישור "הצגה" שמאפשר להציג את פריט המידע של הביקור. ב-Codelab הזה נטמיע את ההעברות שצוינו קודם, תוך שמירה על הפונקציונליות המתוארת.

3. הגדרה/הכנה

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

1. הגדרת הפרויקט

אם כבר פרסתם את האפליקציה של מודול 15, מומלץ להשתמש שוב באותו פרויקט (וקוד). אפשר גם ליצור פרויקט חדש או להשתמש בפרויקט קיים אחר. מוודאים שלפרויקט יש חשבון לחיוב פעיל ושהאפליקציה App Engine מופעלת.

2. הורדת אפליקציה לדוגמה בסיסית

אחד מהתנאים המוקדמים ל-Codelab הזה הוא אפליקציה לדוגמה של מודול 15. אם אין לכם אותה, אתם יכולים להוריד אותה מהתיקייה START של מודול 15 (קישור בהמשך). ב-Codelab הזה נסביר על כל שלב, ובסוף נציג קוד שדומה למה שמופיע בתיקייה FINISH של מודול 16.

הספרייה של קובצי START של מודול 15 אמורה להיראות כך:

$ ls
README.md       app.yaml        main-gcs.py     main.py         templates

הקובץ main-gcs.py הוא גרסה חלופית של main.py ממודול 15, שמאפשרת לבחור קטגוריה של Cloud Storage ששונה מכתובת ה-URL שמוקצית לאפליקציה כברירת מחדל על סמך מזהה הפרויקט: PROJECT_ID.appspot.com. הקובץ הזה לא משתתף ב-Codelab הזה (מודול 16), אבל אפשר להחיל עליו טכניקות העברה דומות אם רוצים.

3. פריסה (מחדש) של אפליקציית הבסיס

השלבים שנותרו לביצוע לפני ההעברה:

  1. היכרות מחודשת עם כלי שורת הפקודה gcloud
  2. פורסים מחדש את האפליקציה לדוגמה באמצעות gcloud app deploy
  3. מוודאים שהאפליקציה פועלת ב-App Engine בלי בעיות

אחרי שתבצעו את השלבים האלה ותוודאו שהאפליקציה של מודול 15 פועלת, בדף הראשון מוצג למשתמשים טופס שבו הם מתבקשים להעלות קובץ של פריט מידע שנוצר במהלך הביקור, וגם כפתור "דילוג" שמאפשר להם לבטל את ההסכמה:

f5b5f9f19d8ae978.png

אחרי שהמשתמשים מעלים קובץ או מדלגים על השלב הזה, האפליקציה מציגה את הדף המוכר "הביקורים האחרונים":

f5ac6b98ee8a34cb.png

בביקורים שכוללים פריט מידע, יופיע קישור "צפייה" משמאל לחותמת הזמן של הביקור, כדי להציג (או להוריד) את פריט המידע. אחרי שמוודאים שהאפליקציה פועלת, אפשר לעבור מהשירותים הקודמים של App Engine (‏webapp2, ‏ NDB, ‏ Blobstore) לחלופות העדכניות (Flask, ‏ Cloud NDB, ‏ Cloud Storage).

4. עדכון קובצי תצורה

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

  1. עדכון ספריות צד שלישי מובנות שנדרשות ב-app.yaml, וגם השארת האפשרות למעבר ל-Python 3
  2. מוסיפים requirements.txt שמציין את כל הספריות הנדרשות שלא מובנות
  3. מוסיפים את appengine_config.py כדי שהאפליקציה תתמוך גם בספריות מובנות וגם בספריות לא מובנות של צד שלישי

app.yaml

עורכים את הקובץ app.yaml על ידי עדכון הקטע libraries. מסירים את jinja2 ומוסיפים את grpcio, setuptools ו-ssl. בוחרים בגרסה העדכנית ביותר שזמינה לכל שלוש הספריות. מוסיפים גם את ההנחיה runtime של Python 3, אבל מסמנים אותה כהערה. בסיום התהליך, זה אמור להיראות כך (אם בחרתם ב-Python 3.9):

לפני:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: jinja2
  version: latest

אחרי:

#runtime: python39
runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest
- name: ssl
  version: latest

השינויים קשורים בעיקר לספריות המובנות של Python 2 שזמינות בשרתי App Engine (כדי שלא תצטרכו לארוז אותן בעצמכם). הסרנו את Jinja2 כי היא מגיעה עם Flask, שאותה נוסיף ל-reqs.txt. בכל פעם שמשתמשים בספריות לקוח של Google Cloud, כמו אלה של Cloud NDB ו-Cloud Storage, צריך את grpcio ו-setuptools. לבסוף, Cloud Storage עצמו דורש את ספריית ה-SSL. הוראת זמן הריצה שסומנה כהערה בחלק העליון מיועדת למקרים שבהם אתם מוכנים להעביר את האפליקציה הזו ל-Python 3. נעסוק בנושא הזה בסוף המדריך.

requirements.txt

מוסיפים קובץ requirements.txt, שדורש את Flask framework ואת ספריות הלקוח של Cloud NDB ו-Cloud Storage, שאף אחת מהן לא מובנית. יוצרים את הקובץ עם התוכן הבא:

flask
google-cloud-ndb
google-cloud-storage

סביבת זמן הריצה של Python 2 ב-App Engine דורשת אריזה עצמית של ספריות צד שלישי לא מובנות, לכן צריך להריץ את הפקודה הבאה כדי להתקין את הספריות האלה בתיקיית lib:

pip install -t lib -r requirements.txt

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

appengine_config.py

הוספת קובץ appengine_config.py שתומך בספריות מובנות ולא מובנות של צד שלישי. יוצרים את הקובץ עם התוכן הבא:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

השלבים שביצעתם אמורים להיות דומים או זהים לשלבים שמפורטים בקטע התקנת ספריות לאפליקציות של Python 2 במסמכי התיעוד של App Engine. באופן ספציפי יותר, התוכן של appengine_config.py אמור להתאים למה שמופיע בשלב 5 שם.

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

5. שינוי קובצי אפליקציה

יבוא

הקבוצה הראשונה של השינויים ב-main.py כוללת החלפה של כל הדברים שמוחלפים. מה ישתנה?

  1. ‫webapp2 מוחלף ב-Flask
  2. במקום להשתמש ב-Jinja2 מ-webapp2_extras, משתמשים ב-Jinja2 שמגיע עם Flask
  3. ‫Blobstore ו-NDB של App Engine מוחלפים ב-Cloud NDB וב-Cloud Storage
  4. רכיבי ה-handler של Blobstore ב-webapp מוחלפים בשילוב של המודול io של הספרייה הרגילה, Flask וכלי העזר של werkzeug
  5. כברירת מחדל, Blobstore כותב לקטגוריה של Cloud Storage שנקראת על שם כתובת ה-URL של האפליקציה (PROJECT_ID.appspot.com). מכיוון שאנחנו מעבירים לספריית הלקוח של Cloud Storage, משתמשים ב-google.auth כדי לקבל את מזהה הפרויקט ולציין את אותו שם קטגוריה בדיוק. (אפשר לשנות את שם הקטגוריה כי הוא כבר לא מקודד באופן קשיח).

לפני:

import webapp2
from webapp2_extras import jinja2
from google.appengine.ext import blobstore, ndb
from google.appengine.ext.webapp import blobstore_handlers

כדי להטמיע את השינויים שברשימה שלמעלה, מחליפים את קטע הייבוא הנוכחי בקובץ main.py בקטע הקוד שבהמשך.

אחרי:

import io

from flask import (Flask, abort, redirect, render_template,
        request, send_file, url_for)
from werkzeug.utils import secure_filename

import google.auth
from google.cloud import exceptions, ndb, storage

אתחול ותמיכה מיותרת ב-Jinja2

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

ביחידת לימוד 16, יוצרים אובייקטים שלא היו באפליקציה הישנה. זה כולל אתחול של אפליקציית Flask ויצירת לקוחות API ל-Cloud NDB ול-Cloud Storage. לבסוף, אנחנו מרכיבים את שם הקטגוריה של Cloud Storage כמו שמתואר למעלה בקטע "ייבוא". ככה זה נראה לפני ואחרי הטמעת העדכונים:

לפני:

class BaseHandler(webapp2.RequestHandler):
    'Derived request handler mixing-in Jinja2 support'
    @webapp2.cached_property
    def jinja2(self):
        return jinja2.get_jinja2(app=self.app)

    def render_response(self, _template, **context):
        self.response.write(self.jinja2.render_template(_template, **context))

אחרי:

app = Flask(__name__)
ds_client = ndb.Client()
gcs_client = storage.Client()
_, PROJECT_ID = google.auth.default()
BUCKET = '%s.appspot.com' % PROJECT_ID

עדכון הגישה ל-Datastore

‫Cloud NDB תואם ברובו ל-App Engine NDB. הבדל אחד שכבר ציינו הוא הצורך בלקוח API. הבדל נוסף הוא שב-NDB, הגישה ל-Datastore צריכה להיות מנוהלת על ידי מנהל ההקשר של Python של לקוח ה-API. בעיקרון, זה אומר שכל הקריאות לגישה ל-Datastore באמצעות ספריית הלקוח Cloud NDB יכולות להתרחש רק בתוך בלוקים של Python with.

זה שינוי אחד. השינוי השני הוא ש-Blobstore והאובייקטים שלו, למשל BlobKey, לא נתמכים ב-Cloud Storage, ולכן צריך לשנות את file_blob ל-ndb.StringProperty. בהמשך מוצגים המחלקה של מודל הנתונים והפונקציות המעודכנות store_visit() ו-fetch_visits() שמשקפות את השינויים האלה:

לפני:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent),
            file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

אחרי:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.StringProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

הנה ייצוג חזותי של השינויים שבוצעו עד עכשיו:

a8f74ca392275822.png

עדכון ה-handlers

רכיב לטיפול בהעלאות

רכיבי handler ב-webapp2 הם מחלקות, ואילו ב-Flask הם פונקציות. במקום שיטה של פועל HTTP, ב-Flask משתמשים בפועל כדי לקשט את הפונקציה. ‫Blobstore ו-webapp handlers שלו מוחלפים בפונקציונליות מ-Cloud Storage, וגם מ-Flask ומהכלים שלו:

לפני:

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    'Upload blob (POST) handler'
    def post(self):
        uploads = self.get_uploads()
        blob_id = uploads[0].key() if uploads else None
        store_visit(self.request.remote_addr, self.request.user_agent, blob_id)
        self.redirect('/', code=307)

אחרי:

@app.route('/upload', methods=['POST'])
def upload():
    'Upload blob (POST) handler'
    fname = None
    upload = request.files.get('file', None)
    if upload:
        fname = secure_filename(upload.filename)
        blob = gcs_client.bucket(BUCKET).blob(fname)
        blob.upload_from_file(upload, content_type=upload.content_type)
    store_visit(request.remote_addr, request.user_agent, fname)
    return redirect(url_for('root'), code=307)

כמה הערות לגבי העדכון הזה:

  • במקום blob_id, פריטי קבצים מזוהים עכשיו לפי שם הקובץ (fname) אם הוא קיים, ולפי None אם לא (המשתמש בחר לא להעלות קובץ).
  • ה-handlers של Blobstore הפכו את תהליך ההעלאה למופשט עבור המשתמשים, אבל Cloud Storage לא עושה את זה, כך שאתם יכולים לראות את הקוד החדש שנוסף שמגדיר את אובייקט ה-blob ואת המיקום (קטגוריה) של הקובץ, וגם את הקריאה שמבצעת את ההעלאה בפועל. (upload_from_file()).
  • ‫webapp2 משתמש בטבלת ניתוב בתחתית קובץ האפליקציה, ואילו נתיבי Flask נמצאים בכל handler מעוטר.
  • שני ה-handlers מסיימים את הפונקציונליות שלהם על ידי הפניה אוטומטית לדף הבית ( /) תוך שמירה על הבקשה POST עם קוד החזרה HTTP 307.

רכיב ה-handler לעיבוד הורדות

העדכון של ה-handler להורדה דומה לזה של ה-handler להעלאה, אבל יש הרבה פחות קוד שצריך לבדוק. מחליפים את הפונקציונליות של Blobstore ו-webapp בפונקציונליות המקבילה של Cloud Storage ו-Flask:

לפני:

class ViewBlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
    'view uploaded blob (GET) handler'
    def get(self, blob_key):
        self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)

אחרי:

@app.route('/view/<path:fname>')
def view(fname):
    'view uploaded blob (GET) handler'
    blob = gcs_client.bucket(BUCKET).blob(fname)
    try:
        media = blob.download_as_bytes()
    except exceptions.NotFound:
        abort(404)
    return send_file(io.BytesIO(media), mimetype=blob.content_type)

הערות לגבי העדכון הזה:

  • שוב, Flask מקשט פונקציות handler עם הנתיב שלהן, בעוד ש-webapp עושה זאת בטבלת ניתוב בתחתית, אז זהה את תחביר התאמת התבניות של האחרון ('/view/([^/]+)?') לעומת זה של Flask ('/view/<path:fname>').
  • כמו ב-upload handler, נדרשת קצת יותר עבודה בצד של Cloud Storage כדי להשיג את הפונקציונליות שמופשטת על ידי ה-handlers של Blobstore, כלומר זיהוי הקובץ (blob) המדובר והורדה מפורשת של הקובץ הבינארי, לעומת קריאה אחת ל-method‏ send_blob() של ה-handler של Blobstore.
  • בשני המקרים, אם פריט המידע לא נמצא, המשתמש מקבל שגיאת HTTP 404.

ה-handler הראשי

השינויים הסופיים באפליקציה הראשית מתבצעים ב-handler הראשי. ה-methods של הפועל webapp2 ב-HTTP הוחלפו בפונקציה אחת שמשלבת את הפונקציונליות שלהן. מחליפים את המחלקה MainHandler בפונקציה root() ומסירים את טבלת הניתוב webapp2, כמו שמוצג כאן:

לפני:

class MainHandler(BaseHandler):
    'main application (GET/POST) handler'
    def get(self):
        self.render_response('index.html',
                upload_url=blobstore.create_upload_url('/upload'))

    def post(self):
        visits = fetch_visits(10)
        self.render_response('index.html', visits=visits)

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/upload', UploadHandler),
    ('/view/([^/]+)?', ViewBlobHandler),
], debug=True)

אחרי:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = fetch_visits(10)
    return render_template('index.html', **context)

במקום methods נפרדים מסוג get() ו-post(), הם בעצם הצהרת if-else ב-root(). בנוסף, מכיוון ש-root() היא פונקציה אחת, יש רק קריאה אחת לעיבוד התבנית גם ל-GET וגם ל-POST, בעוד שזה לא ממש אפשרי ב-webapp2.

כאן אפשר לראות ייצוג חזותי של קבוצת השינויים השנייה והאחרונה ב-main.py:

5ec38818c32fec2.png

(אופציונלי) "שיפור" התאימות לדורות קודמים

אז הפתרון שיצרנו למעלה עובד בצורה מושלמת... אבל רק אם אתם מתחילים מאפס ואין לכם קבצים שנוצרו על ידי Blobstore. עדכנו את האפליקציה כדי לזהות קבצים לפי שם הקובץ במקום לפי BlobKey, ולכן האפליקציה שהושלמה במודול 16 לא תוכל להציג קובצי Blobstore כמו שהם. במילים אחרות, ביצענו שינוי שאינו תואם לאחור במהלך ההעברה הזו. עכשיו אנחנו מציגים גרסה חלופית של main.py שנקראת main-migrate.py (נמצאת במאגר) שמנסה לגשר על הפער הזה.

"ההרחבה" הראשונה שתומכת בקבצים שנוצרו ב-Blobstore היא מודל נתונים שיש לו BlobKeyProperty (בנוסף ל-StringProperty לקבצים שנוצרו ב-Cloud Storage):

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()  # backwards-compatibility
    file_gcs  = ndb.StringProperty()

המאפיין file_blob ישמש לזיהוי קבצים שנוצרו על ידי Blobstore, והמאפיין file_gcs ישמש לזיהוי קבצים של Cloud Storage. עכשיו, כשיוצרים ביקורים חדשים, מאחסנים ערך באופן מפורש ב-file_gcs במקום ב-file_blob, כך שהאירוע store_visit נראה קצת אחרת:

לפני:

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_blob=upload_key).put()

אחרי:

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_gcs=upload_key).put()

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

לפני:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = fetch_visits(10)
    return render_template('index.html', **context)

אחרי:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = etl_visits(fetch_visits(10))
    return render_template('index.html', **context)

בשלב הבא, צריך לאשר את קיומו של file_blob או file_gcs (או אף אחד מהם). אם יש קובץ זמין, בוחרים את הקובץ שקיים ומשתמשים במזהה הזה (BlobKey לקבצים שנוצרו ב-Blobstore או שם הקובץ לקבצים שנוצרו ב-Cloud Storage). כשאנחנו אומרים "קבצים שנוצרו ב-Cloud Storage", אנחנו מתכוונים לקבצים שנוצרו באמצעות ספריית הלקוח של Cloud Storage. גם Blobstore כותב ב-Cloud Storage, אבל במקרה הזה, אלה קבצים שנוצרו על ידי Blobstore.

עכשיו, מה שחשוב יותר, מהי הפונקציה etl_visits() שמשמשת לנרמול או ל-ETL (חילוץ, טרנספורמציה וטעינה) של הנתונים עבור משתמש הקצה? כך הוא נראה:

def etl_visits(visits):
    return [{
            'visitor': v.visitor,
            'timestamp': v.timestamp,
            'file_blob': v.file_gcs if hasattr(v, 'file_gcs') \
                    and v.file_gcs else v.file_blob
            } for v in visits]

הוא כנראה נראה כמו שציפיתם: הקוד עובר בלולאה על כל הביקורים, ובכל ביקור, לוקח את נתוני המבקר וחותמת הזמן כפי שהם, ואז בודק אם file_gcs או file_blob קיימים, ואם כן, בוחר אחד מהם (או None אם אף אחד מהם לא קיים).

הנה איור שממחיש את ההבדלים בין main.py לבין main-migrate.py:

718b05b2adadb2e1.png

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

6. סיכום/ניקוי

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

פריסה ואימות של האפליקציה

לפני שפורסים מחדש את האפליקציה, חשוב להריץ את pip install -t lib -r requirements.txt כדי להעביר את הספריות של הצד השלישי שכלולות בחבילה לתיקיית lib. אם רוצים להריץ את הפתרון שתואם לדורות קודמים, צריך קודם לשנות את השם של main-migrate.py ל-main.py. עכשיו מריצים את gcloud app deploy ומוודאים שהאפליקציה פועלת באופן זהה לאפליקציה של מודול 15. מסך הטופס נראה כך:

f5b5f9f19d8ae978.png

כך נראה הדף "הביקורים האחרונים":

f5ac6b98ee8a34cb.png

כל הכבוד, סיימתם את ה-Codelab הזה שבו החלפתם את Blobstore של App Engine ב-Cloud Storage, את NDB של App Engine ב-Cloud NDB ואת webapp2 ב-Flask. הקוד שלכם אמור להיות זהה לקוד שבתיקייה FINISH (Module 16)‎. החלופה main-migrate.py נמצאת גם היא בתיקייה הזו.

"העברה" ל-Python 3

ההנחיה runtime של Python 3 שמסומנת כהערה בחלק העליון של app.yaml היא כל מה שצריך כדי להעביר את האפליקציה הזו ל-Python 3. קוד המקור עצמו כבר תואם ל-Python 3, כך שלא צריך לבצע בו שינויים. כדי לפרוס את האפליקציה כאפליקציית Python 3, מבצעים את השלבים הבאים:

  1. מבטלים את ההערה של ההוראה runtime של Python 3 בחלק העליון של app.yaml.
  2. מוחקים את כל השורות האחרות בקובץ app.yaml.
  3. מוחקים את הקובץ appengine_config.py. (לא בשימוש בסביבת זמן הריצה של Python 3)
  4. מוחקים את התיקייה lib אם היא קיימת. (לא נדרש בסביבת זמן ריצה של Python 3)

הסרת המשאבים

כללי

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

חשוב לדעת: פריסה בפלטפורמת מחשוב בלי שרת (serverless computing) של Google Cloud, כמו App Engine, כרוכה בעלויות קטנות של פיתוח ואחסון. ל-Cloud Build יש מכסה משלו לשימוש בחינם, וכך גם ל-Cloud Storage. האחסון של התמונה הזו מנצל חלק מהמכסה הזו. עם זאת, יכול להיות שאתם גרים באזור שאין בו תוכנית בחינם, אז כדאי לשים לב לנפח אחסון נדרש שאתם משתמשים בו כדי לצמצם עלויות פוטנציאליות. "תיקיות" ספציפיות ב-Cloud Storage שכדאי לבדוק:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • הקישורים לאחסון שלמעלה תלויים בPROJECT_ID וב *LOC*ation, לדוגמה, "us" אם האפליקציה מתארחת בארה"ב.

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

ספציפי ל-Codelab הזה

השירותים שמפורטים בהמשך ייחודיים ל-Codelab הזה. מידע נוסף זמין במסמכי התיעוד של כל מוצר:

שימו לב שאם עברתם ממודול 15 למודול 16, עדיין יהיו לכם נתונים ב-Blobstore, ולכן כללנו למעלה את פרטי התמחור שלו.

השלבים הבאים

בנוסף למדריך הזה, יש עוד מודולים של העברה שמתמקדים במעבר מחבילות השירותים הקודמות, למשל:

‫App Engine היא כבר לא הפלטפורמה היחידה בלי שרת (serverless) ב-Google Cloud. אם יש לכם אפליקציה קטנה ב-App Engine או אפליקציה עם פונקציונליות מוגבלת, ואתם רוצים להפוך אותה למיקרו-שירות עצמאי, או אם אתם רוצים לפצל אפליקציה מונוליתית למספר רכיבים שניתן לעשות בהם שימוש חוזר, אלה סיבות טובות לשקול מעבר ל-Cloud Functions. אם הכללת קונטיינרים הפכה לחלק מתהליך הפיתוח של האפליקציה, במיוחד אם הוא כולל צינור עיבוד נתונים של CI/CD (אינטגרציה רציפה/פריסה או פיתוח רציף), כדאי לעבור ל-Cloud Run. התרחישים האלה מוסברים במודולים הבאים:

  • מעבר מ-App Engine ל-Cloud Functions: ראו מודול 11
  • מעבר מ-App Engine ל-Cloud Run: ביחידת לימוד 4 מוסבר איך להוסיף את האפליקציה לקונטיינר באמצעות Docker, וביחידת לימוד 5 מוסבר איך לעשות את זה בלי קונטיינרים, בלי ידע ב-Docker ובלי Dockerfile

המעבר לפלטפורמה אחרת בלי שרת (serverless) הוא אופציונלי, ומומלץ לשקול את האפשרויות הטובות ביותר לאפליקציות ולתרחישי השימוש שלכם לפני שתבצעו שינויים.

לא משנה באיזה מודול העברה תבחרו, תוכלו לגשת לכל התוכן של Serverless Migration Station (שיעורי Lab ב-Codelabs, סרטונים, קוד מקור [אם זמין]) במאגר הקוד הפתוח שלו. בREADME של המאגר יש גם הנחיות לגבי ההעברות שכדאי לשקול ו"סדר" רלוונטי של מודולי העברה.

7. מקורות מידע נוספים

בעיות או משוב בנוגע ל-Codelab

אם נתקלתם בבעיות ב-Codelab הזה, חפשו את הבעיה לפני שתדווחו עליה. קישורים לחיפוש וליצירת דיווחים או בקשות חדשים:

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

בטבלה הבאה מופיעים קישורים לתיקיות המאגר של מודול 15 (START) ומודול 16 (FINISH). אפשר גם לגשת אליהם מהמאגר של כל ההעברות ב-App Engine codelab, שאותו אפשר לשכפל או להוריד כקובץ ZIP.

Codelab

Python 2

Python 3

יחידת לימוד 15

קוד

לא רלוונטי

מודול 16 (ה-Codelab הזה)

קוד

(כמו ב-Python 2)

משאבים באינטרנט

בהמשך מופיעים מקורות מידע באינטרנט שעשויים להיות רלוונטיים למדריך הזה:

‫App Engine Blobstore ו-Cloud Storage

פלטפורמת App Engine

מידע נוסף על Cloud

Python

סרטונים

רישיון

היצירה הזו מוגנת ברישיון Creative Commons Attribution 2.0 Generic.