Module 02

Flask on Lambda — Zappa로 Flask를 서버리스로

Zappa가 하는 일, CORS 두 층, ephemeral storage 함정까지

Flask on Lambda

Python 서버를 AWS Lambda에 올리는 Zappa의 작동 원리와 실전 주의사항

Zappa란 무엇인가

"Zappa는 Flask 코드를 AWS Lambda + API Gateway 위에서 돌아가도록 묶어주는 포장지입니다."

Flask는 원래 서버에서 항상 실행되는 방식을 가정합니다. Lambda는 요청이 있을 때만 실행되는 함수입니다. Zappa는 이 둘을 연결합니다 — API Gateway가 HTTP 요청을 받으면 Zappa가 그것을 Flask가 이해하는 WSGI 요청으로 변환합니다.

인터넷
브라우저 HTTP 요청
AWS 진입점
API Gateway (REGIONAL)
Zappa 레이어
HTTP → WSGI 변환
패키지 번들 (venv + 코드)
Flask 앱
Blueprint / Routes
Service Layer
DynamoDB
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로 처리
  }
}
⚠️ manylinux + use_precompiled_packages가 필수입니다. Mac에서 pip install한 패키지는 Mac용 바이너리입니다. Lambda는 Linux 환경에서 실행됩니다. 특히 cryptography(PyJWT 의존성), cffi 같은 C extension 라이브러리는 이 설정 없이는 Lambda에서 ImportError가 납니다.

deploy vs update — 언제 어떤 명령을 쓰는가

zappa deploy
최초 배포 시에만 사용. Lambda 함수 + API Gateway를 새로 생성합니다. 이미 배포된 상태에서 실행하면 오류 발생.
zappa update
코드 변경 후 재배포 시 사용. 기존 Lambda 함수 코드만 교체. API Gateway는 그대로 유지. 일상적인 배포에 사용.
상황명령이유
처음 Lambda + API GW 생성zappa deploy prod인프라 신규 생성
코드 수정 후 재배포zappa update prod코드만 교체 (빠름)
zappa_settings.json 변경zappa update prod대부분 update로 적용됨
ephemeral storage 크기 변경update 후 콘솔에서 수동 확인Zappa가 변경 안 하는 경우 있음
⚠️ ephemeral_storage 설정 후 반드시 콘솔에서 확인하세요. zappa update가 ephemeral storage를 업데이트하지 못하는 경우가 있습니다. Lambda 콘솔 → 함수 → 구성 → 일반 구성에서 임시 스토리지가 1024MB인지 확인하세요. 이 값이 512MB이면 큰 패키지 설치 시 No space left 오류가 납니다.

Cold Start와 Keep-Warm — 첫 요청이 느린 이유

Lambda는 요청이 없으면 컨테이너를 종료합니다. 다음 요청이 올 때 컨테이너를 새로 시작하는 데 걸리는 시간이 Cold Start입니다. Python + Flask는 보통 1~3초 정도 걸립니다.

상태응답 시간설명
Cold Start1~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)"
ℹ️ Keep-Warm은 비용이 거의 없습니다. 5분마다 1회 실행 × 24시간 × 30일 = 8,640회/월. Lambda 무료 티어는 월 100만 회입니다. EventBridge 비용도 무시할 수 있는 수준입니다. 단, Keep-Warm을 위한 EventBridge의 trust policyevents.amazonaws.com이 Lambda를 호출할 수 있도록 권한이 필요합니다 (Module 04 IAM에서 다룹니다).

Ephemeral Storage 함정 — /tmp가 512MB밖에 안 된다

Zappa는 배포 시 패키지를 /tmp에 압축해제합니다. 기본 Lambda /tmp 크기는 512MB입니다. PyJWT, cryptography, 각종 의존성을 합치면 쉽게 초과합니다.

기본값권장값변경 방법
512MB1024MBzappa_settings.json"ephemeral_storage": {"Size": 1024} 추가 후 update — 콘솔에서 수동 확인 필수

📋 오류 증상

# Lambda 실행 시 아래 오류 발생
[ERROR] Runtime.ImportModuleError: No module named 'cryptography'
# 또는
OSError: [Errno 28] No space left on device
ℹ️ Lambda 콘솔에서 직접 수정할 수 있습니다. Lambda 콘솔 → 함수 선택 → 구성 탭 → 일반 구성 → 편집 → 임시 스토리지: 1024 MB로 변경 → 저장. Zappa update 없이 즉시 적용됩니다.

CORS 두 층 구분 — 왜 두 곳에 설정해야 하는가

"CORS 오류가 나면 무조건 Flask에만 설정하려고 한다. 하지만 브라우저의 OPTIONS preflight는 API Gateway가 먼저 받는다."

CORS는 브라우저가 다른 도메인의 API를 호출할 때 적용되는 보안 정책입니다. 우리 구조에서는 https://xxxx.cloudfront.net(프론트엔드)이 https://yyyy.execute-api.ap-northeast-2.amazonaws.com(백엔드)을 호출합니다 — 다른 도메인이므로 CORS가 적용됩니다.

계층담당설정 방법처리 요청
API GatewayOPTIONS preflight 자동 응답zappa_settings.json"cors": true브라우저가 보내는 사전 확인 요청
Flask-CORS실제 요청(POST, GET) 응답 헤더 추가CORS(app, resources={r"/api/*": …})실제 API 요청의 응답 헤더
😰 Flask-CORS만 설정
OPTIONS preflight는 Flask에 도달하기 전에 API Gateway에서 거절. 브라우저 콘솔에 CORS policy: No 'Access-Control-Allow-Origin' 에러 발생.
✅ 두 층 모두 설정
API Gateway가 OPTIONS에 200 응답. Flask-CORS가 POST/GET 응답에 헤더 추가. 브라우저가 정상적으로 요청 허용.

📋 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을 써야 하는 이유

EDGE 타입 (기본값)
CloudFront를 통해 글로벌 배포. 500 에러를 최대 5분 캐싱하는 버그가 있음. SSO 연동 실패 시 500이 캐싱되어 재시도해도 계속 500.
REGIONAL 타입 (권장)
리전 내에서 직접 서빙. 500 에러 캐싱 없음. 우리가 사용하는 CloudFront는 S3(프론트엔드) 전용 — API는 별도로 직접 호출.

📋 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 미설치 환경에서는 무시
ℹ️ truststore는 사내망 SSL 연결에 필요합니다. GS Retail 내부 서버 간 통신 시 회사 자체 CA(Certificate Authority)가 서명한 인증서를 사용합니다. truststore는 Python이 이 CA를 신뢰하도록 시스템 인증서 저장소에서 읽어옵니다. Lambda 환경에는 회사 CA가 없으므로 import 자체를 try/except로 감쌉니다.
이 모듈을 마쳤습니다. Zappa의 작동 원리, deploy vs update 차이, cold start 대응, ephemeral storage 함정, CORS 두 층, API Gateway REGIONAL 타입까지 — Flask on Lambda의 핵심을 이해했습니다.