1. 目标
本研讨会的目的是为用户和从业者提供 Duet AI 实践培训。
在此 Codelab 中,您将学习以下内容:
- 在 GCP 项目中激活 Duet AI,并将其配置为可在 IDE 和 Cloud 控制台中使用。
- 使用 Duet AI 进行代码生成、补全和说明。
- 使用 Duet AI 说明和排查应用问题。
- Duet AI 功能,例如 IDE 聊天和多轮对话、聊天与内嵌代码生成、智能操作(例如代码说明和朗读确认)等。
推介材料
为了展示如何在日常开发中真实地使用面向开发者的 Duet AI,本研讨会的活动将在叙事背景下进行。
一位新开发者加入了一家电子商务公司。他们的任务是向现有的电子商务应用(由多项服务组成)添加一项新服务。这项新服务可提供商品清单中产品的其他信息(尺寸、重量等)。此服务将根据商品尺寸和重量提供更优惠的运费。
由于开发者是新加入公司的,因此他们将使用 Duet AI 进行代码生成、解释和文档编制。
服务编码完成后,平台管理员将使用 Duet AI(聊天)来帮助创建工件(Docker 容器)以及将工件部署到 GCP 所需的资源(例如 Artifact Registry、IAM 权限、代码库、计算基础设施 [GKE 或 CloudRun 等])。
将应用部署到 GCP 后,应用运维人员/SRE 将使用 Duet AI(和 Cloud Ops)来帮助排查新服务中的错误。
角色
本研讨会涵盖以下买家角色:
- 应用开发者 - 需要具备一定的编程和软件开发知识。
此 Duet AI 工作坊变体仅面向开发者。无需了解 GCP 云资源。如需了解如何构建运行此应用所需的 GCP 资源,请点击此处查看相关脚本。您可以按照本指南中的说明部署所需的 GCP 资源。
2. 准备环境
激活 Duet AI
您可以通过 API(gcloud 或 Terraform 等 IaC 工具)或 Cloud 控制台界面在 GCP 项目中启用 Duet AI。
如需在 Google Cloud 项目中启用 Duet AI,您需要启用 Cloud AI Companion API,并为用户授予 Cloud AI Companion User 和 Service Usage Viewer 这两个 Identity and Access Management (IAM) 角色。
通过 gcloud
激活 Cloud Shell:
配置 PROJECT_ID、USER 并启用 Cloud AI Companion API。
export PROJECT_ID=<YOUR PROJECT ID>
export USER=<YOUR USERNAME> # Use your full LDAP, e.g. name@example.com
gcloud config set project ${PROJECT_ID}
gcloud services enable cloudaicompanion.googleapis.com --project ${PROJECT_ID}
输出类似于以下内容:
Updated property [core/project]. Operation "operations/acat.p2-60565640195-f37dc7fe-b093-4451-9b12-934649e2a435" finished successfully.
向 USER 账号授予 Cloud AI Companion User 和 Service Usage Viewer 这两个 Identity and Access Management (IAM) 角色。Cloud Companion API 位于我们将使用的 IDE 和控制台中的功能后面。在控制台中启用界面之前,系统会使用 Service Usage 查看者权限进行快速检查(以便 Duet 界面仅显示在已启用该 API 的项目中)。
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member=user:${USER} --role=roles/cloudaicompanion.user
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member=user:${USER} --role=roles/serviceusage.serviceUsageViewer
输出类似于以下内容:
... - members: - user:<YOUR USER ACCOUNT> role: roles/cloudaicompanion.user ... - members: - user:<YOUR USER ACCOUNT> role: roles/serviceusage.serviceUsageViewer
通过 Cloud 控制台
如需启用该 API,请前往 Google Cloud 控制台中的 Cloud AI Companion API 页面。
在项目选择器中,选择项目。
点击启用。
页面会更新并显示状态为已启用。现在,所有拥有所需 IAM 角色的用户都可以在所选 Google Cloud 项目中使用 Duet AI。
如需授予使用 Duet AI 所需的 IAM 角色,请前往 IAM 页面。
在主账号列中,找到要为其启用 Duet AI 访问权限的用户,然后在对应的行中点击铅笔图标 ✏️ 修改主账号。
在修改权限窗格中,点击添加添加其他角色。
在“选择角色”中,选择 Cloud AI Companion User。
点击添加其他角色,然后选择 Service Usage Viewer。
点击保存。
设置 IDE
开发者可以根据自己的需求选择最适合的 IDE。Duet AI 代码辅助功能可在多种 IDE 中使用,例如 Visual Studio Code、JetBrains IDE(IntelliJ、PyCharm、GoLand、WebStorm 等)、Cloud Workstations、Cloud Shell 编辑器。
在本实验中,您可以使用 Cloud Workstations 或 Cloud Shell 编辑器。
本讲座使用 Cloud Shell 编辑器。
请注意,Cloud Workstations 可能需要 20-30 分钟才能完成设置。
如需立即使用,请使用 Cloud Shell 编辑器。
点击 Cloud Shell 顶部菜单栏中的铅笔图标 ✏️,打开 Cloud Shell 编辑器。
Cloud Shell Editor 的界面和用户体验与 VSCode 非常相似。

按 CTRL(在 Windows 中)/CMD(在 Mac 中)+ ,(逗号)进入“设置”窗格。
在搜索栏中,输入“duet ai”。
确保或启用 Cloudcode › Duet AI: Enable 和 Cloudcode › Duet AI › Inline Suggestions: Enable Auto

在底部状态栏中,点击 Cloud Code - Sign In(Cloud Code - 登录),然后按照登录工作流操作。
如果您已登录,状态栏会显示 Cloud Code - No project(Cloud Code - 无项目)。
点击“Cloud Code - 无项目”,顶部会显示一个操作下拉窗格。点击选择 Google Cloud 项目。

开始输入您的项目 ID,您的项目应会显示在列表中。

从项目列表中选择您的 PROJECT_ID。
底部状态栏会更新以显示您的项目 ID。如果未显示,您可能需要刷新 Cloud Shell 编辑器标签页。
点击左侧菜单栏中的 Duet AI 图标
,系统随即会显示 Duet AI 聊天窗口。如果您收到一条内容为“选择 GCP 项目”的消息,点击并重新选择项目。
您现在会看到 Duet AI 聊天窗口

3. 设置基础架构

为了在 GCP 中运行新的配送服务,您需要以下 GCP 资源:
- 一个包含数据库的 Cloud SQL 实例。
- 用于运行容器化服务的 GKE 集群。
- 用于存储 Docker 映像的 Artifact Registry。
- 用于存放代码的 Cloud Source Repository。
在 Cloud Shell 终端中,克隆以下代码库并运行以下命令,以在您的 GCP 项目中设置基础设施。
# Set your project
export PROJECT_ID=<INSERT_YOUR_PROJECT_ID>
gcloud config set core/project ${PROJECT_ID}
# Enable Cloudbuild and grant Cloudbuild SA owner role
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format 'value(projectNumber)')
gcloud services enable cloudbuild.googleapis.com
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com --role roles/owner
# Clone the repo
git clone https://github.com/duetailabs/dev.git ~/duetaidev
cd ~/duetaidev
# Run Cloudbuild to create the necessary resources
gcloud builds submit --substitutions=_PROJECT_ID=${PROJECT_ID}
# To destroy all GCP resources, run the following
# gcloud builds submit --substitutions=_PROJECT_ID=${PROJECT_ID} --config=cloudbuild_destroy.yaml
4. 开发 Python Flask 服务

我们将创建的服务最终将包含以下文件。您无需立即创建这些文件,只需按照以下说明逐一创建即可:
package-service.yaml- 包含高度、宽度、重量和特殊处理说明等数据的包裹服务的开放 API 规范。data_model.py- 软件包服务 API 规范的数据模型。还会在 product_details 数据库中创建packages表。connect_connector.py- CloudSQL 连接(定义引擎、会话和基本 ORM)db_init.py- 将示例数据生成到packages表中。main.py- 一个 Python Flask 服务,具有一个GET端点,用于根据 product_id 从packages数据中检索软件包详细信息。test.py- 单元测试requirement.txt- Python 要求Dockerfile- 将此应用容器化
如果您在练习过程中遇到任何棘手的问题,请参阅本 Codelab 的附录,其中包含所有最终文件,可供您参考。
在上一步中,您创建了一个 Cloud Source Repositories 代码库。克隆代码库。您将在克隆的代码库文件夹中构建应用文件。
在 Cloud Shell 终端中,运行以下命令以克隆代码库。
cd ~ gcloud source repos clone shipping shipping cd ~/shipping
通过 Cloud Shell 编辑器的左侧菜单打开 Duet AI 聊天侧边栏。该图标类似于
。您现在可以使用 Duet AI 进行代码辅助。
package-service.yaml
在未打开任何文件的情况下,让 Duet 为配送服务生成 OpenAPI 规范。
提示 1:生成一个 OpenAPI YAML 规范,用于提供给定数值产品 ID 的配送和包裹信息。服务应包含有关包裹高度、宽度、深度、重量以及任何特殊处理说明的信息。

生成的代码窗口右上角列出了三个选项。
您可以COPY
代码并将其粘贴到文件中。
您可以将代码 ADD
到编辑器中当前打开的文件。
或者,您也可以在新的文件中OPEN
代码。
点击 OPEN
新文件中的代码。
点击 CTRL/CMD + s 保存文件,并将文件存储在应用文件夹中,文件名为 package-service.yaml。点击“确定”。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
您还可以尝试各种提示,看看 Duet AI 的回答。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
data_model.py
接下来,您将基于 OpenAPI 规范为服务创建数据模型 Python 文件。
打开 package-service.yaml 文件后,输入以下提示。
提示 1:使用 Python SQLAlchemy ORM,为此 API 服务生成数据模型。还包括一个单独的函数和一个用于创建数据库表的主入口点。

我们来看看生成的每个部分。Duet AI 仍然只是一个助理,虽然它可以帮助您快速编写代码,但您仍应在编写过程中查看并理解生成的内容。
首先,有一个类,名为 Package,种类为 Base,用于定义 packages 数据库的数据模型,如下所示:
class Package(Base):
__tablename__ = 'packages'
id = Column(Integer, primary_key=True)
product_id = Column(String(255))
height = Column(Float)
width = Column(Float)
depth = Column(Float)
weight = Column(Float)
special_handling_instructions = Column(String(255))
接下来,您需要一个在数据库中创建表的函数,如下所示:
def create_tables(engine):
Base.metadata.create_all(engine)
最后,您需要一个运行 create_tables 函数的主函数,以在 CloudSQL 数据库中实际构建表,如下所示:
if __name__ == '__main__':
from sqlalchemy import create_engine
engine = create_engine('sqlite:///shipping.db')
create_tables(engine)
print('Tables created successfully.')
请注意,main 函数正在使用本地 sqlite 数据库创建引擎。如需使用 CloudSQL,您需要更改此设置。您稍后可以执行此操作。
使用 OPEN
在新文件中编写代码的工作流程与之前相同。将代码保存在名为 data_model.py 的文件中(请注意名称中的下划线,而不是短划线)。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
connect-connector.py
创建 CloudSQL 连接器。
打开 data_model.py 文件后,输入以下提示。
提示 1:使用 cloud-sql-python-connector 库,生成一个用于初始化 Cloud SQL Postgres 实例的连接池的函数。

请注意,此回答未使用 cloud-sql-python-connector 库。您可以在同一聊天串中添加具体信息,从而优化提示,让 Duet 更好地理解您的意图。
我们来使用另一个提示。
提示 2:必须使用 cloud-sql-python-connector 库。

确保它使用 cloud-sql-python-connector 库。
使用 OPEN
在新文件中编写代码的工作流程与之前相同。将代码保存在名为 connect_conector.py 的文件中。您可能需要手动导入 pg8000 库,请参阅以下文件。
清除 Duet AI 聊天记录,然后在 connect_connector.py 文件处于打开状态时,生成要在应用中使用的 DB engine、sessionmaker 和 base ORM。
提示 1:使用 connect_with_connector 方法创建引擎、sessionmaker 类和 Base ORM

响应可能会将 engine、Session 和 Base 附加到 connect_connector.py 文件中。
最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
您还可以尝试各种提示,看看 Duet AI 的回答可能会有哪些变化。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
更新了 data_model.py
您需要使用在上一步中创建的引擎(位于 connect_connector.py 文件中),才能在 CloudSQL 数据库中创建表。
清除 Duet AI 聊天记录。打开 data_model.py 文件。尝试使用以下提示。
提示 1:在主函数中,导入并使用 connect_connector.py 中的引擎

您应该会看到从 connect_connector(针对 CloudSQL)导入 engine 的响应。create_table 使用该引擎(而不是默认的 sqlite 本地数据库)。
更新 data_model.py 文件。
最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
您还可以尝试各种提示,看看 Duet AI 的各种回答。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
requirements.txt
为应用创建 requirements.txt 文件。
同时打开 connect_connector.py 和 data_model.py 文件,然后输入以下提示。
提示 1:为此数据模型和服务生成 pip requirements 文件
提示 2:使用最新版本为此数据模型和服务生成 pip 要求文件

验证名称和版本是否正确。例如,在上面的响应中,google-cloud-sql-connecter 名称和版本均不正确。手动修正版本,并创建一个如下所示的 requirements.txt 文件:
cloud-sql-python-connector==1.2.4
sqlalchemy==1.4.36
pg8000==1.22.0
在命令终端中运行以下命令:
pip3 install -r requirements.txt
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
在 CloudSQL 中创建软件包表
为 CloudSQL 数据库连接器设置环境变量。
export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export DB_USER=evolution
export DB_PASS=evolution
export DB_NAME=product_details
现在运行 data_model.py。
python data_model.py
输出类似于以下内容(请查看代码以了解实际预期结果):
Tables created successfully.
连接到 CloudSQL 实例,并检查数据库是否已创建。
gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details
输入密码(同样是 evolution)后,获取表格。
product_details=> \dt
输出类似于以下内容:
List of relations Schema | Name | Type | Owner --------+----------+-------+----------- public | packages | table | evolution (1 row)
您还可以查看数据模型和表格详细信息。
product_details=> \d+ packages
输出类似于以下内容:
Table "public.packages"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
-------------------------------+-------------------+-----------+----------+--------------------------------------+----------+-------------+--------------+-------------
id | integer | | not null | nextval('packages_id_seq'::regclass) | plain | | |
product_id | integer | | not null | | plain | | |
height | double precision | | not null | | plain | | |
width | double precision | | not null | | plain | | |
depth | double precision | | not null | | plain | | |
weight | double precision | | not null | | plain | | |
special_handling_instructions | character varying | | | | extended | | |
Indexes:
"packages_pkey" PRIMARY KEY, btree (id)
Access method: heap
输入 \q 以退出 CloudSQL。
db_init.py
接下来,我们向 packages 表中添加一些示例数据。
清除 Duet AI 聊天记录。打开 data_model.py 文件后,尝试使用以下提示。
提示 1:生成一个函数,用于创建 10 个示例软件包行并将它们提交到软件包表
提示 2:使用来自 connect_connector 的会话,生成一个函数,该函数可创建 10 个示例软件包行并将它们提交到软件包表

使用 OPEN
在新文件中编写代码的工作流程与之前相同。将代码保存在名为 db_init.py 的文件中。
最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
您还可以尝试各种提示,看看 Duet AI 的各种回答。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
创建示例软件包数据
从命令行运行 db_init.py。
python db_init.py
输出类似于以下内容:
Packages created successfully.
再次连接到 CloudSQL 实例,并验证示例数据是否已添加到软件包表中。
连接到 CloudSQL 实例,并检查数据库是否已创建。
gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details
输入密码(也是 evolution)后,从软件包表中获取所有数据。
product_details=> SELECT * FROM packages;
输出类似于以下内容:
id | product_id | height | width | depth | weight | special_handling_instructions ----+------------+--------+-------+-------+--------+----------------------------------- 1 | 0 | 10 | 10 | 10 | 10 | No special handling instructions. 2 | 1 | 10 | 10 | 10 | 10 | No special handling instructions. 3 | 2 | 10 | 10 | 10 | 10 | No special handling instructions. 4 | 3 | 10 | 10 | 10 | 10 | No special handling instructions. 5 | 4 | 10 | 10 | 10 | 10 | No special handling instructions. 6 | 5 | 10 | 10 | 10 | 10 | No special handling instructions. 7 | 6 | 10 | 10 | 10 | 10 | No special handling instructions. 8 | 7 | 10 | 10 | 10 | 10 | No special handling instructions. 9 | 8 | 10 | 10 | 10 | 10 | No special handling instructions. 10 | 9 | 10 | 10 | 10 | 10 | No special handling instructions. (10 rows)
输入 \q 以退出 CloudSQL。
main.py
打开 data_model.py、package-service.yaml 和 connect_connector.py 文件后,为应用创建 main.py。
提示 1:使用 Python Flask 库 - 为此服务创建一个使用 HTTP REST 端点的实现
提示 2:使用 Python Flask 库 - 创建一个使用 HTTP REST 端点的实现来提供此服务。从 connect_connector.py 导入并使用 SessionMaker 来获取软件包数据。
提示 3:使用 Python Flask 库 - 为此服务创建一个使用 HTTP REST 端点的实现。从 data_model.py 导入并使用 Package,并从 connect_conector.py 导入并使用 SessionMaker 来获取软件包数据。
提示 4:使用 Python Flask 库 - 为此服务创建一个使用 HTTP REST 端点的实现。从 data_model.py 和 connect_conector.py 导入并使用 Package 和 SessionMaker 来处理软件包数据。为 app.run 使用主机 IP 0.0.0.0

更新了 main.py 的要求。
提示:为 main.py 创建 requirements 文件

将此内容附加到 requirements.txt 文件。请务必使用 Flask 版本 3.0.0。
使用 OPEN
在新的文件工作流程中编写代码,与之前一样。将代码保存在名为 main.py 的文件中。
最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
5. 测试和运行应用
安装要求。
pip3 install -r requirements.txt
运行 main.py。
python main.py
输出类似于以下内容:
* Serving Flask app 'main' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://10.88.0.3:5000 Press CTRL+C to quit
在第二个终端中,测试 /packages/<product_id> 端点。
curl localhost:5000/packages/1
输出类似于以下内容:
{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}
您还可以测试示例数据中的任何其他商品 ID。
输入 CTRL_C 以退出终端中正在运行的 Docker 容器。
生成单元测试
打开 main.py 文件,然后生成单元测试。
提示 1:生成单元测试。

使用 OPEN
在新文件中编写代码的工作流程与之前相同。将代码保存在名为 test.py 的文件中。
在 test_get_package 函数中,必须定义 product_id。您可以手动添加。
最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
点击 Duet AI 边栏顶部的回收站图标
,重置 Duet AI 聊天记录。
运行单元测试
运行单元测试。
python test.py
输出类似于以下内容:
. ---------------------------------------------------------------------- Ran 1 test in 1.061s OK
关闭 Cloud Shell Editor 中的所有文件,然后点击顶部状态栏中的回收站图标
清除聊天记录。
Dockerfile
为此应用创建 Dockerfile。
打开 main.py 并尝试使用以下提示。
提示 1:为此应用生成 Dockerfile。
提示 2:为此应用生成 Dockerfile。将所有文件复制到容器。

您还需要为 INSTANCE_CONNECTION_NAME、DB_USER、DB_PASS 和 DB_NAME 设置 ENVARS。您可以手动执行此操作。您的 Dockerfile 应如下所示:
FROM python:3.10-slim
WORKDIR /app
COPY . ./
RUN pip install -r requirements.txt
# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details
CMD ["python", "main.py"]
使用 OPEN
在新文件中编写代码的工作流程与之前相同。将代码保存在名为 Dockerfile 的文件中。
最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。
在本地运行应用
打开 Dockerfile 后,尝试使用以下提示。
提示 1:如何使用此 Dockerfile 在本地运行容器

按照说明操作。
# Build docker build -t shipping . # And run docker run -p 5000:5000 -it shipping
输出类似于以下内容:
* Serving Flask app 'main' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://172.17.0.2:5000 Press CTRL+C to quit
从第二个终端窗口中,访问容器。
curl localhost:5000/packages/1
输出类似于以下内容:
{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}
容器化应用正在运行。
输入 CTRL_C 以退出终端中正在运行的 Docker 容器。
在 Artifact Registry 中构建容器映像
构建容器映像并将其推送到 Artifact Registry。
cd ~/shipping
gcloud auth configure-docker us-central1-docker.pkg.dev
docker build -t us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping .
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping
应用容器现在位于 us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping,可以部署到 GKE。
6. 将应用部署到 GKE 集群
在为本研讨会构建 GCP 资源时,您已创建了一个 GKE Autopilot 集群。连接到 GKE 集群。
gcloud container clusters get-credentials gke1 \
--region=us-central1
使用 Google 服务账号为 Kubernetes 默认服务账号添加注释。
kubectl annotate serviceaccount default iam.gke.io/gcp-service-account=cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com
输出类似于以下内容:
serviceaccount/default annotated
准备并应用 k8s.yaml 文件。
cp ~/duetaidev/k8s.yaml_tmpl ~/shipping/.
export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export IMAGE_REPO=us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping
envsubst < ~/shipping/k8s.yaml_tmpl > k8s.yaml
kubectl apply -f k8s.yaml
输出类似于以下内容:
deployment.apps/shipping created service/shipping created
等待 Pod 运行,并等待系统为服务分配外部负载平衡器 IP 地址。
kubectl get pods kubectl get service shipping
输出类似于以下内容:
# kubectl get pods NAME READY STATUS RESTARTS AGE shipping-f5d6f8d5-56cvk 1/1 Running 0 4m47s shipping-f5d6f8d5-cj4vv 1/1 Running 0 4m48s shipping-f5d6f8d5-rrdj2 1/1 Running 0 4m47s # kubectl get service shipping NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE shipping LoadBalancer 34.118.225.125 34.16.39.182 80:30076/TCP 5m41s
对于 GKE Autopilot 集群,请等待片刻,直到资源准备就绪。
通过 EXTERNAL-IP 地址访问服务。
export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1
输出类似于以下内容:
{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}
7. 加分题:排查应用问题
从 cloudsqlsa 服务账号中移除 CloudSQL Client IAM 角色。这会导致连接到 CloudSQL 数据库时出错。
gcloud projects remove-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
重启配送 Pod。
kubectl rollout restart deployment shipping
Pod 重启后,请尝试再次访问 shipping 服务。
export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1
输出类似于以下内容:
... <title>500 Internal Server Error</title> <h1>Internal Server Error</h1> <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
通过前往 Kubernetes Engine > 工作负载来检查日志

点击 shipping 部署,然后点击日志标签页。

点击状态栏右侧的在 Log Explorer 中查看
图标。系统会打开一个新的日志浏览器窗口。

点击其中一个 Traceback 错误条目,然后点击说明此日志条目。

您可以阅读错误说明。
接下来,我们让 Duet AI 帮助排查错误。
尝试使用以下提示。
提示 1:帮我排查此错误

在提示中输入错误消息。
提示 2:Forbidden(禁止):经过身份验证的 IAM 主账号似乎未获授权来发出 API 请求。验证您的 GCP 项目中是否已启用“Cloud SQL Admin API”,以及是否已向 IAM 主账号授予“Cloud SQL Client”角色

然后。
提示 3:如何使用 gcloud 将 Cloud SQL Client 角色分配给 Google 服务账号?

为 cloudsqlsa 分配 Cloud SQL 客户角色。
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
请稍等片刻,然后再次尝试访问该应用。
export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1
输出类似于以下内容:
{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}
您已成功使用 Cloud Logging、日志浏览器和日志说明器功能排查了问题。
8. 总结
恭喜!您已成功完成此 Codelab。
在此 Codelab 中,您学习了以下内容:
- 在 GCP 项目中激活 Duet AI,并将其配置为可在 IDE 和 Cloud 控制台中使用。
- 使用 Duet AI 进行代码生成、补全和说明。
- 使用 Duet AI 说明和排查应用问题。
- Duet AI 功能,例如 IDE 聊天和多轮对话、聊天与内嵌代码生成、智能操作(例如代码说明和朗读确认)等。
9. 附录
package-service.yaml
swagger: "2.0"
info:
title: Shipping and Package Information API
description: This API provides information about shipping and packages.
version: 1.0.0
host: shipping.googleapis.com
schemes:
- https
produces:
- application/json
paths:
/packages/{product_id}:
get:
summary: Get information about a package
description: This method returns information about a package, including its height, width, depth, weight, and any special handling instructions.
parameters:
- name: product_id
in: path
required: true
type: integer
format: int64
responses:
"200":
description: A successful response
schema:
type: object
properties:
height:
type: integer
format: int64
width:
type: integer
format: int64
depth:
type: integer
format: int64
weight:
type: integer
format: int64
special_handling_instructions:
type: string
"404":
description: The product_id was not found
data_model.py
from sqlalchemy import Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from connect_connector import engine
Base = declarative_base()
class Package(Base):
__tablename__ = 'packages'
id = Column(Integer, primary_key=True)
product_id = Column(Integer, nullable=False)
height = Column(Float, nullable=False)
width = Column(Float, nullable=False)
depth = Column(Float, nullable=False)
weight = Column(Float, nullable=False)
special_handling_instructions = Column(String, nullable=True)
def create_tables():
Base.metadata.create_all(engine)
if __name__ == '__main__':
create_tables()
print('Tables created successfully.')
connect_connector.py
import os
from google.cloud.sql.connector import Connector, IPTypes
import sqlalchemy
# You may need to manually import pg8000 and Base as follows
import pg8000
from sqlalchemy.ext.declarative import declarative_base
def connect_with_connector() -> sqlalchemy.engine.base.Engine:
"""Initializes a connection pool for a Cloud SQL instance of Postgres."""
# Note: Saving credentials in environment variables is convenient, but not
# secure - consider a more secure solution such as
# Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
# keep secrets safe.
instance_connection_name = os.environ[
"INSTANCE_CONNECTION_NAME"
] # e.g. 'project:region:instance'
db_user = os.environ["DB_USER"] # e.g. 'my-database-user'
db_pass = os.environ["DB_PASS"] # e.g. 'my-database-password'
db_name = os.environ["DB_NAME"] # e.g. 'my-database'
ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC
connector = Connector()
def getconn() -> sqlalchemy.engine.base.Engine:
conn: sqlalchemy.engine.base.Engine = connector.connect(
instance_connection_name,
"pg8000",
user=db_user,
password=db_pass,
db=db_name,
ip_type=ip_type,
)
return conn
pool = sqlalchemy.create_engine(
"postgresql+pg8000://",
creator=getconn,
# ...
)
return pool
# Create a connection pool
engine = connect_with_connector()
# Create a sessionmaker class to create new sessions
SessionMaker = sqlalchemy.orm.sessionmaker(bind=engine)
# Create a Base class for ORM
# You may need to manually fix the following
Base = declarative_base()
db_init.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from connect_connector import engine
from data_model import Package
def create_packages():
# Create a session
session = sessionmaker(bind=engine)()
# Create 10 sample packages
for i in range(10):
package = Package(
product_id=i,
height=10.0,
width=10.0,
depth=10.0,
weight=10.0,
special_handling_instructions="No special handling instructions."
)
# Add the package to the session
session.add(package)
# Commit the changes
session.commit()
if __name__ == '__main__':
create_packages()
print('Packages created successfully.')
main.py
from flask import Flask, request, jsonify
from data_model import Package
from connect_connector import SessionMaker
app = Flask(__name__)
session_maker = SessionMaker()
@app.route("/packages/<int:product_id>", methods=["GET"])
def get_package(product_id):
"""Get information about a package."""
session = session_maker
package = session.query(Package).filter(Package.product_id == product_id).first()
if package is None:
return jsonify({"message": "Package not found."}), 404
return jsonify(
{
"height": package.height,
"width": package.width,
"depth": package.depth,
"weight": package.weight,
"special_handling_instructions": package.special_handling_instructions,
}
), 200
if __name__ == "__main__":
app.run(host="0.0.0.0")
test.py
import unittest
from data_model import Package
from connect_connector import SessionMaker
from main import app
class TestPackage(unittest.TestCase):
def setUp(self):
self.session_maker = SessionMaker()
def tearDown(self):
self.session_maker.close()
def test_get_package(self):
"""Test the `get_package()` function."""
package = Package(
product_id=11, # Ensure that the product_id different from the sample data
height=10,
width=10,
depth=10,
weight=10,
special_handling_instructions="Fragile",
)
session = self.session_maker
session.add(package)
session.commit()
response = app.test_client().get("/packages/11")
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.json,
{
"height": 10,
"width": 10,
"depth": 10,
"weight": 10,
"special_handling_instructions": "Fragile",
},
)
if __name__ == "__main__":
unittest.main()
requirements.txt
cloud-sql-python-connector==1.2.4
sqlalchemy==1.4.36
pg8000==1.22.0
Flask==3.0.0
gunicorn==20.1.0
psycopg2-binary==2.9.3
Dockerfile
FROM python:3.10-slim
WORKDIR /app
COPY . ./
RUN pip install -r requirements.txt
# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details
CMD ["python", "main.py"]