Di chuyển từ App Engine Blobstore sang Cloud Storage (Mô-đun 16)

1. Tổng quan

Loạt lớp học lập trình Serverless Migration Station (hướng dẫn thực hành, tự học) và các video có liên quan nhằm giúp các nhà phát triển không máy chủ của Google Cloud hiện đại hoá ứng dụng của họ bằng cách hướng dẫn họ thực hiện một hoặc nhiều quá trình di chuyển, chủ yếu là chuyển khỏi các dịch vụ cũ. Việc này giúp ứng dụng của bạn dễ di chuyển hơn, đồng thời mang đến cho bạn nhiều lựa chọn và tính linh hoạt hơn, cho phép bạn tích hợp và truy cập vào nhiều sản phẩm Cloud hơn, cũng như dễ dàng nâng cấp lên các bản phát hành ngôn ngữ mới hơn. Mặc dù ban đầu tập trung vào những người dùng Cloud sớm nhất, chủ yếu là các nhà phát triển App Engine (môi trường tiêu chuẩn), nhưng loạt bài này đủ rộng để bao gồm các nền tảng không máy chủ khác như Cloud Functions và Cloud Run hoặc các nền tảng khác nếu có.

Lớp học lập trình này hướng dẫn bạn cách di chuyển từ App Engine Blobstore sang Cloud Storage. Ngoài ra, còn có các hoạt động di chuyển ngầm từ:

Hãy tham khảo mọi mô-đun di chuyển có liên quan để biết thêm thông tin từng bước.

Bạn sẽ tìm hiểu cách

  • Thêm cách sử dụng API/thư viện App Engine Blobstore
  • Lưu trữ nội dung tải lên của người dùng vào dịch vụ Blobstore
  • Chuẩn bị cho bước tiếp theo để di chuyển sang Cloud Storage

Bạn cần có

Khảo sát

Bạn sẽ sử dụng hướng dẫn này như thế nào?

Chỉ đọc hết Đọc và hoàn thành bài tập

Bạn đánh giá thế nào về trải nghiệm của mình với Python?

Mới làm quen Trung cấp Thành thạo

Bạn đánh giá thế nào về trải nghiệm sử dụng các dịch vụ của Google Cloud?

Mới bắt đầu Trung cấp Thành thạo

2. Thông tin cơ bản

Lớp học lập trình này bắt đầu bằng ứng dụng mẫu trong Mô-đun 15 và minh hoạ cách di chuyển từ Blobstore (và NDB) sang Cloud Storage (và Cloud NDB). Quá trình di chuyển bao gồm việc thay thế các phần phụ thuộc vào dịch vụ đi kèm cũ của App Engine, cho phép bạn di chuyển ứng dụng sang một nền tảng không máy chủ khác của Cloud hoặc nền tảng lưu trữ khác nếu muốn.

Quá trình di chuyển này đòi hỏi nhiều công sức hơn một chút so với các quá trình di chuyển khác trong loạt bài này. Blobstore có các phần phụ thuộc trên khung ứng dụng web ban đầu, và đó là lý do ứng dụng mẫu sử dụng khung webapp2 thay vì Flask. Hướng dẫn này trình bày cách di chuyển sang Cloud Storage, Cloud NDB, Flask và Python 3.

Ứng dụng vẫn đăng ký "lượt truy cập" của người dùng cuối và hiển thị 10 lượt truy cập gần đây nhất, nhưng lớp học lập trình trước đó (Mô-đun 15) đã thêm chức năng mới để phù hợp với việc sử dụng Blobstore: ứng dụng nhắc người dùng cuối tải cấu phần phần mềm (một tệp) tương ứng với "lượt truy cập" của họ lên. Người dùng có thể làm như vậy hoặc chọn "bỏ qua" để chọn không tham gia. Bất kể quyết định của người dùng là gì, trang tiếp theo sẽ hiển thị kết quả giống như các phiên bản trước của ứng dụng này, cho thấy những lượt truy cập gần đây nhất. Một điểm khác biệt nữa là các lượt truy cập có cấu phần phần mềm tương ứng sẽ có đường liên kết "xem" để hiển thị cấu phần phần mềm của lượt truy cập. Lớp học lập trình này triển khai các hoạt động di chuyển đã đề cập trước đó, đồng thời giữ nguyên chức năng đã mô tả.

3. Thiết lập/Chuẩn bị

Trước khi chuyển sang phần chính của hướng dẫn, hãy thiết lập dự án, lấy mã, sau đó triển khai ứng dụng cơ sở để đảm bảo rằng chúng ta bắt đầu bằng mã hoạt động.

1. Thiết lập dự án

Nếu đã triển khai ứng dụng của mô-đun 15, bạn nên sử dụng lại chính dự án (và mã) đó. Ngoài ra, bạn có thể tạo một dự án hoàn toàn mới hoặc sử dụng lại một dự án hiện có khác. Đảm bảo dự án có tài khoản thanh toán đang hoạt động và App Engine được bật.

2. Tải ứng dụng mẫu cơ sở

Một trong những điều kiện tiên quyết của lớp học lập trình này là bạn phải có một ứng dụng mẫu đang hoạt động của Mô-đun 15. Nếu chưa có, bạn có thể lấy ứng dụng này từ thư mục "START" (BẮT ĐẦU) của Mô-đun 15 (đường liên kết bên dưới). Lớp học lập trình này sẽ hướng dẫn bạn từng bước, kết thúc bằng mã tương tự như mã trong thư mục "FINISH" (HOÀN THÀNH) của Mô-đun 16.

Thư mục của các tệp BẮT ĐẦU trong Mô-đun 15 sẽ có dạng như sau:

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

Tệp main-gcs.py là một phiên bản thay thế của main.py trong Mô-đun 15, cho phép chọn một bộ chứa Cloud Storage khác với URL mặc định được chỉ định của ứng dụng dựa trên mã dự án: PROJECT_ID.appspot.com. Tệp này không đóng vai trò gì trong lớp học lập trình (Mô-đun 16) này, ngoại trừ việc bạn có thể áp dụng các kỹ thuật di chuyển tương tự cho tệp đó nếu muốn.

3. Triển khai (lại) ứng dụng cơ sở

Các bước chuẩn bị còn lại mà bạn cần thực hiện ngay:

  1. Làm quen lại với công cụ dòng lệnh gcloud
  2. Triển khai lại ứng dụng mẫu bằng gcloud app deploy
  3. Xác nhận ứng dụng chạy trên App Engine mà không gặp vấn đề

Sau khi bạn thực hiện thành công các bước đó và xác nhận ứng dụng của Mô-đun 15 hoạt động. Trang đầu tiên chào đón người dùng bằng một biểu mẫu nhắc tải tệp cấu phần phần mềm của lượt truy cập lên, cùng với một lựa chọn là nút "bỏ qua" để chọn không tải lên:

f5b5f9f19d8ae978.png

Sau khi người dùng tải tệp lên hoặc bỏ qua, ứng dụng sẽ hiển thị trang "lần truy cập gần đây nhất" quen thuộc:

f5ac6b98ee8a34cb.png

Những lượt truy cập có cấu phần phần mềm sẽ có đường liên kết "xem" ở bên phải dấu thời gian của lượt truy cập để hiển thị (hoặc tải xuống) cấu phần phần mềm. Sau khi xác nhận chức năng của ứng dụng, bạn đã sẵn sàng di chuyển khỏi các dịch vụ cũ của App Engine (webapp2, NDB, Blobstore) sang các dịch vụ thay thế hiện đại (Flask, Cloud NDB, Cloud Storage).

4. Cập nhật tệp cấu hình

Có 3 tệp cấu hình được áp dụng cho phiên bản cập nhật của ứng dụng. Các nhiệm vụ bắt buộc là:

  1. Cập nhật các thư viện bên thứ ba tích hợp sẵn bắt buộc trong app.yaml, đồng thời cho phép di chuyển sang Python 3
  2. Thêm một requirements.txt, chỉ định tất cả các thư viện bắt buộc không được tích hợp sẵn
  3. Thêm appengine_config.py để ứng dụng hỗ trợ cả thư viện tích hợp sẵn và thư viện không tích hợp sẵn của bên thứ ba

app.yaml

Chỉnh sửa tệp app.yaml bằng cách cập nhật phần libraries. Xoá jinja2 và thêm grpcio, setuptools và ssl. Chọn phiên bản mới nhất hiện có cho cả ba thư viện. Đồng thời, thêm lệnh runtime của Python 3 nhưng được đánh dấu là nội dung nhận xét. Khi bạn hoàn tất, giao diện sẽ có dạng như sau (nếu bạn chọn Python 3.9):

TRƯỚC:

runtime: python27
threadsafe: yes
api_version: 1

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

libraries:
- name: jinja2
  version: latest

SAU:

#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

Các thay đổi chủ yếu liên quan đến thư viện tích hợp sẵn Python 2 có trên máy chủ App Engine (vì vậy, bạn không cần phải tự đóng gói). Chúng tôi đã xoá Jinja2 vì Jinja2 đi kèm với Flask, mà chúng tôi sẽ thêm vào reqs.txt. Bất cứ khi nào bạn sử dụng thư viện ứng dụng Google Cloud, chẳng hạn như thư viện cho Cloud NDB và Cloud Storage, bạn đều cần có grpcio và setuptools. Cuối cùng, bản thân Cloud Storage yêu cầu thư viện ssl. Chỉ thị thời gian chạy được đánh dấu là nội dung nhận xét ở trên cùng là dành cho trường hợp bạn đã sẵn sàng chuyển ứng dụng này sang Python 3. Chúng ta sẽ tìm hiểu chủ đề này ở cuối hướng dẫn.

requirements.txt

Thêm tệp requirements.txt, yêu cầu khung Flask, thư viện ứng dụng Cloud NDB và Cloud Storage, không có thư viện nào được tích hợp sẵn. Tạo tệp có nội dung sau:

flask
google-cloud-ndb
google-cloud-storage

Môi trường thời gian chạy Python 2 App Engine yêu cầu tự đóng gói các thư viện bên thứ ba không tích hợp sẵn, vì vậy, hãy thực thi lệnh sau để cài đặt các thư viện này vào thư mục lib:

pip install -t lib -r requirements.txt

Nếu có cả Python 2 và 3 trên máy phát triển, bạn có thể phải sử dụng lệnh pip2 để đảm bảo nhận được phiên bản Python 2 của các thư viện này. Sau khi nâng cấp lên Python 3, bạn không cần phải tự đóng gói nữa.

appengine_config.py

Thêm tệp appengine_config.py hỗ trợ các thư viện bên thứ ba tích hợp sẵn và không tích hợp sẵn. Tạo tệp có nội dung sau:

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)

Các bước vừa hoàn tất phải tương tự hoặc giống hệt với các bước được liệt kê trong phần Cài đặt thư viện cho ứng dụng Python 2 của tài liệu về App Engine, và cụ thể hơn, nội dung của appengine_config.py phải khớp với nội dung trong Bước 5.

Công việc trên các tệp cấu hình đã hoàn tất, vì vậy, hãy chuyển sang ứng dụng.

5. Sửa đổi tệp ứng dụng

Nhập

Nhóm thay đổi đầu tiên cho main.py bao gồm việc loại bỏ tất cả những thứ cần thay thế. Sau đây là những thay đổi:

  1. webapp2 được thay thế bằng Flask
  2. Thay vì sử dụng Jinja2 từ webapp2_extras, hãy sử dụng Jinja2 đi kèm với Flask
  3. App Engine Blobstore và NDB được thay thế bằng Cloud NDB và Cloud Storage
  4. Trình xử lý Blobstore trong webapp được thay thế bằng sự kết hợp giữa mô-đun thư viện chuẩn io, Flask và các tiện ích werkzeug
  5. Theo mặc định, Blobstore ghi vào một bộ chứa Cloud Storage được đặt tên theo URL của ứng dụng (PROJECT_ID.appspot.com). Vì chúng ta đang chuyển sang thư viện ứng dụng Cloud Storage, nên google.auth được dùng để lấy mã dự án nhằm chỉ định chính xác tên bộ chứa đó. (Bạn có thể thay đổi tên bộ chứa vì tên này không còn được cố định giá trị trong mã nữa.)

TRƯỚC:

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

Triển khai các thay đổi trong danh sách trên bằng cách thay thế phần nhập hiện tại trong main.py bằng đoạn mã dưới đây.

SAU:

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

Khởi chạy và hỗ trợ Jinja2 không cần thiết

Khối mã tiếp theo cần thay thế là BaseHandler chỉ định việc sử dụng Jinja2 từ webapp2_extras. Điều này là không cần thiết vì Jinja2 đi kèm với Flask và là công cụ tạo mẫu mặc định của Flask, vì vậy, hãy xoá Jinja2.

Về phía Mô-đun 16, hãy tạo thực thể cho các đối tượng mà chúng ta không có trong ứng dụng cũ. Việc này bao gồm cả việc khởi chạy ứng dụng Flask và tạo ứng dụng API cho Cloud NDB và Cloud Storage. Cuối cùng, chúng ta sẽ kết hợp tên bộ chứa Cloud Storage như mô tả ở trên trong phần nhập. Dưới đây là hình ảnh trước và sau khi triển khai những thay đổi này:

TRƯỚC:

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))

SAU:

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

Cập nhật quyền truy cập vào Datastore

Cloud NDB hầu như tương thích với NDB của App Engine. Một điểm khác biệt đã được đề cập là nhu cầu về ứng dụng API. Một điểm khác biệt nữa là Datastore yêu cầu quyền truy cập phải được kiểm soát bằng trình quản lý bối cảnh Python của ứng dụng API. Về cơ bản, điều này có nghĩa là tất cả lệnh gọi truy cập Datastore bằng thư viện ứng dụng Cloud NDB chỉ có thể diễn ra trong các khối Python with.

Đó là một thay đổi; thay đổi còn lại là Blobstore và các đối tượng của nó, ví dụ: BlobKey, không được Cloud Storage hỗ trợ, vì vậy, hãy thay đổi file_blob thành ndb.StringProperty. Dưới đây là lớp mô hình dữ liệu và các hàm store_visit() và fetch_visits() đã cập nhật phản ánh những thay đổi này:

TRƯỚC:

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)

SAU:

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)

Sau đây là hình ảnh minh hoạ những thay đổi đã được thực hiện cho đến nay:

a8f74ca392275822.png

Cập nhật trình xử lý

Trình xử lý tải lên

Trình xử lý trong webapp2 là các lớp, còn trong Flask là các hàm. Thay vì phương thức động từ HTTP, Flask sử dụng động từ để trang trí hàm. Blobstore và các trình xử lý webapp của Blobstore được thay thế bằng chức năng của Cloud Storage cũng như Flask và các tiện ích của Flask:

TRƯỚC:

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)

SAU:

@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)

Một số lưu ý về nội dung cập nhật này:

  • Thay vì blob_id, các cấu phần phần mềm tệp hiện được xác định bằng tên tệp (fname) nếu có, và None nếu không (người dùng chọn không tải tệp lên).
  • Trình xử lý Blobstore tóm tắt quy trình tải lên cho người dùng, nhưng Cloud Storage thì không. Vì vậy, bạn có thể thấy mã mới được thêm vào để đặt đối tượng blob và vị trí (bộ chứa) của tệp cũng như lệnh gọi thực hiện quá trình tải lên thực tế. (upload_from_file()).
  • webapp2 sử dụng bảng định tuyến ở cuối tệp ứng dụng, còn các tuyến Flask nằm trong mỗi trình xử lý được trang trí.
  • Cả hai trình xử lý đều hoàn tất chức năng của mình bằng cách chuyển hướng đến trang chủ ( / ) trong khi vẫn giữ nguyên yêu cầu POST bằng mã trả về HTTP 307.

Trình xử lý tải xuống

Việc cập nhật trình xử lý tải xuống cũng tuân theo một mẫu tương tự như trình xử lý tải lên, chỉ có điều là có ít mã hơn để xem. Thay thế chức năng Blobstore và webapp bằng các chức năng tương đương của Cloud Storage và Flask:

TRƯỚC:

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)

SAU:

@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)

Lưu ý về bản cập nhật này:

  • Một lần nữa, Flask trang trí các hàm xử lý bằng tuyến của chúng trong khi webapp thực hiện việc này trong bảng định tuyến ở dưới cùng, vì vậy, hãy nhận ra cú pháp so khớp mẫu của (('/view/([^/]+)?') so với Flask ('/view/<path:fname>').
  • Giống như trình xử lý tải lên, bạn cần thực hiện thêm một số thao tác ở phía Cloud Storage để có được chức năng được trình xử lý Blobstore trừu tượng hoá, cụ thể là xác định tệp (blob) được đề cập và tải tệp nhị phân xuống một cách rõ ràng so với lệnh gọi phương thức send_blob() duy nhất của trình xử lý Blobstore.
  • Trong cả hai trường hợp, người dùng sẽ nhận được lỗi HTTP 404 nếu không tìm thấy cấu phần phần mềm.

Trình xử lý chính

Những thay đổi cuối cùng đối với ứng dụng chính sẽ diễn ra trong trình xử lý chính. Các phương thức động từ HTTP webapp2 được thay thế bằng một hàm duy nhất kết hợp chức năng của chúng. Thay thế lớp MainHandler bằng hàm root() và xoá bảng định tuyến webapp2 như dưới đây:

TRƯỚC:

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)

SAU:

@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)

Thay vì tách riêng các phương thức get() và post(), về cơ bản, chúng là một câu lệnh if-else trong root(). Ngoài ra, vì root() là một hàm duy nhất, nên chỉ có một lệnh gọi để kết xuất mẫu cho cả GET và POST, trong khi điều này không thực sự khả thi trong webapp2.

Sau đây là hình ảnh minh hoạ cho nhóm thay đổi thứ hai và cũng là nhóm thay đổi cuối cùng đối với main.py:

5ec38818c32fec2.png

(không bắt buộc) "Cải tiến" khả năng tương thích ngược

Vì vậy, giải pháp được tạo ở trên hoạt động hoàn hảo... nhưng chỉ khi bạn bắt đầu từ đầu và không có tệp do Blobstore tạo. Vì chúng tôi đã cập nhật ứng dụng để xác định tệp theo tên tệp thay vì BlobKey, nên ứng dụng hoàn chỉnh của mô-đun 16 sẽ không thể xem các tệp Blobstore. Nói cách khác, chúng tôi đã thực hiện một thay đổi không tương thích ngược khi thực hiện quá trình di chuyển này. Bây giờ, chúng tôi sẽ trình bày một phiên bản thay thế của main.py có tên là main-migrate.py (trong kho lưu trữ) để thu hẹp khoảng cách này.

"Phần mở rộng" đầu tiên hỗ trợ các tệp do Blobstore tạo là một mô hình dữ liệu có BlobKeyProperty (ngoài StringProperty cho các tệp do Cloud Storage tạo):

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()

Thuộc tính file_blob sẽ được dùng để xác định các tệp do Blobstore tạo, còn file_gcs là dành cho các tệp Cloud Storage. Giờ đây, khi tạo lượt ghé thăm cửa hàng thực tế mới, hãy lưu trữ rõ ràng một giá trị trong file_gcs thay vì file_blob, vì vậy store_visit sẽ có một chút khác biệt:

TRƯỚC:

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()

SAU:

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()

Khi tìm nạp các lượt truy cập gần đây nhất, hãy "chuẩn hoá" dữ liệu trước khi gửi dữ liệu đó đến mẫu:

TRƯỚC:

@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)

SAU:

@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)

Tiếp theo, hãy xác nhận xem file_blob hoặc file_gcs có tồn tại hay không (hoặc không có tệp nào tồn tại). Nếu có tệp, hãy chọn tệp hiện có và sử dụng giá trị nhận dạng đó (BlobKey cho tệp do Blobstore tạo hoặc tên tệp cho tệp do Cloud Storage tạo). Khi nói đến "các tệp do Cloud Storage tạo", chúng tôi muốn nói đến các tệp được tạo bằng thư viện ứng dụng Cloud Storage. Blobstore cũng ghi vào Cloud Storage, nhưng trong trường hợp này, đó sẽ là các tệp do Blobstore tạo.

Bây giờ, điều quan trọng hơn là hàm etl_visits() này được dùng để chuẩn hoá hoặc ETL (trích xuất, biến đổi và tải) dữ liệu cho người dùng cuối là gì? Thông báo sẽ có dạng như sau:

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]

Có thể kết quả sẽ giống như bạn mong đợi: mã lặp lại qua tất cả các lượt truy cập và đối với mỗi lượt truy cập, mã sẽ lấy nguyên văn dữ liệu khách truy cập và dấu thời gian, sau đó kiểm tra xem file_gcs hoặc file_blob có tồn tại hay không và nếu có, mã sẽ chọn một trong hai (hoặc None nếu không có giá trị nào tồn tại).

Dưới đây là hình minh hoạ về sự khác biệt giữa main.py và main-migrate.py:

718b05b2adadb2e1.png

Nếu bạn bắt đầu từ đầu mà không có tệp do Blobstore tạo, hãy sử dụng main.py. Tuy nhiên, nếu bạn đang chuyển đổi và muốn hỗ trợ các tệp do cả Blobstore và Cloud Storage tạo, hãy xem main-migrate.py để biết ví dụ về cách xử lý tình huống này nhằm giúp bạn lập kế hoạch di chuyển cho các ứng dụng của riêng mình. Khi thực hiện các hoạt động di chuyển phức tạp, các trường hợp đặc biệt có thể phát sinh, vì vậy, ví dụ này nhằm cho thấy mối quan hệ chặt chẽ hơn trong việc hiện đại hoá các ứng dụng thực tế bằng dữ liệu thực tế.

6. Tóm tắt/Dọn dẹp

Phần này kết thúc lớp học lập trình này bằng cách triển khai ứng dụng, xác minh ứng dụng hoạt động như dự kiến và trong mọi kết quả được phản ánh. Sau khi xác thực ứng dụng, hãy thực hiện mọi bước dọn dẹp và cân nhắc các bước tiếp theo.

Triển khai và xác minh ứng dụng

Trước khi triển khai lại ứng dụng, hãy nhớ chạy pip install -t lib -r requirements.txt để đưa các thư viện bên thứ ba tự đóng gói đó vào thư mục lib. Nếu bạn muốn chạy giải pháp tương thích ngược, trước tiên hãy đổi tên main-migrate.py thành main.py. Bây giờ, hãy chạy gcloud app deploy và xác nhận rằng ứng dụng hoạt động giống hệt với ứng dụng của mô-đun 15. Màn hình biểu mẫu sẽ có dạng như sau:

f5b5f9f19d8ae978.png

Trang lượt ghé thăm gần đây nhất có dạng như sau:

f5ac6b98ee8a34cb.png

Chúc mừng bạn đã hoàn thành lớp học lập trình này, thay thế App Engine Blobstore bằng Cloud Storage, App Engine NDB bằng Cloud NDB và webapp2 bằng Flask. Mã của bạn hiện sẽ khớp với nội dung trong thư mục FINISH (Mô-đun 16). main-migrate.py thay thế cũng có trong thư mục đó.

"Di chuyển" sang Python 3

Chỉ thị runtime của Python 3 được đánh dấu là nội dung nhận xét ở đầu app.yaml là tất cả những gì cần thiết để chuyển ứng dụng này sang Python 3. Bản thân mã nguồn đã tương thích với Python 3 nên bạn không cần thay đổi gì ở đó. Để triển khai ứng dụng này dưới dạng ứng dụng Python 3, hãy thực hiện các bước sau:

  1. Bỏ ghi chú lệnh runtime của Python 3 ở đầu app.yaml.
  2. Xoá tất cả các dòng khác trong app.yaml.
  3. Xoá tệp appengine_config.py. (không dùng trong thời gian chạy Python 3)
  4. Xoá thư mục lib nếu có. (không cần thiết với môi trường thời gian chạy Python 3)

Dọn dẹp

Giải pháp chung

Nếu đã hoàn tất, bạn nên tắt ứng dụng App Engine để tránh phát sinh phí. Tuy nhiên, nếu bạn muốn thử nghiệm thêm, nền tảng App Engine có hạn mức miễn phí và miễn là bạn không vượt quá hạn mức sử dụng đó, bạn sẽ không bị tính phí. Đó là chi phí cho tài nguyên điện toán, nhưng cũng có thể có phí cho các dịch vụ App Engine có liên quan, vì vậy, hãy xem trang giá của App Engine để biết thêm thông tin. Nếu quá trình di chuyển này liên quan đến các dịch vụ khác của Cloud, thì các dịch vụ đó sẽ được tính phí riêng. Trong cả hai trường hợp, nếu có, hãy xem phần "Cụ thể cho lớp học lập trình này" bên dưới.

Để minh bạch hoàn toàn, việc triển khai lên một nền tảng điện toán không máy chủ của Google Cloud như App Engine sẽ phát sinh chi phí xây dựng và lưu trữ nhỏ. Cloud Build có hạn mức miễn phí riêng, tương tự như Cloud Storage. Việc lưu trữ hình ảnh đó sẽ sử dụng một phần hạn mức. Tuy nhiên, bạn có thể sống ở một khu vực không có hạn mức miễn phí như vậy, vì vậy, hãy lưu ý đến mức sử dụng bộ nhớ của bạn để giảm thiểu chi phí tiềm ẩn. Các "thư mục" cụ thể trên Cloud Storage mà bạn nên xem xét bao gồm:

  • 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
  • Các đường liên kết đến bộ nhớ ở trên phụ thuộc vào PROJECT_ID và *LOC*ation của bạn, ví dụ: "us" nếu ứng dụng của bạn được lưu trữ ở Hoa Kỳ.

Mặt khác, nếu bạn không tiếp tục với ứng dụng này hoặc các lớp học lập trình di chuyển có liên quan khác và muốn xoá hoàn toàn mọi thứ, hãy tắt dự án của bạn.

Dành riêng cho lớp học lập trình này

Các dịch vụ được liệt kê dưới đây chỉ dành riêng cho lớp học lập trình này. Hãy tham khảo tài liệu của từng sản phẩm để biết thêm thông tin:

Xin lưu ý rằng nếu đã di chuyển từ Học phần 15 sang Học phần 16, bạn vẫn sẽ có dữ liệu trong Blobstore. Đó là lý do chúng tôi cung cấp thông tin về giá của Blobstore ở trên.

Các bước tiếp theo

Ngoài hướng dẫn này, bạn nên cân nhắc các mô-đun di chuyển khác tập trung vào việc chuyển khỏi các dịch vụ đi kèm cũ, bao gồm:

  • Mô-đun 2: di chuyển từ ndb của App Engine sang Cloud NDB
  • Mô-đun 7-9: di chuyển từ công việc đẩy của Hàng đợi tác vụ của App Engine sang Cloud Tasks
  • Mô-đun 12-13: di chuyển từ App Engine Memcache sang Cloud Memorystore
  • Mô-đun 18-19: di chuyển từ Hàng đợi tác vụ của App Engine (tác vụ kéo) sang Cloud Pub/Sub

App Engine không còn là nền tảng không máy chủ duy nhất trong Google Cloud. Nếu bạn có một ứng dụng App Engine nhỏ hoặc một ứng dụng có chức năng hạn chế và muốn biến ứng dụng đó thành một dịch vụ vi mô độc lập, hoặc bạn muốn chia một ứng dụng nguyên khối thành nhiều thành phần có thể sử dụng lại, thì đây là những lý do chính đáng để cân nhắc chuyển sang Cloud Functions. Nếu việc đóng gói ứng dụng vào vùng chứa đã trở thành một phần trong quy trình phát triển ứng dụng của bạn, đặc biệt là nếu quy trình này bao gồm một quy trình CI/CD (tích hợp liên tục/phân phối hoặc triển khai liên tục), hãy cân nhắc việc di chuyển sang Cloud Run. Các trường hợp này được đề cập trong các mô-đun sau:

  • Di chuyển từ App Engine sang Cloud Functions: xem Mô-đun 11
  • Di chuyển từ App Engine sang Cloud Run: xem Mô-đun 4 để tạo vùng chứa cho ứng dụng bằng Docker hoặc Mô-đun 5 để thực hiện mà không cần vùng chứa, kiến thức về Docker hoặc Dockerfile

Bạn có thể chuyển sang một nền tảng không máy chủ khác, nhưng bạn nên cân nhắc các lựa chọn phù hợp nhất cho ứng dụng và trường hợp sử dụng của mình trước khi thực hiện bất kỳ thay đổi nào.

Bất kể bạn cân nhắc mô-đun di chuyển nào tiếp theo, bạn đều có thể truy cập vào tất cả nội dung của Serverless Migration Station (lớp học lập trình, video, mã nguồn [nếu có]) tại kho lưu trữ nguồn mở của nội dung đó. README của kho lưu trữ cũng cung cấp hướng dẫn về những quá trình di chuyển cần xem xét và "thứ tự" liên quan của các Mô-đun di chuyển.

7. Tài nguyên khác

Vấn đề/ý kiến phản hồi về lớp học lập trình

Nếu bạn phát hiện thấy vấn đề nào trong lớp học lập trình này, vui lòng tìm kiếm vấn đề đó trước khi gửi báo cáo. Đường liên kết để tìm kiếm và tạo vấn đề mới:

Tài nguyên di chuyển

Bạn có thể tìm thấy đường liên kết đến các thư mục kho lưu trữ cho Mô-đun 15 (BẮT ĐẦU) và Mô-đun 16 (HOÀN THÀNH) trong bảng dưới đây. Bạn cũng có thể truy cập vào các tệp này từ kho lưu trữ cho tất cả các hoạt động di chuyển trong lớp học lập trình App Engine. Bạn có thể sao chép hoặc tải tệp ZIP xuống.

Lớp học lập trình

Python 2

Python 3

Mô-đun 15

mã

Không có

Mô-đun 16 (lớp học lập trình này)

mã

(giống như Python 2)

Tài nguyên trực tuyến

Dưới đây là các tài nguyên trực tuyến có thể liên quan đến hướng dẫn này:

App Engine Blobstore và Cloud Storage

Nền tảng App Engine

Thông tin khác về Cloud

Python

Video

Giấy phép

Tác phẩm này được cấp phép theo Giấy phép Ghi công theo Creative Commons 2.0 Chung.