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

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

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

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

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

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

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

מה תצטרכו

סקר

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

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

איך היית מדרג את חוויית השימוש שלך ב-Python?

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

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

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

2. רקע

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

המיגרציה הזו דורשת קצת יותר מאמץ בהשוואה למיגרציות אחרות בסדרה הזו. ל-Blobstore יש תלות ב-framework המקורי של webapp, ולכן אפליקציית הדוגמה משתמשת ב-framework של webapp2 במקום ב-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.

הספרייה של קובצי ההתחלה של מודול 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. (Re)Deploy baseline app

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

  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 ואת ספריות הלקוח Cloud NDB ו-Cloud Storage, שאף אחת מהן לא מוטמעת כברירת מחדל. יוצרים את הקובץ עם התוכן הבא:

flask
google-cloud-ndb
google-cloud-storage

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

pip install -t lib -r requirements.txt

אם במכונת הפיתוח שלכם מותקנות גם Python 2 וגם Python 3, יכול להיות שתצטרכו להשתמש בפקודה 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. השירותים App Engine Blobstore ו-NDB מוחלפים ב-Cloud NDB וב-Cloud Storage
  4. ה-handlers של 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. ההבדל השני הוא שבמקרה האחרון, הגישה ל-Datastore צריכה להיות מבוקרת על ידי מנהל ההקשר של Python של לקוח ה-API. המשמעות היא שכל קריאות הגישה ל-Datastore באמצעות ספריית הלקוח Cloud NDB יכולות להתרחש רק בבלוקים של Python with.

זה שינוי אחד. השינוי השני הוא ש-Cloud Storage לא תומך ב-Blobstore ובאובייקטים שלו, למשל BlobKeys, ולכן צריך לשנות את 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

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

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

לפני:

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 נמצאים בכל מטפל מעוטר.
  • שני ה-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 כדי להשיג פונקציונליות שמופשטת על ידי Blobstore handlers, כלומר זיהוי הקובץ (blob) המדובר והורדה מפורשת של הקובץ הבינארי לעומת קריאה יחידה לשיטת send_blob() של Blobstore handler.
  • בשני המקרים, אם לא נמצא ארטיפקט, המערכת מחזירה למשתמש שגיאת HTTP 404.

המאפיין handler הראשי

השינויים הסופיים באפליקציה הראשית מתבצעים ב-handler הראשי. הפונקציות 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)

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

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

5ec38818c32fec2.png

(אופציונלי) שיפור תאימות לאחור

לכן הפתרון שנוצר למעלה עובד בצורה מושלמת... אבל רק אם מתחילים מאפס ואין קבצים שנוצרו על ידי Blobstore. בגלל שעדכנו את האפליקציה כך שהיא מזהה קבצים לפי שם הקובץ במקום לפי BlobKey, לא תהיה אפשרות להציג קבצים ב-Blobstore באפליקציה של מודול 16 כמו שהיא. במילים אחרות, ביצענו שינוי שלא תואם לאחור במהלך המיגרציה הזו. עכשיו אנחנו מציגים גרסה חלופית של 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 ורוצים לתמוך בקבצים שנוצרו על ידי 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 הזה בנושא החלפת App Engine Blobstore ב-Cloud Storage, החלפת App Engine NDB ב-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 יש מכסת שימוש בחינם, ולכן כל עוד לא חורגים מרמת השימוש הזו, לא אמורים לחייב אתכם. החישוב הזה מתייחס ל-Compute, אבל יכול להיות שיהיו גם חיובים על שירותים רלוונטיים של App Engine. לכן, כדאי לעיין בדף התמחור שלו כדי לקבל מידע נוסף. אם ההעברה הזו כוללת שירותי ענן אחרים, הם יחויבו בנפרד. בכל מקרה, אם רלוונטי, כדאי לעיין בקטע 'ספציפי ל-codelab הזה' שבהמשך.

חשוב לדעת: פריסה בפלטפורמת מחשוב ללא שרת של 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 אם האפליקציה מאוחסנת בארה"ב.

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

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

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

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

השלבים הבאים

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

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

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

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

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

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

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

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

מקורות מידע על העברת נתונים

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

Codelab

Python 2

Python 3

יחידת לימוד 15

קוד

לא רלוונטי

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

קוד

(same as Python 2)

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

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

‫App Engine Blobstore ו-Cloud Storage

פלטפורמת App Engine

מידע אחר על Cloud

Python

סרטונים

רישיון

עבודה זו מורשית תחת רישיון Creative Commons שמותנה בייחוס 2.0 כללי.