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 theo tốc độ của riêng bạn) và các video liên quan nhằm giúp các nhà phát triển Google Cloud không máy chủ hiện đại hoá các ứ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 quy trình di chuyển, chủ yếu là chuyển từ các dịch vụ cũ. Làm như vậy sẽ giúp ứng dụng của bạn dễ dàng di chuyển hơn, đồng thời mang đến cho bạn nhiều lựa chọn và sự 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 trên đám mây 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 FunctionsCloud Run, hoặc ở những nơi khác nếu có thể.

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ừ:

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 việc sử dụng API/thư viện Blobstore của App Engine
  • Lưu trữ tệp người dùng tải lên 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 Đọ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?

Người mới bắt đầu Trung cấp Thành thạo

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

Người mới bắt đầu Trung cấp Thành thạo

2. Thông tin khái quát

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 các dịch vụ cũ đi kèm của App Engine. Nhờ đó, bạn có thể di chuyển các ứng dụng của mình sang một nền tảng không máy chủ khác trên 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 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 vào khung ứng dụng web ban đầu, đó 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ác quy trình 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 để đáp ứng việc sử dụng Blobstore: ứng dụng nhắc người dùng cuối tải một cấu phần phần mềm (tệp) tương ứng với "lượt truy cập" của họ. Người dùng có thể làm như vậy hoặc chọn "bỏ qua" để 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ị cùng một đầu ra 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à những lượt truy cập có cấu phần phần mềm tương ứng sẽ có một đườ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 quy trình di chuyển đã đề cập trước đó trong khi vẫn giữ nguyên chức năng được mô tả.

3. Thiết lập/Công việc chuẩn bị

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

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

Nếu đã triển khai ứng dụng 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ó một 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 trong Mô-đun 15. Nếu chưa có, bạn có thể lấy ứng dụng này trong 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 đoạn mã tương tự như đoạn mã trong thư mục "FINISH" (HOÀN TẤT) 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à phiên bản thay thế của main.py trong Mô-đun 15, cho phép chọn một nhóm Cloud Storage khác với nhóm mặc định của URL đượ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 này (Mô-đun 16), ngoài 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 rằng ứ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 trong Phần 15 hoạt động. Trang ban đầu chào đón người dùng bằng một biểu mẫu nhắc tải tệp hiện vật của lượt truy cập lên cùng với một lựa chọn (nút "bỏ qua") để chọn không tham gia:

f5b5f9f19d8ae978.png

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

f5ac6b98ee8a34cb.png

Những lượt truy cập có chứa một hiện vật 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) hiện vật đó. Sau khi xác nhận chức năng của ứng dụng, bạn có thể di chuyển từ các dịch vụ cũ của App Engine (webapp2, NDB, Blobstore) sang các lựa chọn thay thế hiện đại (Flask, Cloud NDB, Cloud Storage).

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

Ba tệp cấu hình sẽ được dùng cho phiên bản mới của ứng dụng. Các việc cần làm là:

  1. Cập nhật các thư viện bắt buộc của bên thứ ba trong app.yaml cũng như mở đường cho việc 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, setuptoolsssl. Chọn phiên bản mới nhất hiện có cho cả 3 thư viện. Bạn cũng có thể thêm chỉ thị runtime Python 3, nhưng hãy thêm dưới dạng chú thích. Khi bạn hoàn tất, tệp 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

Những thay đổi này chủ yếu liên quan đến các thư viện tích hợp sẵn Python 2 có trên máy chủ App Engine (nên bạn không cần tự gói chúng). Chúng tôi đã xoá Jinja2 vì nó đi kèm với Flask. Chúng tôi sẽ thêm Flask vào reqs.txt. Bất cứ khi nào bạn sử dụng các 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, Cloud Storage cần có thư viện ssl. Chỉ thị thời gian chạy được 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ẽ đề cập đến chủ đề này ở cuối hướng dẫn.

requirements.txt

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

flask
google-cloud-ndb
google-cloud-storage

Thời gian chạy App Engine Python 2 yêu cầu tự liên kết các thư viện không phải là thư viện tích hợp sẵn của bên thứ ba, 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 dùng lệnh pip2 để đảm bảo nhận được các phiên bản Python 2 của những thư viện này. Sau khi nâng cấp lên Python 3, bạn không cần tự đóng gói nữa.

appengine_config.py

Thêm một tệp appengine_config.py hỗ trợ các thư viện tích hợp sẵn và không tích hợp sẵn của bên thứ ba. 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 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 App Engine, cụ thể hơn là 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 thay thế tất cả những thứ cần thay thế. Sau đây là những nội dung thay đổi:

  1. webapp2 được thay thế bằng Flask
  2. Thay vì dùng Jinja2 từ webapp2_extras, hãy 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. Các trình xử lý Blobstore trong webapp được thay thế bằng một tổ hợp gồm 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 vùng 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 vùng chứa. (Bạn có thể thay đổi tên nhóm vì tên này không còn được mã hoá cứng 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ã bên dưới.

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, nên hãy xoá nó.

Ở 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ũ. Thao tác này bao gồm việc khởi động ứ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ẽ đặt tên cho 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 nội dung cập nhật 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 hết đều tương thích với App Engine NDB. Một điểm khác biệt đã được đề cập là nhu cầu về một ứng dụng API. Một điểm khác biệt nữa là Datastore yêu cầu trình quản lý bối cảnh Python của ứng dụng API kiểm soát quyền truy cập. Về cơ bản, điều này có nghĩa là tất cả cá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 with của Python.

Đó là một thay đổi; thay đổi khác 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()fetch_visits() đã được 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

Đang 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, trong khi đó, trình xử lý 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à trình xử lý webapp của nó đượ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 nó:

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 của 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).
  • Các trình xử lý Blobstore đã trừu tượng hoá quy trình tải lên khỏi 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 để đặt đối tượng blob và vị trí (thùng chứa) của tệp, cũng như lệnh gọi thực hiện quy 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, trong khi các tuyến Flask nằm trong mỗi trình xử lý được trang trí.
  • Cả hai trình xử lý đều kết thúc chức năng bằng cách chuyển hướng đến trang chủ ( / ) trong khi vẫn giữ nguyên yêu cầu POST với 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 tuân theo một mẫu tương tự như trình xử lý tải lên, chỉ có điều có ít mã hơn nhiều để 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 trình xử lý bằng tuyến đường 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 dạng cú pháp so khớp mẫu ('/view/([^/]+)?' của webapp so với cú pháp của Flask ('/view/<path:fname>').
  • Tương tự 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ừu tượng hoá bởi trình xử lý Blobstore, cụ thể là xác định tệp (blob) có liên quan và tải rõ ràng tệp nhị phân xuố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, lỗi HTTP 404 sẽ được trả về cho người dùng nếu không tìm thấy một 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 các phương thức đó. Thay thế lớp MainHandler bằng hàm root() và xoá bảng định tuyến webapp2 như minh hoạ 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ì các phương thức get()post() riêng biệt, 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ả GETPOST, trong khi điều này không thực sự có thể thực hiện được trong webapp2.

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

5ec38818c32fec2.png

(không bắt buộc) "Nâng cao" 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 nào 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 thà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 quy trình di chuyển này. Giờ đây, chúng ta sẽ trình bày một phiên bản thay thế của main.py có tên là main-migrate.py (có trong kho lưu trữ) nhằm cố gắng khắc phục khoảng trống này.

"Tiện ích" đầu tiên hỗ trợ các tệp được tạo bằng Blobstore là một mô hình dữ liệu có BlobKeyProperty (ngoài StringProperty cho các tệp được tạo bằng 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()

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 trên Cloud Storage. Giờ đây, khi tạo lượt truy cập 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ẽ trông hơi khác một chú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 lượt truy cập gần đây nhất, hãy "chuẩn hoá" dữ liệu trước khi gửi đế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 sự tồn tại của file_blob hoặc file_gcs (hoặc cả hai). Nếu có tệp, hãy chọn tệp hiện có và sử dụng mã 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 "tệp do Cloud Storage tạo", chúng tôi muốn nói đến những 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.

Quan trọng hơn, chức năng etl_visits() này 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ó lẽ mã này sẽ trông giống như bạn mong đợi: mã sẽ lặp lại 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 dữ liệu về khách truy cập và dấu thời gian theo đúng nghĩa đen, sau đó kiểm tra xem có file_gcs hoặc file_blob hay không. Nếu có, mã sẽ chọn một trong hai (hoặc None nếu không có dữ liệu nào).

Sau đây là hình minh hoạ về sự khác biệt giữa main.pymain-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 Cloud Storage tạo, hãy xem main-migrate.py làm ví dụ về cách xử lý trường hợp như vậy để 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 quy trình di chuyển phức tạp, có thể sẽ phát sinh các trường hợp đặc biệt. Vì vậy, ví dụ này nhằm mục đích cho thấy sự phù hợp hơn khi 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 bằng cách triển khai ứng dụng, xác minh rằng ứng dụng hoạt động như dự kiến và trong mọi đầu ra đượ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 để lấy các thư viện tự đóng gói của bên thứ ba trong 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 như ứng dụng trong Mô-đun 15. Màn hình biểu mẫu sẽ có dạng như sau:

f5b5f9f19d8ae978.png

Trang lượt truy cập gần đây nhất sẽ 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ế Blobstore của App Engine bằng Cloud Storage, NDB của App Engine bằng Cloud NDB và webapp2 bằng Flask. Giờ đây, mã của bạn sẽ khớp với mã trong thư mục FINISH (Module 16). main-migrate.py thay thế cũng có trong thư mục đó.

"Di chuyển" Python 3

Chỉ cần chỉ thị runtime Python 3 được nhận xét ở đầu app.yaml là đủ để 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. Huỷ đánh dấu lệnh runtime 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 thư mục này tồn tại. (không cần thiết với 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 bị tính phí. Tuy nhiên, nếu muốn kiểm thử hoặc thử nghiệm thêm, nền tảng App Engine có một hạn mức miễn phí. Vì vậy, miễn là không vượt quá cấp sử dụng đó, bạn sẽ không bị tính phí. Đó là mức phí cho hoạt động tính toán, nhưng cũng có thể có các khoản phí cho các dịch vụ App Engine có liên quan. Vì vậy, hãy xem trang định giá của dịch vụ này để 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ụ đám mây khác, 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.

Để công bố đầy đủ, việc triển khai trê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 một khoản chi phí nhỏ cho việc tạo và lưu trữ. 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ẽ chiếm một phần hạn mức. Tuy nhiên, có thể bạn sinh sống ở một khu vực không có gói miễn phí như vậy, vì vậy, hãy lưu ý đến mức sử dụng bộ nhớ để giảm thiểu chi phí phát sinh. Bạn nên xem xét các "thư mục" cụ thể trên Cloud Storage, 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*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.

Cụ thể đối với lớp học lập trình này

Các dịch vụ trong danh sách dưới đây là riêng biệt đối với 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ừ Mô-đun 15 sang 16, bạn vẫn sẽ có dữ liệu trong Blobstore. Đó là lý do chúng tôi đưa thông tin về giá của Blobstore vào phần 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 từ các dịch vụ cũ đi kèm, bao gồm:

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

App Engine không còn là nền tảng duy nhất không cần máy chủ trên Google Cloud nữa. 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 vi dịch vụ độ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ể dùng lại, thì đây là những lý do chính đáng để cân nhắc việc chuyển sang Cloud Functions. Nếu việc tạ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 liên tục 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 để đóng gói ứng dụng của bạn bằng Docker hoặc Mô-đùn 5 để thực hiện việc này mà không cần vùng chứa, kiến thức về Docker hoặc Dockerfile

Bạn không bắt buộc phải chuyển sang một nền tảng không máy chủ khác. Bạn nên cân nhắc những 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ở. README của kho lưu trữ này cũng cung cấp hướng dẫn về những hoạt động di chuyển cần cân nhắc 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 gặp vấn đề với lớp học lập trình này, vui lòng tìm kiếm vấn đề của bạn trước khi 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 (KẾT THÚC) trong bảng bên dưới. Bạn cũng có thể truy cập vào các hướng dẫn này trong kho lưu trữ cho tất cả các hoạt động di chuyển codelab 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

Phụ lục 15

code

Không có

Học phần 16 (lớp học lập trình này)

code

(tương tự 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:

Blobstore và Cloud Storage của App Engine

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.