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

1. 目標

本研討會旨在為使用者和從業人員提供實作 Duet AI 教育體驗。

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

  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 或 Cloud Run 等。

將應用程式部署至 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 使用者」和「服務使用情形檢視者」這兩個 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 帳戶。Cloud Companion API 位於我們將使用的 IDE 和主控台功能中。在控制台中啟用 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 控制台

如要啟用 API,請前往 Google Cloud 控制台的「Cloud AI Companion API」頁面。

在專案選取器中,選取專案。

按一下「啟用」

頁面隨即更新,並顯示「已啟用」狀態。所選 Google Cloud 專案現已提供 Duet AI,可供所有具備必要 IAM 角色的使用者使用。

如要授予使用 Duet AI 所需的 IAM 角色,請前往「IAM」IAM頁面。

在「主體」欄中,找出要啟用 Duet AI 存取權的「使用者」,然後按一下該列中的「鉛筆」圖示 🏆?️「編輯主體」

在「編輯」存取窗格中,按一下「新增其他角色」

在「選取角色」中,選取「Cloud AI Companion 使用者」

按一下「新增其他角色」,然後選取「服務使用情形檢視者」

按一下 [儲存]

設定 IDE

開發人員可以根據自身需求,從眾多 IDE 中選擇最適合的 IDE。多個 IDE 提供 Duet AI 程式碼輔助功能,例如 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 編輯器的 UI 和使用者體驗與 VSCode 非常相似。

d6a6565f83576063.png

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

在搜尋列中輸入「duet ai」。

確保或啟用「Cloudcode › Duet AI:啟用」和「Cloudcode › Duet AI › 內嵌建議:啟用 Auto」

111b8d587330ec74.png

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

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

按一下「Cloud Code - 沒有專案」,頂端就會顯示動作下拉式選單窗格。按一下「Select a Google Cloud 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 原始碼存放區。

在 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 - Package-service 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:將此應用程式容器化

如果您在練習期間遇到任何固定式問題,最終檔案會列於本程式碼研究室的 APPENDIX 中,以供參考。

在上一個步驟中,您已建立 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 產生運送服務的 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

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

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

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

data_model.py

接下來,您將根據 OpenAPI 規格,為服務建立資料模型 Python 檔案。

開啟 package-service.yaml 檔案後,輸入下列提示。

提示 1:使用 Python SQLalchemy ORM 為這個 API 服務產生資料模型。同時提供獨立函式和建立資料庫資料表的主要進入點。

b873a6a28bd28ca1.png

接著來看看各個產生的部分Duet AI 仍是助理,雖然可以協助快速編寫程式碼,但還是建議您隨時隨地檢查及理解產生的內容。

首先,有一個名為 BaseClass (名為 Base),該類別定義了 packages 資料庫的資料模型,如下所示:Package

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 資料庫建立引擎。您必須變更 Cloud SQL 才能使用 Cloud SQL。你稍後會這麼做

在新的檔案工作流程中使用 OPEN a4c7ed6d845df343.png 程式碼,和先前一樣。將程式碼儲存在名為 data_model.py 的檔案中 (請注意名稱中的底線,而非破折號)。

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

connect-connector.py

建立 Cloud SQL 連接器。

開啟 data_model.py 檔案後,輸入下列提示。

提示 1:使用 cloud-sql-python-connector 程式庫,產生為 Postgres 的 Cloud SQL 執行個體初始化連線集區的函式。

ed05cb6ff85d34c5.png

請注意,回應未使用 cloud-sql-python-connector 程式庫。你可以調整提示,針對同一則即時通訊討論串加上具體內容,讓 Gemini 稍微提醒。

我們再輸入另一個提示吧

提示 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 方法建立引擎、工作階段製作工具類別和 Base ORM

6e4214b72ab13a63.png

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

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

您也可以嘗試各種提示,查看 Duet AI 回覆可能變化版本的版本。

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

正在更新 data_model.py

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

清除 Duet AI 對話記錄。開啟 data_model.py 檔案。請嘗試下列提示。

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

2e768c9b6c523b9a.png

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

更新 data_model.py 檔案。

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

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

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

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 對話記錄,請按一下 Duet AI 側欄頂端的垃圾桶圖示 f574ca2c1e114856.png

在 Cloud SQL 中建立套件資料表

設定 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.

連線至 Cloud SQL 執行個體,並檢查資料庫已建立。

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 即可退出 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 的檔案。

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

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

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

建立範例套件資料

從指令列執行 db_init.py

python db_init.py

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

Packages created successfully.

再次連線至 CloudSQL 執行個體,並確認範例資料已新增至套件資料表中。

連線至 Cloud SQL 執行個體,並檢查資料庫已建立。

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 即可退出 Cloud SQL。

main.py

開啟 data_model.pypackage-service.yamlconnect_connector.py 檔案,為應用程式建立 main.py

提示 1:使用 Python flask 程式庫 - 為這項服務建立使用 HTTP 靜態端點的實作

提示 2:使用 Python flask 程式庫 - 為這項服務建立使用 http 靜態端點的實作。如要匯入套件資料,請使用 Connect_conector.py 的 SessionMaker。

提示 3:使用 Python flask 程式庫 - 為這項服務建立使用 http 靜態端點的實作。匯入並使用從 data_model.py 和 SessionMaker 從 connect_conector.py 到套件資料。

提示 4:使用 Python flask 程式庫 - 為這項服務建立使用 http 靜態端點的實作。匯入並使用從 data_model.py 和 SessionMaker 從 connect_conector.py 到套件資料。將主機 IP 0.0.0.0 用於 app.run

6d794fc52a90e6ae.png

更新 main.py 的相關規定。

提示:建立 main.py 的要求檔案

1cc0b318d2d4ca2f.png

將這個檔案附加至 requirements.txt 個檔案。請務必使用 Flask 3.0.0 版。

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

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

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

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。您可以手動新增。

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

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

執行單元測試

執行單元測試。

python test.py

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

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

OK

按一下頂端狀態列的垃圾桶圖示 1ecccfe10d6c540.png,關閉 Cloud Shell 編輯器中的所有檔案並清除即時通訊記錄。

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 的檔案中。

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

在本機執行應用程式

開啟 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

將 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 用戶端 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 圖示。新的「Log Explorer」視窗會隨即開啟。

e86d1c265e176bc4.png

按一下其中一個 Traceback 錯誤項目,然後點選「Explain this Log Entry」(說明這個記錄項目)

d6af045cf03008bc.png

您可以閱讀錯誤的說明。

接下來,我們要讓 Duet AI 協助排解錯誤。

請嘗試下列提示。

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

9288dd6045369167.png

在提示中輸入錯誤訊息。

提示 2:禁止:通過驗證的 IAM 主體似乎無權提出 API 要求。驗證「Cloud SQL Admin API」已在您的 GCP 專案和「Cloud SQL 用戶端」中啟用已授予 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 LoggingLog Explorer記錄說明功能中使用 Duet AI,以便排解問題。

8. 結語

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

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

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