본문 바로가기
Back-end/Fastapi

#5. Fastapi로 모델 서빙부터 DevOps 까지 : 모델을 클라우드에 서빙하기

by hsloth 2024. 4. 28.

 

본 포스팅은 AI 개발자 분에게 Fastapi을 이용해 간단한 모델을 서빙해보고, AWS에 올려 서버 배포 및 CI/CD 자동화까지 하는 것을 경험시켜주기 위해 제작되었습니다.

 

 

이전 시간에는 XGBRegressor라는 모델을 이용해서 간단하세 보스턴의 집 값을 예측하는 모델을 만들고, 해당 모델을 fastapi에 서빙하는 것까지 해봤다.

https://suloth.tistory.com/194

 

#4. Fastapi로 모델 서빙부터 DevOps 까지 : XGBRegressor를 이용한 간단한 모델 구현 및 모델 서빙

본 포스팅은 AI 개발자 분에게 Fastapi을 이용해 간단한 모델을 서빙해보고, AWS에 올려 서버 배포 및 CI/CD 자동화까지 하는 것을 경험시켜주기 위해 제작되었습니다.  이전 시간에는 Param, Query, Body

suloth.tistory.com

 

이번 시간에는 서빙한 모델을 AWS에 띄우는 것을 해볼 예정이다. (간단하게, git과 github, aws만을 이용해서 할 예정이다)

 

그전에!!!

이전 시간에 내주었던 과제를 살펴보자.

 

@router.get 말고 @router.post를 사용해서 crim과 room말고 다른 모든 속성들을 post의 body로 받아서 예측하도록 api를 만들어보자.

기존의 runModel과 routers/house.py의 getPredictionOfHousePrice 함수는 놔두고

rumModel2 함수와 getPredictionOfHousePrice2 함수를 만들어서 작성해보도록 하자.

 

가 이전 시간에 내주었던 과제였다.

 

이번에 내준 과제는 조금 복잡했을수도 있다.

먼저, 사용자로부터 받을 Data를 정의하자. 우리는 Post 메소드를 통해, Request Body로 받을 예정이니. pydantic의 BaseModel을 상속받안 클래스를 만들어주어야한다.

나같은 경우에는 src폴더에 dtos 폴더를 따로 만들어 주었다. 그리고 house.py 파일을 만들었다.

__pycache__는 무시하고 보자.

 

그리고 다음과 같이 코드를 작성해주었다.

from pydantic import BaseModel

class GetPredictionOfHousePrice2BodyDto(BaseModel):
  crim: float
  zn: float
  indus: float
  chas: int
  nox: float
  room: float
  age: float
  dis: float
  rad: int
  tax: float
  ptratio: float
  b: float
  lstat: float

 

우리가 router에 만들 예정인 getPredictionOfHousePrice2 에 대한 Body Dto 라는 뜻을 가진 class이다.

다음 클래스를 body의 타입으로 사용할 수 있다.

Dto는 Data Transfer Object라는 뜻이다. 그냥 타입 정의했다고 생각하자.

 

그리고 routers/house.py로 가서 다음 코드를 추가로 작성하자.

from fastapi import APIRouter
# runModel2도 불러와주자.
from src.services.house import runModel, runModel2
# dtos/house.py 경로에서 정의한 Body 타입을 가진 class를 불러온다.
from src.dtos.house import GetPredictionOfHousePrice2BodyDto

house_router = router = APIRouter()


@router.get("/price/predict")
async def getPredictionOfHousePrice(crim: float, room: float):
  price = await runModel(crim, room)
    
  return price

# 추가된 코드. post 요청으로 model을 돌린다.
@router.post("/price/predict")
# body인자로 아까 만들었던 Dto class를 설정해준다.
async def getPredictionOfHousePrice2(body: GetPredictionOfHousePrice2BodyDto):
  price = await runModel2(body)
  
  return price

 

@router.post("/price/predict") 를 사용해서 /price/predict 경로로 post 요청이 들어오면 함수가 실행되도록 했다.

그리고 getPredictionOfHousePrice2 함수의 인자로 body를 받는데, GetPredictionOfHousePrice2BodyDto를 타입으로 가진 클래스를 받도록 한다.

그리고 해당 body를 runModel2 함수에 인자로 넣어준다.

 

 

다음으로는 services/house.py 로 가서 다음과 같이 코드를 작성하자.

from typing import List
import xgboost as xgb
import pandas as pd

# 추가된 코드. src/dtos/house.py 에서 Dto를 불러온다.
from src.dtos.house import GetPredictionOfHousePrice2BodyDto

# XGBRegressor 모델을 불러온다.
loaded_model = xgb.XGBRegressor()

# colab에서 학습시킨 모델의 가중치 파일을 불러와서 모델에 적용시킨다.
loaded_model.load_model('src/models/xgb_model.json')

async def runModel(crim: float, room: float) -> List[float]:
  
  # 미리 준비된 input 데이터(임시). 나중에는 Http Request에 담아서 보낼 것이다.
  dic = {
    "CRIM": [crim],
    "ZN": [18.0],
    "INDUS": [22.37],
    "CHAS": [0],
    "NOX": [0.145],
    "RM": [room],
    "AGE": [66.7],
    "DIS": [4.291],
    "RAD": [13],
    "TAX": [333.333],
    "PTRATIO": [21.0],
    "B": [197.6],
    "LSTAT": [23.4],
  }
    
  # dictionary 형태를 DataFrame 형태로 변환한다.
  input = pd.DataFrame.from_dict(dic, orient='columns')
  
  # input 값을 이용해서 예측값을 만들고, z에 대입한다.  
  z = loaded_model.predict(input)

  # 변수 z의 타입이 numpy이기 때문에 list로 바꿔준다.
  result: List[float] = z.tolist()
  
  return result

# 추가된 함수.
async def runModel2(input: GetPredictionOfHousePrice2BodyDto) -> List[float]:
  
  dic = {
    "CRIM": [input.crim],
    "ZN": [input.zn],
    "INDUS": [input.indus],
    "CHAS": [input.chas],
    "NOX": [input.nox],
    "RM": [input.room],
    "AGE": [input.age],
    "DIS": [input.dis],
    "RAD": [input.rad],
    "TAX": [input.tax],
    "PTRATIO": [input.ptratio],
    "B": [input.b],
    "LSTAT": [input.lstat],
  }
    
  # dictionary 형태를 DataFrame 형태로 변환한다.
  input = pd.DataFrame.from_dict(dic, orient='columns')
  
  # input 값을 이용해서 예측값을 만들고, z에 대입한다.  
  z = loaded_model.predict(input)

  # 변수 z의 타입이 numpy이기 때문에 list로 바꿔준다.
  result: List[float] = z.tolist()
  
  return result

 

rumModel2 함수를 위와 같이 만들면 된다. 별거 없다. body인자를 dic쪽의 속성에 하나씩 넣어주면 끝이다.

 

그리고 서버를 실행시켜주자. (가상환경도 실행시키는 것 잊지 말자)

 

그리고 다음과 같이 대~충 모든 값들을 넣어주고 execute를 눌러주면

 

결과가 뜬다! 혹시모르니 각자 한 번 인자 하나하나 값을 변경해보면서 결과가 달라지는지 확인해보자.

 


지금까지 진행한 프로젝트 Github에 업로드 하기


Github를 사용하기 전에, 프로젝트의 최상위 폴더에 requirements.txt 파일이 있을 것이다. (없으면 만들자)

그리고 다음과 같이 작성하자.

 

 

먼저 Github에서 repository를 만들어보자. 없다면 가입하도록 하자!

repositories 탭으로 가서 으론쪽 위를 보면 New 버튼이 있는데 해당 버튼을 클릭하고

 

repository name을 fastapi-tutorial로 하고, public, Add .gitignore= Python으로 설정하고 Create Repository 버튼을 누르면 repoistory가 생성된다.

 

 

이제 git을 이용해서 Github에 지금까지 진행한 프로젝트를 업로드해보자.

먼저 터미널에서 프로젝트 폴더(프로젝트 최상위 폴더)로 이동한 후, 다음 명령들을 입력하자.

# git을 사용해서 관리하는 폴더임을 명시한다.
git init

# 프로젝트와 git repository를 연결한다. 여기서 <>는 제외하고 명령어를 입력해주자.
# 나의 경우는 git remote add origin https://github.com/8471919/fastapi-tutorial 이다.
git remote add origin <github repository 주소>

# 원격 저장소(Github를 뜻한다)의 브랜치 목록을 가져온다.
git fetch

 

이 다음 명령을 칠 때, 주의할 점이 있다. 자신의 Git의 branch와 Github의 branch가 다를 수 있다.

최근에 git을 다운받았으면 main 브랜치를 사용할 텐데, 예전에 다운로드 받았으면 기본으로 master branch를 사용할 것이다.

그냥 묻지도말고 따지지 말고 아래 명령을 치자.

# 현재 프로젝트를 관리하는 git의 관리 브랜치를 main으로 바꾼다.
# 하지만 다른 프로젝트에서 또 git init을 하면 master 브랜치를 사용하게 된다.
git branch -M main

# 만약 계속해서 main을 사용하고 싶다면 다음 명령어도 입력해주자.
git config --global init.defaultBranch main

 

다음으로는 원격 저장소(Github)에서 소스 코드를 받아와야한다.

git pull origin main

 

다음과 같이 뜨면 된 것이다.

 

 

 

그리고 내 컴퓨터에 있는 fastapi 코드를 github로 올리기 위한 명령어를 작성하자.

git add .
git commit -m "fastapi tutorial"

git push origin main

 

깃허브를 확인해보면 다음과 같이 코드가 올라가 있는 것을 확인할 수 있다.

위 사진은 강의 진행 결과와 조금 다르다.

 


AWS에 Fastapi 서버 올리기 (Fastapi 서버 AWS에 배포하기)


 

가장 먼저 AWS에 가입을 해주자. 가입할 때, 카드를 등록하는데 AWS를 이용하다가 깜빡하고 연습을 위해 사용했던 리소스를 삭제하지 않는다면 해당 카드로 요금이 빠져나가게 된다.

 

AWS에 로그인을 하고 왼쪽 위 검색창에 ec2를 입력해서 EC2 서비스를 클릭하자.

EC2는 컴퓨터라고 생각하면 된다. EC2 인스턴스를 생성하게되면 AWS측에서 서버용 컴퓨터를 하나 구입하는 것이다.(요금은 시간단위로 나가는... 렌트 형식의 컴퓨터 구입이라고 생각하면 될 듯 하다.)

 

 

여기서 인스턴스 탭의 인스턴스로 들어가주자. 혹은 인스턴스(실행 중)을 클릭하자.

 

 

그러면 아래와 같은 화면이 뜬다.

 

여기서 인스턴스 시작을 누른다.

 

ec2 instance의 이름을 fastapi-tutorial 로 지어주자.

 

 

애플리케이션 및 OS 이미지를 Ubuntu로 설정해주고, Ubuntu Server 24.04 LTS 로 해주자. (프리 티어 사용 가능)

아키텍처는 64비트 (x86)로 설정해주자.

 

 

인스턴스 유형은 t2.micro(프리 티어 사용 가능으로)

키 페어는 일단 키 페이 없이 계속 진행으로 설정해주자.

 

나머지는 그대로 설정하고 바로 인스턴스 시작을 눌러주자.

 

여기서 인스턴스 개수는 1로 해주자...

 

그리고 인스턴스가 만들어지면 모든 인스턴스 보기를 누르자.

 

만약 인스턴스 목록을 봤는데 아무것도 안떠도 당황하지 말고 새로고침을 눌러보자.

 

 

 

위와 같이 인스턴스가 뜰텐데, 인스턴스 ID를 눌러보자(i-03a.. 어쩌구 저거를 눌러보자)

 

그러면 위와 같은 화면이 뜰텐데, 조금 아래로 내려가사 보안 탭을 눌러주자.

 

 

 

여기서 보안 그룹에 있는 링크(sg-046a7973c410...)를 클릭하자.

 

위에서 인바운드 규칙 편집을 클릭하자.

인바운드 규칙이란. 밖에서 안으로 들어오는 요청에 대한 보안 규칙이다.

우리가 컴퓨터에서 서비스를 이용할 때 서버에 요청을 보내면, 서버 입장에서는 밖에서 안으로 들어오는 요청이니, 인바운드 규칙을 정해주는 것이다.

 

 

여기서 규칙 추가를 누르고, 위 사진에서 두 번째 인바운드 규칙(-) 처럼 만들자.

유형은 사용자 지정 TCP로, 포트 범위는 3000으로, 돋보기 버튼에서는 0.0.0.0/0 을 선택해서 규칙 저장을 눌러주자.

이 규칙의 의미는, TCP로 들어오는 요청은 3000번 포트로 들어오는 것만 허락하는데, 그 요청에 어떤 ip에서 온 것이든 다 허용(0.0.0.0/0)한다는 뜻이다. (0의 의미는 all이라고 생각하자)

서버로 요청이 들어오면, 해당 요청에는 요청을 보낸 컴퓨터의 ip가 담겨있기 때문이다.

 

 

그리고 다시 인스턴스 정보로 돌아가서 연결 버튼을 눌러주자.

 

 

 

여기서 다시 연결을 누르면, 해당 ec2의 터미널 창으로 들어가진다.

GUI는 없다. (아이콘이나 그래픽 인터페이스는 없다) 어차피 서버용으로 사용할 것이기 때문에 필요없는 부분은 날려서 Command 입력기만 존재한다.

 

그리고 다음 명령어를 입력하자.

 

이미 git과 python이 설치되어 있을 것이다. (python은 리눅스에서 python3로 되어있다)

python이 설치되어있긴 하지만 pip는 설치되어 있지 않으므로 다음 명령어로 pip를 설치해보자.

sudo apt update

sudo apt install python3-pip

중간에 질문이 나오면 y를 입력해줘서 설치를 계속 진행하자.

그러면 pip3가 설치되었다.

 

먼저, git을 이용해 깃허브 레포지토리에 올린 소스코드를 다운로드 받아주자.

git clone <github repository 주소>

ls

# 자신의 프로젝트 폴더로 이동한다.
cd fastapi-tutorial

 

 

그리고 맨 위에서 작성했던 requirements.txt 파일을 이용해서 필요한 패키지들을 설치해줄 것이다.

pip install -r requirements.txt

# 만약 오류가 발생한다면 다음 명령어를 먼저 입력해주자.
# sudo rm /usr/lib/python<version>/EXTERNALLY-MANAGED
sudo rm /usr/lib/python3.12/EXTERNALLY-MANAGED

# 그리고 다시
pip install -r requirements.txt


# 만약에 설치가 안된다면 직접 설치해주자...
pip install fastapi
pip install uvicorn
pip install xgboost
pip install pandas
pip install scikit-learn

 

 

그리고 uvicorn을 사용해 서버를 실행시켜보자.

# 여기서 host옵션을 주지 않으면 localhost(aws ec2)말고는 서버에 접속이 되지 않기 때문에
# 꼭 0.0.0.0으로 설정해주어야 한다.
uvicorn src.main:app --host=0.0.0.0 --port=3000

 

그러면 아래와 같은 오류가 뜰 것이다.

 

친절하게 명령어도 알려준다. 입력해주자.

sudo apt install uvicorn

아마 uvicorn은 따로 linux에서 설치를 해줘야 하나보다.

 

그리고 다시 서버를 실행하면

실행이 잘 된다.

 

그리고 해당 터미널 창을 끄지 말고, EC2 인스턴스 정보 페이지로 가보면 퍼블릭 IPv4 주소 가 있을 것이다.

 

위에서 동그라미 부분에 있는 ip주소를 이용해서 서버에 접속할 것이다. 

(참고로 해당 ip는 일정 주기로 변경되니, 고정해주고 싶으면 탄력적 ip라는것을 사용해서 고정해야한다)

 

해당 주소로 ip주소:3000/docs 로 들어가보면은 스웨거가 뜰 것이다!

 

그리고 스웨거를 실행시켜보면 (여기서는 Post /api/house/price/predict 를 실행시켜보겠다)

 

다음과 같이 정상적으로 실행되는 것을 확인할 수 있다.

 

이제 해당 ip로 누구나 접속해서 api요청을 날릴 수 있다. (다른 컴퓨터로도 접속해서 테스트 해보도록 하자)


AWS EC2 삭제하기


AWS EC2에 대한 실습이 끝났다. 

실습이 끝났으면 EC2 인스턴스를 지워야한다. 인스턴스를 그대로 놔두면 돈을 야금야금 먹을 수 있기 때문에 (아마 프리티어라서 1년동안은 돈이 안나갈테지만... 혹시 모르니까! 그리고 1년 후까지 안지우면 요금이 나갈 수 있으므로)

 

먼저 AWS의 EC2 -> 인스턴스 로 들어가보자.

그리고 삭제하고 싶은 인스턴스를 체크해주고 인스턴스 상태 -> 인스턴스 종료 버튼을 누르면 몇 분 이내에 삭제가 될 것이다.

종료 = 삭제 라고 보면 된다.

 

 


일단 여기까지 진행하고 "Fastapi로 모델 서빙부터 DevOps 까지" 시리즈는 잠시 쉬어가겠습니다.

 

해당 프로젝트가 궁금하신 분은 아래 깃허브를 참조해주세요!

 

https://github.com/8471919/fastapi-tutorial-for-ai-engineer

 

GitHub - 8471919/fastapi-tutorial-for-ai-engineer

Contribute to 8471919/fastapi-tutorial-for-ai-engineer development by creating an account on GitHub.

github.com