開發人員程式碼研究室適用的 Duet AI 技術實作研討會指南

1. 目標

本研討會旨在為使用者和實務工作者提供 Duet AI 實作教育訓練。

在本程式碼研究室中,您將學到以下內容:

  1. 在 GCP 專案中啟用 Duet AI,並設定在 IDE 和 Cloud Console 中使用。
  2. 使用 Duet AI 生成、完成及說明程式碼。
  3. 使用 Duet AI 解釋及排解應用程式問題。
  4. 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) 協助排解新服務中的錯誤。

角色

研討會涵蓋以下目標對象:

  1. 應用程式開發人員 - 必須具備程式設計和軟體開發知識。

這項 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_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.

將「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 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 編輯器的使用者介面和使用者體驗與 VSCode 非常相似。

d6a6565f83576063.png

按一下 CTRL (Windows)/CMD (Mac) + , (逗號),進入「設定」窗格。

在搜尋列中輸入「Duet AI」。

確認或啟用「Cloudcode」>「Duet AI: Enable」和「Cloudcode」>「Duet AI」>「Inline Suggestions: Enable Auto」

111b8d587330ec74.png

點按底部狀態列的「Cloud Code - Sign In」,然後按照登入工作流程操作。

如果已登入,狀態列會顯示「Cloud Code - No project」

按一下「Cloud Code - No project」,頂端會顯示動作下拉式窗格。按一下「選取 Google Cloud 專案」

3241a59811e3c84a.png

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

c5358fc837588fe.png

從專案清單中選取 PROJECT_ID。

底部的狀態列會更新,顯示專案 ID。如果沒有,可能需要重新整理 Cloud Shell 編輯器分頁。

按一下左側選單列中的 Duet AI 圖示 d97fc4e7b594c3af.png,系統就會顯示 Duet AI 即時通訊視窗。如果系統顯示「Select GCP Project」(選取 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 - 適用於包裹服務的 Open API 規格,包含高度、寬度、重量和特殊處理指示等資料。
  2. data_model.py - 封裝服務 API 規格的資料模型。也會在 product_details 資料庫中建立 packages 資料表。
  3. connect_connector.py - CloudSQL 連線 (定義引擎、工作階段和 Base ORM)
  4. db_init.py - 將樣本資料生成至 packages 資料表。
  5. main.py - Python Flask 服務,具有 GET 端點,可根據 product_id 從 packages 資料擷取套件詳細資料。
  6. test.py - 單元測試
  7. requirement.txt - Python 需求
  8. Dockerfile - 將這個應用程式容器化

如果在練習過程中遇到任何難題,請參閱本程式碼研究室「附錄」中的最終檔案。

在上一個步驟中,您建立了 Cloud Source Repository。複製存放區。您會在複製的存放區資料夾中建構應用程式檔案。

在 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

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

你也可以嘗試各種提示,看看 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 函式的主函式,才能在 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 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.png

回應可能會將 engineSessionBase 附加至 connect_connector.py 檔案。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

您也可以嘗試各種提示,看看 Duet AI 回覆的潛在變化。

按一下 Duet AI 側欄頂端的垃圾桶圖示 f574ca2c1e114856.png,即可重設 Duet AI 對話記錄。

更新 data_model.py

您必須使用上一個步驟中建立的引擎 (位於 connect_connector.py 檔案中),才能在 Cloud SQL 資料庫中建立資料表。

清除 Duet AI 即時通訊記錄。開啟 data_model.py 檔案,請嘗試使用下列提示。

提示 1:在主要函式中,從 connect_connector.py 匯入並使用引擎

2e768c9b6c523b9a.png

您應該會看到從 connect_connector (適用於 CloudSQL) 匯入 engine 的回應。create_table 會使用該引擎 (而非預設的 sqlite 本機資料庫)。

更新 data_model.py 檔案。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

你也可以嘗試各種提示,看看 Duet AI 的回覆。

按一下 Duet AI 側欄頂端的垃圾桶圖示 f574ca2c1e114856.png,即可重設 Duet AI 對話記錄。

requirements.txt

為應用程式建立 requirements.txt 檔案。

開啟 connect_connector.pydata_model.py 檔案,然後輸入下列提示。

提示 1:為這個資料模型和服務產生 pip 需求檔案

提示 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 中建立套件資料表

設定 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 個範例套件資料列並將其提交至套件資料表的函式

34a9afc5f04ba5.png

使用 OPEN a4c7ed6d845df343.png,在新的檔案工作流程中編寫程式碼,就像之前一樣。將程式碼儲存至名為 db_init.py 的檔案。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

你也可以嘗試各種提示,看看 Duet AI 的回覆。

按一下 Duet AI 側欄頂端的垃圾桶圖示 f574ca2c1e114856.png,即可重設 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.pypackage-service.yamlconnect_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 執行應用程式

6d794fc52a90e6ae.png

更新「main.py」的需求條件。

提示:為 main.py 建立 requirements 檔案

1cc0b318d2d4ca2f.png

將此內容附加至 requirements.txt 檔案。請務必使用 Flask 3.0.0 版。

使用 OPEN a4c7ed6d845df343.png ,在新的檔案工作流程中編寫程式碼,就像之前一樣。將程式碼儲存至名為 main.py 的檔案。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

按一下 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。你可以手動新增。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

按一下 Duet AI 側欄頂端的垃圾桶圖示 f574ca2c1e114856.png,即可重設 Duet AI 對話記錄。

執行單元測試

執行單元測試。

python test.py

輸出結果會與下列內容相似:

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

OK

關閉 Cloud Shell 編輯器中的所有檔案,然後點選頂端狀態列中的垃圾桶圖示 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 的檔案。

最終檔案位於本程式碼研究室的附錄部分。如果沒有,請手動進行適當變更。

在本機執行應用程式

開啟 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 執行,並為 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」>「工作負載」,檢查記錄。

d225b1916c829167.png

按一下 shipping 部署作業,然後點選「記錄」分頁標籤。

1d0459141483d6a7.png

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

e86d1c265e176bc4.png

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

d6af045cf03008bc.png

您可以閱讀錯誤說明。

接著,請 Duet AI 協助排解錯誤。

請嘗試使用下列提示。

提示 1:協助我排解這項錯誤

9288dd6045369167.png

在提示中輸入錯誤訊息。

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

f1e64fbdc435d31c.png

然後。

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

bb8926b995a8875c.png

將 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. 結語

恭喜!您已成功完成本程式碼研究室。

在本程式碼研究室中,您已瞭解以下內容:

  1. 在 GCP 專案中啟用 Duet AI,並設定在 IDE 和 Cloud Console 中使用。
  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"]