1. סקירה כללית
מטרת סדרת ה-codelabs של Serverless Migration Station (הדרכות מעשיות בקצב אישי) והסרטונים שקשורים אליה היא לעזור למפתחים של Google Cloud Serverless לחדש את האפליקציות שלהם. לשם כך, הם מקבלים הדרכה לגבי העברה אחת או יותר, בעיקר מעבר משירותים מדור קודם. כך האפליקציות שלכם יהיו ניידות יותר, ותקבלו יותר אפשרויות וגמישות. תוכלו לשלב את האפליקציות עם מגוון רחב יותר של מוצרי Cloud ולגשת אליהם, ולשדרג בקלות רבה יותר לגרסאות חדשות יותר של השפה. הסדרה הזו מתמקדת בהתחלה במשתמשי הענן הראשונים, בעיקר מפתחים של App Engine (סביבה רגילה), אבל היא רחבה מספיק כדי לכלול פלטפורמות אחרות של Serverless כמו Cloud Functions ו-Cloud Run, או במקומות אחרים אם רלוונטי.
ב-Codelab הזה תלמדו איך לבצע מיגרציה מ-App Engine Blobstore אל Cloud Storage. יש גם העברות מרומזות מהמקורות הבאים:
-
webapp2web framework to Flask (covered by Module 1) - App Engine NDB ל-Cloud NDB לגישה ל-Datastore (מוסבר במודול 2)
- Python 2 עד 3 (האפליקציה שהועברה תואמת ל-Python 2 ול-Python 3)
למידע נוסף על כל שלב, אפשר לעיין במודולים הרלוונטיים להעברת נתונים.
כאן אפשר להבין איך
- הוספת שימוש ב-App Engine Blobstore API/library
- אחסון העלאות של משתמשים בשירות Blobstore
- הכנה לשלב הבא של המעבר ל-Cloud Storage
מה תצטרכו
- פרויקט ב-Google Cloud Platform עם חשבון לחיוב פעיל ב-GCP
- מיומנויות בסיסיות ב-Python
- ידע מעשי בפקודות נפוצות של Linux
- ידע בסיסי בפיתוח ופריסה של אפליקציות App Engine
- אפליקציית App Engine תקינה של מודול 15: מומלץ להשלים את ה-codelab של מודול 15 או להעתיק את האפליקציה של מודול 15 מהמאגר
סקר
איך תשתמשו במדריך הזה?
איך היית מדרג את חוויית השימוש שלך ב-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 (Python 2)
- סיום: תיקיית מודול 16 (Python 2)
- מאגר שלם (לשיבוט או להורדה של קובץ ZIP)
הספרייה של קובצי ההתחלה של מודול 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
השלבים הנותרים שצריך לבצע עכשיו:
- כדאי להכיר מחדש את כלי שורת הפקודה
gcloud - פריסה מחדש של האפליקציה לדוגמה עם
gcloud app deploy - אישור שהאפליקציה פועלת ב-App Engine ללא בעיות
אחרי שתבצעו את השלבים האלה ותאשרו שהאפליקציה של מודול 15 פועלת. בדף הראשון מוצג למשתמשים טופס שבו הם מתבקשים להעלות קובץ של ארטיפקט ביקור, וגם לחצן 'דילוג' שמאפשר להם לבחור שלא להעלות קובץ.

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

בביקורים שכוללים ארטיפקט, יוצג קישור 'תצוגה' משמאל לחותמת הזמן של הביקור, כדי להציג (או להוריד) את הארטיפקט. אחרי שתאשרו את הפונקציונליות של האפליקציה, תוכלו להעביר אותה משירותים מדור קודם של App Engine (webapp2, NDB, Blobstore) לחלופות עדכניות (Flask, Cloud NDB, Cloud Storage).
4. עדכון קובצי תצורה
בגרסה המעודכנת של האפליקציה שלנו יש שלושה קובצי הגדרה. המשימות הנדרשות הן:
- נדרש עדכון של ספריות מובנות של צד שלישי ב-
app.yaml, וגם לאפשר מעבר ל-Python 3 - מוסיפים
requirements.txt, שמציין את כל הספריות הנדרשות שלא מובנות - מוסיפים את
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 כוללים החלפה של כל מה שמוחלף. אלה השינויים שיחולו:
-
webapp2מוחלף ב-Flask - במקום להשתמש ב-Jinja2 מ-
webapp2_extras, משתמשים ב-Jinja2 שמגיע עם Flask - השירותים App Engine Blobstore ו-NDB מוחלפים ב-Cloud NDB וב-Cloud Storage
- ה-handlers של Blobstore ב-
webappמוחלפים בשילוב של מודול הספרייה הרגילio, Flask וכלי השירותwerkzeug - כברירת מחדל, 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)
לפניכם תמונה שממחישה את השינויים שבוצעו עד עכשיו:

עדכון של ה-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:

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

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

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

כל הכבוד, סיימתם את ה-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, מבצעים את השלבים הבאים:
- מבטלים את ההערה של ההוראה
runtimeשל Python 3 בחלק העליון שלapp.yaml. - מוחקים את כל השורות האחרות ב-
app.yaml. - מוחקים את הקובץ
appengine_config.py. (לא בשימוש בסביבת זמן הריצה של Python 3) - מוחקים את התיקייה
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/imagesconsole.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com- הקישורים לאחסון שלמעלה תלויים ב
PROJECT_IDובמיקום *LOC*ation, לדוגמה,usאם האפליקציה מאוחסנת בארה"ב.
מצד שני, אם אתם לא מתכוונים להמשיך עם האפליקציה הזו או עם Codelabs אחרים שקשורים להעברה, ואתם רוצים למחוק הכול באופן סופי, אתם יכולים להשבית את הפרויקט.
ספציפי ל-Codelab הזה
השירותים שמופיעים בהמשך הם ייחודיים ל-codelab הזה. מידע נוסף זמין במסמכי התיעוד של כל מוצר:
- השירות App Engine Blobstore נכלל במכסות ומגבלות של נתונים מאוחסנים, ולכן כדאי לעיין גם בדף התמחור של חבילות שירותים מדור קודם.
- ל-Cloud Storage יש מסלול חינמי לאזורים ספציפיים. למידע נוסף, אפשר לעיין גם בדף התמחור הכללי.
- שירות App Engine Datastore מסופק על ידי Cloud Datastore (Cloud Firestore במצב Datastore), שגם לו יש רמת שימוש ללא תשלום. מידע נוסף זמין במחירון שלו".
שימו לב: אם עברתם מ-Module 15 ל-Module 16, עדיין יהיו לכם נתונים ב-Blobstore, ולכן אנחנו כוללים את פרטי התמחור שלו למעלה.
השלבים הבאים
בנוסף למדריך הזה, יש מודולים אחרים להעברה שמתמקדים במעבר משירותים קודמים בחבילה, שכדאי לעיין בהם:
- מודול 2: מעבר מ-App Engine
ndbאל Cloud NDB - יחידות 7-9: מעבר ממשימות push של App Engine Task Queue אל Cloud Tasks
- מודולים 12-13: מעבר מ-App Engine Memcache ל-Cloud Memorystore
- מודולים 18-19: מעבר מ-App Engine Task Queue (משימות משיכה) אל Cloud Pub/Sub
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
- שירות Blobstore של App Engine
- מעבר לספריית הלקוח של Cloud Storage
- דף הבית של Cloud Storage
- מאמרי העזרה של Cloud Storage
פלטפורמת App Engine
- מסמכי App Engine
- זמן ריצה של Python 2 App Engine (סביבה סטנדרטית)
- שימוש בספריות מובנות של App Engine ב-Python 2 App Engine
- זמן ריצה של Python 3 App Engine (סביבה רגילה)
- ההבדלים בין סביבות זמן הריצה של Python 2 ו-Python 3 App Engine (סביבה סטנדרטית)
- מדריך להעברה מ-Python 2 ל-Python 3 ב-App Engine (סביבה רגילה)
- מידע על התמחור ועל המכסות ב-App Engine
- השקת פלטפורמת App Engine מהדור השני (2018)
- השוואה בין פלטפורמות מהדור הראשון לבין פלטפורמות מהדור השני
- תמיכה לטווח ארוך בסביבות זמן ריצה מדור קודם
- מאגר דוגמאות להעברת תיעוד
- מאגר דוגמאות להעברה שנוצרו על ידי הקהילה
מידע אחר על Cloud
- Python ב-Google Cloud Platform
- ספריות לקוח Python של Google Cloud
- רמת השימוש 'תמיד בחינם' ב-Google Cloud
- Google Cloud SDK (כלי לשורת פקודה
gcloud) - כל מסמכי התיעוד של Google Cloud
Python
- מערכות תבניות של Django ו-Jinja2
webapp2web frameworkwebapp2מסמכי תיעודwebapp2_extrasקישוריםwebapp2_extrasמסמכי התיעוד של Jinja2- Flask web framework
סרטונים
- Serverless Migration Station
- מסעות ללא שרת
- הרשמה למינוי לערוץ Google Cloud Tech
- הרשמה לניוזלטר של Google Developers
רישיון
עבודה זו מורשית תחת רישיון Creative Commons שמותנה בייחוס 2.0 כללי.