본문 바로가기
개발/웹

Flask Microservice 구축 - Zappa로 AWS Lambda에 Flask 띄우기

by pandatta 2023. 2. 25.

안녕하세요, 정말 오랜만에 플라스크(Flask)로 돌아왔습니다. 그 동안 회사에서는 꾸준히 플라스크를 쓰고 있었는데 딱히 하드 스킬이 업그레이드될만한 사건이 없었어서, 이번에 AWS를 이용한 플라스크 마이크로서비스(Flask microservice)를 구축해보기로 했습니다.

마이크로서비스란?

마이크로서비스란 뭘까요? 위키백과를 보시면, "마이크로서비스(microservice)는 애플리케이션을 느슨하게 결합된 서비스의 모임으로 구조화하는 서비스 지향 아키텍처(SOA) 스타일의 일종인 소프트웨어 개발 기법이다." 라고 소개하고 있습니다. 기존의 애플리케이션은 하나의 프론트엔드를 보여주기 위해 DB, 웹서버, 메시지큐, ... 등의 서비스를 모두 하나의 서버 위에 띄워 만들어 왔습니다. 이와 다르게, 2010년쯤 부터는 각각의 서비스를 별도로 운영/유지보수하고, 서비스 간 통신으로 애플리케이션을 구성해나가기 시작한 것이 바로 마이크로서비스, 또는 마이크로서비스 아키텍처(MicroService Architecture, MSA)입니다.

요즘은 많은 기업들이 사업 초기부터 AWS(Amazon Web services)GCP(Google Cloud Platform) 에서 제공하는 여러 클라우드 서비스를 연결해 MSA를 구축합니다. 클라우드 서비스는 EC2와 같은 단순 컴퓨팅 서버뿐만 아니라, RDS/DynamoDB 같은 SQL/NoSQL DB, Route53 같은 도메인 서버, 람다(Lambda) 같은 서버리스 컴퓨팅 서비스까지 제공하므로, 회사에서 온프레미스(on-premise)로 서버를 여러 대 사서 마이크로서비스를 구축하는 것보다 훨씬 효율적입니다. 사업 초기에는 하나의 서버 위에 단일 서비스, 즉 모노리스(monolith) 구조로만 애플리케이션을 구성하다가, 사업이 커지면서 클라우드 서버 위 마이크로서비스 아키텍처로 전환해나가는 회사도 많습니다.

플라스크 마이크로서비스? 람다? 자파?

플라스크(Flask)는 파이썬(Python) 몇 줄만으로 웹 애플리케이션을 만들 수 있는 웹 프레임워크(web framework)입니다. 장고(Django)패스트API(FastAPI)와 함께 파이썬 웹 프레임워크 삼대장으로 불리며, 업계에서도 실제로 많이 사용됩니다. 저도 회사에서 많이 사용하고, 사용도 간단하기 때문에 플라스크로 마이크로서비스를 구축해보고자 했고, 클라우스 서비스는 역시 회사에서 사용하고 익숙한 AWS를 사용하고자 했습니다.

마이크로서비스의 제일 첫 단계는 무엇보다도 웹 애플리케이션을 띄워보는 것입니다. EC2처럼 온전한 컴퓨팅 서버를 대여해서 보통의 서버에서 엔진엑스(NGINX)같은 웹 서버를 깔고, 구니콘(Gunicorn)같은 WSGI(Web Server Gateway Interface) 위에 플라스크 웹 애플리케이션 서버(Web Application Server, WAS)를 띄워도 되지만, 이렇게 설명만 들어도 귀찮습니다;; 이런 방식도 나름의 장단점이 있어 이렇게 구성하는 서비스도 많으니, 궁금하시면 제 포스트 - Flask 블로그 제작기 (2) - Nginx와 Gunicorn로 Flask 배포를 보시고 구축해보세요!

요즘 널리 사용되는 방법은, AWS 람다와 같은 서버리스(serverless) 컴퓨팅 서비스를 사용하는 것입니다. 서버리스 서비스는 이름 그대로 사용자가 서버에 대해 고민할 필요 없이, 애플리케이션을 구성하는 코드만 서비스에 맡기면 애플리케이션을 띄워주고, 외부에서 애플리케이션에 접근할 때마다 적절한 서버 자원을 클라우드에서 할당해줍니다. 서비스 이름이 람다인 이유도 한 줄 짜리 람다 함수처럼 한 줄짜리 코드도 애플리케이션으로 만들어주기 때문이겠죠?

람다가 사용할 수 있는 애플리케이션 코드를 짜려면, 람다가 구성한 애플리케이션이 외부에서 들어오는 HTTP 요청을 받아서 던져주는 변수 이름인 event를 받는 코드를 짜야 합니다. 하지만 일반적인 플라스크 애플리케이션은 Flask 객체를 만들어서 해당 객체에서 직접 HTTP 요청을 받는 형식이기 때문에, 람다에서 event를 받으면 이를 잘 변환해서 Flask 객체에 HTTP 요청으로 던져주는 추가적인 코드가 필요합니다. 이를 해주는 서드파티(third-party) 프레임워크가 바로 자파(Zappa)입니다. 자파는 람다와 플라스크(또는 장고) 애플리케이션 사이의 다리 역할뿐 아니라, 플라스크 애플리케이션을 람다에 대신 배포(deploy)해주는 기능도 있습니다.

저는 gnyiii님 블로그자파 GitHub 설명을 참고해서 플라스크 애플리케이션을 성공적으로 람다에 배포할 수 있었습니다.

그럼 해보자! 일단 가상환경 설치부터

자파는 파이썬 가상환경(virtual environment)를 만들어 그 가상환경 째로 람다에 배포하므로, 가상환경 구성이 무엇보다 중요합니다(저도 여기서 많이 헤맸습니다). 공식 설명에는 pyenv 로 가상환경을 만들지만, 저는 익숙하고 가벼운 virtualenv 로 만들었습니다. 콘다(conda)pipenv도 가능하다고는 하는데... 저는 처음에는 콘다로 하려다가 피를 봤습니다ㅠ pip(package installer for python)는 파이썬 사용자라면 다들 있다고 생각하고 진행하겠습니다.

$ pip3 install virtualenv  # virtualenv 라이브러리 설치
$ virtualenv venv  # venv라는 이름의 가상환경 생성
$ source venv/Scripts/activate  # 가상환경 실행

이제 venv라는 이름의, 여러분의 컴퓨터 기본적인 운영체제(Operating System, OS) 위에 별도의 가상환경이 생겼습니다(종료는 deactivate라고 입력하면 됩니다). venv/Scripts/에 여러분의 파이썬과 pip가 복사되어 들어갔기 때문에, 가상환경 위에서 설치되는 모든 pip 라이브러리는 여러분의 원래 pip 라이브러리와 독립하여 실행됩니다. 따라서 플라스크와 자파가 이미 설치되어있더라도 다시 설치해야합니다.

$ pip3 install Flask
$ pip3 install zappa

+) 자파 설치 도중에 cp949 codec ... 어쩌구 에러가 나시는 분들은, 자파에 필요한 카파(kappa) 라이브러리 설치 도중에 한국어 운영체제 코덱을 지원하지 않기 때문에 에러가 나는 것입니다. 다음과 같이 하시면 해결됩니다.

$ git clone https://github.com/garnaat/kappa -b 0.6.0 # 카파 GitHub에서 직접 소스를 받습니다
$ cd kappa
$ # setup.py의 return open(os.path.join(os.path.dirname(__file__), fname)) 줄을
$ # return open(os.path.join(os.path.dirname(__file__), fname), encoding='utf-8')로 변경
$ # 이제 카파는 운영체제가 한국어 코덱이어도 한국어로 읽지 않습니다
$ pip install .  # 직접 카파를 설치합니다
$ cd ..
$ pip install zappa # 자파를 다시 설치합니다

세상 간단한 플라스크 웹 애플리케이션 제작

플라스크는 정말 간단하게 웹 애플리케이션을 만들 수 있습니다. flask라는 프로젝트 이름을 한 빈 폴더를 하나 만들고, flask.py(이름이 달라도 됩니다) 라는 이름을 한 파일을 만든 다음 아래와 같이 작성해줍니다.

from flask import Flask

app = Flask(__name__)  # 파일 이름과 같은 Flask 앱 객체를 만듭니다

@app.route("/")  # "/" 경로로 들어오면 이 함수를 마주칩니다
def hello():
    return "Hello, World!"  # "/" 경로로 들어오면 "Hello, World!"를 출력합니다

이제 플라스크 웹 애플리케이션을 실행해봅시다.

$ export FLASK_APP=flask  # Flask 객체가 선언된 파일명을 앱으로 지정합니다
$ flask run

127.0.0.1:5000 또는 localhost:5000으로 접속하면, Hello, World!가 출력됩니다. 웹 브라우저가 내 컴퓨터 로컬호스트(localhost)의 5000번 포트(기본값입니다)로 접속해서 그 루트(root, /) 경로로 라우팅(routing)되는 hello() 함수를 실행합니다.

AWS IAM 설정

이제 이 플라스크 애플리케이션을 자파를 통해 AWS 람다에 띄우고 싶은데요, 이를 위해 자파가 내 AWS 계정을 조작할 수 있도록 권한을 주어야합니다.

AWS에는 AWS IAM(Identity and Access Management)이라는 AWS 권한 관리 서비스가 있습니다. AWS에 회원가입 및 로그인 후, 화면 우측 상단의 내 아이디를 클릭하여 나오는 보안 자격 증명에 들어가 하단 액세스 키 만들기 에서 루트 액세스 키(root access key)를 만들어봅시다. 이 때 Access Key IDSecret Access Key를 발급받아 어딘가에 메모해두도록 합시다.

자파는 AWS CLI(Command Line Interface)를 통해 내 AWS 계정을 조작합니다. AWS CLI를 설치한 뒤, 커맨드라인에서 다음과 같이 AWS 계정 정보를 입력하면 됩니다.

$ aws configure
AWS Access Key ID [None]: QWER********
AWS Secret Access Key [None]: asdf********
Default region name [None]: ap-northeast-2  # 한국은 기본이 ap-northeast-2입니다
Default output format [None]: json  # 입력하지 않아도 됩니다

(마침내) 자파로 람다에 플라스크 애플리케이션 배포

이제 자파는 aws 커맨드를 이용해 우리의 AWS 계정을 조작할 수 있습니다. 우리가 만들었던 플라스크 웹 애플리케이션을 자파를 통해 람다로 배포해봅시다.

우선 웹 애플리케이션 flask.py가 있는 폴더에서, 다음과 같이 간단한 설정이 필요합니다.

$ zappa init
...  # 1. 람다 함수의 이름에 사용될, 가상환경의 이름을 정합니다
What do you want to call this environment (default 'dev'): dev
...  # 2. 람다 함수 코드가 저장될 S3 버킷을 자동으로 생성합니다
Your Zappa deployments will need to be uploaded to a private S3 bucket.
If you don't have a bucket yet, we'll create one for you too.
What do you want to call your bucket? (default 'zappa-qwerty1234')
...  # 3. 플라스크 애플리케이션을 인식합니다
It looks like this is a Flask application.
What's the modular path to your app's function?
This will likely be something like 'your_module.app'.
We discovered: flask.app
"Where is your app's function? (default 'flask.app'): flask.app
...  # 4. ap-northeast-2 외의 다른 곳에서 서비스할지 정합니다
Would you like to deploy this application globally? (default 'n') [y/n/(p)rimary]: n
...  # 5. 설정을 만들어 저장합니다
Okay, here's your zappa_settings.json:
{
    "dev": {
        "aws_region": "ap-northeast-2",
        "app_function": "flask.app",
        "profile_name": "default",
        "project_name": "flask",
        "runtime": "python3.9",
        "s3_bucket": "zappa-qwerty1234"
    }
}
Does this look okay? (default 'y') [y/n]: y

여기서 몇 가지 알고 넘어가야 하는 사항이 있습니다.

  1. 람다 함수의 이름은 {프로젝트명}-{가상환경명}이 됩니다. 프로젝트명은 프로젝트 폴더 이름, 여기서는 flask이고, 가상환경명은 지정해준 값, 여기서는 dev이므로, 람다 함수 이름은 flask-dev가 됩니다.
  2. 람다 함수 코드는 AWS S3 버킷에 저장됩니다. AWS S3는 아무 파일이나 저장할 수 있는 서비스이고, 버킷은 코드를 담는 바구니입니다.
  3. 자파가 플라스크 애플리케이션을 자동 인식하지만, 장고가 설치되어있을 경우 장고로 인식하지만, 그냥 아무 값이나 입력하고 넘어가도 나중에 바꿀 수 있으므로 상관없습니다. zappa_settings.json 파일을 열어서 위와 같은 서식으로 변경하고, "app_function": "{파일명}.{플라스크 객체명}"으로 적어주세요.

이제 마지막 단계입니다. 자파를 배포해봅시다!

$ zappa deploy dev  # 아까 설정한 가상환경 이름입니다
Downloading and Installing dependencies..
...
Uploading flask-dev-******.zip (*.*MiB)..  # 전체 가상환경이 zip 압축 업로드됩니다
...
Deploying API Gateway..  # 람다 함수를 AWS API Gateway에 연결해 URL을 자동으로 만들어줍니다
...
Deployment complete! https://q1w2e3r4t5y6.execute-api.ap-northeast-2.amazonaws.com/dev

주어진 https://q1w2e3r4t5y6.execute-api.ap-northeast-2.amazonaws.com/dev로 접속하면, Hello, World!가 출력되는 걸 확인할 수 있습니다. 이제 여러분의 웹 애플리케이션이 자파를 통해 AWS S3에 업로드된 뒤 AWS 람다를 통해 서비스되기 때문에, 이를 AWS API Gateway로 접근할 수 있는 것입니다. AWS 콘솔을 통해 AWS 람다로 접속하면 서비스를 확인할 수 있습니다.만약 URL을 받지 못했거나 제대로 출력되지 않았다면 가상환경 설정 문제일 것이므로, 제일 위의 가상환경 셋팅부터 차근차근 진행해보도록 합시다. 저는 콘다 가상환경과 virtualenv 가상환경의 pip 라이브러리 간에 충돌이 있어서 헤맸던 경험이 있습니다.

하지만 이와 같은 업로드는 동봉된 라이브러리가 조금만 크더라도 람다의 용량 제한 250MB에 걸린다는 한계가 있습니다. 따라서 다음 시간에는 플라스크 웹 애플리케이션이 담긴 도커(Docker) 이미지를 자파를 통해 AWS ECR(Elastic Container Registry)에 업로드하여 AWS 람다로 서비스해봄으로써, 람다의 도커 이미지 용량 제한이 10GB라는 점을 이용해 라이브러리 용량 제한 문제를 극복해보도록 하겠습니다.

댓글