面向开发者的 Duet AI 技术实操研讨会指南 Codelab

1. 目标

本研讨会的目的是为用户和从业者提供 Duet AI 实践培训。

在此 Codelab 中,您将学习以下内容:

  1. 在 GCP 项目中激活 Duet AI,并将其配置为可在 IDE 和 Cloud 控制台中使用。
  2. 使用 Duet AI 进行代码生成、补全和说明。
  3. 使用 Duet AI 说明和排查应用问题。
  4. Duet AI 功能,例如 IDE 聊天和多轮对话、聊天与内嵌代码生成、智能操作(例如代码说明和朗读确认)等。

推介材料

为了展示如何在日常开发中真实地使用面向开发者的 Duet AI,本研讨会的活动将在叙事背景下进行。

一位新开发者加入了一家电子商务公司。他们的任务是向现有的电子商务应用(由多项服务组成)添加一项新服务。这项新服务可提供商品清单中产品的其他信息(尺寸、重量等)。此服务将根据商品尺寸和重量提供更优惠的运费。

由于开发者是新加入公司的,因此他们将使用 Duet AI 进行代码生成、解释和文档编制。

服务编码完成后,平台管理员将使用 Duet AI(聊天)来帮助创建工件(Docker 容器)以及将工件部署到 GCP 所需的资源(例如 Artifact Registry、IAM 权限、代码库、计算基础设施 [GKE 或 CloudRun 等])。

将应用部署到 GCP 后,应用运维人员/SRE 将使用 Duet AI(和 Cloud Ops)来帮助排查新服务中的错误。

角色

本研讨会涵盖以下买家角色:

  1. 应用开发者 - 需要具备一定的编程和软件开发知识。

此 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_IDUSER 并启用 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 CodeJetBrains IDE(IntelliJ、PyCharm、GoLand、WebStorm 等)、Cloud WorkstationsCloud Shell 编辑器

在本实验中,您可以使用 Cloud Workstations 或 Cloud Shell 编辑器。

本讲座使用 Cloud Shell 编辑器。

请注意,Cloud Workstations 可能需要 20-30 分钟才能完成设置。

如需立即使用,请使用 Cloud Shell 编辑器

点击 Cloud Shell 顶部菜单栏中的铅笔图标 ✏️,打开 Cloud Shell 编辑器。

Cloud Shell Editor 的界面和用户体验与 VSCode 非常相似。

d6a6565f83576063.png

CTRL(在 Windows 中)/CMD(在 Mac 中)+ ,(逗号)进入“设置”窗格。

在搜索栏中,输入“duet ai”。

确保或启用 Cloudcode › Duet AI: EnableCloudcode › Duet AI › Inline Suggestions: Enable Auto

111b8d587330ec74.png

在底部状态栏中,点击 Cloud Code - Sign In(Cloud Code - 登录),然后按照登录工作流操作。

如果您已登录,状态栏会显示 Cloud Code - No project(Cloud Code - 无项目)。

点击“Cloud Code - 无项目”,顶部会显示一个操作下拉窗格。点击选择 Google Cloud 项目

3241a59811e3c84a.png

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

c5358fc837588fe.png

从项目列表中选择您的 PROJECT_ID。

底部状态栏会更新以显示您的项目 ID。如果未显示,您可能需要刷新 Cloud Shell 编辑器标签页。

点击左侧菜单栏中的 Duet AI 图标 d97fc4e7b594c3af.png,系统随即会显示 Duet AI 聊天窗口。如果您收到一条内容为“选择 GCP 项目”的消息,点击并重新选择项目。

您现在会看到 Duet AI 聊天窗口

781f888360229ca6.png

3. 设置基础架构

d3234d237f00fdbb.png

为了在 GCP 中运行新的配送服务,您需要以下 GCP 资源:

  1. 一个包含数据库的 Cloud SQL 实例。
  2. 用于运行容器化服务的 GKE 集群。
  3. 用于存储 Docker 映像的 Artifact Registry。
  4. 用于存放代码的 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 服务

9745ba5c70782e76.png

我们将创建的服务最终将包含以下文件。您无需立即创建这些文件,只需按照以下说明逐一创建即可:

  1. package-service.yaml - 包含高度、宽度、重量和特殊处理说明等数据的包裹服务的开放 API 规范。
  2. data_model.py - 软件包服务 API 规范的数据模型。还会在 product_details 数据库中创建 packages 表。
  3. connect_connector.py - CloudSQL 连接(定义引擎、会话和基本 ORM)
  4. db_init.py - 将示例数据生成到 packages 表中。
  5. main.py - 一个 Python Flask 服务,具有一个 GET 端点,用于根据 product_id 从 packages 数据中检索软件包详细信息。
  6. test.py - 单元测试
  7. requirement.txt - Python 要求
  8. Dockerfile - 将此应用容器化

如果您在练习过程中遇到任何棘手的问题,请参阅本 Codelab 的附录,其中包含所有最终文件,可供您参考。

在上一步中,您创建了一个 Cloud Source Repositories 代码库。克隆代码库。您将在克隆的代码库文件夹中构建应用文件。

在 Cloud Shell 终端中,运行以下命令以克隆代码库。

cd ~
gcloud source repos clone shipping shipping
cd ~/shipping 

通过 Cloud Shell 编辑器的左侧菜单打开 Duet AI 聊天侧边栏。该图标类似于 8b135a000b259175.png。您现在可以使用 Duet AI 进行代码辅助。

package-service.yaml

在未打开任何文件的情况下,让 Duet 为配送服务生成 OpenAPI 规范。

提示 1:生成一个 OpenAPI YAML 规范,用于提供给定数值产品 ID 的配送和包裹信息。服务应包含有关包裹高度、宽度、深度、重量以及任何特殊处理说明的信息。

ba12626f491a1204.png

生成的代码窗口右上角列出了三个选项。

您可以COPY 71194556d8061dae.png代码并将其粘贴到文件中。

您可以将代码 ADD df645de8c65607a.png 到编辑器中当前打开的文件。

或者,您也可以在新的文件中OPEN a4c7ed6d845df343.png 代码。

点击 OPEN a4c7ed6d845df343.png 新文件中的代码。

点击 CTRL/CMD + s 保存文件,并将文件存储在应用文件夹中,文件名为 package-service.yaml。点击“确定”。

f6ebd5b836949366.png

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

您还可以尝试各种提示,看看 Duet AI 的回答。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 Duet AI 聊天记录。

data_model.py

接下来,您将基于 OpenAPI 规范为服务创建数据模型 Python 文件。

打开 package-service.yaml 文件后,输入以下提示。

提示 1:使用 Python SQLAlchemy ORM,为此 API 服务生成数据模型。还包括一个单独的函数和一个用于创建数据库表的主入口点。

b873a6a28bd28ca1.png

我们来看看生成的每个部分。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 a4c7ed6d845df343.png 在新文件中编写代码的工作流程与之前相同。将代码保存在名为 data_model.py 的文件中(请注意名称中的下划线,而不是短划线)。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 Duet AI 聊天记录。

connect-connector.py

创建 CloudSQL 连接器。

打开 data_model.py 文件后,输入以下提示。

提示 1:使用 cloud-sql-python-connector 库,生成一个用于初始化 Cloud SQL Postgres 实例的连接池的函数。

ed05cb6ff85d34c5.png

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

我们来使用另一个提示。

提示 2:必须使用 cloud-sql-python-connector 库。

d09095b44dde35bf.png

确保它使用 cloud-sql-python-connector 库。

使用 OPEN a4c7ed6d845df343.png 在新文件中编写代码的工作流程与之前相同。将代码保存在名为 connect_conector.py 的文件中。您可能需要手动导入 pg8000 库,请参阅以下文件。

清除 Duet AI 聊天记录,然后在 connect_connector.py 文件处于打开状态时,生成要在应用中使用的 DB enginesessionmakerbase ORM。

提示 1:使用 connect_with_connector 方法创建引擎、sessionmaker 类和 Base ORM

6e4214b72ab13a63.png

响应可能会将 engineSessionBase 附加到 connect_connector.py 文件中。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

您还可以尝试各种提示,看看 Duet AI 的回答可能会有哪些变化。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 Duet AI 聊天记录。

更新了 data_model.py

您需要使用在上一步中创建的引擎(位于 connect_connector.py 文件中),才能在 CloudSQL 数据库中创建表。

清除 Duet AI 聊天记录。打开 data_model.py 文件。尝试使用以下提示。

提示 1:在主函数中,导入并使用 connect_connector.py 中的引擎

2e768c9b6c523b9a.png

您应该会看到从 connect_connector(针对 CloudSQL)导入 engine 的响应。create_table 使用该引擎(而不是默认的 sqlite 本地数据库)。

更新 data_model.py 文件。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

您还可以尝试各种提示,看看 Duet AI 的各种回答。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 Duet AI 聊天记录。

requirements.txt

为应用创建 requirements.txt 文件。

同时打开 connect_connector.pydata_model.py 文件,然后输入以下提示。

提示 1:为此数据模型和服务生成 pip requirements 文件

提示 2:使用最新版本为此数据模型和服务生成 pip 要求文件

69fae373bc5c6a18.png

验证名称和版本是否正确。例如,在上面的响应中,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 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 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 个示例软件包行并将它们提交到软件包表

34a9afc5f04ba5.png

使用 OPEN a4c7ed6d845df343.png 在新文件中编写代码的工作流程与之前相同。将代码保存在名为 db_init.py 的文件中。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

您还可以尝试各种提示,看看 Duet AI 的各种回答。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 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.pypackage-service.yamlconnect_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

6d794fc52a90e6ae.png

更新了 main.py 的要求。

提示:为 main.py 创建 requirements 文件

1cc0b318d2d4ca2f.png

将此内容附加到 requirements.txt 文件。请务必使用 Flask 版本 3.0.0。

使用 OPEN a4c7ed6d845df343.png 在新的文件工作流程中编写代码,与之前一样。将代码保存在名为 main.py 的文件中。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 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:生成单元测试。

e861e5b63e1b2657.png

使用 OPEN a4c7ed6d845df343.png 在新文件中编写代码的工作流程与之前相同。将代码保存在名为 test.py 的文件中。

test_get_package 函数中,必须定义 product_id。您可以手动添加。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

点击 Duet AI 边栏顶部的回收站图标 f574ca2c1e114856.png,重置 Duet AI 聊天记录。

运行单元测试

运行单元测试。

python test.py

输出类似于以下内容:

.
----------------------------------------------------------------------
Ran 1 test in 1.061s

OK

关闭 Cloud Shell Editor 中的所有文件,然后点击顶部状态栏中的回收站图标 1ecccfe10d6c540.png 清除聊天记录。

Dockerfile

为此应用创建 Dockerfile

打开 main.py 并尝试使用以下提示。

提示 1:为此应用生成 Dockerfile。

提示 2:为此应用生成 Dockerfile。将所有文件复制到容器。

9c473caea437a5c3.png

您还需要为 INSTANCE_CONNECTION_NAMEDB_USERDB_PASSDB_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 a4c7ed6d845df343.png 在新文件中编写代码的工作流程与之前相同。将代码保存在名为 Dockerfile 的文件中。

最终文件位于本 Codelab 的“附录”部分。如果未自动更改,请手动进行相应更改。

在本地运行应用

打开 Dockerfile 后,尝试使用以下提示。

提示 1:如何使用此 Dockerfile 在本地运行容器

570fd5c296ca8c83.png

按照说明操作。

# 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 > 工作负载来检查日志

d225b1916c829167.png

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

1d0459141483d6a7.png

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

e86d1c265e176bc4.png

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

d6af045cf03008bc.png

您可以阅读错误说明。

接下来,我们让 Duet AI 帮助排查错误。

尝试使用以下提示。

提示 1:帮我排查此错误

9288dd6045369167.png

在提示中输入错误消息。

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

f1e64fbdc435d31c.png

然后。

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

bb8926b995a8875c.png

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 中,您学习了以下内容:

  1. 在 GCP 项目中激活 Duet AI,并将其配置为可在 IDE 和 Cloud 控制台中使用。
  2. 使用 Duet AI 进行代码生成、补全和说明。
  3. 使用 Duet AI 说明和排查应用问题。
  4. 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"]