간단한 Python Flask 앱을 빌드하고 AWS Elastic Beanstalk(EB)에 배포하여 관리형 로드 밸런서, 상태 검사(Health Check), 쉬운 재배포 기능을 갖춘 퍼블릭 접속 가능 앱을 만들어 보겠습니다. 이 과정은 EC2를 수동으로 설정하는 번거로움 없이 "로컬에서는 잘 돌아가는데, 어떻게 안정적으로 호스팅할까?"라는 고민을 해결해 줍니다. 사전 준비 사항으로는 Python 3.12 이상, Git, AWS 계정, Elastic Beanstalk/EC2/S3에 대한 IAM 권한, 그리고 설치 및 구성이 완료된 AWS CLI(또는 AWS SSO)가 필요합니다.
설정: 로컬 환경, 도구 및 프로젝트 구조
Elastic Beanstalk 배포는 앱이 가상 환경 워크플로우와 명확한 엔트리 포인트(Entry point)로 패키징되어 있을 때 가장 잘 작동합니다. 여기서는 EB CLI를 사용하여 환경을 생성하고 버전을 푸시하겠습니다.
먼저 프로젝트 폴더와 가상 환경을 생성하고 필요한 의존성 패키지를 설치합니다.
mkdir flask-eb-demo
cd flask-eb-demo
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install Flask==3.0.3 gunicorn==22.0.0
pip freeze > requirements.txt
예상 결과: Flask와 Gunicorn이 나열된 requirements.txt 파일이 생성되고, 셸 프롬프트에 가상 환경이 활성화된 상태가 표시됩니다.
⚠️ 참고: Elastic Beanstalk의 Python 플랫폼은 프로젝트 루트 디렉토리에 있는 requirements.txt를 통해 의존성을 파악합니다. 이 파일이 없거나 하위 폴더에 있으면 배포는 "성공"하더라도 실행 시 패키지 누락으로 앱이 작동하지 않을 수 있습니다.
다음으로 AWS CLI와 EB CLI를 설치하고 구성합니다. EB CLI는 EB 애플리케이션 및 환경을 생성하고 버전을 배포하는 데 사용되는 도구입니다.
# AWS CLI (macOS Homebrew 기준)
brew install awscli
# EB CLI
pip install awsebcli==3.20.10
# 설치 확인
aws --version
eb --version
예상 출력: 두 도구의 버전 정보(예: aws-cli/2.x 및 EB CLI 3.20.10)가 표시되어야 합니다.
마지막으로 최소한의 프로젝트 구조를 만듭니다. 이러한 레이아웃은 Flask 앱을 명시적으로 관리하고 EB 엔트리 포인트를 쉽게 참조할 수 있게 해줍니다.
mkdir -p app
touch app/__init__.py app/web.py .gitignore
cat > .gitignore << 'EOF'
.venv/
__pycache__/
*.pyc
.DS_Store
.elasticbeanstalk/
EOF
구현: EB 친화적인 Flask 앱 빌드
Elastic Beanstalk의 Python 플랫폼은 일반적으로 Gunicorn 뒤에서 앱을 실행하므로, WSGI 호출 가능 객체(callable)를 노출하고 환경 변수를 통해 설정을 관리하는 것이 좋습니다. 이렇게 하면 비밀 정보를 코드에 직접 작성(Hardcoding)하는 것을 방지하고 EB가 설정을 주입하는 방식과 일치시킬 수 있습니다.
상태 검사 엔드포인트와 간단한 인덱스 라우트를 포함한 Flask 앱을 작성합니다.
from __future__ import annotations
import os
from dataclasses import dataclass
from flask import Flask, jsonify
@dataclass(frozen=True)
class Settings:
app_name: str
environment: str
def load_settings() -> Settings:
return Settings(
app_name=os.environ.get("APP_NAME", "flask-eb-demo"),
environment=os.environ.get("APP_ENV", "local"),
)
def create_app() -> Flask:
settings = load_settings()
app = Flask(__name__)
@app.get("/")
def index():
return jsonify(
message="Hello from Flask on Elastic Beanstalk!",
app_name=settings.app_name,
environment=settings.environment,
)
@app.get("/health")
def health():
return jsonify(status="ok")
return app
app = create_app()
이제 로컬 개발용 실행 스크립트를 추가합니다. EB는 프로덕션에서 이를 사용하지 않지만, 로컬 테스트 시 유용합니다.
from __future__ import annotations
import os
from app.web import app
if __name__ == "__main__":
port = int(os.environ.get("PORT", "5000"))
app.run(host="0.0.0.0", port=port, debug=True)
AWS에 올리기 전에 로컬에서 실행하여 모든 것이 정상 작동하는지 확인합니다.
source .venv/bin/activate
python app/__init__.py
예상 출력: Flask가 http://127.0.0.1:5000에서 서비스 중임을 알리는 로그가 표시됩니다. / 경로에 접속하면 JSON이 반환되어야 하며, /health는 {"status":"ok"}를 반환해야 합니다.
⚠️ 참고: Flask의 내장 개발 서버를 사용하여 배포하지 마세요. EB는 프로덕션용 WSGI 서버(Gunicorn)를 기대합니다. 프로덕션에서
app.run()에 의존하면 부하가 걸릴 때 동작이 불안정해지거나 로드 밸런서 뒤에서 타임아웃이 발생할 수 있습니다.
구현: Elastic Beanstalk 구성 (Gunicorn 및 상태 검사)
Elastic Beanstalk은 앱을 실행하는 방법을 알아야 합니다. Python의 경우, Gunicorn을 시작하고 WSGI 앱 객체를 가리키는 Procfile을 사용하는 것이 일반적인 방식입니다.
저장소 루트에 Procfile을 생성합니다.
cat > Procfile << 'EOF'
web: gunicorn --bind 0.0.0.0:${PORT:-8000} app.web:app
EOF
예상 결과: EB가 PORT 환경 변수로 제공된 포트를 사용하여 Gunicorn으로 앱을 시작할 수 있게 됩니다.
다음으로, 상태 검사를 개선하고 로드 밸런서가 작성한 /health 엔드포인트를 사용하도록 EB 플랫폼 구성을 추가합니다. 이는 설정 파일 내의 EB "옵션 설정(option settings)"을 통해 수행합니다.
mkdir -p .ebextensions
cat > .ebextensions/01-healthcheck.config << 'EOF'
option_settings:
aws:elasticbeanstalk:application:
Application Healthcheck URL: /health
EOF
예상 결과: EB 콘솔의 상태 정보가 단순히 "포트 열림"이 아닌, 앱의 실제 상태 검사 엔드포인트 결과를 반영하게 됩니다.
⚠️ 참고: 상태 검사 경로가 리다이렉트를 반환하거나, HTML 에러 페이지를 보여주거나, 인증을 요구하는 경우 EB는 인스턴스를 비정상(unhealthy)으로 판단하고 계속해서 교체할 수 있습니다.
/health는 응답이 빠르고 인증이 필요 없도록 유지하세요.
프로젝트에서 Elastic Beanstalk을 초기화합니다. Python 플랫폼(해당 리전에서 사용 가능한 경우 Python 3.12)을 선택하고 로드 밸런서가 포함된 환경을 생성합니다.
# 먼저 AWS CLI 인증을 수행합니다 (다음 중 한 가지 방식 선택)
# 방법 A: 고정 자격 증명 (장기적으로는 권장되지 않음)
# aws configure
# 방법 B: AWS SSO (조직에서 지원하는 경우 권장)
# aws configure sso
# EB 초기화 (대화형)
eb init -p python-3.12 flask-eb-demo
eb init 도중 AWS 리전을 선택하고, 디버깅을 위해 인스턴스에 직접 로그인해야 하는 경우에만 SSH를 설정하세요.
환경을 생성합니다. "LoadBalanced" 유형을 사용하면 Application Load Balancer가 생성되며 나중에 다중 인스턴스 확장이 가능해집니다.
eb create flask-eb-demo-prod --elb-type application --instance_type t3.micro
예상 결과: 몇 분 후 EB가 http://flask-eb-demo-prod.XXXXXXXX.us-east-1.elasticbeanstalk.com과 같은 퍼블릭 URL을 출력합니다.
⚠️ 참고: 계정이 신규인 경우 환경 생성 중에 VPC/서브넷 또는 EC2 쿼터(할당량) 오류가 발생할 수 있습니다. AWS 콘솔의 EB Events 탭을 확인하세요. 보통 누락된 권한이나 쿼터 부족에 대해 상세히 설명되어 있습니다.
앱이 올바른 환경 이름과 정보를 보고할 수 있도록 EB에 환경 변수를 설정합니다. 이는 코드 변경 없이 설정을 관리하는 방법이기도 합니다.
eb setenv APP_ENV=production APP_NAME=flask-eb-demo
현재 버전을 배포합니다. EB는 저장소를 패키징하여 S3에 업로드하고 인스턴스에 배포합니다.
eb deploy
예상 결과: 배포가 "Successfully deployed" 메시지와 함께 완료되고, 상태 검사 통과 후 EB가 환경 상태를 Green/Healthy로 보고합니다.
구현: 간단한 CI 친화적 배포 워크플로우 추가 (선택 사항이지만 실용적임)
반복 가능한 배포를 원한다면 의존성 고정, Procfile 존재 확인, 단일 배포 명령 실행 등 배포 단계를 결정론적으로 유지해야 합니다. 이 섹션에서는 로컬이나 CI 환경에서 재사용할 수 있는 작은 스크립트를 추가합니다.
빠른 로컬 확인 후 배포를 수행하는 스크립트를 작성합니다.
cat > deploy.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
source .venv/bin/activate
python -c "import app.web; print('WSGI import OK:', app.web.app.name)"
curl -fsS http://127.0.0.1:5000/health >/dev/null || true
eb deploy
eb status
EOF
chmod +x deploy.sh
예상 결과: ./deploy.sh를 실행하면 "WSGI import OK"가 출력된 후 EB 배포가 진행됩니다.
⚠️ 참고: EB는 커밋된 내용이 아니라 작업 디렉토리에 있는 내용을 배포합니다. 재현 가능한 릴리스를 원한다면 (CI에서 흔히 하듯) 깨끗한 Git 체크아웃 상태에서 배포하고, 동작을 변화시킬 수 있는 추적되지 않는 파일(untracked files)이 포함되지 않도록 주의하세요.
테스트 및 확인: 앱의 정상 작동 여부 확인
먼저 브라우저에서 환경 URL을 엽니다. 메시지, 앱 이름, 환경 정보가 포함된 JSON이 보여야 합니다.
터미널을 통해 확인하면 체크 과정을 스크립트화하고 상태 코드를 확인할 수 있어 편리합니다.
# "eb status"에서 확인한 EB URL로 교체하세요
EB_URL="http://flask-eb-demo-prod.XXXXXXXX.us-east-1.elasticbeanstalk.com"
curl -i "$EB_URL/"
curl -i "$EB_URL/health"
예상 출력: 두 엔드포인트 모두 HTTP 200을 반환하며, /health 응답 본문에 {"status":"ok"}가 포함됩니다. AWS 콘솔의 EB 환경 상태(Health)는 Ok(보통 "녹색")로 표시되어야 합니다.
문제가 발생하면 로그와 이벤트를 확인하세요. EB는 가장 중요한 오류를 Events 탭에 표시하며, 전체 로그는 CLI를 통해 내려받을 수 있습니다.
eb events --follow
eb logs --all
⚠️ 참고: 가장 흔한 배포 실패 원인은 잘못된 Gunicorn 대상 지정(
app.web:app오타)이나 requirements.txt의 의존성 누락입니다. "ModuleNotFoundError"나 "Worker failed to boot" 에러가 보인다면, 재배포 전에python -c명령으로 로컬에서 임포트 경로를 다시 확인하세요.
마무리
지금까지 프로덕션용 WSGI 서버(Gunicorn)를 포함한 Flask 앱을 빌드하고, 이를 실행하도록 Elastic Beanstalk을 구성했으며, EB가 인스턴스 상태를 올바르게 관리할 수 있도록 실제 상태 검사 엔드포인트를 추가해 보았습니다. 다음 단계로는 Route 53 및 ACM TLS를 사용한 커스텀 도메인 추가, 롤링 배포 활성화, 그리고 EB 환경 변수와 보안 그룹을 통한 데이터베이스(RDS) 연결 등을 시도해 볼 수 있습니다.