ย้ายข้อมูลจาก App Engine Blobstore ไปยัง Cloud Storage (โมดูล 16)

1. ภาพรวม

ชุดโปรแกรม Codelab สำหรับการย้ายข้อมูลแบบ Serverless (บทแนะนำแบบลงมือทำด้วยตนเอง) และวิดีโอที่เกี่ยวข้องมีจุดประสงค์เพื่อช่วยให้นักพัฒนาแอป Google Cloud Serverless ปรับการดำเนินการให้ทันสมัยได้ด้วยคำแนะนำการย้ายข้อมูลอย่างน้อย 1 รายการ โดยให้ย้ายออกจากบริการเดิมเป็นหลัก การดำเนินการดังกล่าวทำให้แอปพกพาไปได้ทุกที่ รวมถึงมอบตัวเลือกและความยืดหยุ่นที่มากขึ้น ทำให้สามารถผสานรวมและเข้าถึงผลิตภัณฑ์ 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) กระบวนการย้ายข้อมูลเกี่ยวข้องกับการแทนที่ทรัพยากร Dependency ในบริการแบบกลุ่มเดิมของ App Engine ซึ่งจะช่วยให้คุณย้ายแอปไปยังแพลตฟอร์มแบบ Serverless อื่นหรือแพลตฟอร์มโฮสติ้งอื่นได้หากต้องการ

การย้ายข้อมูลนี้ต้องใช้ความพยายามมากขึ้นเมื่อเทียบกับการย้ายข้อมูลอื่นๆ ในซีรีส์นี้ Blobstore มีทรัพยากร Dependency ในเฟรมเวิร์กเว็บแอปเดิม จึงเป็นเหตุผลที่แอปตัวอย่างใช้เฟรมเวิร์ก webapp2 แทน Flask บทแนะนำนี้มีฟีเจอร์การย้ายข้อมูลไปยัง Cloud Storage, Cloud NDB, Flask และ Python 3

แอปจะยังคงบันทึก "การเข้าชม" ของผู้ใช้ปลายทาง และแสดง Codelab ล่าสุด 10 รายการ แต่ Codelab (โมดูล 15) ก่อนหน้านี้ได้เพิ่มฟังก์ชันใหม่เพื่อรองรับการใช้งาน Blobstore โดยแอปจะแจ้งผู้ใช้ปลายทางให้อัปโหลดอาร์ติแฟกต์ (ไฟล์) ที่สอดคล้องกับ "การเข้าชม" ของพวกเขา ผู้ใช้สามารถดำเนินการดังกล่าวหรือเลือก "ข้าม" เลือกไม่ใช้ ไม่ว่าผู้ใช้จะตัดสินใจอย่างไร หน้าถัดไปจะแสดงผลลัพธ์เช่นเดียวกับแอปมาใหม่ก่อนหน้านี้ โดยจะแสดงการเข้าชมล่าสุด การเปลี่ยนแปลงอีกข้อหนึ่งคือการเข้าชมที่มีอาร์ติแฟกต์ที่เกี่ยวข้องจะมี "มุมมอง" ลิงก์สำหรับแสดงอาร์ติแฟกต์ของการเข้าชม Codelab นี้จะใช้การย้ายข้อมูลที่กล่าวถึงก่อนหน้านี้โดยยังคงฟังก์ชันการทำงานที่อธิบายอยู่เดิม

3. การตั้งค่า/งานล่วงหน้า

ก่อนจะไปยังส่วนหลักของบทแนะนำ เราจะมาสร้างโปรเจ็กต์ รับโค้ด แล้วทำให้แอปพื้นฐานใช้งานได้เพื่อให้เราทราบว่าเราเริ่มเขียนโค้ดแล้ว

1. สร้างโปรเจ็กต์

หากคุณทำให้แอปโมดูล 15 ใช้งานได้แล้ว เราขอแนะนำให้ใช้โปรเจ็กต์ (และโค้ด) เดียวกันนั้นซ้ำ หรือคุณจะสร้างโปรเจ็กต์ใหม่หรือนำโปรเจ็กต์อื่นที่มีอยู่มาใช้ใหม่ก็ได้ ตรวจสอบว่าโปรเจ็กต์มีบัญชีสำหรับการเรียกเก็บเงินที่ใช้งานอยู่และเปิดใช้ App Engine แล้ว

2. รับแอปตัวอย่างพื้นฐาน

ข้อกำหนดเบื้องต้นอย่างหนึ่งของ Codelab นี้คือแอปตัวอย่างโมดูล 15 ที่ใช้งานได้ หากยังไม่มี คุณสามารถรับได้จากโมดูล 15 "เริ่มต้น" (ลิงก์ด้านล่าง) Codelab นี้จะแนะนำคุณทีละขั้นตอน สรุปด้วยโค้ดที่คล้ายกับสิ่งที่อยู่ในโมดูล 16 "FINISH" โฟลเดอร์

ไดเรกทอรีของไฟล์ STARTing ของโมดูล 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. อัปเดตไฟล์การกำหนดค่า

ไฟล์การกำหนดค่า 3 ไฟล์พร้อมใช้งานสำหรับแอปเวอร์ชันที่อัปเดตแล้วของเรา งานที่จำเป็นมีดังนี้

  1. อัปเดตไลบรารีของบุคคลที่สามที่จำเป็นในตัวใน app.yaml รวมถึงเปิดประตูรับการย้ายข้อมูล Python 3
  2. เพิ่ม requirements.txt ซึ่งระบุไลบรารีที่จำเป็นทั้งหมดที่ไม่มีในตัว
  3. เพิ่ม appengine_config.py เพื่อให้แอปรองรับไลบรารีของบุคคลที่สามทั้งแบบที่มีและในตัว

app.yaml

แก้ไขไฟล์ app.yaml โดยการอัปเดตส่วน libraries นำ jinja2 ออกและเพิ่ม grpcio, setuptools และ ssl เลือกเวอร์ชันล่าสุดที่พร้อมใช้งานสำหรับไลบรารีทั้ง 3 แบบ เพิ่มคำสั่ง 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 และ 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. Cloud NDB และ Cloud Storage จะมาแทนที่ App Engine Blobstore และ NDB
  4. เครื่องจัดการ 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 อีกวิธีหนึ่งคือในกรณีที่ต้องควบคุมการเข้าถึงพื้นที่เก็บข้อมูลด้วยเครื่องมือจัดการบริบท Python ของไคลเอ็นต์ API ซึ่งหมายความว่าการเรียกการเข้าถึง Datastore ทั้งหมดที่ใช้ไลบรารีของไคลเอ็นต์ Cloud NDB จะทำได้เฉพาะภายในบล็อก Python with เท่านั้น

นั่นถือเป็นการเปลี่ยนแปลง 1 รายการ อีกอย่างหนึ่งคือ Blobstore และออบเจ็กต์ของ Blobstore เช่น Cloud Storage ไม่รองรับ BlobKey ดังนั้นโปรดเปลี่ยน 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

การอัปเดตตัวจัดการ

ตัวแฮนเดิลการอัปโหลด

เครื่องจัดการใน webapp2 คือคลาสในขณะที่ทำงานอยู่ใน Flask Flask จะใช้กริยาเพื่อเติมแต่งฟังก์ชันแทนเมธอดกริยา HTTP Blobstore และตัวแฮนเดิล 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 ในกรณีอื่นๆ (ผู้ใช้เลือกไม่ใช้การอัปโหลดไฟล์)
  • ตัวแฮนเดิล Blobstore ตัดขั้นตอนการอัปโหลดจากผู้ใช้ออก แต่ Cloud Storage ไม่ดำเนินการ คุณจึงเห็นโค้ดที่เพิ่มเข้ามาใหม่ซึ่งตั้งค่าออบเจ็กต์ BLOB และตำแหน่ง (ที่เก็บข้อมูล) ของไฟล์ รวมถึงการเรียกใช้ที่ดำเนินการอัปโหลดจริง (upload_from_file())
  • webapp2 จะใช้ตารางการกำหนดเส้นทางที่ด้านล่างของไฟล์แอปพลิเคชันในขณะที่พบเส้นทางของ Flask ในเครื่องจัดการที่ปรับแต่งแต่ละรายการ
  • ตัวแฮนเดิลทั้งสองจะรวมฟังก์ชันการทำงานของตนโดยการเปลี่ยนเส้นทางไปที่หน้าแรก ( / ) ในขณะที่เก็บคำขอ POST ที่มีโค้ดการแสดงผล HTTP 307 เอาไว้

ดาวน์โหลดเครื่องจัดการ

การอัปเดตเครื่องจัดการการดาวน์โหลดมีรูปแบบคล้ายกับเครื่องจัดการการอัปโหลด เพียงแต่มีโค้ดที่ต้องดูน้อยกว่านั้นมาก แทนที่ฟังก์ชันการทำงานของ 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 ตกแต่งฟังก์ชันเครื่องจัดการด้วยเส้นทางของตน ในขณะที่ webapp ไปสร้างในตารางการกำหนดเส้นทางที่ด้านล่าง ดังนั้นโปรดจดจำไวยากรณ์การจับคู่รูปแบบของรูปแบบหลัง ('/view/([^/]+)?') กับของ Flask ('/view/<path:fname>')
  • เช่นเดียวกับเครื่องจัดการการอัปโหลด ด้าน Cloud Storage ยังต้องมีการดำเนินการเพิ่มเติมเล็กน้อยในด้านฟังก์ชันการทำงานที่ตัวแฮนเดิล Blobstore ตัดออก ได้แก่ การระบุไฟล์ (BLOB) ที่เป็นปัญหาและการดาวน์โหลดเมธอด send_blob() ของตัวแฮนเดิลไบนารีของ Blobstore อย่างชัดเจน
  • ในทั้งสองกรณี ระบบจะแสดงข้อผิดพลาด HTTP 404 ให้กับผู้ใช้หากไม่พบอาร์ติแฟกต์

เครื่องจัดการหลัก

การเปลี่ยนแปลงสุดท้ายในแอปพลิเคชันหลักจะเกิดขึ้นในเครื่องจัดการหลัก ระบบจะแทนที่เมธอดคำกริยา 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 ไม่ได้

นี่คือภาพของการเปลี่ยนแปลงชุดที่ 2 และชุดสุดท้ายใน 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 (หรือไม่ใช่ทั้ง 2 อย่าง) อยู่ หากมีไฟล์ที่ใช้ได้ ให้เลือกไฟล์ที่มีอยู่และใช้ตัวระบุนั้น (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 อยู่ไหม หากมี ให้เลือก 1 รายการ (หรือ 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 นี้ซึ่งแทนที่ 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 จะมีโควต้าฟรี และตราบใดที่คุณใช้งานไม่เกินระดับการใช้งานดังกล่าว เราก็จะไม่เรียกเก็บเงิน ค่าดังกล่าวมีไว้สําหรับการประมวลผล แต่ก็อาจมีการเรียกเก็บเงินค่าบริการ App Engine ที่เกี่ยวข้องด้วย ดังนั้นโปรดดูข้อมูลเพิ่มเติมในหน้าราคา หากการย้ายข้อมูลนี้เกี่ยวข้องกับบริการระบบคลาวด์อื่นๆ ระบบจะเรียกเก็บเงินแยกต่างหาก ในทั้ง 2 กรณี หากมี โปรดดูส่วน "เฉพาะสำหรับ Codelab นี้" ด้านล่าง

เพื่อการเปิดเผยข้อมูลทั้งหมด การทำให้ใช้งานได้กับแพลตฟอร์มการประมวลผลแบบ Serverless ของ 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*ของคุณ เช่น "us" หากแอปของคุณโฮสต์ในสหรัฐอเมริกา

ในทางกลับกัน หากคุณไม่ต้องการดำเนินการกับแอปพลิเคชันนี้หรือ Codelab การย้ายข้อมูลอื่นๆ ที่เกี่ยวข้องต่อไป และต้องการลบทุกอย่างออกทั้งหมด ให้ปิดโปรเจ็กต์

เฉพาะสำหรับ Codelab นี้

บริการในรายการด้านล่างเป็นบริการเฉพาะสำหรับ Codelab นี้ โปรดดูข้อมูลเพิ่มเติมในเอกสารประกอบของผลิตภัณฑ์แต่ละรายการ

โปรดทราบว่าหากคุณย้ายข้อมูลจากโมดูล 15 ไปยัง 16 คุณจะยังคงมีข้อมูลใน Blobstore เหตุผลที่เรารวมข้อมูลราคาเอาไว้ข้างต้น

ขั้นตอนถัดไป

นอกเหนือจากบทแนะนำนี้ โมดูลการย้ายข้อมูลอื่นๆ ที่มุ่งเน้นการย้ายจากบริการพร้อมแพ็กเกจแบบเดิมที่จะพิจารณามีดังนี้

  • โมดูล 2: ย้ายข้อมูลจาก App Engine ndb ไปยัง Cloud NDB
  • โมดูล 7-9: ย้ายข้อมูลจากงานพุชคิวงานของ App Engine ไปยัง Cloud Tasks
  • โมดูล 12-13: ย้ายข้อมูลจาก App Engine Memcache ไปยัง Cloud Memorystore
  • โมดูล 18-19: ย้ายข้อมูลจากคิวงานของ App Engine (พุลงาน) ไปยัง Cloud Pub/Sub

App Engine ไม่ใช่แพลตฟอร์มแบบ Serverless เพียงแพลตฟอร์มเดียวใน Google Cloud อีกต่อไป หากคุณมีแอป App Engine ขนาดเล็กหรือแอปที่มีฟังก์ชันการทำงานที่จำกัดและต้องการเปลี่ยนเป็น Microservice แบบสแตนด์อโลน หรือต้องการแตกแอปโมโนลิธให้เป็นคอมโพเนนต์ที่ใช้ซ้ำได้หลายรายการ เหตุผลที่ดีเหล่านี้ควรพิจารณาเปลี่ยนไปใช้ Cloud Functions หากการขนส่งด้วยคอนเทนเนอร์เป็นส่วนหนึ่งของเวิร์กโฟลว์การพัฒนาแอปพลิเคชัน โดยเฉพาะอย่างยิ่งหากประกอบด้วยไปป์ไลน์ CI/CD (การรวมอย่างต่อเนื่อง/การส่งมอบหรือการติดตั้งใช้งานอย่างต่อเนื่อง) ให้ลองย้ายข้อมูลไปยัง Cloud Run สถานการณ์เหล่านี้ครอบคลุมในโมดูลต่อไปนี้

  • ย้ายข้อมูลจาก App Engine ไปยัง Cloud Functions: ดูโมดูล 11
  • ย้ายข้อมูลจาก App Engine ไปยัง Cloud Run: ดูโมดูล 4 เพื่อสร้างคอนเทนเนอร์แอปด้วย Docker หรือโมดูล 5 เพื่อดำเนินการดังกล่าวโดยไม่ต้องมีคอนเทนเนอร์ ไม่มีความรู้เกี่ยวกับ Docker หรือDockerfile

คุณจะเปลี่ยนไปใช้แพลตฟอร์มแบบ Serverless อื่นหรือไม่ก็ได้ และเราขอแนะนำให้พิจารณาตัวเลือกที่ดีที่สุดสำหรับแอปและ Use Case ของคุณก่อนทำการเปลี่ยนแปลงใดๆ

ไม่ว่าคุณจะพิจารณาโมดูลการย้ายข้อมูลใดในครั้งถัดไป คุณสามารถเข้าถึงเนื้อหาของสถานีย้ายข้อมูลแบบ Serverless (โค้ดแล็บ วิดีโอ ซอร์สโค้ด [เมื่อพร้อมให้บริการ]) ทั้งหมดได้ที่ที่เก็บโอเพนซอร์ส 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

วิดีโอ

ใบอนุญาต

ผลงานนี้ได้รับอนุญาตภายใต้ใบอนุญาตทั่วไปครีเอทีฟคอมมอนส์แบบระบุแหล่งที่มา 2.0