Flask on Lambda — Zappa로 Flask를 서버리스로
Zappa가 하는 일, CORS 두 층, ephemeral storage 함정까지
Flask on Lambda
Python 서버를 AWS Lambda에 올리는 Zappa의 작동 원리와 실전 주의사항
Zappa란 무엇인가
Flask는 원래 서버에서 항상 실행되는 방식을 가정합니다. Lambda는 요청이 있을 때만 실행되는 함수입니다. Zappa는 이 둘을 연결합니다 — API Gateway가 HTTP 요청을 받으면 Zappa가 그것을 Flask가 이해하는 WSGI 요청으로 변환합니다.
| Zappa가 하는 일 | 설명 |
|---|---|
| 패키지 번들링 | venv의 패키지와 Flask 코드를 ZIP으로 묶어 Lambda에 업로드 |
| WSGI 변환 | API Gateway의 이벤트 객체를 Flask가 이해하는 WSGI environ으로 변환 |
| API Gateway 자동 생성 | Lambda를 호출하는 API Gateway 엔드포인트 자동 생성 |
| IAM 권한 설정 | API Gateway → Lambda 호출 권한 (resource-based policy) 자동 부여 |
zappa_settings.json 핵심 설정 해설
📋 zappa_settings.json — 각 필드 의미
{
"prod": {
"app_function": "samples.api.app.app", // Flask app 객체 위치
"aws_region": "ap-northeast-2", // 서울 리전
"profile_name": "my-aws-profile", // AWS CLI 프로파일
"project_name": "my-project-api", // Lambda 함수 이름 prefix
"runtime": "python3.12",
"python_path": "/opt/homebrew/opt/python@3.12/bin/python3.12",
// ⭐ 핵심: manylinux 배포 — Lambda(Linux)에서 C extension 라이브러리 동작
"use_precompiled_packages": true,
"manylinux": true,
"s3_bucket": "my-zappa-deploy-bucket", // 배포 ZIP 임시 저장소
"role_arn": "arn:aws:iam::123:role/my-lambda-role",
"timeout_seconds": 30,
"memory_size": 512,
"ephemeral_storage": {"Size": 1024}, // ⚠️ 중요: 기본 512MB → 1024MB
// Keep-warm: EventBridge가 5분마다 Lambda를 깨워 cold start 방지
"keep_warm": true,
"keep_warm_expression": "rate(5 minutes)",
"api_key_required": false,
"cors": false // ⚠️ CORS는 Flask-CORS로 처리
}
}
cryptography(PyJWT 의존성), cffi 같은 C extension 라이브러리는 이 설정 없이는 Lambda에서 ImportError가 납니다.deploy vs update — 언제 어떤 명령을 쓰는가
| 상황 | 명령 | 이유 |
|---|---|---|
| 처음 Lambda + API GW 생성 | zappa deploy prod | 인프라 신규 생성 |
| 코드 수정 후 재배포 | zappa update prod | 코드만 교체 (빠름) |
| zappa_settings.json 변경 | zappa update prod | 대부분 update로 적용됨 |
| ephemeral storage 크기 변경 | update 후 콘솔에서 수동 확인 | Zappa가 변경 안 하는 경우 있음 |
zappa update가 ephemeral storage를 업데이트하지 못하는 경우가 있습니다. Lambda 콘솔 → 함수 → 구성 → 일반 구성에서 임시 스토리지가 1024MB인지 확인하세요. 이 값이 512MB이면 큰 패키지 설치 시 No space left 오류가 납니다.Cold Start와 Keep-Warm — 첫 요청이 느린 이유
Lambda는 요청이 없으면 컨테이너를 종료합니다. 다음 요청이 올 때 컨테이너를 새로 시작하는 데 걸리는 시간이 Cold Start입니다. Python + Flask는 보통 1~3초 정도 걸립니다.
| 상태 | 응답 시간 | 설명 |
|---|---|---|
| Cold Start | 1~3초 추가 지연 | 15~20분 요청 없을 때 컨테이너 종료 후 재시작 |
| Warm | 정상 속도 | 컨테이너가 이미 실행 중 — 코드만 실행 |
Keep-Warm 작동 방식
EventBridge(구 CloudWatch Events)가 5분마다 Lambda를 가짜 요청으로 깨웁니다. Lambda는 이것이 실제 HTTP 요청이 아닌 것을 감지하고 즉시 응답합니다.
📋 zappa_settings.json Keep-Warm 설정
"keep_warm": true,
"keep_warm_expression": "rate(5 minutes)"
events.amazonaws.com이 Lambda를 호출할 수 있도록 권한이 필요합니다 (Module 04 IAM에서 다룹니다).Ephemeral Storage 함정 — /tmp가 512MB밖에 안 된다
Zappa는 배포 시 패키지를 /tmp에 압축해제합니다. 기본 Lambda /tmp 크기는 512MB입니다. PyJWT, cryptography, 각종 의존성을 합치면 쉽게 초과합니다.
| 기본값 | 권장값 | 변경 방법 |
|---|---|---|
| 512MB | 1024MB | zappa_settings.json에 "ephemeral_storage": {"Size": 1024} 추가 후 update — 콘솔에서 수동 확인 필수 |
📋 오류 증상
# Lambda 실행 시 아래 오류 발생
[ERROR] Runtime.ImportModuleError: No module named 'cryptography'
# 또는
OSError: [Errno 28] No space left on device
CORS 두 층 구분 — 왜 두 곳에 설정해야 하는가
CORS는 브라우저가 다른 도메인의 API를 호출할 때 적용되는 보안 정책입니다. 우리 구조에서는 https://xxxx.cloudfront.net(프론트엔드)이 https://yyyy.execute-api.ap-northeast-2.amazonaws.com(백엔드)을 호출합니다 — 다른 도메인이므로 CORS가 적용됩니다.
| 계층 | 담당 | 설정 방법 | 처리 요청 |
|---|---|---|---|
| API Gateway | OPTIONS preflight 자동 응답 | zappa_settings.json에 "cors": true | 브라우저가 보내는 사전 확인 요청 |
| Flask-CORS | 실제 요청(POST, GET) 응답 헤더 추가 | CORS(app, resources={r"/api/*": …}) | 실제 API 요청의 응답 헤더 |
CORS policy: No 'Access-Control-Allow-Origin' 에러 발생.📋 Flask-CORS 설정 (app.py)
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={
r"/api/*": {
"origins": [
"https://xxxx.cloudfront.net", # 프로덕션 CloudFront
"http://localhost:3000", # 로컬 개발
],
"methods": ["GET", "POST", "OPTIONS"],
"allow_headers": ["Authorization", "Content-Type"],
}
})
zappa_settings.json의 "cors": true와 Flask-CORS는 역할이 다릅니다. Zappa의 cors는 API Gateway 레벨에서 OPTIONS 자동 응답만 처리합니다. Flask-CORS는 실제 응답 헤더를 추가합니다. 두 설정이 모두 있어야 CORS가 완전히 동작합니다.API Gateway 타입 — REGIONAL을 써야 하는 이유
📋 Zappa에서 REGIONAL 설정
{
"prod": {
"api_gateway_endpoint_type": "REGIONAL", // 반드시 명시
...
}
}
조건부 import 패턴 — Lambda와 로컬 환경 분기
Lambda 환경과 로컬 개발 환경은 다릅니다. Lambda에는 dotenv가 없고, GS Retail 사내망에는 truststore가 필요합니다. 두 경우를 모두 처리하는 조건부 import 패턴을 씁니다.
📋 app.py — 조건부 import
import os
# 로컬 개발 시에만 .env 파일 로드 (Lambda에는 없음)
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass # Lambda 환경 — 환경변수는 zappa_settings.json에서 주입
# GS Retail 사내망 SSL truststore (회사 CA 인증서)
try:
import truststore
truststore.inject_into_ssl()
except ImportError:
pass # truststore 미설치 환경에서는 무시