cmod.ify

[Technical Review] 말랑이 메이커 #2: SAM으로 FastAPI 서버리스 마이그레이션 및 트러블슈팅 본문

Project

[Technical Review] 말랑이 메이커 #2: SAM으로 FastAPI 서버리스 마이그레이션 및 트러블슈팅

modifyC 2026. 2. 24. 09:52
728x90
반응형

https://pf.kakao.com/_kRFRX

1. 전체 아키텍처 흐름

  1. FastAPI에 Mangum 부착: 기존 웹 프레임워크를 Lambda가 이해할 수 있게 변환
  2. Docker & ECR: 코드를 컨테이너 이미지로 말아서 AWS ECR에 푸시
  3. AWS Lambda: 올라간 이미지를 기반으로 서버리스 함수 실행
  4. API Gateway: Lambda를 외부(카카오톡 챗봇 등)와 연결해 주는 대문 역할
  5. IAM Role: Lambda가 DynamoDB에 접근할 수 있도록 권한 제한
  6. AWS SAM: 이 모든 인프라 생성과 배포 과정을 템플릿 하나로 자동화
  7. 테스트: 로컬 빌드 및 실환경 테스트

2. AWS IAM 권한 세팅

기존에 쓰던 malang-dev 계정은 딱 DynamoDB 접근 권한만 열려 있었음.

권한 최소화 원칙을 지키기 위해 배포 전용 IAM 사용자 그룹을 새로 파고, SAM 배포에 딱 필요한 권한만 쥐여줬다.

서비스 권한 이름 (AWS 관리형 정책) 왜 필요한가? (설명)
IAM IAMFullAccess Lambda가 사용할 Role을 SAM이 배포 과정에서 직접 만들어야 해서
CloudFormation AWSCloudFormationFullAccess SAM은 내부적으로 CloudFormation을 엔진으로 써서 인프라를 구축
Lambda AWSLambda_FullAccess 람다 함수를 생성하고, 업데이트하고, 삭제하는 핵심 권한
ECR AmazonEC2ContainerRegistryFullAccess 로컬에서 빌드한 Docker 이미지를 AWS 저장소에 업로드(Push)해야 하니까 필수
S3 AmazonS3FullAccess SAM이 배포할 때 템플릿이랑 설정 파일들을 임시로 얹어둘 공간이 필요
API Gateway AmazonAPIGatewayAdministrator 외부에서 람다를 찌를 수 있게 URL 대문을 만들어야 하니까 필요
DynamoDB AmazonDynamoDBFullAccess 우리 말랑이 유저 데이터를 읽고 써야 하니까 당연히 필요

(추가1) sam deploy로 배포 시엔 IAM FULL ACCESS가 필요하지만 배포가 완료 됐을 땐 READONLY로 권한 설정 변경 해야 함

 

3. 주요 코드 및 설정 변경 (SAM & FastAPI)

인프라 세팅이 끝났으니, 본격적으로 코드를 서버리스 환경에 맞게 뜯어고침

 

  • SAM 템플릿 (template.yaml): 소스 코드 직배포가 아니라 도커 이미지를 쓴다고 PackageType: Image로 명시.

  • 환경별 DB 분기 (db.py): 로컬은 .env로 자격 증명을 읽고, Lambda 환경은 부여된 IAM Role을 쓰도록 os.getenv("AWS_LAMBDA_FUNCTION_NAME")를 기준으로 분기 처리.

  • 어댑터 장착 (main.py): handler = Mangum(app, lifespan="off") 한 줄로 FastAPI와 Lambda를 연결.

  • 도커 베이스 변경 (Dockerfile): public.ecr.aws/lambda/python:3.10 같은 AWS 전용 이미지로 베이스를 교체하고 CMD ["main.handler"] 설정.

4. 배포 

1단계: 서버 구울 준비 (sam build --use-container)

파이썬 코드랑 Dockerfile을 통째로 섞어서 하나의 완성된 '도커 컨테이너 이미지'로 만드는 과정

  • 왜 --use-container가 필요할까?  template.yaml에서 PackageType: Image라고 선언. 일반 파이썬 코드를 짚(zip) 파일로 묶어서 올리는 게 아니라, 람다가 실행될 환경이랑 똑같은 도커 컨테이너 안에서 빌드하라고 SAM에게 명령하는 것임. 이 옵션을 빼먹으면 로컬 PC 환경 기준으로 빌드가 돼서 나중에 람다에서 꼬일 수 있음

2단계: 최초 배포 (sam deploy --guided 또는 -g)

이건 AWS에 "나 말랑이 서버 처음 올릴 건데, 호구조사 좀 해줘"라고 하는 단계. --guided를 붙이면 SAM이 배포에 필요한 필수 설정들을 대화형으로 캐물어.

 

여기서 만났던 주요 질문들

  • Stack Name: 우리 프로젝트 묶음 이름이야. (예: malang-maker)
  • AWS Region: 어디 서버에 올릴 거냐는 건데, 서울이니까 당연히 ap-northeast-2.
  • Image Repository: 도커 이미지를 AWS의 어디(ECR)에 저장할 건지
  • Confirm changes before deploy: 배포하기 전에 바뀐 내용 보여줄까? (Y 누르면 안전함)
  • Allow SAM CLI IAM role creation: SAM이 알아서 람다 실행 권한(Role) 만들어줘도 돼? (당연히 Y. 이거 안 하면 우리가 일일이 권한 다 만들어줘야 해)

폴더에 samconfig.toml이라는 파일이 하나 생기는데 이게 일종의 '자동 로그인 쿠키'임. 대답한 설정들이 다 여기 저장됨

 

3단계: 수정 후 재배포 (일상적인 배포)

이제 samconfig.toml이 있으니까, 앞으로 코드 수정하고 다시 올릴 땐 꼬치꼬치 안 물어봄

  1. rmdir /s /q .aws-sam 기존에 빌드하다 남은 찌꺼기(캐시) 폴더를 미련 없이 날려버리는 명령어. (윈도우 기준)
  2. sam build --use-container 깨끗한 상태에서 도커 이미지를 다시 예쁘게 굽기.
  3. sam deploy 더 이상 --guided를 안 붙여도 됨. 알아서 기존 설정 읽어서 AWS에 업데이트된 이미지만 싹 올려줌

5. 트러블슈팅

이슈 1: 카카오톡 NetworkAccessForbidden (응답 없음)

  • 현상: 카카오톡 채널에서 메시지를 보내도 응답이 없고, 카카오 설정 페이지에서는 '네트워크 오류'만 뜸. 심지어 503 같은 에러 코드조차 안 뜨는 상황.
  • 진짜 원인: Dockerfile의 베이스 이미지가 문제였음. 기존에는 일반적인 Python slim 이미지를 사용했는데, AWS Lambda는 전용 런타임 환경이 필요함. 일반 이미지를 쓰면 Lambda가 코드를 실행할 준비(Init)조차 못 하고 죽어버리니까 카카오 입장에서는 "연결 거부"로 판단한 것.
  • 해결: 베이스 이미지를 AWS에서 제공하는 Lambda 전용 이미지로 변경.
    # 변경 전: FROM python:3.10-slim (Lambda에서 실행 불가)
    # 변경 후: 
    FROM public.ecr.aws/lambda/python:3.10
    

 

이슈 2: 카카오톡 챗봇 NetworkAccessForbidden (503 에러)

  • 현상: 카카오 챗봇에서 응답이 없고 네트워크 오류가 뜸.
  • 원인: 서버가 500대 에러를 뱉으면 카카오 측에서 연결을 강제로 끊어버림. 즉, 내 코드 문제.
  • 접근: sam logs -n MalangMakerFunction --stack-name malang-maker --tail로 람다 로그부터 까봄.
  • handler = Mangum(app)  -> handler = Mangum(app, lifespan="off") 로 코드 변경 수정하고 재배포 해 봄. 근데 이슈 3이 나타나게 됨.

이슈 3: Runtime.ImportModuleError (Unable to import module 'main')

  • 현상: 로그를 보니 람다가 도커 안의 main.py를 아예 못 찾고 있었음.
  • 원인: 이미지 경로가 꼬였거나, 기존 빌드 캐시(찌꺼기)가 엉뚱하게 올라간 상태.
  • 해결: rmdir /s /q .aws-sam으로 캐시 완전 멸망시키고, sam build --use-container로 도커 환경에서 깔끔하게 재빌드 후 재배포.

이슈 4: AWS_REGION 예약어 충돌 (Configuration Warning)

  • 현상: template.yaml의 환경 변수 설정에서 AWS_REGION이라는 이름을 그대로 사용했더니, 배포 시 경고가 뜨거나 Lambda 실행 시 지역 정보를 제대로 못 불러오는 문제가 발생함.
  • 원인: AWS_REGION은 AWS Lambda가 예약(Reserved)해둔 환경 변수. 람다 런타임이 실행될 때 자동으로 설정되는 값. 이걸 사용자가 임의로 설정(Overwrite)하려고 하면 설정 충돌 경고가 뜨거나, 런타임에서 강제로 값을 고정해버려서 보낸 값이 무시될 수 있음
  • 해결: 변수 이름을 MY_APP_REGION처럼 커스텀 명칭으로 변경해서 충돌을 피함.
    # 변경 전
    Variables:
      AWS_REGION: ap-northeast-2  # 예약어라 위험!
    
    # 변경 후
    Variables:
      MY_APP_REGION: ap-northeast-2  # 안전한 커스텀 변수
    

이슈 5 이미지 파일 증발:

 

  • 원인: 빌드 후 람다 로그를 까보니 이미지 파일을 못 찾는 에러가 떴고, 범인은 .dockerignore에 실수로 적어둔 img 폴더 때문이었음 (이미지만 서버에 안 올라감).
  • 해결: .dockerignore에서 해당 줄을 바로 지워버리고 깨끗하게 재빌드해서 배포.

 

이슈 6: 브라우저에서 {"detail":"Method Not Allowed"} 등장

  • 현상: 배포 성공 후 주어진 API Gateway URL로 들어갔더니 저 에러가 뜸.
  • 해결: 이건 에러가 아니라 성공의 증거! 브라우저 주소창은 GET 요청인데, 내 API는 POST만 받게 되어 있어서 프레임워크가 정상적으로 방어한 거야. 서버가 아주 건강하게 살아있다는 뜻
728x90
반응형