본문 바로가기
Linux,Cloud

[AWS] AWS ElasticBeanstalk(EB)으로 파이썬 플라스크 사이트 배포하기

by 돈민찌 2021. 10. 14.
반응형

프론트에 이어서 백 배포 팁도 공유합니다. 여기까지 작성하고 푸쉬한 워크플로우가 200개는 넘는다는 얘기가....ㅎㅎ 덜 똑똑하면 손발이 더 고생하는거쥬 조금이라도 도움이 되시면 좋겠군요!!

우선 제가 배포하려는 사이트는 마이크로 프레임워크인 플라스크로 서버단을 구성하고, 기본적인 html과 js,css로 만든 평범한 웹사이트입니다. 데이터베이스는 몽고디비를 사용 중이라 pymongo를 사용했습니다. 기본적인 requirements.txt는 다음과 같습니다.

certifi==2021.5.30
chardet==4.0.0
click==7.1.2
Flask==1.1.4
Flask-Cors==3.0.10
gunicorn==19.10.0
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.3
MarkupSafe==1.1.1
pymongo==3.12.0
requests==2.25.1
six==1.16.0
urllib3==1.26.7
Werkzeug==1.0.1

코드블럭으로 쓰기는 했지만 이게 좋은 거니까 이걸 받아라 그런 뜻이 아닙니다. 각자의 프로젝트에서 사용한 라이브러리들이 담길 수 있도록 과정 진행할 것입니다!!

우선 EB(ElasticBeanstalk)란 무슨 뜻인지 알아볼까요?

Elastic은 탄력+신축성 있는 + Beanstalk는 잭과 콩나무에 나오는 콩나무줄기!!
네 모르겠네요 다음에 알아볼게요!! 

AWS에서는 편리한 개발을 위해 저장소로 활용할 수 있는 S3, 원격으로 서버 컴퓨터를 빌릴 수 있는 인스턴스와, 그것들을 연결하여 유기적으로 서버의 확대와 축소를 조절하는 로드 밸런서, 오토 스케일링을 제공하고 있지만 그것들을 하나하나 설정하면서 웹사이트를 배포하는 것은 번거롭고 까다롭습니다. 그래서 AWS에서는 ElasticBeanstalk를 제공합니다 한방에 S3+EC2 인스턴스+로드밸런서+NGINX서버(GUNICORN) 까지 묶어 관리할 수 있고 필요하면 RDS라는 SQL 데이터베이스도 보조 부품처럼 끼워 사용할 수 있는 종합선물세트예요 (그걸 소화하는건 개발자의 몫이지만..)

Elastic Beanstalk를 사용하는 데 드는 비용은 없지만 이 과정을 위해 생성하는 AWS 리소스는 라이브로 실행됩니다. 이 글의 마지막 부분에서 리소스를 종료하지 않으면 해당 리소스에 대한 표준 사용 요금이 발생합니다. 간단한 플라스크 웹사이트의 요금 합계는 일반적으로 1달러 미만입니다. 요금을 최소화하는 방법에 대한 자세한 내용은 AWS 프리 티어를 참조하세요!!

우선 AWS 계정을 준비합니다. 네. 하셨죠?

그리고 플라스크 애플리케이션을 생성합니다. 파일 이름도, 내부의 어플리케이션 객체 이름도 application으로 해주세요!

from flask import Flask
from flask import render_template

application = Flask(__name__)


@application.route("/")
def hello_world():
    return render_template("index.html")
    

if __name__ == "__main__":
	application.run()

물론 여러분의 프로젝트는 이것보다 더 복잡하게 만들어져있겠죠? 자 이제 리전을 선택합니다. 저는 약간 미국병이 있어서 미국 북부 리전으로 생성하였는데요. 과정 전체를 따라한다면 큰 상관 없으실 겁니다. 애매하게 보고 가시면 좀 상관이 있을거예요 SSL 인증서 관련해서... 귀찮게 굴더라구요 협박 아닙니다 진짜루..

파이참에서 플라스크 앱을 생성하셨다면, 음 파이참 프로버전을 사용하고 계시면 플라스크 템플릿이 따로 있으니 그거대로 만들어주시면 되구요. 그렇지 않으신 분들은 다음과 같이 만들어 주세요.

파일이 많아 갑자기 진도가 확 나간 느낌인데 하나씩 설명드릴게요. 일단 가장 필수적인 폴더 구조는 index.html 및 템플릿 파일들을 담을 templates(오타주의!) 폴더와 css/js/font/image 등을 보관할 static 폴더, 그리고 application.py 파일이 있습니다. 그 외에는 venv는 가상환경 생성한 폴더구요. .gitignore는 자동재배포 등을 위해 깃허브 같은 소스저장소를 사용할 때 venv나 .idea 같은 군더더기(또는 중요한 파일)들을 업로드하지 않도록, 막아주는 파일이구요. .ebignore는 엘라스틱빈스토크에 배포할 때 빼놓을 파일이나 폴더 리스트를 기록하는 걸로 내용은 거의 같습니다. LICENSE는 깃허브에서 프로젝트 형성할 때 받은 MIT 라이센스 파일이구요. Readme.md는 프로젝트 설명하는 마크다운 파일입니다 당연히 없어도 되구요. requirements는 프로젝트에 사용된 파이썬 라이브러리들을 명시해 자동 배포 시에 서버에서 알아서 필수 프로그램들을 받을 수 있도록 만들어줘야합니다. Procfile과 gunicorn_config는 이따가 설명할게요. robots.txt는 신경 안쓰셔도 됩니다. 크롤러들한테 어떤 페이지를 수집해도 되는지 알려주는 용도예요. 

이 중에 제가 필수적이라고 생각하는 것은 templates와 static, application.py 세가지는 당연히 필수구요. 그 외에는 라이센스 파일과 로봇.txt, Readme 파일 외에는 필요한 것 같습니다. 저도 여기저기서 배운거라 확실하지는 않은데, 저  파일들이 없을 때 오류 로그에서 걸렸던 적이 있어요. 다 알려드릴게요...

우선 엘라스틱 빈스토크 페이지에 들어가 앱과 그 앱을 실행할 환경을 만들어줘야 합니다. 콘솔 화면 우상단에 리전이 표시되어 있으니 잘 기억해주시구요! 꼭 우리나라(ap-northeast-2)여야 한다거나 미국(us-east-1) 이어야 한다거나 그런것은 아니지만 둘 중에 하나로 하는 것이 좋은 것 같아요. 필수는 아닌데 제 생각엔 그렇습니다.

AWS의 상징인 주황색 버튼을 클릭해 시작해봅시다 간단한 버튼 몇번의 조작으로 EC,S3 등등이 딸려온다니 돈귀신이 붙었나 너무 편리하네요!!ㅎㅎ

웹 앱을 생성할 것입니다. 저는 클라우드프론트 때 그랬던 것처럼 사이트의 주소와 똑같이 할 생각입니다. 이번엔 필수적인 것은 아니고 다른 인스턴스, S3 버킷들과 헷갈리지 않으려고 하는 거라 생각해 주시면 됩니다.

플랫폼을 선택해 줍니다. Node.js도 한번 이걸로 배포해보고 싶네요... 흔들리지 말고 파이썬 클릭합니다.

추가 옵션 구성을 클릭합니다. 

  • 소프트웨어: WSGIPath (플라스크 앱이 동작할 파이썬 파일 이름), 환경 속성 (환경변수) 편집 가능
  • 인스턴스: 인스턴스 보안그룹 선택가능
  • 용량: 로드밸런싱, 오토스케일링 조절 가능
  • 로드밸런서: 로드밸런싱 관련된 옵션
  • 롤링업데이트 및 배포: 한꺼번에 파일 전체를 배포할지 부품 하나씩 바꿔가며 배포할지
  • 보안: 인스턴스 키페어, IAM 사용자 프로필, IAM 역할
  • 모니터링: CloudWatch라는 시스템 로그 스트리밍 서비스(유료) 설정가능
  • 관리형 업데이트: 푸시나 배포를 따로 하지 않아도 기간마다 자동으로 재배포할지 옵션
  • 알림: 문제 발생 시 알려줄 이메일 기입
  • 네트워크: VPC 설정
  • 데이터베이스: RDS 설정. 데이터베이스를 외부의 것으로 사용할 경우 생략
  • 태그: 다음에 찾기 좋게 별칭이나 이름표 붙여놓기

모두 다 신경 쓰실 필요는 없구요. 소프트웨어에서 파일 이름을 application이 아닌 다른 것으로 바꿔주거나, 환경 변수를 입력할 수 있고, 로드밸런서 탭은  https 라우팅을 등록할 수 있어요. 보안 탭은 키페어 등록 가능하구요. 인스턴스 탭에서 보안그룹 설정도 가능합니다. SQL(MySQL, Aurora, PostgreDB, Maria 등) 사용하실거라면 데이터베이스 연결해서 생성하면 나중에 EB를 지울때 함께 지워지는 간편한 기능이 있습니다. 이 정도만 자세히 보시면 좋을 것 같아요 결국에 또 찾게 될 아이들입니다...

일단은 샘플 코드만 삽입해서 업로드합니다!!

그리고 확인을 누르면 약 10분 정도 검은 화면에 하얀 글씨가 올라오는 것이 보입니다. 봐둬야 도움될 내용은 나오지 않으니 그 동안 기지개 한번 펴시고 고양이 쓰다듬어주고 오세요. (없어요?? 왜 없어요....)

네 완료된 화면입니다. 크게 왼쪽 부분은 자주 들어가실 만한 곳이 환경/애플리케이션/구성/로그 정도가 있을 것 같구요. 지금 초록색으로 체크 로고가 되어있는 "상태"가 빨간색으로 변할때마다 여러분들의 숨통이 조여올 것입니다. 그리고 아래 INFO 역시 빨간색이나 주황색일때는 위험신호이니까 잘 봐주시고, 그렇게 이상신호가 있을 때마다 왼쪽의 로그 탭을 찾아주세요(저는 로그 있는 줄도 모르고 괜히 깜깜이로 고생한게 한트럭이네요...)

자 이제 구경 다 했으니까 AWS의 IAM으로 갑시다! 여기서 사용자 하나를 만들어야(수정해야) 해요. AmazonEC2FullAccess, AmazonS3FullAccess와 CloudFrontFullAccess (여기까지가 프론트클라우드 배포에 필요한 부분) 그리고 마지막으로 AdministratorAccess-AWSElasticBeanstalk까지 추가해주시면 됩니다!

전부 FullAccess인데, 일단은 공부와 테스트를 겸한 것이기 때문에 권한을 넉넉하게 허용한 거라 생각해주시면 됩니다. 많이 허락할 수록 그만큼 실수할 부분도 늘어나는 거니까 실제로 하게 될 때는 최소한 딱 맞는 권한만 허용하는 것이 좋다고 합니다. 나중에 포트여는 것도 왜 다 열지? 할 필요 없어요. 실전에 들어가면 그런거 다 정해준대욥

애플리케이션을 확인해봅시다. 위에서 여러분에게는 없지만 저에게는 있는 파일들이 몇개 있었죠? 만들어줍시다.

Procfile 이게 없으니까 잘 안되더라구요 제 생각에는 필수입니다!

web: gunicorn --worker-tmp-dir /dev/shm --config gunicorn_config.py application:application

gunicorn_config.py 파이썬 파일입니다!! Procfile에서 참고하는 내용이니 정확하게 제목 입력!!

bind = "0.0.0.0:8000"
workers = 3

.gitignore 와  .ebignore (gitignore가 존재하고, 리포지토리에 실수로 잘못 올린 파일이 없다면 eb...는 생략 가능) 이렇게 많은 파일을 막을 필요가 있나요? 아뇨 없습니다. 실제로 이 중에 여러분의 작업환경에 있을 만한 것은 .idea, venv 정도예요. 다양한 프로젝트 환경에 맞춘 템플릿이니 그대로 써주셔도 무방합니다.

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
.idea/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
node_modules/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

그럼 이제 requirements.txt를 만들어보겠습니다.

여러분이 작업 중이신 Virtual environment (venv) 에서 pip freeze를 해야합니다. 평소에 버전관리에 신경써오신 분들은 이 부분이 무리 없이 잘 하실 수 있을텐데, 그렇지 않으면 좀 어려울 수 있습니다. 가장 중요한 것은 AWS의 배포 시스템(GUNICORN 사용)은 requirements.txt를 읽어들이고 파이썬 환경을 형성합니다. 여러분의 컴퓨터 전체에 깔려있는 모든 pip list가 들어가서는 안되고, 그렇다고 딱 import 한 것만 들어가도 안됩니다. (의존성 때문)

지금 작업 중이신 환경에서 pip freeze를 입력해보세요. 제가 위에 작성한 것보다 훨씬 많은 모듈들이 나타나나요??? 평소에 사용하시던 모듈들이 모두 나타난다면, 가상환경을 사용 중이지 않으시다는 겁니다!

1. 가상환경이 뭐죠? 그런거 해본 적 없는 경우: 가상환경 pip를 설치하고 실행해주세요!

이 두 커맨드를 입력해주세요.

2. 저는 이미 venv가 있어요 안쓰고 있을 뿐! => 가상환경 활성화하는 방법:

3. 이제 이 상태에서 pip freeze를 해봅시다

평소 버전관리를 귀신같이 하고 계시다면 글로벌 pip 보다 가상환경의 pip이 더 많을 수도 있습니다. 그렇지 않다고 제대로 관리하고 있지 않은 것은 아니지만요.

혹시 지금 개발 중인 프로젝트의 중요한 라이브러리가 모두 포함되어 있는지 확인해주시고 없다면 venv 활성화 상태에서 pip install 라이브러리명 입력해서 받아주세요!!

4. requirements 만들기: 

pip freeze > requirements.txt

이제 파일이 하나 생기셨을 겁니다!!! 아잇 거의 다 왔습니다.

이제 깃허브로 가볼 시간입니다.

자동 재배포를 사용하기 위해서 리포지토리 secrets를 셋팅해야겠죠?

저는 프론트클라우드와 모두 같은 IAM을 사용해서 따로 추가한 것이 없습니다. (오히려 버킷네임과 디스트리뷰션은 사용하지 않게 됐네요) 그러니까 앞 글을 따라 오셨다면 따로 하실 일이 없습니다. 그렇지 않으시다면 IAM에서 만든 사용자의 액세스키와 시크릿키를 여기에 업데이트해주세요!

지난번에 CloudFront 때처럼 actions를 활용해서 자동재배포를 구축할 것입니다. 이번에도 미리 짜둔 스크립트 (저도 받은거이긴 합니다)를 사용할게요. // 뒤에 있는 글들은 실제로 사용할 때에는 생략해주세요!

위치: .github/workflows/파일명.yml

frontcloud와 elasticbeanstalk 각각의 yml

name: myprojectb // 이름은 큰 상관없지만 같은 리포에 CloudFront도 했다면 구분해주세요!
on:
  push:
    branches:
      - release // 저는 release 배포용 브랜치를 따로 뛌지만 main이나 master에 하셔도 됩니닿ㅎ
jobs:
  build:
    runs-on: ubuntu-18.04 // ubuntu-latest 버전도 괜찮습니다.
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} // 시크릿에 등록해야 합니다!
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} // 시크릿에 등록해야 합니다!
      AWS_REGION: 'us-east-1'  // 미국 북부만 챙기는 아마존....! 서울로 해도 됩니다!

    steps:
      - name: Checkout source code.
        uses: actions/checkout@master // 역시 actions/checkout 리포 참고하셔도 됩니다

      - name: Set up Python 3.8  // 파이썬 버전은 3.8! 개발 환경도 맞춰주세요!
        uses: actions/setup-python@v1  / /파이썬 환경을 설치하는 액션입니다!
        with:
          python-version: "3.8"

      - name: Generate deployment package  // .git+.gitihub를 제외한 모든 파일을 압축합니다
        run: zip -r deploy.zip . -x '*.git*'  // deploy라는 이름으로요!

      - name: Get timestamp
        uses: gerred/actions/current-time@master  // 현재 시각을 배포명에 넣기 위한 사전작업!
        id: current-time

      - name: Run string replace
        uses: frabert/replace-string-action@master  // 현재 시각을 y-m-d 형식으로 바꾸는 액션
        id: format-time
        with:
          pattern: '[:\.]+'  // 모든 :콜론과 .도트를 찾습니다.
          string: "${{ steps.current-time.outputs.time }}"
          replace-with: '-'  // 모두 -하이픈으로 바꿔줍니다!
          flags: 'g'

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v18
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: small-meal  // 배포할 애플리케이션 이름
          environment_name: Smallmeal-env  // 배포할 애플리케이션 환경 이름
          wait_for_deployment: true  // 배포가 될 때까지 기다려줍니다! 기본적으로 true라 생략해도 됩니다.
          // wait_for_environment_recovery 라는 속성도 있습니다. 환경이 불안정(심각)할 때 회복되기를 계속 기다립니다.
          version_label: "python-${{ steps.format-time.outputs.replaced }}"
          region: us-east-1  // 리전을 한번 더 적어주세요!
          deployment_package: deploy.zip  // 배포할 파일은 deploy.zip이라는 이름으로 올라갑니다!

네 제 경우처럼 따로 릴리즈(배포)용 브랜치를 설정해뒀다면 거기에 그게 아니라 main 이나 master에 하셨다면 그곳에 지금까지 작업하신 파일을 커밋 푸시해주세요! 그럼 이제 actions에 들어가봅니다

뭔가 노란색으로 진행되고 있다면 잘 되고 있는 겁니다 자세히 들여다보세요.

다른 단계는 큰 문제 없이 잘 될 것이고, deploy to EB 부분이 잘 막힐 수 있습니다. 막혔을 때 체크해야 할 것들 말씀드릴게요. 

첫번째. 액션에서 빨간 글씨가 나오며 깃허브 액션이 중단되었다!

 - 위의 사진 속 액션 탭에서 나오는 오류 로그를 읽고 대처한다!!

두번째. 액션에서는 빨간색 글씨가 나타나지는 않지만 노란색으로 경고 같은 것이 뜨고 끝나버렸다?

 - 배포 중인 엘라스틱 빈스톸의 상태를 확인하러 가야합니다.

물론 이렇게 초록색 사인이 나타나 있지 않으실 수 있습니다. 근데 밑의 이벤트 로그만 가지고 오류를 파악하기가 힘들 것입니다. 그럴 때 왼쪽에 네비게이션에 "로그" 탭을 누릅니다.

마지막(tail) 100줄만 출력해서 한 파일로 보여달라고 합니다. 그러면 아래 다운로드하라는 컨텐츠가 생기는데, 클릭해보시면 보통 다운로드가 아닌 바로 볼 수 있는 새 창이 나타납니다.

이렇게 내용을 훑어보면서 WARN 표시가 되어 있는 줄에 어떤 문제가 있는지 파악하고 그 부분을 수정해 다시 푸시해야 합니다. 보통 localhost 로 몽고디비 같은 데이터베이스를 사용 중이거나, 사용할 수 없는 커맨드가 코드에 들어가 있을 때 나타납니다. xray와 httpd 이 두가지는 유료 기능인데다 핵심적으로 중요한 부분은 아니니 생략하셔도 됩니다. 저는 몽고디비로 좀 애를 먹다가, 몽고디비를 아예 다른 인스턴스를 만들어 몽고디비만 가동하는 것으로 대체했습니다. 위에서 보신 분들도 있겠지만 EB 자체에 RDS라는 SQL(MySQL, Maria, Aurora... 등)을 직접 연결해 사용할 수도 있고 이렇게 하는것이 성능적으로 우수하기도 하지만, 데이터베이스와 서버가 한 공간에 있는 만큼 위험부담도 커지고, 또 NoSQL 작업 중에 SQL로 쿼리문을 작성하는 과정이 매우 지난하고 힘들기 때문에 flask 작업 중에는 굳이 추천하지 않습니다. JPA와 같이 ORM(Object Relational Mapping - 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것)을 지원하는 스프링 같은 작업환경이 아니라면 굳이 RDS를 구현하려고 직접 SQL 쿼리문을 작성해야 하는 PyMySQL이나 flask_sqlalchemy 등을 미리 맛보실 필요는 없습니다(웃지마 내 얘기야...)

그리고 EB와 CloudFront의 역할을 딱딱 나누어서 한쪽은 웹을 한쪽은 서버를 구현할 계획이시라면 flask_cors 모듈을 설치해서 사용해 주셔야 합니다. 아래처럼요.

# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify, render_template, json
from flask_cors import CORS
from pymongo import MongoClient  # 몽고디비
import requests  # 서버 요청 패키지
import copy
import os


application = Flask(__name__)
cors = CORS(application, resources={r"/*": {"origins": "*"}})
if application.env == 'development':
    os.popen('mongod')
# 배포 전에 원격 db로 교체!
client = MongoClient(os.environ.get("DB_PATH"))

그리고 혹시 인코딩 문제가 생기시는 분들은 제 코드의 맨 윗부분에 있는 # -*- coding: utf-8 -*- 부분을 그대로 붙여넣어주세요!!! 그럼 이만...

자동재배포의 꿈을 버리지 못하고 수없이 실패한 동민이에게 위로와 칭찬을....

 

반응형

댓글