Skip to content

Fastapi + Serverless, AWS Lambda에 배포하기

This entry is part 1 of 2 in the series Fastapi + Serverless, AWS Lambda에 배포하기

OpenAI사의 API 를 사용하여 여러 프로젝트를 진행하고자 하는데, 매번 관련 설정을 하고 로직을 짜는게 비효율적이라고 느껴졌다. API서버를 만들어 서비스 마다 생성 AI를 필요로하는 로직에 해당 API서버의 특정 Endpoint로 요청을 보내서 활용하면 웹 서비스의 구조도 간단해지고 여러 서비스를 효율적으로 개발하고 유지보수 하는데 좋을 것 같았다. MSA 흉내도 내보고자 AI를 사용하는 부분은 서비스와 같은 서버에서 분리하여 따로 서버를 만들기로 했다.

빠른 개발과 쉬운 유지보수가 가능한 언어와 프레임워크로 API 서버를 만들어 활용할 계획이었고, Python과 Fastapi를 활용하여 해당 서버를 개발하기로 했다. 내가 Python을 좋아하기도 하고, 이전에 Flask를 사용해서 OpenAI API로 프로젝트를 한 적이 있었는데 개발하기 굉장히 편했던 기억이나서 선택에 많은 영향을 끼쳤다.

Fastapi를 활용해 서버를 만들고 클라우드의 VM을 빌려 Dockerizing해서 배포할 생각이었지만 서버를 세팅하고 관련 보안이나 통신등 환경 설정할 것들이 벌써 걱정되기 시작했다. 그래서 Serverless로 배포하기로 결정했다.

Serverless서비스를 제공하는 여러 클라우드 플랫폼중에서는 AWS의 Lmabda를 사용하기로 했다.

출처 – https://en.wikipedia.org/wiki/AWS_Lambda

https://aws.amazon.com/ko/lambda/?nc2=h_ql_prod_fs_lbd

일단 프리티어로 매월 100만건의 요청과 400,000GB-초의 컴퓨팅 시간을 준다. 프리티어로 제공되는 자원을 모두 사용해도 이후 요청 100만건당 0.2USD로 아주 저렴하게 사용할 수 있다.

새로 회원가입해서 1년동안 유지되는 서비스들과와는 다르게 모든 회원에게 제한없이 제공된다.

Lambda Container

이제 Fastapi로 개발한 서버를 Lambda에 올려야 한다. 위에서 말한 것 처럼 원래 도커 이미지를 활용해 컨테이너로 VM에 올릴 생각이었기 때문에 이미 도커 관련한 설정이 되어있어서 컨테이너째로 Lambda에 올릴 방법을 찾기 시작했다.

출처 – AWS 한국사용자모임 – AWSKRUG

위 영상에서 정말 양질의 정보를 많이 얻을 수 있었다. Lambda Container의 장단점에 대해서도 잘 소개 해주고 계셨다.

출처 – AWS 한국사용자모임 – AWSKRUG
출처 – AWS 한국사용자모임 – AWSKRUG

Lambda에 올릴서버는 생성 AI가 여러 텍스트나 이미지를 만들어 주는 서버기 때문에 생성하는데에도 30초 이상 걸리기 때문에 ms단위의 Cold Start는 생각하고 있지 않았다. UX적으로 로딩 애니메이션이나 Stream으로 텍스트를 받아와서 바로 생성하는 것처럼 보이게 하면 될 것 이라고 생각했기 때문이다.

하지만 영상에서는 Container를 인스턴스에 띄우는데 15초정도의 Cold Start가 걸린다고 하였다.. 아마 이미지 용량자체가 큰 프로젝트일 것으로 예상되지만 굳이 Lambda Container를 고집할 필요도 없다고 생각했다. 나중에 CI/CD까지 고려하면 AWS의 ECR을 사용해야 하는데 S3를 사용하는 것이 비용적인 측면에서도 나을 것 같았다.

그렇다고 Docker를 완전히 걷어내지는 않고 로컬의 프로젝트 루트디렉토리에 남겨두었다. 나중에 Tensorflow같은 패키지를 사용해 Deep learning작업이 추가되거나 VM에 배포할 수도 있을 것 같아서 보험?으로.. (물론 배포하는 디렉토리에 포함되지 않는다.)

Mangum

Fastapi는 ASGI (Asynchronous Server Gateway Interface)웹 프레임워크이다. AWS Lambda는 이벤트를 트리거로 사용하기 때문에 이를 FastAPI 애플리케이션에 맞게 처리해야 한다. Mangum은 Lambda 이벤트를 ASGI 애플리케이션으로 변환해주는 패키지다.

아래 명령어로 mangum을 설치한다.

pip install mangum

그리고 아래 코드 처럼 app을 묶어서 핸들러로 만들어주면 끝이다.

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

from mangum import Mangum

app = FastAPI(
    title="AI API SERVER",
    description="OpenAI API-powered API server providing multiple services",
    version="0.1",
)


@app.get("/health")
async def health_check():
    return {"status": "ok"}


handler = Mangum(app)

Serverless Framework

Serverless로 배포를 할때 어떤식으로 할지 고민을 많이 했다. CI/CD도 관심이 있었기에 처음부터 끝까지 AWS Console를 통해서 배포하는 것은 고려하지 않았다.

그러다 여러 클라우드 서비스들에 사용할 수 있는 Serverless라는 프레임워크를 알게 되었다.

Serverless 공식 홈페이지

Fastapi서버를 Serverless를 이용해서 Lambda에 배포한 사례가 많지는 않아서 조금 걱정했는데 공식문서가 알아보기 쉽게 정말 잘 되어있어서 어렵지 않았다. (오히려 AWS Console에서 많이 헤맸다..)

인터넷의 아티클들은 대부분 API Gateway를 사용한 사례였는데 아래 문서를 보고 Function URL을 쉽게 설정하여 배포할 수 있었다.

아래 링크는 Serverless공식문서의 Lambda Function URL 부분이다.

https://www.serverless.com/framework/docs/providers/aws/guide/functions#lambda-function-urls

serverless-python-requirements

serverless-python-requirements는 서버리스의 파이썬 패키지 종속성 설치 플러그인으로 배포시에 requirements.txt나 Pipfile에 정의된 패키지들을 번들로 묶어준다.

https://www.serverless.com/plugins/serverless-python-requirements

sls plugin install -n serverless-python-requirements

위 명령어로 플러그인을 설치 한다.

custom:
  pythonRequirements:
    dockerizePip: true
    layer:
      name: python-app-layer
      compatibleRuntimes:
        - python3.11

위 코드를 serverless.yml에 추가하면 번들로 묶인 파이썬의 종속성 패키지들을 Layer로 추가할 수 있다.

serverless-dotenv-plugin

.env파일에 설정된 환경변수들을 활용해 배포할 수 있게 해주는 플러그인이다. 아래 링크를 들어가서 문서를 읽어보면 플러그인이 없어도 변수를 지정하고 동적으로도 할당할 수 있지만 .env를 사용한다면 그냥 아래 플러그인을 설치해서 설정만하면 알아서 해준다.

API Key등 민감한 정보를 환경변수를 이용해 저장하려고 했기에 .env파일에 따로 저장하고 git hub나 S3에는 올라가지 않도록 ignore파일들에 잘 설정하였다.

https://www.serverless.com/plugins/serverless-dotenv-plugin

npm i -D serverless-dotenv-plugin

위 명령어로 플러그인을 설치한다.

functions:
  ai-api:
    handler: main.handler
    description: OpenAI API-powered API server providing multiple services
    timeout: 60
    memorySize: 128
    environment:
    url: true

플러그인을 설치했다면 functions에 environment: 만 추가해주면 알아서 .env파일의 설정들을 이용해서 배포한다.

Lmabda Function의 Configuration에서 Environment variables를 확인해 보면 환경변수가 잘 배포된 것을 볼 수 있다.

serverless.yml

이번에 배포하면서 사용한 serverless.yml 전체 파일은 아래와 같다.

service: ai-api-server

useDotenv: true

provider:
  name: aws
  runtime: python3.11
  region: ap-northeast-2

plugins:
  - serverless-python-requirements
  - serverless-dotenv-plugin

package:
  individually: true

custom:
  pythonRequirements:
    dockerizePip: true
    layer:
      name: python-app-layer
      compatibleRuntimes:
        - python3.11

functions:
  ai-api:
    handler: main.handler
    description: OpenAI API-powered API server providing multiple services
    timeout: 60
    memorySize: 128
    environment:
    url: true

    package:
      patterns:
        - '!node_modules/**'
        - '!yarn.lock'
        - '!package-lock.json'
        - '!package.json'
        - '!venv/**'
        - '!.env'

    layers:
      - { Ref: PythonRequirementsLambdaLayer }

serverless config credentials

AWS에 배포하는 것을 CLI에서 할 수 있도록 아래의 명령어를 통해 AWS에서 생성한 IAM의 access key와 secret key를 넣어 설정해준다.

serverless config credentials --provider provider --key key --secret secret

https://www.serverless.com/framework/docs/providers/aws/cli-reference/config-credentials

마지막으로 배포명령어를 실행하면된다.

sls deploy

터미널에서 정상적으로 배포가 완료되었다는 로그가 뜬다!(사실 배포를 10번도 넘게했다. 대부분은 IAM 권한관련 에러였고 AWS의 Console에서 하나씩 추가하며 해결했다.)


+ Unable to import module ‘main’ 에러 해결

결국 터미널에서 정상적으로 배포가 완료되었다는 로그가 떠서 생성된 function url로 접속을 해보았지만 에러가 발생했다.

cloudwatch를 통해 아래 log를 확인할 수 있었다.

[ERROR] Runtime.ImportModuleError: Unable to import module ‘main’: attempted relative import with no known parent package

엄청난 삽질 끝에 허무하게 해결했지만 아마 대부분 여기서 무사히 배포할 수 있을 것이라 생각한다.

만약 같은 에러가 발생한다면 다음 글에서 해결과정을 확인할 수 있다.

Series NavigationUnable to import module ‘main’ 에러 해결 >>

Leave a Reply

Your email address will not be published. Required fields are marked *