1. 概览
无服务器迁移站系列 Codelab(自定进度的实操教程)和相关视频旨在指导 Google Cloud 无服务器开发者完成一次或多次迁移(主要是从旧服务迁移),从而实现应用的现代化改造。这样做可以提高应用的可移植性,为您提供更多选择和灵活性,让您能够集成并访问更广泛的 Cloud 产品,并更轻松地升级到较新版本。虽然本系列最初主要面向的是最早接触 Cloud 的用户(主要是 App Engine(标准环境)开发者),但涵盖的范围非常广泛,涵盖了 Cloud Functions 和 Cloud Run 等其他无服务器平台,或其他无服务器平台(如适用)。
此 Codelab 旨在向 Python 2 App Engine 开发者展示如何从 App Engine 用户 API/服务迁移到 Cloud Identity Platform (GCIP)。此外,我们还提供从 App Engine NDB 到 Cloud NDB 以访问 Datastore 的隐式迁移(主要在迁移模块 2 中介绍),并升级到 Python 3。
第 20 单元介绍如何在单元 1 的示例应用中添加 Users API。在本单元中,您将学习完成第 20 单元的应用,将其使用情况迁移到 Cloud Identity Platform。
在接下来的实验中
- 将 App Engine 用户服务替换为 Cloud Identity Platform
- 将 App Engine NDB 替换为 Cloud NDB(另请参阅第 2 单元)
- 使用 Firebase Auth 设置不同的身份验证身份提供方
- 使用 Cloud Resource Manager API 获取项目 IAM 信息
- 使用 Firebase Admin SDK 获取用户信息
- 将示例应用移植到 Python 3
所需条件
- 具有有效的 GCP 结算账号的 Google Cloud Platform 项目
- 基本 Python 技能
- 常用 Linux 命令的实践知识
- 具备开发和部署 App Engine 应用的基础知识
- 可运行的模块 20 App Engine 示例应用
调查问卷
您打算如何使用本教程?
<ph type="x-smartling-placeholder">您如何评价使用 Python 的体验?
您如何评价自己在使用 Google Cloud 服务方面的经验水平?
<ph type="x-smartling-placeholder">2. 背景
App Engine 用户服务是供 App Engine 应用使用的用户身份验证系统。它提供 Google 登录作为其身份提供方,提供方便的应用内使用的登录和退出链接,并且支持管理员用户的概念和仅限管理员使用的功能。为了提高应用可移植性,Google Cloud 建议从旧版 App Engine 捆绑服务迁移到 Cloud 独立服务,例如从用户服务迁移到 Cloud Identity Platform 等。
Identity Platform 基于 Firebase Authentication,增加了多项企业功能,包括多重身份验证、OIDC 和SAML SSO 支持、多租户、SLA 承诺的 99.95% 可用性等。Identity Platform 和 Firebase Authentication 产品比较页面上也突出显示了这些差异。这两种产品的功能明显多于用户服务所提供的功能。
第 21 单元的 Codelab 演示了如何将应用的用户身份验证从用户服务切换到 Identity Platform 功能,这些功能最能反映第 20 单元中展示的功能。模块 21 还介绍了如何从 App Engine NDB 迁移到 Cloud NDB for Datastore,并重复执行模块 2 迁移。
虽然第 20 单元的代码作为 Python 2 示例应用,源代码本身与 Python 2 和 3 兼容,即使在第 21 单元中迁移到 Identity Platform(和 Cloud NDB)后,它也仍然兼容。在升级到 Python 3 时,可以继续使用用户服务,因为迁移到 Identity Platform 是可选操作。请参阅第 17 单元 Codelab 和视频,了解如何在升级到第 2 代运行时(例如 Python 3)的同时继续使用捆绑的服务。
本教程包含以下步骤:
- 设置/准备工作
- 更新配置
- 修改应用代码
3. 设置/准备工作
本节介绍如何执行以下操作:
- 设置 Cloud 项目
- 获取基准示例应用
- (重新)部署并验证基准应用
- 启用新的 Google Cloud 服务/API
以下步骤可确保您从可正常工作的代码入手,该代码已做好迁移到独立 Cloud 服务的准备。
1. 设置项目
如果您已完成第 20 单元 Codelab,请重复使用同一项目(和代码)。或者,创建一个全新的项目或重复使用其他现有项目。确保项目具有有效的结算账号和已启用的 App Engine 应用。在此 Codelab 中,您可以找到项目 ID 并随时使用,然后在遇到 PROJ_ID
变量时使用它。
2. 获取基准示例应用
前提条件之一是一个有效的模块 20 App Engine 应用,因此请完成其 Codelab(推荐;链接见上文),或从代码库中复制模块 20 代码。无论您使用的是我们自己的解决方案还是我们的解决方案,我们都将从这里开始(“START”)。此 Codelab 会逐步引导您完成迁移,最后使用与模块 21 代码库文件夹(“FINISH”)中的代码类似的代码。
- START:Module 20 folder (Python 2)
- 完成:模块 21 文件夹(Python 2 或 Python 3)
- 整个代码库(用于克隆或下载 ZIP 文件)
复制模块 20 代码库文件夹。它应类似于下面的输出;如果您已完成第 20 单元 Codelab,则可能具有 lib
文件夹:
$ ls README.md appengine_config.py templates app.yaml main.py requirements.txt
3. (重新)部署并验证基准应用
请执行以下步骤,部署模块 20 应用:
- 删除
lib
文件夹(如果有),并运行pip install -t lib -r requirements.txt
重新填充该文件夹。如果您同时安装了 Python 2 和 Python 3,则可能需要使用pip2
。 - 确保您已安装并初始化
gcloud
命令行工具,并查看了其使用情况。 - 如果您不想在发出每个
gcloud
命令后都输入PROJ_ID
,请先使用gcloud config set project
PROJ_ID
设置 Cloud 项目。 - 使用
gcloud app deploy
部署示例应用 - 确认应用按预期运行,没有出现错误。如果您已完成第 20 单元 Codelab,该应用将在顶部显示用户登录信息(用户电子邮件地址、可能的“管理员徽章”和登录/退出按钮)以及最近的访问活动(如下图所示)。
以普通用户的身份登录会显示用户的电子邮件地址,按钮变为“退出”按钮:
以管理员用户身份登录会导致用户的电子邮件地址与“(管理员)”一同显示:
4. 启用新的 Google Cloud API/服务
简介
模块 20 应用使用 App Engine NDB 和 Users API,这两款捆绑服务不需要额外设置,但有独立 Cloud 服务,并且更新后的应用将同时使用 Cloud Identity Platform 和 Cloud Datastore(通过 Cloud NDB 客户端库)。此外,我们需要确定 App Engine 管理员用户,还需要使用 Cloud Resource Manager API。
费用
- App Engine 和 Cloud Datastore 具有“始终免费”的特性Tier 配额,只要您不超出这些限制,完成本教程就不会产生费用。如需了解详情,另请参阅 App Engine 价格页面和 Cloud Datastore 价格页面。
- Cloud Identity Platform 的使用费用取决于月活跃用户数 (MAU) 或身份验证验证次数;某些版本的“免费”每种使用模式都可用如需了解详情,请参阅其价格页面。此外,虽然 App Engine 和 Cloud Datastore 需要计费,但只要您不超过无付款方式的每日配额,使用 GCIP 本身就不需要启用结算功能。因此,对于不涉及计费的 Cloud API/服务的 Cloud 项目,请考虑这样做。
- Cloud Resource Manager API 的大部分使用免费,详见价格页面。
用户可通过 Cloud 控制台或命令行(通过 Cloud SDK 中的 gcloud
命令)启用 Cloud API,具体取决于您的偏好。我们先从 Cloud Datastore 和 Cloud Resource Manager API 开始。
在 Cloud 控制台中
在 Cloud 控制台中,转到 API 管理器的“库”页面(针对正确的项目),然后使用搜索栏搜索 API。
启用以下 API:
分别找到并点击每个 API 的启用按钮,系统可能会提示您输入结算信息。例如,以下是 Resource Manager API 页面:
启用按钮后(通常在几秒钟后),该按钮会变为 管理 :
以相同的方式启用 Cloud Datastore:
通过命令行
虽然从控制台启用 API 可以直观地提供信息,但有些人更喜欢使用命令行。您还能一次性启用任意数量的 API。发出此命令以启用 Cloud Datastore 和 Cloud Resource Manager API,并等待操作完成,如下图所示:
$ gcloud services enable cloudresourcemanager.googleapis.com datastore.googleapis.com Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.
系统可能会提示您输入结算信息。
“网址”部分称为 API 服务名称,可在每个 API 的库页面底部找到。如果您希望为自己的应用启用其他 Cloud API,可以在相应的 API 页面上找到相应的服务名称。以下命令列出了您可以启用的 API 的所有服务名称:
gcloud services list
--available --filter="name:googleapis.com"
。
无论是在 Cloud 控制台中还是在命令行中,完成上述步骤后,我们的示例现在都可以访问这些 API 了。接下来,请启用 Cloud Identity Platform 并进行必要的代码更改。
启用并设置 Cloud Identity Platform(仅限 Cloud 控制台)
Cloud Identity Platform 是一项 Marketplace 服务,因为它连接或依赖 Google Cloud 以外的资源(例如 Firebase Authentication)。目前,您只能通过 Cloud 控制台启用 Marketplace 服务。请按以下步骤操作:
- 转到 Cloud Marketplace 中的 Cloud Identity Platform 页面,然后点击其中的启用按钮。如果出现提示,请从 Firebase Authentication 升级,这样做可解锁其他功能,例如前面后台部分所述的功能。以下是突出显示了启用按钮的 Marketplace 页面:
- 启用 Identity Platform 后,系统可能会自动将您定向到 Identity Providers(身份提供商)页面。如果没有,请使用这个方便的链接前往。
- 启用 Google 身份验证提供方。如果未设置任何提供商,请点击 Add a Provider(添加提供商),然后选择 Google。返回此屏幕后,系统应该会启用 Google 条目。Google 是我们在本教程中使用的唯一身份验证提供方,用于将 App Engine 用户服务镜像为轻量级 Google 登录服务。在您自己的应用中,您可以启用其他身份验证提供方。
- 选择并设置 Google 和其他所需的身份验证提供方后,点击 Application Setup Details,然后从确保对话框窗口中复制 Web 标签页
config
对象中的apiKey
和authDomain
,并将它们保存在安全的地方。为什么不复制全部内容呢?此对话框中的代码段经过硬编码并注明了日期,因此,您只需保存最重要的部分,然后在代码中使用它们,即可提高并发 Firebase 身份验证的使用率。复制这些值并将其保存在安全的地方后,点击关闭按钮,完成所有必要的设置。
4. 更新配置
配置更新包括更改各种配置文件,以及在 Cloud Identity Platform 生态系统中创建 App Engine 等效项。
appengine_config.py
- 如果要升级到 Python 3,请删除
appengine_config.py
- 如果您计划对 Identity Platform 进行现代化改造,但继续使用 Python 2,请勿删除该文件。我们稍后会在 Python 2 向后移植期间更新它。
requirements.txt
模块 20 的 requirements.txt
文件仅列出了 Flask。对于模块 21,请添加以下软件包:
requirements.txt
的内容现在应如下所示:
flask
google-auth
google-cloud-ndb
google-cloud-resource-manager
firebase-admin
app.yaml
- 升级到 Python 3 意味着要简化
app.yaml
文件。移除运行时指令以外的所有内容,并将其设置为当前支持的 Python 3 版本。该示例目前使用版本 3.10。 - 如果您仍在使用 Python 2,则此处无需执行任何操作。
之前:
runtime: python27
threadsafe: yes
api_version: 1
handlers:
- url: /.*
script: main.app
模块 20 示例应用没有静态文件处理程序。如果您的应用存在此类异常情况,请勿更改。您可以根据需要移除所有脚本处理程序,也可以将其留在那里以供参考,只要您将其句柄更改为 auto
即可,如 app.yaml
迁移指南中所述。通过这些更改,更新后的 Python 3 app.yaml
简化为:
之后:
runtime: python310
其他配置更新
无论是继续使用 Python 2 还是移植到 Python 3,如果您有 lib
文件夹,请将其删除。
5. 修改应用代码
本部分主要更新主应用文件 main.py
,将 App Engine 用户服务替换为 Cloud Identity Platform。更新主应用后,您将需要更新网页模板 templates/index.html
。
更新导入和初始化
请按照以下步骤更新导入项并初始化应用资源:
- 对于导入,请将 App Engine NDB 替换为 Cloud NDB。
- 除了 Cloud NDB,还可以导入 Cloud Resource Manager。
- Identity Platform 基于 Firebase Auth,因此请导入 Firebase Admin SDK。
- Cloud API 需要使用 API 客户端,因此请在初始化 Flask 的正下方为 Cloud NDB 启动该客户端。
虽然此处导入了 Cloud Resource Manager 软件包,但我们稍后会在应用初始化中用到它。以下是模块 20 中的导入和初始化,以及执行上述更改后各部分应执行的操作:
之前:
from flask import Flask, render_template, request
from google.appengine.api import users
from google.appengine.ext import ndb
app = Flask(__name__)
之后:
from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app
# initialize Flask and Cloud NDB API client
app = Flask(__name__)
ds_client = ndb.Client()
对 App Engine 管理员用户的支持
有两个支持识别管理员用户的组件添加到应用中:
_get_gae_admins()
- 整理一组管理员用户;调用一次并保存is_admin()
- 检查登录的用户是否为管理员用户;在用户登录时调用
实用函数 _get_gae_admins()
会调用 Resource Manager API 来提取当前的 Cloud IAM allow-policy。allow-policy 会定义并强制执行向哪些主账号(真人用户、服务账号等)授予的角色。设置包括:
- 正在提取 Cloud 项目 ID (
PROJ_ID
) - 创建 Resource Manager API 客户端 (
rm_client
) - 创建一组(只读)App Engine Admin 角色 (
_TARGETS
)
Resource Manager 需要 Cloud 项目 ID,因此请导入 google.auth.default()
并调用该函数来获取项目 ID。该调用包含一个看起来像网址的参数,但属于 OAuth2 权限范围。在云端运行应用时(例如在 Compute Engine 虚拟机或 App Engine 应用中),系统会提供具有广泛权限的默认服务账号。按照最小权限最佳做法,我们建议您创建自己的用户管理的服务账号。
对于 API 调用,最好进一步将应用范围缩小到正常运行所需的最低级别。我们要进行的 Resource Manager API 调用是 get_iam_policy()
,它需要以下任一范围才能运行:
https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/cloud-platform.read-only
https://www.googleapis.com/auth/cloudplatformprojects
https://www.googleapis.com/auth/cloudplatformprojects.readonly
示例应用只需要对 allow-policy 的只读权限。它不会修改政策,也不需要访问整个项目。这意味着该应用不需要前三项权限中的任何一项。最后一个函数是必需项,这就是我们将为示例应用实现的功能。
该函数的正文会创建一组空的管理员用户 (admins
),通过 get_iam_policy()
获取 allow_policy
,并循环遍历其所有绑定,专门查找 App Engine Admin 角色:
roles/viewer
roles/editor
roles/owner
roles/appengine.appAdmin
对于找到的每个目标角色,它会整理哪些用户属于该角色,并将这些用户添加到整个管理员用户集中。最后,将返回在此 App Engine 实例的生命周期内找到并缓存为一个常量 (_ADMINS
) 的所有管理员用户。我们很快就会接到您的电话。
将以下 _get_gae_admins()
函数定义添加到 main.py
(实例化 Cloud NDB API 客户端 (ds_client
) 正下方):
def _get_gae_admins():
'return set of App Engine admins'
# setup constants for calling Cloud Resource Manager API
_, PROJ_ID = default( # Application Default Credentials and project ID
['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
rm_client = resourcemanager.ProjectsClient()
_TARGETS = frozenset(( # App Engine admin roles
'roles/viewer',
'roles/editor',
'roles/owner',
'roles/appengine.appAdmin',
))
# collate users who are members of at least one GAE admin role (_TARGETS)
admins = set() # set of all App Engine admins
allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
for b in allow_policy.bindings: # bindings in IAM allow-policy
if b.role in _TARGETS: # only look at GAE admin roles
admins.update(user.split(':', 1).pop() for user in b.members)
return admins
当用户登录应用时,会出现以下情况:
- 用户登录 Firebase 后,系统会利用 Web 模板进行快速检查。
- 当模板中的身份验证状态发生变化时,系统会对
/is_admin
进行 Ajax 样式的fetch()
调用,其处理程序是下一个函数is_admin()
。 - Firebase ID 令牌会在 POST 正文中传递给
is_admin()
,后者会从标头中抓取此令牌,并调用 Firebase Admin SDK 进行验证。如果该用户是有效用户,请提取其电子邮件地址,并检查其是否为管理员用户。 - 布尔值结果作为成功的 200 返回给模板。
将 is_admin()
添加到 main.py
中,紧跟在 _get_gae_admins()
之后:
@app.route('/is_admin', methods=['POST'])
def is_admin():
'check if user (via their Firebase ID token) is GAE admin (POST) handler'
id_token = request.headers.get('Authorization')
email = auth.verify_id_token(id_token).get('email')
return {'admin': email in _ADMINS}, 200
这两个函数中的所有代码都必须复制用户服务提供的功能,特别是其 is_current_user_admin()
函数。与模块 21 中我们实施替换解决方案不同,模块 20 中的函数调用完成所有繁重工作。好消息是,该应用不再依赖于仅限 App Engine 的服务,这意味着您可以将应用迁移到 Cloud Run 或其他服务。此外,您还可以更改“管理员用户”的定义只需切换到 _TARGETS
中的所需角色即可为自己的应用创建应用;而“用户”服务则经过硬编码,适用于 App Engine 管理员角色。
初始化 Firebase Auth 并缓存 App Engine 管理员用户
我们可以在顶部附近初始化 Flask 应用并创建 Cloud NDB API 客户端的位置附近初始化 Firebase Auth,但在定义完所有管理代码之前,我们现在不需要这样做。同样,现在已经定义了 _get_gae_admins()
,可以调用它来缓存管理员用户列表。
将以下代码行添加到 is_admin()
的函数正文下:
# initialize Firebase and fetch set of App Engine admins
initialize_app()
_ADMINS = _get_gae_admins()
访问数据模型更新
Visit
数据模型不会更改。要访问 Datastore,必须明确使用 Cloud NDB API 客户端上下文管理器 ds_client.context()
。在代码中,这意味着您将 Datastore 调用同时封装在 Python with
块内的 store_visit()
和 fetch_visits()
中。此更新与模块 2 相同。进行如下更改:
之前:
class Visit(ndb.Model):
'Visit entity registers visitor IP address & timestamp'
visitor = ndb.StringProperty()
timestamp = ndb.DateTimeProperty(auto_now_add=True)
def store_visit(remote_addr, user_agent):
'create new Visit entity in Datastore'
Visit(visitor='{}: {}'.format(remote_addr, user_agent)).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)
def store_visit(remote_addr, user_agent):
'create new Visit entity in Datastore'
with ds_client.context():
Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
def fetch_visits(limit):
'get most recent visits'
with ds_client.context():
return Visit.query().order(-Visit.timestamp).fetch(limit)
将用户登录逻辑移至网页模板
App Engine 用户服务是服务器端服务,而 Firebase Auth 和 Cloud Identity Platform 主要是客户端服务。因此,模块 20 应用中的大部分用户管理代码都将移至模块 21 网页模板。
在 main.py
中,网络上下文将五条重要数据传递给模板,列出的前四条数据与用户管理相关联,并且会因用户是否已登录:
who
- 已登录用户的电子邮件地址,否则为 useradmin
- (管理员)标记(如果已登录的用户是管理员)sign
- 显示 Login(登录)或 Logout(退出)按钮link
- 点击按钮时的登录或退出链接visits
- 最近的访问
之前:
@app.route('/')
def root():
'main application (GET) handler'
store_visit(request.remote_addr, request.user_agent)
visits = fetch_visits(10)
# put together users context for web template
user = users.get_current_user()
context = { # logged in
'who': user.nickname(),
'admin': '(admin)' if users.is_current_user_admin() else '',
'sign': 'Logout',
'link': '/_ah/logout?continue=%s://%s/' % (
request.environ['wsgi.url_scheme'],
request.environ['HTTP_HOST'],
), # alternative to users.create_logout_url()
} if user else { # not logged in
'who': 'user',
'admin': '',
'sign': 'Login',
'link': users.create_login_url('/'),
}
# add visits to context and render template
context['visits'] = visits # display whether logged in or not
return render_template('index.html', **context)
所有用户管理工作都转移到 Web 模板上,因此只剩下访问次数,主处理程序将回到模块 1 应用中一开始就用到的处理程序:
之后:
@app.route('/')
def root():
'main application (GET) handler'
store_visit(request.remote_addr, request.user_agent)
visits = fetch_visits(10)
return render_template('index.html', visits=visits)
更新网站模板
上一部分的所有更新在模板中都是什么样子?主要将用户管理从应用迁移到在模板中运行的 Firebase Auth,并将我们移至 JavaScript 的所有代码的部分端口移至 JavaScript。我们发现main.py
大幅缩小,因此预计templates/index.html
的增长幅度可大可小。
之前:
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
</head>
<body>
<p>
Welcome, {{ who }} <code>{{ admin }}</code>
<button id="logbtn">{{ sign }}</button>
</p><hr>
<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>
<script>
document.getElementById("logbtn").onclick = () => {
window.location.href = '{{ link }}';
};
</script>
</body>
</html>
将整个 Web 模板替换为以下内容:
之后:
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<script type="module">
// import Firebase module attributes
import {
initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
GoogleAuthProvider,
getAuth,
onAuthStateChanged,
signInWithPopup,
signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";
// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
};
// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});
// define login and logout button functions
function login() {
signInWithPopup(auth, provider);
};
function logout() {
signOut(auth);
};
// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
if (user && user != null) {
var email = user.email;
who.innerHTML = email;
logbtn.onclick = logout;
logbtn.innerHTML = "Logout";
var idToken = await user.getIdToken();
var rsp = await fetch("/is_admin", {
method: "POST",
headers: {Authorization: idToken}
});
var data = await rsp.json();
if (data.admin) {
admin.style.display = "inline";
}
} else {
who.innerHTML = "user";
admin.style.display = "none";
logbtn.onclick = login;
logbtn.innerHTML = "Login";
}
});
</script>
</head>
<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>
<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>
<script>
var who = document.getElementById("who");
var admin = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>
此 HTML 正文中有许多组件,所以我们一次只处理一部分。
Firebase 导入
仍位于 HTML 文档的标题中,当页面标题过后,导入所需的 Firebase 组件。为了提高效率,Firebase 组件现在分成了多个模块。初始化 Firebase 的代码是从 Firebase 应用主模块导入的,而管理 Firebase 身份验证、将 Google 作为身份验证提供方、登录和退出以及身份验证状态的函数将更改“回调”均从 Firebase Auth 模块导入:
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<script type="module">
// import Firebase module attributes
import {
initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
GoogleAuthProvider,
getAuth,
onAuthStateChanged,
signInWithPopup,
signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";
Firebase 配置
在本教程的 Identity Platform 设置部分前面,您在应用设置详情对话框中保存了 apiKey
和 authDomain
。将这些值添加到下一部分中的 firebaseConfig
变量。您可在注释中查看有关详细说明的链接:
// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
};
Firebase 初始化
下一部分将使用此配置信息初始化 Firebase。
// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});
此设置可设置将 Google 用作身份验证提供方的功能,并提供用于显示账号选择器的注释掉选项,即使浏览器会话中只注册了一个 Google 账号也是如此。也就是说,如果您有多个账号,就会看到这个“账号选择器”与预期一样:但是,如果会话中只有一位用户,则登录过程会自动完成,无需任何用户互动。(弹出窗口随即会显示,然后消失)。通过取消注释自定义参数行,您可以强制向一个用户显示账号选择器对话框(而不是立即登录应用)。启用后,即使是单用户登录,也会弹出账号选择器:
登录和退出登录功能
下面几行代码构成了登录或退出按钮点击的函数:
// define login and logout button functions
function login() {
signInWithPopup(auth, provider);
};
function logout() {
signOut(auth);
};
登录和退出操作
此 <script>
代码块中的最后一个主要部分是每次身份验证更改(登录或退出)时调用的函数。
// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
if (user && user != null) {
var email = user.email;
who.innerHTML = email;
logbtn.onclick = logout;
logbtn.innerHTML = "Logout";
var idToken = await user.getIdToken();
var rsp = await fetch("/is_admin", {
method: "POST",
headers: {Authorization: idToken}
});
var data = await rsp.json();
if (data.admin) {
admin.style.display = "inline";
}
} else {
who.innerHTML = "user";
admin.style.display = "none";
logbtn.onclick = login;
logbtn.innerHTML = "Login";
}
});
</script>
</head>
模块 20 中用于确定是否发送“用户已登录”的代码模板上下文与“用户已退出”在这里转换上下文。如果用户成功登录,顶部的条件会生成 true
,从而触发以下操作:
- 已设置该用户的电子邮件地址,以便显示。
- Login(登录)按钮会变为 Logout(退出)。
- 对
/is_admin
进行 Ajax 样式调用,以确定是否显示(admin)
管理员用户徽章。
当用户退出时,系统会执行 else
子句以重置所有用户信息:
- 用户名设为 user
- 已移除所有管理员徽章
- 退出按钮更改回登录
模板变量
在标题部分结束后,正文会从模板变量开始,这些变量会被 HTML 元素替换,并会根据需要更改:
- 显示的用户名
(admin)
管理员徽章(如果适用)- Login(登录)或 Logout(退出)按钮
<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>
最近的访问和 HTML 元素变量
最近的访问代码不会更改,最后一个 <script>
代码块为上面列出的登录和退出会发生变化的 HTML 元素设置变量:
<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>
<script>
var who = document.getElementById("who");
var admin = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>
以上就是从 App Engine NDB 和 Users API 切换到 Cloud NDB 和 Identity Platform 并升级到 Python 3 时在应用和 Web 模板中需要进行的更改。恭喜您获得新的第 21 单元示例应用!您可以在模块 21b 代码库文件夹中查看我们的版本。
此 Codelab 的下一部分是可选的 (*),并且仅适用于应用必须使用 Python 2 的用户,它们将引导您完成获得正常运行的 Python 2 Module 21 应用所需的步骤。
6. *Python 2 向后移植
此可选部分适用于执行 Identity Platform 迁移但必须继续在 Python 2 运行时上运行的开发者。如果您不想担心,请跳过此部分。
如需创建模块 21 应用的正常运行的 Python 2 版本,您需要以下各项:
- 运行时要求:支持 Python 2 的配置文件,以及需要在主应用中进行更改,以避免 Python 3 不兼容
- 库方面的细微更改:在一些必需的功能添加到 Resource Manager 客户端库之前,Python 2 已被弃用。因此,您需要通过其他方式使用缺失的功能。
现在,我们执行这些步骤,从配置开始。
恢复 appengine_config.py
在本教程的前面部分,我们引导您删除了 appengine_config.py
,因为 Python 3 App Engine 运行时不使用它。对于 Python 2,您不仅必须保留它,还需要更新模块 20 appengine_config.py
,以支持使用内置第三方库,即 grpcio
和 setuptools
。当您的 App Engine 应用使用 Cloud 客户端库(例如用于 Cloud NDB 和 Cloud Resource Manager 的客户端库)时,就需要使用这些软件包。
您会立即将这些软件包添加到 app.yaml
,但为了让您的应用能够访问它们,必须调用 setuptools
中的 pkg_resources.working_set.add_entry()
函数。这样一来,安装在 lib
文件夹中的复制(自行捆绑或供应商提供的)第三方库能够与内置库通信。
对 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)
仅凭此代码不足以支持使用 setuptools
和 grpcio
。由于还需要再添加几行内容,因此请更新 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)
如需详细了解支持 Cloud 客户端库所需进行的更改,请参阅“迁移捆绑服务”文档。
app.yaml
与 appengine_config.py
类似,app.yaml
文件必须还原为支持 Python 2 的文件。我们先从原始的模块 20 app.yaml
开始:
之前:
runtime: python27
threadsafe: yes
api_version: 1
handlers:
- url: /.*
script: main.app
除了前面提到的 setuptools
和 grpcio
之外,还有一个依赖项(与 Identity Platform 迁移没有明确相关),需要使用 Cloud Storage 客户端库,而该依赖项还需要另一个内置的第三方软件包 ssl
。在新的 libraries
部分中添加所有这三项,然后选择“latest”将这些软件包的可用版本复制到 app.yaml
:
之后:
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
requirements.txt
对于第 21 单元,我们在 Python 3 requirements.txt
中添加了 Google Auth、Cloud NDB、Cloud Resource Manager 和 Firebase Admin SDK。Python 2 的情况则更为复杂:
- Resource Manager API 提供示例应用所需的 allow-policy 功能。很遗憾,Cloud Resource Manager 客户端库的最终 Python 2 版本中尚未提供这项支持。(它仅适用于 Python 3 版本。)
- 因此,需要一种通过 API 访问该功能的替代方法。解决方案是使用较低级别的 Google API 客户端库与该 API 进行通信。如需切换到此客户端库,请将
google-cloud-resource-manager
替换为较低级别的google-api-python-client
软件包。 - 由于 Python 2 已停用,因此支持模块 21 的依赖关系图要求将某些软件包锁定为特定版本。即使某些软件包未在 Python 3
app.yaml
中指定,也必须调出这些软件包。
之前:
flask
从模块 20 requirements.txt
开始,对于正常工作的模块 21 应用,将其更新为以下内容:
之后:
grpcio==1.0.0
protobuf<3.18.0
six>=1.13.0
flask
google-gax<0.13.0
google-api-core==1.31.1
google-api-python-client<=1.11.0
google-auth<2.0dev
google-cloud-datastore==1.15.3
google-cloud-firestore==1.9.0
google-cloud-ndb
google-cloud-pubsub==1.7.0
firebase-admin
随着依赖项的更改,软件包和版本号将在代码库中更新,但在撰写本文时,此 app.yaml
足以满足正常运行的应用需求。
其他配置更新
如果您尚未删除此 Codelab 前面的 lib
文件夹,请立即删除。使用更新后的 requirements.txt
,发出以下熟悉的命令,将这些要求安装到 lib
中:
pip install -t lib -r requirements.txt # or pip2
如果您的开发系统上同时安装了 Python 2 和 Python 3,则可能需要使用 pip2
而不是 pip
。
修改应用代码
幸运的是,大多数所需的更改都发生在配置文件中。应用代码中所需的唯一更改是对 API 进行细微更新,以使用较低级别的 Google API 客户端库(而不是 Resource Manager 客户端库)访问 API。templates/index.html
网站模板无需更新。
更新导入和初始化
将 Resource Manager 客户端库 (google.cloud.resourcemanager
) 替换为 Google API 客户端库 (googleapiclient.discovery
),如下所示:
之前:
from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app
之后:
from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb
from googleapiclient import discovery
from firebase_admin import auth, initialize_app
针对 App Engine 管理员用户的支持
您需要在 _get_gae_admins()
中进行一些更改,以支持使用较低级别的客户端库。我们先讨论一下具体变化,然后再提供要更新的所有代码。
Python 2 代码要求同时使用从 google.auth.default()
返回的凭据和项目 ID。这些凭据未在 Python 3 中使用,因此被分配给了通用下划线 ( _
) 虚拟变量。由于 Python 2 版本需要使用它,因此请将下划线更改为 CREDS
。此外,您不应创建 Resource Manager API 客户端,而是创建在概念上与 API 客户端类似的 API 服务端点,因此我们保留相同的变量名称 (rm_client
)。一个区别在于,实例化服务端点需要凭据 (CREDS
)。
这些更改反映在以下代码中:
之前:
_, PROJ_ID = default( # Application Default Credentials and project ID
['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
rm_client = resourcemanager.ProjectsClient()
之后:
CREDS, PROJ_ID = default( # Application Default Credentials and project ID
['https://www.googleapis.com/auth/cloud-platform'])
rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)
另一个区别是,Resource Manager 客户端库返回的 allow-policy 对象使用点分属性表示法,而较低级别的客户端库会返回使用方括号 ( [ ]
) 的 Python 字典。例如,针对 Resource Manager 客户端库使用 binding.role
,针对较低级别的库使用 binding['role']
。前者还使用“underscore_Separates”而不是首选“CamelCased”的较低级别的库以及传递 API 参数的方式稍有不同。
这些使用差异如下所示:
之前:
allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
for b in allow_policy.bindings: # bindings in IAM allow-policy
if b.role in _TARGETS: # only look at GAE admin roles
admins.update(user.split(':', 1).pop() for user in b.members)
之后:
allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
for b in allow_policy['bindings']: # bindings in IAM allow-policy
if b['role'] in _TARGETS: # only look at GAE admin roles
admins.update(user.split(':', 1).pop() for user in b['members'])
汇总所有这些更改,将 Python 3 _get_gae_admins()
替换为以下等效的 Python 2 版本:
def _get_gae_admins():
'return set of App Engine admins'
# setup constants for calling Cloud Resource Manager API
CREDS, PROJ_ID = default( # Application Default Credentials and project ID
['https://www.googleapis.com/auth/cloud-platform'])
rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)
_TARGETS = frozenset(( # App Engine admin roles
'roles/viewer',
'roles/editor',
'roles/owner',
'roles/appengine.appAdmin',
))
# collate users who are members of at least one GAE admin role (_TARGETS)
admins = set() # set of all App Engine admins
allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
for b in allow_policy['bindings']: # bindings in IAM allow-policy
if b['role'] in _TARGETS: # only look at GAE admin roles
admins.update(user.split(':', 1).pop() for user in b['members'])
return admins
is_admin()
函数不需要任何更新,因为它依赖于已更新的 _get_gae_admins()
。
以上就是将 Python 3 Module 21 应用向后移植到 Python 2 所需的更改。恭喜您获得更新后的第 21 单元示例应用!您可以在 Module 21a repo folder 中找到所有代码。
7. 摘要/清理
此 Codelab 中的最后步骤是确保运行此应用的主账号(用户或服务账号)拥有适当的权限,然后部署您的应用以确认应用按预期运行,并且更改会反映在输出中。
能够读取 IAM allow-policy
之前,我们向您介绍了被视为 App Engine 管理员用户需要具备的四个角色,但现在您需要熟悉第五个角色:
roles/viewer
roles/editor
roles/owner
roles/appengine.appAdmin
roles/resourcemanager.projectIamAdmin
(针对访问 IAM 允许政策的主账号)
roles/resourcemanager.projectIamAdmin
角色使主账号能够确定最终用户是否为任何 App Engine 管理员角色的成员。如果没有 roles/resourcemanager.projectIamAdmin
成员资格,则无法调用 Cloud Resource Manager API 获取 allow-policy。
您无需在此处执行任何明确操作,因为您的应用将在 App Engine 的默认服务账号下运行,该账号会自动获得此角色的成员。即使您在开发阶段使用默认服务账号,我们也强烈建议您创建和使用用户管理的服务账号,并授予应用正常运行所需的最小权限。如需向此类服务账号授予成员资格,请运行以下命令:
$ gcloud projects add-iam-policy-binding PROJ_ID --member="serviceAccount:USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com" --role=roles/resourcemanager.projectIamAdmin
PROJ_ID
是 Cloud 项目 ID,USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com
是您为应用创建的用户管理的服务账号。此命令会为您的项目输出更新后的 IAM 政策,您可以在其中确认服务账号具有 roles/resourcemanager.projectIamAdmin
成员资格。如需了解详情,请参阅参考文档。再次强调,您无需在此 Codelab 中发出该命令,但可以将其保存作为参考,对您自己的应用进行现代化改造。
部署并验证应用
使用标准的 gcloud app deploy
命令将您的应用上传到云端。部署后,您应该会看到功能与模块 20 应用几乎完全相同,只不过您成功地将 App Engine 用户服务替换为了用于用户管理的 Cloud Identity Platform(和 Firebase Auth):
与模块 20 相比,您会发现一个不同之处在于,点击“登录”后会看到弹出式窗口,而不是重定向,如下面的一些屏幕截图所示。不过,与模块 20 一样,该行为会因浏览器注册的 Google 账号数量而略有不同。
如果没有用户在该浏览器中注册,或者只有单个用户尚未登录,则会显示一个常规的“Google 登录”弹出式窗口:
如果单个用户已在您的浏览器上注册,但在其他位置登录,系统不会显示任何对话框(或弹出并立即关闭),而应用会进入登录状态(显示用户的电子邮件地址和退出按钮)。
有些开发者可能想要提供账号选择器,即便是针对单个用户:
若要实现这一点,请取消对网站模板中的 provider.setCustomParameters({prompt: 'select_account'});
行的注释,如前所述。
如果有多个用户,系统会弹出账号选择器对话框(见下文)。如果用户尚未登录,系统会提示用户。如果已登录,则弹出式窗口会消失,并且应用会进入登录状态。
模块 21 的登录状态与模块 20 的界面完全相同:
管理员用户登录后,也是如此:
与模块 21 不同,模块 20 始终从应用(服务器端代码)访问网络模板内容的逻辑。模块 20 的一个缺陷是,当最终用户第一次点击应用时会记录一次访问,而当用户登录时会有另一次访问。
对于模块 21,登录逻辑仅在网页模板(客户端代码)中执行。您无需通过服务器端检查即可确定要显示的内容。系统对服务器执行的唯一调用是在最终用户登录后检查管理员用户。这意味着登录和退出不会记录额外的访问,因此针对用户管理操作,最近的访问列表保持不变。请注意,上面的屏幕截图显示了多个用户登录的同一组四次访问。
第 20 单元的屏幕截图展示了“双重访问 bug”。系统会针对每个登录或退出操作分别显示访问日志。检查最近访问的时间戳,找出每个显示时间顺序的屏幕截图。
清理
常规
如果您目前已完成,我们建议您停用 App Engine 应用,以免产生费用。不过,如果您希望测试或实验更多内容,App Engine 平台有免费配额,因此只要您不超过该使用量水平,您就不必支付费用。这只是计算费用,但相关 App Engine 服务可能也会产生费用,因此请查看其价格页面了解详情。如果此迁移涉及其他 Cloud 服务,这些服务单独计费。无论是哪种情况(如适用),请参阅“此 Codelab 的具体说明”部分。
为了全面披露,部署到像 App Engine 这样的 Google Cloud 无服务器计算平台会产生少量构建和存储费用。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 独有的服务。有关详情,请参阅各个产品的文档:
- App Engine Datastore 服务由 Cloud Datastore(Datastore 模式的 Cloud Firestore)提供,该服务也有一个免费层级;如需了解详情,请参阅其价格页面。
- Cloud Identity Platform 的使用在一定程度上是“免费”的具体取决于您使用的 Google Cloud 控制台服务。如需了解详情,请参阅其价格页面。
- Cloud Resource Manager API 的大部分使用免费,详见价格页面。
后续步骤
除了本教程外,其他侧重于摆脱旧版捆绑式服务的迁移模块包括:
- 第 2 单元:从 App Engine
ndb
迁移到 Cloud NDB - 模块 7-9:从 App Engine 任务队列(推送任务)迁移到 Cloud Tasks
- 模块 12-13:从 App Engine Memcache 迁移到 Cloud Memorystore
- 第 15-16 单元:从 App Engine Blob 存储区迁移到 Cloud Storage
- 模块 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
的情况下实现容器化
您可以自行选择是否切换到其他无服务器平台,我们建议您先考虑最适合您的应用和用例的方案,然后再做任何更改。
无论您接下来考虑使用哪种迁移模块,都可以通过其开源代码库访问所有 Serverless Migration Station 内容(Codelab、视频、源代码 [如果有])。代码库的 README
还提供了有关应考虑哪些迁移以及任何相关“顺序”的指南。迁移模块
8. 其他资源
下面列出的是一些其他资源,开发者可进一步探索此模块或相关迁移模块。您可以在下面就这些内容提供反馈,找到代码链接,以及可能对您有用的各种文档。
Codelab 问题/反馈
如果您在此 Codelab 中发现任何问题,请先搜索您的问题,然后再提交。用于搜索和创建新问题的链接:
迁移时可参考的资源
下表列出了模块 20(START)和模块 21 (FINISH) 对应的代码库文件夹的链接。
Codelab | Python 2 | Python 3 |
(不适用) | ||
第 21 单元(此 Codelab) |
在线参考
以下是与本教程相关的资源:
Cloud Identity Platform 和 Cloud Marketplace
- Identity Platform 产品页面
- Firebase Authentication
- Identity Platform 和 Firebase Auth 产品比较页面
- Identity Platform 价格信息
- Identity Platform 配额(以及无付款方式的用量)
- Identity Platform 提供方设置
- Cloud Marketplace 产品页面
- Marketplace 中的 Identity Platform 页面
Cloud Resource Manager、Cloud IAM、Firebase Admin SDK
- Resource Manager 产品页面
- Resource Manager 价格信息
- Resource Manager 客户端库
- Cloud IAM 概览(角色、allow-policy 等)
- Firebase Admin SDK (Python)
App Engine 用户、App Engine NDB、Cloud NDB、Cloud Datastore
- App Engine 用户概览
- App Engine NDB 文档
- App Engine NDB 代码库
- Cloud NDB 客户端库
- Cloud NDB 代码库
- Cloud Datastore 产品页面
- Cloud Datastore 价格信息
其他迁移模块参考文档
- 迁移模块简介
- 所有“无服务器迁移站”资源
- 有关迁移到 Python 3 的文档
- 迁移模块 17“在第 2 代运行时中使用捆绑服务”Codelab
- 迁移模块 20“将 App Engine 用户服务添加到 Flask 应用”Codelab
App Engine 迁移
App Engine 平台
- App Engine 文档
- Python 2 App Engine(标准环境)运行时
- 在 Python 2 App Engine 上使用 App Engine 内置库
- Python 3 App Engine(标准环境)运行时
- Python 2 与3 个 App Engine(标准环境)运行时
- Python 2 到 3 App Engine(标准环境)迁移指南
- App Engine 价格和配额信息
- 第二代 App Engine 平台发布(2018 年)
- 比较第一个与第二代平台
- 对旧版运行时的长期支持
- 文档迁移示例
- 社区提供的迁移示例
Cloud SDK
- Google Cloud SDK
- Cloud SDK
gcloud
命令行工具 - 启用(和停用)Google API
- Cloud Console API 管理器(启用/停用 API)
- 通过
gcloud
启用 Google API - 使用
gcloud
列出 Google API
其他 Cloud 信息
- 在 Google Cloud 上运行 Python
- Python 客户端库文档
- Python 客户端库代码库
- “始终免费”层级
- Cloud SDK
- Cloud SDK
gcloud
命令行工具 - 所有 Google Cloud 文档
视频
许可
此作品已获得 Creative Commons Attribution 2.0 通用许可授权。