1. 目標
本研討會旨在為使用者和實務工作者提供 Duet AI 實作教育訓練。
在本程式碼研究室中,您將學到以下內容:
- 在 GCP 專案中啟用 Duet AI,並設定在 IDE 和 Cloud Console 中使用。
- 使用 Duet AI 生成、完成及說明程式碼。
- 使用 Duet AI 解釋及排解應用程式問題。
- Duet AI 功能,例如 IDE 即時通訊和多輪對話、即時通訊與內嵌程式碼生成、程式碼說明和背誦確認等智慧動作。
說明
為展現 Duet AI for Developers 如何在日常開發工作中發揮實用價值,本研討會的活動將以敘事形式進行。
電子商務公司來了一位新開發人員。他們的工作是為現有的電子商務應用程式 (由多項服務組成) 新增服務。這項新服務會提供產品目錄中產品的額外資訊 (尺寸、重量等)。這項服務會根據產品尺寸和重量,提供更划算的運費。
由於開發人員是新進員工,因此會使用 Duet AI 生成、說明及記錄程式碼。
服務編碼完成後,平台管理員會使用 Duet AI (即時通訊) 協助建立構件 (Docker 容器),以及將構件部署至 GCP 所需的資源 (例如 Artifact Registry、IAM 權限、程式碼存放區、運算基礎架構,即 GKE 或 Cloud Run 等)。
應用程式部署至 GCP 後,應用程式運算子/SRE 會使用 Duet AI (和 Cloud Ops) 協助排解新服務中的錯誤。
角色
研討會涵蓋以下目標對象:
- 應用程式開發人員 - 必須具備程式設計和軟體開發知識。
這項 Duet AI 工作坊僅適用於開發人員。您不需要瞭解 GCP 雲端資源,如要瞭解如何建構執行這個應用程式所需的 GCP 資源,請參閱這篇文章。您可以按照本指南中的操作說明,部署必要的 GCP 資源。
2. 準備環境
啟用 Duet AI
您可以透過 API (gcloud 或 Terraform 等 IaC 工具) 或 Cloud Console UI,在 GCP 專案中啟用 Duet AI。
如要在 Google Cloud 專案中啟用 Duet AI,請啟用 Cloud AI Companion API,並授予使用者「Cloud AI Companion 使用者」和「服務使用情形檢視者」這兩個 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.
將「Cloud AI Companion 使用者」和「服務使用情形檢視者」這兩個 Identity and Access Management (IAM) 角色授予 USER 帳戶。IDE 和控制台中的功能都會使用 Cloud Companion API。啟用控制台中的 UI 之前,系統會先快速檢查服務使用情況檢視者權限,確保 Duet UI 只會顯示在已啟用 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 Console
如要啟用 API,請前往 Google Cloud 控制台的 Cloud AI Companion API 頁面。
在專案選擇器中選取專案。
按一下「啟用」。
頁面會更新並顯示「已啟用」狀態。完成後,只要使用者具備必要的 IAM 角色,就能在選取的 Google Cloud 專案使用 Duet AI。
如要授予使用 Duet AI 時所需的 IAM 角色,請前往「身分與存取權管理」頁面。
在「主體」欄中,找出要啟用 Duet AI 存取權的「使用者」,然後點選該列中的鉛筆圖示 ✏️「編輯主體」。
在「編輯」存取權窗格中,按一下「新增其他角色」。
在「選取角色」中,選取「Cloud AI Companion 使用者」。
按一下「新增其他角色」,然後選取「服務用量檢視者」。
按一下 [儲存]。
設定 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 編輯器的使用者介面和使用者體驗與 VSCode 非常相似。

按一下 CTRL (Windows)/CMD (Mac) + , (逗號),進入「設定」窗格。
在搜尋列中輸入「Duet AI」。
確認或啟用「Cloudcode」>「Duet AI: Enable」和「Cloudcode」>「Duet AI」>「Inline Suggestions: Enable Auto」

點按底部狀態列的「Cloud Code - Sign In」,然後按照登入工作流程操作。
如果已登入,狀態列會顯示「Cloud Code - No project」。
按一下「Cloud Code - No project」,頂端會顯示動作下拉式窗格。按一下「選取 Google Cloud 專案」。

開始輸入專案 ID,專案就會顯示在清單中。

從專案清單中選取 PROJECT_ID。
底部的狀態列會更新,顯示專案 ID。如果沒有,可能需要重新整理 Cloud Shell 編輯器分頁。
按一下左側選單列中的 Duet AI 圖示
,系統就會顯示 Duet AI 即時通訊視窗。如果系統顯示「Select GCP Project」(選取 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- 適用於包裹服務的 Open API 規格,包含高度、寬度、重量和特殊處理指示等資料。data_model.py- 封裝服務 API 規格的資料模型。也會在 product_details 資料庫中建立packages資料表。connect_connector.py- CloudSQL 連線 (定義引擎、工作階段和 Base ORM)db_init.py- 將樣本資料生成至packages資料表。main.py- Python Flask 服務,具有GET端點,可根據 product_id 從packages資料擷取套件詳細資料。test.py- 單元測試requirement.txt- Python 需求Dockerfile- 將這個應用程式容器化
如果在練習過程中遇到任何難題,請參閱本程式碼研究室「附錄」中的最終檔案。
在上一個步驟中,您建立了 Cloud Source Repository。複製存放區。您會在複製的存放區資料夾中建構應用程式檔案。
在 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。按一下 [確定]。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
你也可以嘗試各種提示,看看 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 函式的主函式,才能在 Cloud SQL 資料庫中實際建構資料表,如下所示:
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 程式庫,產生可初始化 Postgres 適用的 Cloud SQL 執行個體連線集區的函式。

請注意,回應並未使用 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 檔案。
最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
您也可以嘗試各種提示,看看 Duet AI 回覆的潛在變化。
按一下 Duet AI 側欄頂端的垃圾桶圖示
,即可重設 Duet AI 對話記錄。
更新 data_model.py
您必須使用上一個步驟中建立的引擎 (位於 connect_connector.py 檔案中),才能在 Cloud SQL 資料庫中建立資料表。
清除 Duet AI 即時通訊記錄。開啟 data_model.py 檔案,請嘗試使用下列提示。
提示 1:在主要函式中,從 connect_connector.py 匯入並使用引擎

您應該會看到從 connect_connector (適用於 CloudSQL) 匯入 engine 的回應。create_table 會使用該引擎 (而非預設的 sqlite 本機資料庫)。
更新 data_model.py 檔案。
最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
你也可以嘗試各種提示,看看 Duet AI 的回覆。
按一下 Duet AI 側欄頂端的垃圾桶圖示
,即可重設 Duet AI 對話記錄。
requirements.txt
為應用程式建立 requirements.txt 檔案。
開啟 connect_connector.py 和 data_model.py 檔案,然後輸入下列提示。
提示 1:為這個資料模型和服務產生 pip 需求檔案
提示 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 中建立套件資料表
設定 Cloud SQL 資料庫連接器的環境變數。
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.
連線至 Cloud SQL 執行個體,並檢查資料庫是否已建立。
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 即可退出 Cloud SQL。
db_init.py
接著,我們要在 packages 資料表中新增一些範例資料。
清除 Duet AI 即時通訊記錄。開啟 data_model.py 檔案後,請嘗試使用下列提示。
提示 1:產生函式,建立 10 個範例套件資料列,並將其提交至套件資料表
提示 2:使用 connect_connector 中的工作階段,產生可建立 10 個範例套件資料列並將其提交至套件資料表的函式

使用 OPEN
,在新的檔案工作流程中編寫程式碼,就像之前一樣。將程式碼儲存至名為 db_init.py 的檔案。
最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
你也可以嘗試各種提示,看看 Duet AI 的回覆。
按一下 Duet AI 側欄頂端的垃圾桶圖示
,即可重設 Duet AI 對話記錄。
建立範例套件資料
從指令列執行 db_init.py。
python db_init.py
輸出結果會與下列內容相似:
Packages created successfully.
再次連線至 Cloud SQL 執行個體,並確認範例資料已新增至 packages 資料表。
連線至 Cloud SQL 執行個體,並檢查資料庫是否已建立。
gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details
輸入密碼 (也是 evolution) 後,即可從 packages 資料表取得所有資料。
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 即可退出 Cloud SQL。
main.py
開啟 data_model.py、package-service.yaml 和 connect_connector.py 檔案,然後為應用程式建立 main.py。
提示 1:使用 Python Flask 程式庫 - 建立使用此服務的 HTTP REST 端點的實作項目
提示 2:使用 python flask 程式庫 - 建立此服務的實作項目,使用 http REST 端點。從 connect_conector.py 匯入並使用 SessionMaker,取得套件資料。
提示 3:使用 Python Flask 程式庫,為這項服務建立使用 HTTP REST 端點的實作項目。從 data_model.py 匯入並使用 Package,以及從 connect_conector.py 匯入並使用 SessionMaker,以取得套件資料。
提示 4:使用 Python Flask 程式庫,為這項服務建立使用 HTTP REST 端點的實作項目。從 data_model.py 匯入並使用 Package,以及從 connect_conector.py 匯入並使用 SessionMaker,以取得套件資料。使用主機 IP 0.0.0.0 執行應用程式

更新「main.py」的需求條件。
提示:為 main.py 建立 requirements 檔案

將此內容附加至 requirements.txt 檔案。請務必使用 Flask 3.0.0 版。
使用 OPEN
,在新的檔案工作流程中編寫程式碼,就像之前一樣。將程式碼儲存至名為 main.py 的檔案。
最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
按一下 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。你可以手動新增。
最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
按一下 Duet AI 側欄頂端的垃圾桶圖示
,即可重設 Duet AI 對話記錄。
執行單元測試
執行單元測試。
python test.py
輸出結果會與下列內容相似:
. ---------------------------------------------------------------------- Ran 1 test in 1.061s OK
關閉 Cloud Shell 編輯器中的所有檔案,然後點選頂端狀態列中的垃圾桶圖示
,清除對話記錄。
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 的檔案。
最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。
在本機執行應用程式
開啟 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 執行,並為 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 服務帳戶移除 Cloud SQL 用戶端 IAM 角色。這會導致連線至 Cloud SQL 資料庫時發生錯誤。
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 部署作業,然後點選「記錄」分頁標籤。

按一下狀態列右側的「在記錄檔探索器中查看」
圖示。系統會開啟新的「記錄檔探索工具」視窗。

按一下其中一個 Traceback 錯誤項目,然後點選「說明這個記錄項目」。

您可以閱讀錯誤說明。
接著,請 Duet AI 協助排解錯誤。
請嘗試使用下列提示。
提示 1:協助我排解這項錯誤

在提示中輸入錯誤訊息。
提示 2:Forbidden:經過驗證的 IAM 主體似乎未獲授權,無法發出 API 要求。確認已在 GCP 專案中啟用「Cloud SQL Admin API」,且已將「Cloud SQL Client」角色授予 IAM 主體

然後。
提示 3:如何使用 gcloud 將 Cloud SQL 用戶端角色指派給 Google 服務帳戶?

將 Cloud SQL 用戶端角色指派給 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 Logging、記錄檔探索工具和記錄檔說明工具中使用 Duet AI,排解問題。
8. 結語
恭喜!您已成功完成本程式碼研究室。
在本程式碼研究室中,您已瞭解以下內容:
- 在 GCP 專案中啟用 Duet AI,並設定在 IDE 和 Cloud Console 中使用。
- 使用 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"]