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

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

סדרת הServerless Migration Station של Codelabs (מדריכים מעשיים בקצב עצמי) וסרטונים קשורים נועדו לעזור להעביר מפתחים ללא שרת (serverless) של Google Cloud, באמצעות העברת אפליקציות מדור קודם באמצעות שירותי Google Cloud. כך האפליקציות שלכם יהיו יותר ניידות ויהיו לכם יותר אפשרויות וגמישות, כך שתוכלו להשתלב עם מגוון רחב יותר של מוצרי 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 ומדגים איך לעבור מ-Bobstore (ו-NDB) ל-Cloud Storage (ו-Cloud NDB). תהליך ההעברה כולל החלפת יחסי תלות בשירותים בחבילה מדור קודם של App Engine, שמאפשרים להעביר את האפליקציות לפלטפורמה אחרת ללא שרת Cloud או לפלטפורמת אירוח אחרת לפי הצורך.

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

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

3. הגדרה/עבודה מוקדמת

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

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

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

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

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

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

$ 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) פריסת אפליקציות בסיסיות

שאר השלבים לפני העבודה שצריך לבצע עכשיו:

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

לאחר שתבצעו את השלבים האלה בהצלחה, ותוודאו שאפליקציית מודול 15 פועלת. הדף ההתחלתי מקבל הודעה למשתמשים באמצעות טופס שמבקש להעלות קובץ Artifact של ביקור שיש להעלות, עם אפשרות - 'דילוג' כדי לבטל את ההסכמה:

f5b5f9f19d8ae978.png

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

f5ac6b98ee8a34cb.png

לביקורים שמוצג בהם פריט מידע שנוצר בתהליך הפיתוח (Artifact) יוצגו 'תצוגה' קישור משמאל לחותמת הזמן של הביקור כדי להציג (או להוריד) את פריט המידע שנוצר בתהליך הפיתוח (Artifact). אחרי שתאשרו את הפונקציונליות של האפליקציה, תוכלו לעבור מהשירותים הקודמים של 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 עם framework של 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 וגם 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. 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. אפשרות אחרת היא שנדרשת שליטה בגישה ל-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

עדכון רכיבי ה-handler

העלאת handler

handlers ב-webapp2 הם קורסים בזמן שהם פועלים ב-Flask. במקום שיטת פועל של HTTP, Flask משתמש בפועל כדי לקשט את הפונקציה. Blobstore ורכיבי ה-handler של webapp מוחלפים בפונקציונליות של 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 מקושט.
  • שני המטפלים מסיימים את הפונקציונליות שלהם על ידי הפניה אוטומטית לדף הבית ( / ) תוך שמירה על בקשת 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>').
  • בדומה ל-handler של ההעלאה, נדרשת עוד קצת עבודה בצד Cloud Storage בשביל פונקציונליות שמופשטת על ידי ה-handlers של Blobstore, כלומר זיהוי הקובץ (blob) הרלוונטי והורדה מפורשת של בקשת ה-method היחידה send_blob() של ה-handler הבינארי לעומת ה-blobstore.
  • אם לא נמצא פריט מידע שנוצר בתהליך הפיתוח (Artifact), בשני המקרים, תוחזר שגיאת HTTP 404.

handler עיקרי

השינויים האחרונים באפליקציה הראשית מתבצעים ב-handler הראשי. השיטות של פועל ה-HTTP webapp2 מוחלפות בפונקציה אחת שמשלבת את הפונקציונליות שלהן. מחליפים את המחלקה 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, ולכן באפליקציית מודול 16 שהושלם לה אין אפשרות להציג קבצים של Blobstore. במילים אחרות, ביצענו שינוי שאינו תואם לאחור שמבצע את ההעברה הזו. עכשיו מוצגת גרסה חלופית של main.py בשם main-migrate.py (שנמצאת במאגר) שמנסה לגשר על הפער הזה.

ה"תוסף" הראשון לתמיכה בקבצים שנוצרו ב-Bluobstore הוא מודל נתונים שיש לו 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:

718b05b2adb2e1.png

אם רק התחלת את התהליך בלי קבצים שנוצרו ב-Blubstore, כדאי להשתמש ב-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 הזה בהחלפת App Engine Blobstore ב-Cloud Storage, App Engine NDB ב-Cloud NDB ו-webapp2 ב-Flask. הקוד אמור עכשיו להתאים למה שמופיע בתיקייה FINISH (מודול 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. למידע נוסף, יש לעיין בדף התמחור של האפליקציה. אם ההעברה הזו כוללת שירותי ענן אחרים, הם מחויבים בנפרד. בכל מקרה, אם רלוונטי, ראו "ספציפי ל-Codelab זה" שבהמשך.

גילוי נאות מלא, פריסה בפלטפורמת מחשוב ללא שרת (serverless) של Google Cloud, כמו App Engine, כרוכה בעלויות נמוכות של build ואחסון. ל-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*שלך, לדוגמה, "us" אם האפליקציה מתארחת בארה"ב.

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

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

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

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

השלבים הבאים

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

  • מודול 2: העברה מ-App Engine ndb ל-Cloud NDB
  • מודולים 7-9: העברה ממשימות דחיפה לתור המשימות של App Engine אל Cloud Tasks
  • מודולים 12-13: העברה מ-Memcache של App Engine ל-Cloud Memorystore
  • מודולים 18-19: העברה מתור המשימות של App Engine (משימות משיכה) אל Cloud Pub/Sub

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

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

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

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

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

משוב או בעיות ב-Codelab

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

משאבים להעברה

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

Codelab

ֶPython 2

ֶPython 3

יחידת לימוד 15

קוד

לא רלוונטי

יחידת לימוד 16 (Codelab זה)

קוד

(כמו ב-Python 2)

מקורות מידע אונליין

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

Blobstore ו-Cloud Storage של App Engine

פלטפורמת App Engine

מידע אחר בענן

Python

סרטונים

רישיון

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