1. 목표
이 워크숍의 목적은 사용자 및 실무자에게 실습형 Duet AI 교육을 제공하는 것입니다.
이 Codelab에서는 다음 내용을 알아봅니다.
- GCP 프로젝트에서 Duet AI를 활성화하고 IDE 및 Cloud 콘솔에서 사용할 수 있도록 구성합니다.
- 코드 생성, 완성, 설명을 위해 Duet AI를 사용합니다.
- Duet AI를 사용하여 애플리케이션 문제를 설명하고 해결합니다.
- IDE 채팅 및 멀티턴 채팅, 채팅과 인라인 코드 생성 비교, 코드 설명 및 인용 확인과 같은 스마트 작업 등 Duet AI 기능
내러티브
개발자를 위한 Duet AI가 일상적인 개발에서 어떻게 사용되는지 보여주기 위해 이 워크숍의 활동은 내러티브 컨텍스트에서 진행됩니다.
새로운 개발자가 전자상거래 회사에 입사합니다. 기존 전자상거래 애플리케이션 (여러 서비스로 구성됨)에 새 서비스를 추가하는 업무를 맡았습니다. 새 서비스는 제품 카탈로그의 제품에 대한 추가 정보 (크기, 무게 등)를 제공합니다. 이 서비스를 사용하면 제품 크기와 무게를 기준으로 더 저렴한 배송비를 책정할 수 있습니다.
개발자는 회사에 새로 입사했으므로 코드 생성, 설명, 문서화에 Duet AI를 사용합니다.
서비스가 코딩되면 플랫폼 관리자가 Duet AI(채팅)를 사용하여 아티팩트(Docker 컨테이너)와 아티팩트를 GCP에 배포하는 데 필요한 리소스(예: Artifact Registry, IAM 권한, 코드 저장소, 컴퓨팅 인프라(GKE 또는 CloudRun 등))를 만듭니다.
애플리케이션이 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 컴패니언 사용자 및 서비스 사용량 뷰어 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 컴패니언 사용자 및 서비스 사용량 뷰어 Identity and Access Management (IAM) 역할을 부여합니다. 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 프로젝트에서 필요한 IAM 역할을 가진 모든 사용자가 Duet AI를 사용할 수 있습니다.
Duet AI를 사용하는 데 필요한 IAM 역할을 부여하려면 IAM 페이지로 이동합니다.
주 구성원 열에서 Duet AI에 대한 액세스를 사용 설정하려는 사용자를 찾은 후 해당 행에서 연필 아이콘 ✏️ 주 구성원 수정을 클릭합니다.
액세스 수정 창에서 추가 다른 역할 추가를 클릭합니다.
역할 선택에서 Cloud AI 컴패니언 사용자를 선택합니다.
다른 역할 추가를 클릭하고 서비스 사용량 뷰어를 선택합니다.
저장을 클릭합니다.
IDE 설정
개발자는 필요에 가장 적합한 다양한 IDE 중에서 선택할 수 있습니다. Duet AI 코드 지원은 Visual Studio Code, JetBrains IDE (IntelliJ, PyCharm, GoLand, WebStorm 등), Cloud Workstations, Cloud Shell 편집기와 같은 여러 IDE에서 사용할 수 있습니다.
이 실습에서는 Cloud Workstations 또는 Cloud Shell Editor를 사용할 수 있습니다.
이 워크숍에서는 Cloud Shell 편집기를 사용합니다.
Cloud Workstations를 설정하는 데 20~30분이 걸릴 수 있습니다.
즉시 사용하려면 Cloud Shell 편집기를 사용하세요.
Cloud Shell의 상단 메뉴 바에서 연필 아이콘 ✏️을 클릭하여 Cloud Shell 편집기를 엽니다.
Cloud Shell 편집기의 UI와 UX는 VSCode와 매우 유사합니다.

Ctrl (Windows)/Cmd (Mac) + , (쉼표)를 클릭하여 설정 창으로 이동합니다.
검색창에 'duet ai'를 입력합니다.
Cloudcode › Duet AI: 사용 설정 및 Cloudcode › Duet AI › 인라인 추천: 자동 사용 설정이 사용 설정되어 있는지 확인하거나 사용 설정합니다.

하단의 상태 표시줄에서 Cloud Code - 로그인을 클릭하고 로그인 워크플로를 따릅니다.
이미 로그인한 경우 상태 표시줄에 Cloud Code - 프로젝트 없음이 표시됩니다.
Cloud Code - 프로젝트 없음을 클릭하면 상단에 작업 드롭다운 창이 표시됩니다. Google Cloud 프로젝트 선택을 클릭합니다.

프로젝트 ID를 입력하기 시작하면 목록에 프로젝트가 표시됩니다.

프로젝트 목록에서 PROJECT_ID를 선택합니다.
하단의 상태 표시줄이 업데이트되어 프로젝트 ID가 표시됩니다. 그렇지 않으면 Cloud Shell 편집기 탭을 새로고침해야 할 수 있습니다.
왼쪽 메뉴 바에서 Duet AI 아이콘
을 클릭하면 Duet AI 채팅 창이 표시됩니다. GCP 프로젝트 선택이라는 메시지가 표시되면 프로젝트를 클릭하고 다시 선택합니다.
이제 Duet AI 채팅 창이 표시됩니다.

3. 인프라 설정

GCP에서 새 배송 서비스를 실행하려면 다음 GCP 리소스가 필요합니다.
- 데이터베이스가 있는 Cloud SQL 인스턴스입니다.
- 컨테이너화된 서비스를 실행할 GKE 클러스터입니다.
- Docker 이미지를 저장할 Artifact Registry
- 코드용 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 서비스 개발

만들 서비스는 궁극적으로 다음 파일로 구성됩니다. 지금 이러한 파일을 만들 필요는 없으며 아래 안내에 따라 한 번에 하나씩 만들면 됩니다.
package-service.yaml- 높이, 너비, 무게, 특별 취급 안내와 같은 데이터가 있는 패키지 서비스의 Open API 사양입니다.data_model.py- package-service API 사양의 데이터 모델입니다. product_details DB에packages테이블도 만듭니다.connect_connector.py- CloudSQL 연결 (엔진, 세션, 기본 ORM 정의)db_init.py-packages테이블에 샘플 데이터를 생성합니다.main.py- product_id를 기반으로packages데이터에서 패키지 세부정보를 가져오는GET엔드포인트가 있는 Python Flask 서비스입니다.test.py- 단위 테스트requirement.txt- Python 요구사항Dockerfile- 이 애플리케이션을 컨테이너화합니다.
연습 중에 문제가 발생하면 이 Codelab의 부록에서 최종 파일을 모두 참조할 수 있습니다.
이전 단계에서 Cloud Source Repositories를 만들었습니다. 저장소를 클론합니다. 클론된 저장소 폴더에서 애플리케이션 파일을 빌드합니다.
Cloud Shell 터미널에서 다음 명령어를 실행하여 저장소를 클론합니다.
cd ~ gcloud source repos clone shipping shipping cd ~/shipping
Cloud Shell 편집기 왼쪽 메뉴에서 Duet AI Chat 사이드바를 엽니다. 아이콘은
과 같습니다. 이제 코드 지원을 위해 Duet AI를 사용할 수 있습니다.
package-service.yaml
파일을 열지 않은 상태에서 Duet에 배송 서비스의 Open API 사양을 생성해 달라고 요청합니다.
프롬프트 1: 숫자 제품 ID가 주어지면 배송 및 패키지 정보를 제공하는 서비스의 OpenAPI yaml 사양을 생성해 줘. 서비스에는 패키지의 높이, 너비, 깊이, 무게 및 특별 취급 지침에 관한 정보가 포함되어야 합니다.

생성된 코드 창의 오른쪽 상단에 세 가지 옵션이 표시됩니다.
코드를 COPY
하여 파일에 붙여넣을 수 있습니다.
코드를 편집기에서 현재 열려 있는 파일에 ADD
할 수 있습니다.
또는 새 파일에 코드를 OPEN
할 수 있습니다.
OPEN을 클릭하고 새 파일에 코드를
합니다.
CTRL/CMD + s을 클릭하여 파일을 저장하고, package-service.yaml이라는 파일 이름으로 애플리케이션 폴더에 파일을 저장합니다. '확인'을 클릭합니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
다양한 프롬프트를 시도하여 Duet AI의 대답을 확인할 수도 있습니다.
Duet AI 사이드바 상단의 휴지통 아이콘
을 클릭하여 Duet AI 채팅 기록을 재설정합니다.
data_model.py
다음으로 OpenAPI 사양을 기반으로 서비스의 데이터 모델 Python 파일을 만듭니다.
package-service.yaml 파일을 연 상태에서 다음 프롬프트를 입력합니다.
프롬프트 1: python sqlalchemy ORM을 사용하여 이 API 서비스의 데이터 모델을 생성해 줘. 데이터베이스 테이블을 만드는 별도의 함수와 기본 진입점도 포함하세요.

생성된 각 부분을 살펴보겠습니다. Duet AI는 여전히 어시스턴트이며 코드를 빠르게 작성하는 데 도움이 되지만, 생성된 콘텐츠를 검토하고 이해해야 합니다.
먼저 다음과 같이 packages 데이터베이스의 데이터 모델을 정의하는 종류Base의 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 함수를 실행하여 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
코드를 사용합니다. 코드를 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 파일에 추가할 수 있습니다.
최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
다양한 프롬프트를 시도하여 Duet AI의 응답이 어떻게 달라지는지 확인할 수도 있습니다.
Duet AI 사이드바 상단의 휴지통 아이콘
을 클릭하여 Duet AI 채팅 기록을 재설정합니다.
data_model.py 업데이트
CloudSQL 데이터베이스에 테이블을 만들려면 이전 단계 (connect_connector.py 파일)에서 만든 엔진을 사용해야 합니다.
Duet AI 채팅 기록을 삭제합니다. data_model.py 파일을 엽니다. 다음 프롬프트를 사용해 보세요.
프롬프트 1: 기본 함수에서 connect_connector.py의 엔진을 가져와 사용하세요.

connect_connector (CloudSQL용)에서 engine를 가져오는 응답이 표시됩니다. create_table은 기본 sqlite 로컬 DB 대신 해당 엔진을 사용합니다.
data_model.py 파일을 업데이트합니다.
최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
다양한 프롬프트를 시도하여 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에 패키지 테이블 만들기
CloudSQL 데이터베이스 커넥터의 환경 변수를 설정합니다.
export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export DB_USER=evolution
export DB_PASS=evolution
export DB_NAME=product_details
이제 data_model.py를 실행합니다.
python data_model.py
출력은 다음과 비슷합니다. 실제 예상되는 출력은 코드를 확인하세요.
Tables created successfully.
CloudSQL 인스턴스에 연결하여 데이터베이스가 생성되었는지 확인합니다.
gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details
비밀번호 (evolution)를 입력한 후 테이블을 가져옵니다.
product_details=> \dt
출력은 다음과 비슷합니다.
List of relations Schema | Name | Type | Owner --------+----------+-------+----------- public | packages | table | evolution (1 row)
데이터 모델과 테이블 세부정보를 확인할 수도 있습니다.
product_details=> \d+ packages
출력은 다음과 비슷합니다.
Table "public.packages"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
-------------------------------+-------------------+-----------+----------+--------------------------------------+----------+-------------+--------------+-------------
id | integer | | not null | nextval('packages_id_seq'::regclass) | plain | | |
product_id | integer | | not null | | plain | | |
height | double precision | | not null | | plain | | |
width | double precision | | not null | | plain | | |
depth | double precision | | not null | | plain | | |
weight | double precision | | not null | | plain | | |
special_handling_instructions | character varying | | | | extended | | |
Indexes:
"packages_pkey" PRIMARY KEY, btree (id)
Access method: heap
\q를 입력하여 Cloud SQL을 종료합니다.
db_init.py
이제 packages 테이블에 샘플 데이터를 추가해 보겠습니다.
Duet AI 채팅 기록을 삭제합니다. data_model.py 파일을 연 상태에서 다음 프롬프트를 사용해 보세요.
프롬프트 1: 샘플 패키지 행 10개를 만들고 이를 패키지 테이블에 커밋하는 함수 생성
프롬프트 2: connect_connector의 세션을 사용하여 샘플 패키지 행 10개를 만들고 이를 패키지 테이블에 커밋하는 함수를 생성해 줘.

이전과 같이 새 파일 워크플로에서 OPEN
코드를 사용합니다. 코드를 db_init.py 파일에 저장합니다.
최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
다양한 프롬프트를 시도하여 Duet AI의 다양한 대답을 확인할 수도 있습니다.
Duet AI 사이드바 상단의 휴지통 아이콘
을 클릭하여 Duet AI 채팅 기록을 재설정합니다.
샘플 패키지 데이터 만들기
명령줄에서 db_init.py를 실행합니다.
python db_init.py
출력은 다음과 비슷합니다.
Packages created successfully.
CloudSQL 인스턴스에 다시 연결하고 샘플 데이터가 패키지 테이블에 추가되었는지 확인합니다.
CloudSQL 인스턴스에 연결하여 데이터베이스가 생성되었는지 확인합니다.
gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details
비밀번호 (evolution)를 입력한 후 패키지 테이블에서 모든 데이터를 가져옵니다.
product_details=> SELECT * FROM packages;
출력은 다음과 비슷합니다.
id | product_id | height | width | depth | weight | special_handling_instructions ----+------------+--------+-------+-------+--------+----------------------------------- 1 | 0 | 10 | 10 | 10 | 10 | No special handling instructions. 2 | 1 | 10 | 10 | 10 | 10 | No special handling instructions. 3 | 2 | 10 | 10 | 10 | 10 | No special handling instructions. 4 | 3 | 10 | 10 | 10 | 10 | No special handling instructions. 5 | 4 | 10 | 10 | 10 | 10 | No special handling instructions. 6 | 5 | 10 | 10 | 10 | 10 | No special handling instructions. 7 | 6 | 10 | 10 | 10 | 10 | No special handling instructions. 8 | 7 | 10 | 10 | 10 | 10 | No special handling instructions. 9 | 8 | 10 | 10 | 10 | 10 | No special handling instructions. 10 | 9 | 10 | 10 | 10 | 10 | No special handling instructions. (10 rows)
\q를 입력하여 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의 패키지와 connect_conector.py의 SessionMaker를 가져와 패키지 데이터를 사용하세요.
프롬프트 4: python flask 라이브러리를 사용하여 이 서비스에 http rest 엔드포인트를 사용하는 구현을 만드세요. data_model.py의 패키지와 connect_conector.py의 SessionMaker를 가져와 패키지 데이터에 사용하세요. app.run에 호스트 IP 0.0.0.0 사용

main.py의 요구사항을 업데이트합니다.
프롬프트: main.py의 요구사항 파일 만들기

requirements.txt 파일에 이를 추가합니다. Flask 버전 3.0.0을 사용해야 합니다.
이전과 같이 새 파일 워크플로에서 OPEN
를 사용하여 코드를 작성합니다. 코드를 main.py 파일에 저장합니다.
최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
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를 테스트할 수도 있습니다.
터미널에서 실행 중인 Docker 컨테이너를 종료하려면 CTRL_C를 입력합니다.
단위 테스트 생성하기
main.py 파일을 열고 단위 테스트를 생성합니다.
프롬프트 1: 단위 테스트 생성

이전과 같이 새 파일 워크플로에서 OPEN
코드를 사용합니다. 코드를 test.py 파일에 저장합니다.
test_get_package 함수에서 product_id를 정의해야 합니다. 직접 추가할 수 있습니다.
최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
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이라는 파일에 저장합니다.
최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 적절한 변경사항을 수동으로 적용하세요.
애플리케이션을 로컬로 실행
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}
컨테이너화된 애플리케이션이 작동합니다.
터미널에서 실행 중인 Docker 컨테이너를 종료하려면 CTRL_C를 입력합니다.
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
포드가 실행되고 서비스에 외부 부하 분산기 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
포드가 다시 시작되면 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: 금지됨: 인증된 IAM 주 구성원이 API 요청을 할 권한이 없는 것 같습니다. GCP 프로젝트 내에서 'Cloud SQL Admin API'가 사용 설정되어 있고 IAM 보안 주체에 'Cloud SQL 클라이언트' 역할이 부여되었는지 확인합니다.

그리고
프롬프트 3: gcloud를 사용하여 Google 서비스 계정에 Cloud SQL 클라이언트 역할을 할당하려면 어떻게 해야 하나요?

cloudsqlsa에 Cloud SQL 클라이언트 역할을 할당합니다.
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
잠시 기다린 후 애플리케이션에 다시 액세스해 보세요.
export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1
출력은 다음과 비슷합니다.
{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}
Cloud Logging, 로그 탐색기, 로그 설명 도구 기능에서 Duet AI를 사용하여 문제를 해결했습니다.
8. 결론
축하합니다. 이 Codelab을 완료했습니다.
이 Codelab에서는 다음 내용을 알아봤습니다.
- GCP 프로젝트에서 Duet AI를 활성화하고 IDE 및 Cloud 콘솔에서 사용할 수 있도록 구성합니다.
- 코드 생성, 완성, 설명을 위해 Duet AI를 사용합니다.
- Duet AI를 사용하여 애플리케이션 문제를 설명하고 해결합니다.
- IDE 채팅 및 멀티턴 채팅, 채팅과 인라인 코드 생성 비교, 코드 설명 및 인용 확인과 같은 스마트 작업 등 Duet AI 기능
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"]