从 App Engine Blob 存储区迁移到 Cloud Storage(模块 16)

1. 概览

无服务器迁移站系列 Codelab(自定进度的动手教程)和相关视频旨在帮助 Google Cloud 无服务器开发者通过一次或多次迁移(主要是从旧版服务迁移)来指导他们的应用现代化。这样做可让您的应用更易于移植,并为您提供更多选择和灵活性,使您能够与更多 Cloud 产品集成并访问这些产品,还能更轻松地升级到新的语言版本。虽然最初侧重于最早的 Cloud 用户(主要是 App Engine [标准环境] 开发者),但本系列文章的范围足够广,可涵盖其他无服务器平台(例如 Cloud FunctionsCloud Run),或者其他平台(如果适用)。

此 Codelab 将介绍如何从 App Engine Blobstore 迁移到 Cloud Storage。还有以下隐式迁移:

如需了解更多分步信息,请参阅任何相关的迁移模块。

在接下来的实验中

  • 添加了对 App Engine Blobstore API/库的使用
  • 将用户上传的内容存储到 Blobstore 服务
  • 准备下一步,迁移到 Cloud Storage

所需条件

调查问卷

您将如何使用本教程?

仅阅读教程内容 阅读并完成练习

您如何评价使用 Python 的体验?

新手水平 中等水平 熟练水平

您如何评价自己在使用 Google Cloud 服务方面的经验水平?

新手水平 中等水平 熟练水平

2. 背景

本 Codelab 从模块 15 中的示例应用开始,演示了如何从 Blobstore(和 NDB)迁移到 Cloud Storage(和 Cloud NDB)。迁移过程涉及替换对 App Engine 旧版捆绑服务的依赖项,这样一来,您就可以根据需要将应用迁移到其他 Cloud 无服务器平台或其他托管平台。

与本系列中的其他迁移相比,此迁移需要付出更多精力。Blobstore 依赖于原始的 webapp 框架,因此示例应用使用 webapp2 框架而不是 Flask。本教程重点介绍如何迁移到 Cloud Storage、Cloud NDB、Flask 和 Python 3。

应用仍会注册最终用户的“访问”并显示最近的 10 次访问,但之前的(模块 15)Codelab 添加了新功能来适应 Blobstore 用法:应用会提示最终用户上传与其“访问”对应的制品(文件)。用户可以选择这样做,也可以选择“跳过”以选择停用。无论用户做出何种决定,下一页都会呈现与此应用的先前版本相同的输出,显示最近的访问记录。另一个变化是,如果访问具有相应的制品,则会显示一个“查看”链接,用于显示访问的制品。此 Codelab 会实现前面提到的迁移,同时保留所述功能。

3. 设置/准备工作

在开始学习本教程的主要部分之前,让我们设置项目、获取代码,然后部署基准应用,以便我们知道我们从工作代码开始。

1. 设置项目

如果您已部署模块 15 应用,我们建议您重复使用同一项目(和代码)。或者,您可以创建一个全新的项目或重复使用另一个现有项目。确保该项目具有有效的结算账号,并且已启用 App Engine。

2. 获取基准示例应用

此 Codelab 的前提条件是拥有有效的工作模块 15 示例应用。如果您没有此模块,可以从模块 15 的“START”文件夹(下面的链接)中获取。本 Codelab 将引导您完成每个步骤,最后获得类似于模块 16“FINISH”文件夹中的代码。

模块 15 启动文件的目录应如下所示:

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

main-gcs.py 文件是模块 15 中 main.py 的替代版本,允许选择与应用的默认分配网址不同的 Cloud Storage 存储分区,该网址基于项目 ID: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. 更新配置文件

对于应用的更新版本,我们需要使用三个配置文件。所需完成的任务如下:

  1. 更新了 app.yaml 中所需的内置第三方库,并为迁移到 Python 3 留出了空间
  2. 添加一个 requirements.txt,用于指定所有必需的非内置库
  3. 添加 appengine_config.py,以便应用同时支持内置和非内置第三方库

app.yaml

通过更新 libraries 部分来修改 app.yaml 文件。移除了 jinja2,并添加了 grpciosetuptoolsssl。选择这三个库的最新可用版本。同时添加 Python 3 runtime 指令,但将其注释掉。完成后,它应如下所示(如果您选择的是 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

这些更改主要涉及 App Engine 服务器上提供的 Python 2 内置库(因此您不必自行捆绑它们)。我们移除了 Jinja2,因为它随附于 Flask,而我们将把 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)

刚刚完成的步骤应与 App Engine 文档的 为 Python 2 应用安装库部分中列出的步骤类似或完全相同,更具体地说,appengine_config.py 的内容应与该部分中的第 5 步一致。

配置文件方面的工作已完成,接下来我们继续处理应用。

5. 修改应用文件

导入

main.py 的第一组更改包括替换所有要替换的内容。具体变化如下:

  1. webapp2 已替换为 Flask
  2. 不使用 webapp2_extras 中的 Jinja2,而是使用 Flask 随附的 Jinja2
  3. App Engine Blobstore 和 NDB 已被 Cloud NDB 和 Cloud Storage 取代
  4. webapp 中的 Blobstore 处理程序被 io 标准库模块、Flask 和 werkzeug 实用程序的组合所取代
  5. 默认情况下,Blobstore 会将数据写入以应用网址 (PROJECT_ID.appspot.com) 命名的 Cloud Storage 存储分区。由于我们要移植到 Cloud Storage 客户端库,因此使用 google.auth 获取项目 ID 以指定完全相同的存储分区名称。(您可以更改存储分区名称,因为该名称不再是硬编码的。)

之前

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,它指定了从 webapp2_extras 使用 Jinja2。这是不必要的,因为 Jinja2 随 Flask 一起提供,并且是 Flask 的默认模板引擎,因此请将其移除。

在模块 16 侧,实例化旧版应用中没有的对象。这包括初始化 Flask 应用,以及为 Cloud NDB 和 Cloud Storage 创建 API 客户端。最后,我们将 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 访问权限由 API 客户端的 Python 上下文管理器控制。从本质上讲,这意味着使用 Cloud NDB 客户端库的所有 Datastore 访问调用只能在 Python with 块内进行。

这是一项更改;另一项更改是,Cloud Storage 不支持 Blobstore 及其对象(例如 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)

关于此更新的一些说明:

  • 现在,文件制品通过文件名 (fname)(如果存在)和 None(否则,即用户选择不上传文件)进行标识,而不是通过 blob_id 进行标识。
  • Blobstore 处理程序从用户那里抽象出了上传过程,但 Cloud Storage 没有这样做,因此您可以看到新添加的代码,这些代码用于设置文件的 blob 对象和位置(存储分区),以及执行实际上传的调用。(upload_from_file())。
  • webapp2 使用应用文件底部的路由表,而 Flask 路由位于每个带装饰器的处理程序中。
  • 这两个处理程序都会通过重定向到首页 ( /) 来完成其功能,同时通过 HTTP 307 返回代码保留 POST 请求。

下载处理程序

更新下载处理程序与更新上传处理程序类似,只是需要查看的代码少得多。将 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>'
  • 与上传处理程序一样,对于 Blobstore 处理程序抽象出来的功能,Cloud Storage 端需要做更多工作,即识别相关文件 (blob) 并显式下载二进制文件,而不是像 Blobstore 处理程序那样只调用一个 send_blob() 方法。
  • 在这两种情况下,如果未找到制品,系统都会向用户返回 HTTP 404 错误。

主要处理程序

对主要应用的最终更改发生在主要处理程序中。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() 方法,而本质上是 root() 中的 if-else 语句。此外,由于 root() 是一个单一函数,因此只需一次调用即可渲染 GETPOST 的模板,而这在 webapp2 中是不太可能实现的。

下图直观地展示了对 main.py 的第二组也是最后一组更改:

5ec38818c32fec2.png

(可选)向后兼容性“增强”

因此,上述解决方案非常有效,但前提是您从头开始,并且没有 Blobstore 创建的文件。由于我们更新了应用,使其能够通过文件名(而非 BlobKey)识别文件,因此完成的模块 16 应用无法按原样查看 Blobstore 文件。换句话说,我们在执行此迁移时进行了不向后兼容的更改。我们现在提供 main.py 的替代版本 main-migrate.py(可在 Repo 中找到),该版本尝试弥合这一差距。

支持 Blobstore 创建的文件的第一个“扩展”是一个数据模型,该模型具有 BlobKeyProperty(除了 Cloud Storage 创建的文件的 StringProperty 之外):

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_blobfile_gcs(或两者都不存在)。如果有可用文件,请选择现有文件并使用相应标识符(对于 Blobstore 创建的文件,使用 BlobKey;对于 Cloud Storage 创建的文件,使用文件名)。我们所说的“Cloud Storage 创建的文件”是指使用 Cloud Storage 客户端库创建的文件。Blobstore 也会写入 Cloud Storage,但在此情况下,写入的是 Blobstore 创建的文件。

现在,更重要的是,用于为最终用户规范化或 ETL(提取、转换和加载)数据的 etl_visits() 函数是什么?如下所示:

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_gcsfile_blob,如果存在,则选择其中一个(如果都不存在,则选择 None)。

下图说明了 main.pymain-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(模块 16)文件夹中的内容相匹配。该文件夹中还存在替代 main-migrate.py

Python 3“迁移”

只需取消 app.yaml 顶部注释掉的 Python 3 runtime 指令,即可将此应用移植到 Python 3。源代码本身已与 Python 3 兼容,因此无需进行任何更改。如需将其部署为 Python 3 应用,请执行以下步骤:

  1. 取消注释 app.yaml 顶部的 Python 3 runtime 指令。
  2. 删除 app.yaml 中的所有其他行。
  3. 删除 appengine_config.py 文件。(在 Python 3 运行时中未使用)
  4. 删除 lib 文件夹(如果存在)。(对于 Python 3 运行时,此步骤是不必要的)

清理

常规

如果您暂时不想继续操作,建议您停用 App Engine 应用,以免产生结算费用。不过,如果您想进一步测试或实验,App Engine 平台有免费配额,因此只要您不超过该使用层级,就不会产生费用。这是计算费用,但相关 App Engine 服务也可能会产生费用,因此请查看其价格页面了解详情。如果此迁移涉及其他 Cloud 服务,则这些服务会单独计费。在任何一种情况下,如果适用,请参阅下文中的“本 Codelab 特有的问题”部分。

为了完全公开透明,我们在此说明,部署到 Google Cloud 无服务器计算平台(例如 App Engine)会产生少量 build 和存储费用Cloud BuildCloud 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*ation,例如,如果您的应用托管在美国,则为“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 不再是 Google Cloud 中唯一的无服务器平台。如果您有一个小型 App Engine 应用或功能有限的应用,并希望将其转换为独立的微服务,或者您希望将单体式应用拆分为多个可重用的组件,那么这些都是考虑迁移到 Cloud Functions 的充分理由。如果容器化已成为应用开发工作流的一部分,尤其是当它包含 CI/CD(持续集成/持续交付或部署)流水线时,请考虑迁移到 Cloud Run。以下模块涵盖了这些使用场景:

  • 从 App Engine 迁移到 Cloud Functions:请参阅模块 11
  • 从 App Engine 迁移到 Cloud Run:请参阅模块 4,了解如何使用 Docker 将应用容器化;或参阅模块 5,了解如何在不使用容器、Docker 知识或 Dockerfile 的情况下完成迁移

您可以选择改用其他无服务器平台,但我们建议您先考虑最适合您的应用和使用情形的选项,然后再进行任何更改。

无论您接下来考虑哪个迁移模块,都可以在 开源代码库中访问所有无服务器迁移站内容(Codelab、视频、源代码 [如有])。该代码库的 README 还提供了有关应考虑哪些迁移以及任何相关的迁移模块“顺序”的指南。

7. 其他资源

Codelab 问题/反馈

如果您发现本 Codelab 存在任何问题,请先搜索您的问题,然后再提交。用于搜索和创建新问题的链接:

迁移时可参考的资源

您可以在下表中找到模块 15(开始)和模块 16(完成)的代码库文件夹的链接。您还可以从所有 App Engine Codelab 迁移的代码库中访问这些示例,您可以克隆该代码库或下载 ZIP 文件。

Codelab

Python 2

Python 3

模块 15

代码

不适用

第 16 模块(本 Codelab)

代码

(与 Python 2 相同)

在线资源

以下是一些可能与本教程相关的在线资源:

App Engine Blobstore 和 Cloud Storage

App Engine 平台

其他云信息

Python

视频

许可

此作品已获得 Creative Commons Attribution 2.0 通用许可授权。