模块 1:从 App Engine webapp2 迁移到 Flask

这一系列的 Codelab(自定进度的动手教程)旨在帮助 Google App Engine(标准)开发者通过一系列迁移来指导他们的应用现代化。最重要的步骤是摆脱原始的运行时捆绑服务,因为下一代运行时更加灵活,为用户提供了更多的服务选项。移至新一代运行时,您可以更轻松地与 Google Cloud 产品集成,使用更多支持的服务,并支持当前的语言版本。

此初始教程介绍了在 App Engine 应用中实现 Web 框架现代化的迁移步骤:从 webapp2 迁移到 Flask。在您的应用中,您可以使用任何处理路由的 Web 框架,但是在本教程中,我们使用 Flask,因为社区广泛使用了 Flask。

您将了解如何

  • 使用第三方库(内置或其他方法)
  • 更新配置文件
  • 将一个简单的应用从 webapp2 迁移到 Flask

所需条件

调查问卷

如何使用此 Codelab?

只是阅读 阅读并完成练习

当 App Engine 于 2008 年在 Python 2.5 上首次启动时,webapp webapp 框架便被捆绑了。几年后,在 2013 年 2.7 版运行时环境淘汰了 2.5 版运行时环境时它由接替者 webapp2 替换。

虽然 webapp2(请参阅文档)仍然存在,并且可以在 App Engine 外部用作 WSGI 兼容的 Web 框架,但它不会自己将用户请求路由到应用中的相应代码。而是依靠 App Engine,配置文件和开发者来执行将网络流量路由到相应“处理程序”的路由。此外,webapp2 的核心优势与 App Engine 的捆绑服务有着千丝万缕的联系,可以有效地将它淘汰,即使它在 Python 3 上也可以运作(另请参阅相关问题)。

此模块为实践者提供了将简单的 webapp2 应用迁移到 Flask 的实践经验,Flask 是 App Engine 支持的框架,并且是 Google Cloud 之外的更多服务,从而使应用程序更加可移植。如果 Flask 不是将您的应用迁移到的目标理想框架,则可以选择另一个,只要它执行自己的路由即可。此 Codelab 向信息技术决策者 (ITDM) 和开发者展示了迁移步骤是什么,因此无论您实际迁移到哪个框架,您都可以熟悉此过程。

以下是此迁移的主要步骤:

  1. 设置/准备工作
  2. 添加 Flask 第三方库
  3. 更新应用文件
  4. 更新 HTML 模板文件

在开始学习本教程的主要部分之前,让我们设置项目,获取代码,然后(重新)熟悉 gcloud 命令并部署基准应用,以便我们知道我们从工作代码入手。

1.设置项目

作为现有开发者,您的 App Engine 信息中心可能已经显示了您正在运行的服务。就本教程而言,我们建议您创建一个全新的项目或在本教程中重复使用现有的项目。确保该项目具有有效的结算帐号,并且已启用 App Engine(应用)。

2.下载基准示例应用

GAE 迁移代码库包含您需要的所有代码。克隆或下载其 ZIP 文件。对于本教程,您将从模块 0 文件夹 (START) 中的代码开始,并且在完成本教程后,您的代码应与模块 1 文件夹 (FINISH) 相匹配。如果不是,请检查差异,以便您可以进行下一个实验。

模块 0 文件夹应具有如下所示的文件,如 POSIX ls 命令所示:

$ ls
app.yaml        index.html      main.py

3.(重新)熟悉使用 w/gcloud 命令

如果您的机器上还没有 gcloud 命令,请安装 Google Cloud SDK,并确保 gcloud 是您的执行路径的一部分,并熟悉以下 gcloud 命令:

  1. gcloud components update — 更新 Google Cloud SDK
  2. gcloud auth login — 登录您的凭据凭据帐号
  3. gcloud config list — 列出 GCP 项目配置设置
  4. gcloud config set project PROJECT_ID — 设置 GCP 项目 ID
  5. gcloud app deploy — 部署 App Engine 应用

如果您最近没有使用 gcloud 进行 App Engine 开发,则应先运行前四个命令 (#1-#4) 以进行设置,然后再执行后续步骤。我们快速了解一下这些命令。

首先,gcloud components update 可确保您使用的是最新的 Cloud SDK 版本。运行此命令应该会输出如下内容:

$ gcloud components update

Your current Cloud SDK version is: 317.0.0
You will be upgraded to version: 318.0.0

┌──────────────────────────────────────────────────┐
│        These components will be updated.         │
├──────────────────────────┬────────────┬──────────┤
│           Name           │  Version   │   Size   │
├──────────────────────────┼────────────┼──────────┤
│ Cloud SDK Core Libraries │ 2020.11.06 │ 15.5 MiB │
│ gcloud cli dependencies  │ 2020.11.06 │ 10.6 MiB │
└──────────────────────────┴────────────┴──────────┘

The following release notes are new in this upgrade.
Please read carefully for information about new features, breaking changes,
and bugs fixed.  The latest full release notes can be viewed at:
  https://cloud.google.com/sdk/release_notes

318.0.0 (2020-11-10)

      . . .
      (release notes)
      . . .

    Subscribe to these release notes at
    https://groups.google.com/forum/#!forum/google-cloud-sdk-announce.

Do you want to continue (Y/n)?

╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area                             ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud SDK Core Libraries                   ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: gcloud cli dependencies                    ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud SDK Core Libraries                     ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud cli dependencies                      ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation          ═╣
╚════════════════════════════════════════════════════════════╝

Performing post processing steps...done.

Update done!

To revert your SDK to the previously installed version, you may run:
  $ gcloud components update --version 317.0.0

接下来,使用 gcloud auth logingcloud 命令进行身份验证,您可以在日后遇到问题:

$ gcloud auth login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id= . . .

You are now logged in as [YOUR_EMAIL].
Your current project is [PROJECT_ID].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID

使用 gcloud config list 查看您当前的项目设置:

$ gcloud config list
[core]
account = YOUR_EMAIL
disable_usage_reporting = False
project = PROJECT_ID

Your active configuration is: [default]

以上命令应指导您创建新项目或选择现有项目。如果 gcloud config list 的输出与您要在本教程中使用的所选项目不匹配,请运行 gcloud config set project PROJECT_ID 以设置项目 ID。然后,通过再次运行 gcloud config list 来确认设置的项目 ID 已设置。

$ gcloud config set project PROJECT_ID
Updated property [core/project].

如果您改用 Cloud Console,则可以在需要时使用界面创建新项目,也可以使用已有的现有项目。在项目的信息中心,您应该会看到显示有其 ID 的项目信息卡片(以及项目名称和编号):

项目信息卡片

最后一个命令 (#5) gcloud app deploy 用于将您的应用部署到 App Engine。由于我们刚刚开始运行,所以现在并非必需,但不要部署模块 0 代码以确认模块是否有效。在执行时,选择您希望该应用在哪些地理区域(通常是您所在的国家/地区)运行。不过,一旦设置便无法更改。然后关注其余的部署信息。完成后,系统会通知您的应用将提供的网址。以下是您可能会看到的删节版本:

$ gcloud app deploy
Services to deploy:

descriptor:      [/private/tmp/mod0-baseline/app.yaml]
source:          [/private/tmp/mod0-baseline]
target project:  [PROJECT_ID]
target service:  [default]
target version:  [20201116t220827]
target url:      [https://PROJECT_ID.REG_ABBR.r.appspot.com]

Do you want to continue (Y/n)?

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage                 ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://PROJECT_ID.REG_ABBR.r.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

如果您有一段时间没有使用 App Engine 了,您可能会注意到原来的部署 appcfg.py update 命令已被 gcloud app deploy 取代。如需详细了解 gcloud app deploy,请参阅其文档页面

另一个最近更改是已部署应用的网址,该网址从 http://PROJECT_ID.appspot.com 调整为 http://PROJECT_ID.REG_ABBR.r.appspot.com。大多数应用最终都会改用新格式。如需详细了解网址格式,请参阅请求和路由文档

部署应用后,刷新浏览器(可能几次),以查看最新访问:

visitme 应用

如果您的应用是全新的,您将只会看到一次或少数次访问。

Python 2 App Engine 运行时提供了一组“内置”第三方库,您只需在 app.yaml 文件中指定这些库。虽然此迁移不需要使用,但它们将在下一个迁移教程(适用于模块 2)中完成。

必须在名为 requirements.txt 的文件中指定内置的第三方库,并将其本地安装在与将所有内容上传到 App Engine 的应用代码相同的目录下的 lib 文件夹中。如需了解详情,请参阅捆绑第三方库的文档

复制的库(例如 Flask)要求您告诉 App Engine 使用 appengine_config.py 配置文件在 lib 文件夹中查找它们。appengine_config.py 配置文件与 requirements.txtlib 位于同一顶级应用文件夹中。在本教程的这一部分中,您将学习以下操作:

  • 创建 requirements.txt(指定已复制的 [非内置] 第三方库)
  • 创建 appengine_config.py(识别第三方库)
  • 安装(第三方)软件包和依赖项

1.创建 requirements.txt

创建一个 requirements.txt 文件以指定您的软件包。在我们的示例中,Flask 是必要的第三方库。在撰写本文时,最新版本为 1.1.2,因此请创建含有此行的 requirements.txt

Flask==1.1.2

请参阅 requirements.txt 文档,详细了解可接受的格式。

2.创建 appengine_config.py

下一步是让 App Engine 识别外部第三方库。创建名为 appengine_config.py 且包含以下内容的文件:

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

此代码的作用与我们之前指定的完全相同,即,将 App Engine 指向复制的库的 lib 文件夹。

3.安装软件包和依赖项

现在,运行 pip install 命令以创建 lib 文件夹并在该文件夹中安装 Flask 及其依赖项:

$ pip install -t lib -r requirements.txt

无论您是使用 pip 还是 pip2,在软件包安装完成后,您应该拥有如下所示的 lib 文件夹:

$ ls lib
bin/
click/
click-7.1.2.dist-info/
flask/
Flask-1.1.2.dist-info/
itsdangerous/
itsdangerous-1.1.0.dist-info/
jinja2/
Jinja2-2.11.2.dist-info/
markupsafe/
MarkupSafe-1.1.1.dist-info/
werkzeug/
Werkzeug-1.0.1.dist-info/

现在,让我们更新应用文件 main.py

1.导入数据

导入内容会首先出现在所有 Python 文件中。webapp2 框架导入后跟 ndb Datastore 库,最后是处理 Django 定制模板的 App Engine 扩展程序。您应该会看到以下内容:

  • 之前:
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

移动到 Flask 时,您可以同时导入 Flask 和模板渲染器。删除 webapp2 相关导入对,然后按以下方式替换它们(保持 ndb 导入不变):

  • 之后:
from flask import Flask, render_template, request
from google.appengine.ext import ndb

2.启动

使用 webapp2 的应用需要一个数组(Python 列表),其中列出了任何 Python 文件中的所有路由和处理程序(可能有其他路径):

  • 之前:
app = webapp2.WSGIApplication([
    ('/', MainHandler),
], debug=True)

请注意,app.yaml 执行更高级的路由,可能会调用不同的处理程序。示例应用非常简单,所有路由都可发送到 main.py 处理程序。

Flask 不使用这样的路由表,因此请在 main.py删除这些行。Flask 还需要初始化,因此请在 main.py 顶部的导入项下方添加以下行:

  • 之后:
app = Flask(__name__)

在 Flask 中,初始化框架,然后使用修饰器来定义路由。此外,路由与函数配对,而不是类和方法。

此 Codelab 中包括 Flask 教程的内容超出范围,因此,请花些时间通读 Flask 教程并查看 Flask 文档,以便更好地了解框架。

3.数据模型

此处没有任何更改。Datastore 将是下一个 Codelab 的重点。

4.处理程序

无论您使用哪个框架(webapp2 或 Flask),应用都会执行以下 3 种操作:

  1. 处理根路径 (/) GET 请求
  2. 注册网页“访问”(创建/存储 Visit 对象)
  3. 显示最近的 10 次访问(采用预定义的模板 index.html

webapp2 框架使用基于类的执行模型,其中为每个支持的 HTTP 方法创建处理程序。在我们的简单示例中,我们只有 GET,因此定义了 get() 方法:

  • 之前:
class MainHandler(webapp2.RequestHandler):
    def get(self):
        store_visit(self.request.remote_addr, self.request.user_agent)
        visits = fetch_visits(10) or ()  # empty sequence if None
        tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(tmpl, {'visits': visits}))

如上所述,Flask 采用自己的路由。您可以编写函数,并使用它们应调用的路由修饰这些函数,而不是处理程序类。用户可以指定在修饰器调用中处理的 HTTP 方法,即 @app.route('/app/', methods=['GET', 'POST'])。由于默认值为 GET(也是隐式 HEAD),因此可以省略。

在迁移到 Flask 时,请将 MainHandler 类及其 get() 方法替换为以下 Flask 路由函数:

  • 之后:
@app.route('/')
def root():
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10) or ()  # empty sequence if None
    return render_template('index.html', visits=visits)

当然,这并不代表您的应用,它必定比此示例更加复杂。这些教程的主要目标是帮助您入门、构建一些“记忆内存”,并了解在 App Engine 特定代码中进行更改的位置。如需确认您是否已正确做出此项更改,请将此部分与模块 1 main.py 进行比较。

5. 辅助文件

.gcloudignore 文件没有任何变化。其目的是指定不需要部署到 App Engine 的文件,这些文件对于部署和执行应用是不必要的,包括但不限于辅助 Python、源代码控制、存储库样板文件和其他文件。以下 .gcloudignore 所示(为了简便起见,移除了评论):

.gcloudignore
.git
.gitignore
.hgignore
.hg/
*.pyc
*.pyo
__pycache__/
/setup.cfg
README.md

1.移动模板文件

在基准代码库文件夹(模块 0)中,index.html 模板文件与应用文件位于同一文件夹中。由于 Flask 要求将 HTML 文件放在 templates 文件夹中,因此您必须创建该文件夹 (mkdir templates),并在其中移动 index.html。在符合 POSIX 标准的系统(如 Linux 或 Mac OS X)中,命令如下所示:

mkdir templates
mv index.html templates

2.更新模板文件

index.html 移到 templates 后,可以进行小巧但需要的修改。下面我们来看一下原始模板文件:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

</body>
</html>

webapp2 使用 Django 模板来执行可访问对象(如 visit.timestamp.ctime),但不带括号 ( ),而 Jinja2 明确需要它们。虽然这有点细微的调整,但 Jinja 模板更加强大,因为您可以在调用中传递参数。

在 Django 中,您需要创建“模板标记”或编写过滤条件。了解这一点后,即可向 visit.timestamp.ctime 调用添加一对英文括号,以更新 index.html

  • 之前:
<li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
  • 之后:
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>

这是唯一需要进行的更改;所有迁移的 Codelab 都不需要对 index.html 进行额外更改。

部署应用

完成本教程中的所有更改后,应用文件夹中的文件应该与模块 1 代码库文件夹中的文件相同(或几乎相同)。现在进行部署,您可以看到模块 1 Flask 应用与模块 0 webapp2 版本的运行方式相同。

使用 gcloud app deploy 命令,就像我们部署原始模块 0 代码时一样。您可以通过 PROJECT_ID.appspot.com 访问应用,无论是通过网络浏览器访问 curl,还是使用 wget 命令确认该应用是否按预期运行。

如果出现服务器错误,这通常表示 Python 代码中存在某种拼写错误。查看应用日志进行调查。同时将您的文件与模块 1 代码库中的链接(如上所述)进行比较。

可选:清理

何不准备好清理,以避免在进入下一个迁移 Codelab 时继续计费?作为现有开发者,您可能已经对 App Engine 的价格信息已经跟上了速度。

可选:停用应用

如果您还没准备好学习下一个教程,请停用应用,以免产生费用。准备好运行下一个 Codelab 后,可以重新启用它。在您的应用被停用的情况下,它不会获取任何流量来产生费用,但是您还需要计费的另一事项是,如果 Datastore 使用量超出免费配额,因此请删除一部分使用量,以使其低于该限制。

另一方面,如果您不打算继续迁移,并想删除所有内容,则可以关停项目

后续步骤

有两个迁移模块,涉及已完成的模块 1 代码、模块 2 和模块 7:

  • 模块 2(如果您使用 Datastore,则必须提供该模块)
    • 从 App Engine ndb 迁移到 Cloud NDB
    • 切换到 Cloud NDB 后,许多其他选项可用
      • 将应用容器化为在 Cloud Run 上运行
      • 将应用迁移到 Cloud Datastore 客户端库
      • 将应用迁移到 Cloud Firestore 以访问 Firebase 功能
  • 模块 7(如果您使用 [推送] 任务队列,则必需)
    • 添加 App Engine(推送)taskqueue 用量
    • 准备模块 1 中的应用以迁移到模块 8 中的 Cloud Tasks

App Engine 迁移模块 Codelab 问题/反馈

如果您遇到此 Codelab 的任何问题,请先搜索您的问题,然后再提交。用于搜索新问题和创建新问题的链接:

迁移资源

模块 0 (START) 和模块 1 (FINISH) 的代码库文件夹的链接如下表所示。您也可以通过所有 App Engine 迁移的代码库访问这些代码库,以便克隆或下载 ZIP 文件。

Codelab

Python 2

Python 3

模块 0

代码

(不适用)

模块 1

代码

(不适用)

App Engine 资源

以下是有关此具体迁移的其他资源: