개발자를 위한 Duet AI 기술 실습 워크숍 가이드 Codelab

1. 목표

이 워크숍의 목적은 사용자와 실무자에게 Duet AI 실습 교육을 제공하는 것입니다.

이 Codelab에서는 다음 내용을 알아봅니다.

  1. GCP 프로젝트에서 Duet AI를 활성화하고 IDE 및 Cloud 콘솔에서 사용하도록 구성합니다.
  2. 코드 생성, 완성, 설명에 Duet AI를 사용하세요.
  3. Duet AI를 사용하여 애플리케이션 문제를 설명하고 해결합니다.
  4. IDE 채팅 및 멀티턴 채팅, 채팅 및 인라인 코드 생성, 코드 설명 및 암송 확인과 같은 스마트 작업 등의 Duet AI 기능이 제공됩니다.

내러티브

개발자용 Duet AI가 일상적인 개발에 어떻게 실제로 사용되는지 보여주기 위해 이 워크숍의 활동은 내러티브 맥락에서 진행됩니다.

새로운 개발자가 전자상거래 회사에 합류합니다. 기존 전자상거래 애플리케이션 (여러 서비스로 구성됨)에 새로운 서비스를 추가하는 업무를 담당합니다. 새 서비스는 제품 카탈로그의 제품에 대한 추가 정보 (크기, 중량 등)를 제공합니다. 이 서비스를 통해 제품 크기 및 중량에 따라 배송비를 할인하거나 낮출 수 있습니다.

이 회사에 처음 입사한 개발자는 코드 생성, 설명, 문서화에 Duet AI를 사용할 것입니다.

서비스가 코딩되면 플랫폼 관리자가 Duet AI(채팅)를 사용하여 아티팩트(docker 컨테이너)와 아티팩트를 GCP에 배포하는 데 필요한 리소스(예: Artifact Registry, IAM 권한, 코드 저장소, 컴퓨팅 인프라(GKE 또는 CloudRun 등))를 만들어 줍니다.

애플리케이션이 GCP에 배포되면 애플리케이션 운영자/SRE가 Duet AI (및 Cloud Ops)를 사용하여 새 서비스의 오류 문제를 해결합니다.

사용자

워크숍에서 다루는 대상은 다음과 같습니다.

  1. 애플리케이션 개발자: 프로그래밍 및 소프트웨어 개발에 관한 지식이 필요합니다.

이 변형된 Duet AI 워크숍은 개발자 전용입니다. GCP 클라우드 리소스에 대한 지식은 필요하지 않습니다. 이 애플리케이션을 실행하는 데 필요한 GCP 리소스를 빌드하는 방법에 대한 스크립트는 여기에서 확인할 수 있습니다. 이 가이드의 안내에 따라 필요한 GCP 리소스를 배포할 수 있습니다.

2. 환경 준비

Duet AI 활성화

API (gcloud 또는 Terraform과 같은 IaC 도구) 또는 Cloud 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 편집기를 사용할 수 있습니다.

이 워크숍에서는 Cloud Shell 편집기를 사용합니다.

Cloud Workstations를 설정하는 데 20~30분 정도 걸릴 수 있습니다.

즉시 사용하려면 Cloud Shell 편집기를 사용하세요.

Cloud Shell의 상단 메뉴 바에서 연필 아이콘 ✏️을 클릭하여 Cloud Shell 편집기를 엽니다.

Cloud Shell 편집기의 UI 및 UX는 VSCode와 매우 유사합니다.

d6a6565f83576063.png

Ctrl (Windows)/CMD (Mac) + , (쉼표)를 클릭하여 설정 창으로 이동합니다.

검색창에 'duet ai'를 입력합니다.

Cloudcode › Duet AI: 사용 설정Cloudcode › Duet AI › 인라인 제안: 자동 사용 설정을 확인하거나 사용 설정합니다.

111b8d587330ec74.png

하단의 상태 표시줄에서 Cloud Code - Sign In을 클릭하고 로그인 워크플로를 따릅니다.

이미 로그인한 경우 상태 표시줄에 Cloud Code - 프로젝트 없음이 표시됩니다.

Cloud Code - No project를 클릭하면 상단에 작업 드롭다운 창이 표시됩니다. Google Cloud 프로젝트 선택을 클릭합니다.

3241a59811e3c84a.png

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

c5358fc837588fe.png

프로젝트 목록에서 PROJECT_ID를 선택합니다.

하단 상태 표시줄이 업데이트되어 프로젝트 ID가 표시됩니다. 새로고침되지 않는 경우 Cloud Shell 편집기 탭을 새로고침해야 할 수 있습니다.

왼쪽 메뉴 바에서 Duet AI 아이콘 d97fc4e7b594c3af.png을 클릭하면 Duet AI 채팅 창이 표시됩니다. '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 - package-service API 사양의 데이터 모델입니다. 또한 product_details DB에 packages 테이블이 생성됩니다.
  3. connect_connector.py - CloudSQL 연결 (엔진, 세션 및 기본 ORM 정의)
  4. db_init.py - packages 테이블에 샘플 데이터를 생성합니다.
  5. main.py - product_id를 기반으로 packages 데이터에서 패키지 세부정보를 검색하는 GET 엔드포인트가 있는 Python Flask 서비스입니다.
  6. test.py - 단위 테스트
  7. requirement.txt - Python 요구사항
  8. Dockerfile - 이 애플리케이션을 컨테이너화합니다.

연습 중에 문제가 발생하는 경우 최종 파일은 모두 이 Codelab의 부록에 있으며 참고할 수 있습니다.

이전 단계에서는 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

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

다양한 프롬프트를 시도하여 Duet AI의 대답을 확인할 수도 있습니다.

Duet AI 사이드바 상단의 휴지통 아이콘 f574ca2c1e114856.png을 클릭하여 Duet AI 채팅 기록을 재설정하세요.

data_model.py

다음으로 OpenAPI 사양에 따라 서비스의 데이터 모델 Python 파일을 만듭니다.

package-service.yaml 파일을 열고 다음 프롬프트를 입력합니다.

프롬프트 1: Python sqlalchemy ORM을 사용하여 이 API 서비스의 데이터 모델을 생성합니다. 또한 데이터베이스 테이블을 만드는 별도의 함수와 기본 진입점을 포함합니다.

b873a6a28bd28ca1.png

생성된 각 부분을 살펴보겠습니다. Duet AI는 여전히 어시스턴트입니다. 코드를 빠르게 작성하는 데 도움이 되기는 하지만 계속해서 생성된 콘텐츠를 검토하고 진행하면서 이해해야 합니다.

먼저 다음과 같이 packages 데이터베이스의 데이터 모델을 정의하는 종류BasePackage라는 클래스가 있습니다.

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)

마지막으로 다음과 같이 실제로 CloudSQL 데이터베이스에 테이블을 빌드하려면 create_tables 함수를 실행하는 기본 함수가 필요합니다.

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

Cloud SQL 커넥터를 만듭니다.

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 engine, sessionmaker, base ORM을 생성합니다.

프롬프트 1: connect_with_connector 메서드를 사용하여 엔진, 세션메이커 클래스, 기본 ORM 만들기

6e4214b72ab13a63.png

응답은 connect_connector.py 파일에 engine, Session, Base를 추가할 수도 있습니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

다양한 프롬프트를 시도하여 Duet AI의 대답 변형 가능성을 확인할 수도 있습니다.

Duet AI 사이드바 상단의 휴지통 아이콘 f574ca2c1e114856.png을 클릭하여 Duet AI 채팅 기록을 재설정하세요.

data_model.py 업데이트

CloudSQL 데이터베이스에 테이블을 만들려면 이전 단계에서 만든 엔진 (connect_connector.py 파일)을 사용해야 합니다.

Duet AI 채팅 기록을 삭제합니다. data_model.py 파일을 엽니다. 다음 프롬프트를 사용해 보세요.

프롬프트 1: 기본 함수에서 connect_connector.py에 있는 엔진을 가져와 사용합니다.

2e768c9b6c523b9a.png

connect_connector에서 engine를 가져오는 응답이 표시됩니다 (CloudSQL). create_table는 기본 sqlite 로컬 DB 대신 이 엔진을 사용합니다.

data_model.py 파일을 업데이트합니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

다양한 프롬프트를 시도하여 다양한 Duet AI의 응답을 확인할 수도 있습니다.

Duet AI 사이드바 상단의 휴지통 아이콘 f574ca2c1e114856.png을 클릭하여 Duet AI 채팅 기록을 재설정하세요.

requirements.txt

애플리케이션의 requirements.txt 파일을 만듭니다.

connect_connector.pydata_model.py 파일을 모두 열고 다음 프롬프트를 입력합니다.

프롬프트 1: 이 데이터 모델 및 서비스에 대한 pip 요구사항 파일 생성

프롬프트 2: 최신 버전을 사용하여 이 데이터 모델 및 서비스의 pip 요구사항 파일 생성

69fae373bc5c6a18.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 채팅 기록을 재설정하세요.

Cloud SQL에서 패키지 테이블 만들기

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.

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를 입력하여 CloudSQL을 종료합니다.

db_init.py

다음으로 packages 테이블에 샘플 데이터를 추가해 보겠습니다.

Duet AI 채팅 기록을 삭제합니다. data_model.py 파일을 열고 다음 프롬프트를 시도합니다.

프롬프트 1: 샘플 패키지 행 10개를 만들어 패키지 테이블에 커밋하는 함수 생성

프롬프트 2: connect_connector의 세션을 사용하여 10개의 샘플 패키지 행을 만들고 패키지 테이블에 커밋하는 함수 생성

34a9afc5f04ba5.png

OPEN a4c7ed6d845df343.png를 사용하여 이전과 같이 새 파일 워크플로에서 코드를 실행합니다. 코드를 db_init.py 파일에 저장합니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

다양한 프롬프트를 시도하여 다양한 Duet AI의 응답을 확인할 수도 있습니다.

Duet AI 사이드바 상단의 휴지통 아이콘 f574ca2c1e114856.png을 클릭하여 Duet AI 채팅 기록을 재설정하세요.

샘플 패키지 데이터 만들기

명령줄에서 db_init.py를 실행합니다.

python db_init.py

출력은 다음과 비슷합니다.

Packages created successfully.

Cloud SQL 인스턴스에 다시 연결하고 샘플 데이터가 패키지 테이블에 추가되었는지 확인합니다.

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를 입력하여 CloudSQL을 종료합니다.

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 사용

6d794fc52a90e6ae.png

main.py의 요구사항을 업데이트합니다.

프롬프트: main.py의 요구사항 파일 만들기

1cc0b318d2d4ca2f.png

requirements.txt 파일에 다음을 추가합니다. Flask 버전 3.0.0을 사용해야 합니다.

OPEN a4c7ed6d845df343.png를 사용하여 이전과 같이 새 파일 워크플로에서 코드를 실행합니다. 코드를 main.py 파일에 저장합니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

Duet AI 사이드바 상단의 휴지통 아이콘 f574ca2c1e114856.png을 클릭하여 Duet AI 채팅 기록을 재설정하세요.

5. 애플리케이션 테스트 및 실행

요구사항을 설치합니다.

pip3 install -r requirements.txt

main.py을 실행합니다.

python main.py

출력은 다음과 비슷합니다.

 * Serving Flask app 'main'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://10.88.0.3:5000
Press CTRL+C to quit

두 번째 터미널에서 /packages/<product_id> 엔드포인트를 테스트합니다.

curl localhost:5000/packages/1

출력은 다음과 비슷합니다.

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

샘플 데이터에서 다른 제품 ID를 테스트할 수도 있습니다.

CTRL_C를 입력하여 터미널에서 실행 중인 Docker 컨테이너를 종료합니다.

단위 테스트 생성

main.py 파일을 열고 단위 테스트를 생성합니다.

프롬프트 1: 단위 테스트 생성

e861e5b63e1b2657.png

OPEN a4c7ed6d845df343.png를 사용하여 이전과 같이 새 파일 워크플로에서 코드를 실행합니다. 코드를 test.py 파일에 저장합니다.

test_get_package 함수에서 product_id를 정의해야 합니다. 직접 추가할 수 있습니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

Duet AI 사이드바 상단의 휴지통 아이콘 f574ca2c1e114856.png을 클릭하여 Duet AI 채팅 기록을 재설정하세요.

단위 테스트 실행

단위 테스트 실행

python test.py

출력은 다음과 비슷합니다.

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

OK

Cloud Shell 편집기에서 모든 파일을 닫고 상단 상태 표시줄의 휴지통 아이콘 1ecccfe10d6c540.png을 클릭하여 채팅 기록을 지웁니다.

Dockerfile

이 애플리케이션의 Dockerfile을 만듭니다.

main.py를 열고 다음 프롬프트를 시도해 보세요.

프롬프트 1: 이 애플리케이션의 Dockerfile을 생성합니다.

프롬프트 2: 이 애플리케이션에 대한 Dockerfile을 생성합니다. 모든 파일을 컨테이너에 복사합니다.

9c473caea437a5c3.png

INSTANCE_CONNECTION_NAME, DB_USER, DB_PASS, DB_NAMEENVARS도 설정해야 합니다. 이 작업은 수동으로 할 수 있습니다. Dockerfile은 다음과 같이 표시됩니다.

FROM python:3.10-slim

WORKDIR /app

COPY . ./

RUN pip install -r requirements.txt

# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details

CMD ["python", "main.py"]

OPEN a4c7ed6d845df343.png를 사용하여 이전과 같이 새 파일 워크플로에서 코드를 실행합니다. Dockerfile이라는 파일에 코드를 저장합니다.

최종 파일은 이 Codelab의 부록 섹션에 있습니다. 그렇지 않은 경우 수동으로 변경합니다.

로컬에서 애플리케이션 실행

Dockerfile를 열고 다음 프롬프트를 시도해 보세요.

프롬프트 1: 이 Dockerfile을 사용하여 컨테이너를 로컬에서 실행하려면 어떻게 해야 하나요?

570fd5c296ca8c83.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

이제 애플리케이션 컨테이너가 GKE에 배포할 수 있는 us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping에 있습니다.

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

포드가 실행되고 서비스에 외부 부하 분산기 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 역할을 삭제합니다. 이로 인해 CloudSQL 데이터베이스에 연결하는 중에 오류가 발생합니다.

gcloud projects remove-iam-policy-binding ${PROJECT_ID} \
    --member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

배송 포드를 다시 시작합니다.

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 > 워크로드

d225b1916c829167.png

shipping 배포를 클릭한 다음 로그 탭을 클릭합니다.

1d0459141483d6a7.png

상태 표시줄 오른쪽에 있는 로그 탐색기에서 보기 df8b9d19a9fe4c73.png아이콘을 클릭합니다. 그러면 새 로그 탐색기 창이 열립니다.

e86d1c265e176bc4.png

Traceback 오류 항목 중 하나를 클릭한 후 이 로그 항목 설명을 클릭합니다.

d6af045cf03008bc.png

오류에 대한 설명을 읽어볼 수 있습니다.

이제 오류 문제 해결을 위해 Duet AI를 사용해 보겠습니다.

다음 프롬프트를 사용해 보세요.

프롬프트 1: 이 오류의 문제 해결 도움말

9288dd6045369167.png

프롬프트에 오류 메시지를 입력합니다.

프롬프트 2: Forbidden: Authenticated IAM principal does not see rights to make API request. 'Cloud SQL Admin API' 확인 GCP 프로젝트와 ‘Cloud SQL Client’ 내에서 역할이 IAM 주 구성원에게 부여됨

f1e64fbdc435d31c.png

그리고

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

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. 결론

축하합니다. 이 Codelab을 완료했습니다.

이 Codelab에서 알아본 내용은 다음과 같습니다.

  1. GCP 프로젝트에서 Duet AI를 활성화하고 IDE 및 Cloud 콘솔에서 사용하도록 구성합니다.
  2. 코드 생성, 완성, 설명에 Duet AI를 사용하세요.
  3. Duet AI를 사용하여 애플리케이션 문제를 설명하고 해결합니다.
  4. 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"]