面向开发者的 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 Viewer 权限来快速检查该界面(这样 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 页面。

在项目选择器中,选择一个项目。

点击启用

该页面会更新并显示已启用状态。现在,在所选 Google Cloud 项目中,拥有所需 IAM 角色的所有用户均可使用 Duet AI。

如需授予使用 Duet AI 所需的 IAM 角色,请前往 IAM 页面。

主账号列中,找到要为其启用 Duet AI 访问权限的用户,然后点击该行中的铅笔图标 ☰️ 修改主账号

修改访问权限窗格中,点击添加其他角色

在“选择角色”中,选择 Cloud AI Companion User

点击添加其他角色,然后选择 Service Usage Viewer

点击保存

设置 IDE

开发者可以从各种 IDE 中选择最适合其需求的 IDE。Duet AI 代码助理可在多个 IDE 中使用,例如 Visual Studio CodeJetBrains IDE(IntelliJ、PyCharm、GoLand、WebStorm 等)、Cloud WorkstationsCloud Shell Editor

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

此研讨会使用 Cloud Shell Editor。

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

如需立即使用,请使用 Cloud Shell Editor

点击 Cloud Shell 顶部菜单栏中的铅笔图标 VPAID️ 打开 Cloud Shell Editor。

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

d6a6565f83576063.png

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

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

确保或启用 Cloudcode › Duet AI:启用Cloudcode › Duet AI › 内嵌建议:启用自动功能

111b8d587330ec74

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

如果您已登录,状态栏会显示 Cloud Code - No project

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

3241a59811e3c84a

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

c5358fc837588fe.png

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

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

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

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

781f888360229ca6

3. 设置基础架构

d3234d237f00fdbb.png

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

  1. 具有数据库的 Cloud SQL 实例。
  2. 用于运行容器化服务的 GKE 集群。
  3. 用于存储 Docker 映像的 Artifact Registry。
  4. 代码的 Cloud Source Repositories 代码库。

在 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

我们要创建的服务最终将包含以下文件。您现在不需要创建这些文件,可以按照以下说明一次创建一个:

  1. package-service.yaml - 软件包服务的 Open API 规范,包含高度、宽度、重量和特殊处理指令等数据。
  2. data_model.py - 软件包服务 API 规范的数据模型。还会在 product_details 数据库中创建 packages 表。
  3. connect_connector.py - CloudSQL 连接(定义引擎、会话和基本 ORM)
  4. db_init.py - 将示例数据生成到 packages 表中。
  5. main.py - 一种具有 GET 端点的 Python Flask 服务,用于根据 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。您现在可以使用 Duet AI 获得代码协助。

package-service.yaml

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

提示 1:为在给定数字商品 ID 的情况下提供配送和包裹信息的服务生成 OpenAPI yaml 规范。服务应包含包裹高度、宽度、深度、重量等信息,以及任何特殊处理说明。

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 库生成一个函数,用于初始化 Postgres 的 Cloud SQL 实例连接池。

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

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

最终文件位于此 Codelab 的附录部分。如果没有,请手动进行适当的更改。

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

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

更新 data_model.py

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

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

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

2e768c9b6c523b9a

您应该会看到从 connect_connector 导入 engine 的响应(适用于 CloudSQL)。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 需求文件

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

69fae373bc5c6a18

验证名称和版本是否正确。例如,在上面的响应中,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

输入密码(以及进化方式)后,获取表。

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

像之前一样,使用 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

输入密码(同时进化)后,获取软件包表格中的所有数据。

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_conector.py 导入 SessionMaker 并使用 SessionMaker 以获取软件包数据。

提示 3:使用 python flask 库 - 创建一个使用该服务的 http REST 端点的实现。从 data_model.py 和 SessionMaker 中导入并使用来自 connect_conector.py 到软件包数据的软件包。

提示 4:使用 python flask 库 - 创建一个使用该服务的 http REST 端点的实现。从 data_model.py 和 SessionMaker 中,从 connect_conector.py 导入并使用 Package 以获取软件包数据。对 app.run 使用主机 IP 0.0.0.0

6d794fc52a90e6ae

更新 main.py 的要求。

提示:为 main.py 创建需求文件

1cc0b318d2d4ca2f

将它附加到 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

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

Dockerfile

为此应用创建 Dockerfile

打开 main.py,然后尝试按照以下提示操作。

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

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

9c473caea437a5c3

您还需要为 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

按照说明操作。

# 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

为 Kubernetes 默认服务账号添加 Google 服务账号注释。

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 运行并为 Service 分配外部负载平衡器 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

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

e86d1c265e176bc4.png

点击其中一个 Traceback 错误条目,然后点击 Explain this Log Entry

d6af045cf03008bc.png

您可以阅读错误说明。

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

请尝试以下提示。

提示 1:帮我排查此错误

9288dd6045369167

在提示中输入错误消息。

提示 2:禁止:已通过身份验证的 IAM 主账号似乎无权发出 API 请求。验证“Cloud SQL Admin API”已在 GCP 项目和“Cloud SQL Client”中启用已授予 IAM 主账号)

f1e64fbdc435d31c.png

然后,

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

bb8926b995a8875c.png

将 Cloud SQL Client 角色分配给 cloudsqlsa

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 LoggingLog ExplorerLog Explainer 功能中使用 Duet AI 来排查此问题。

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"]