AWS Elastic Beanstalk 413 Request Entity Too Large 오류 해결

문제의 원인

Elastic Beanstalk 를 이용해서 웹 서비스를 Deploy 할 경우 어플리케이션의 Reverse Proxy로 사용되는 Nginx의 설정 때문이다. Nginx의 설정 값 중 client_max_body_size를 이용하면 client가 너무 큰 사이즈의 request를 보내지 못 하도록 제한을 걸 수 있다. 이 설정은 request의 Content-Length 헤더값이 client_max_body_size에 설정된 값을 넘을 수 없도록 제한한다.(악의적인 대용량 파일 업로드 등을 방지하는데 사용)

다만 이 client_max_body_size 의 기본 값이 1MB 정도로 너무 낮게 설정되어 있기 때문에, 1MB를 초과하는 파일을 POST로 업로드 하는 일반적인 상황에서도 에러메세지가 표시되며 요청이 실패하는 경우가 발생 한다.

해결 방법

Elastic Beanstalk 환경이 아니라면, Nginx 설정(nginx.conf)파일을 직접 수정하는게 가장 빠른 해결책 이겠지만, Elastic Beanstalk 을 사용하여 배포된 환경에서 해당 인스턴스에 직접 접속하여 Nginx 설정 파일을 직접 수정하는 것은 좋은 생각이 아니다. 직접 Nginx파일을 수정하면 오토스케일링 상황에서 새로 생성된 인스턴스에서는 여전히 동일한 이슈가 발생하거나 해당 어플리케이션을 새로운 환경에 배포 했을 때에도 동일한 문제가 재발 하여 환경의 강건성(Robustness)을 떨어뜨리는 요인이 되기 때문이다. 어플리케이션 코드 이외의 인프라에 신경을 쓰지 않아도 되도록 만들어주는 Elastic Beanstalk의 장점을 십분 살리기 위해서는 Elastic Beanstalk 에 업로드 되는 어플리케이션 패키지 내에서 해당 이슈를 해결 할 수 있도록 구성하여야 한다.

AWS Elastic Beanstalk 공식 문서 중 Extending Elastic Beanstalk Linux platforms 를 참고 하면 배포되는 환경의 Nginx 설정을 수정하는 방법에 대해 정확히 서술되어 있는데 어플리케이션 루트(아래 구조 예시에선 ~/workspace/my-app/)에서 아래와 같은 경로에 Nginx 설정 파일을 추가하면 해당 설정을 배포 환경의 Nginx가 포함하게 된다.

~/workspace/my-app/
|-- .platform
|   `-- nginx
|       `-- conf.d
|           `-- myconf.conf
`-- other source files

즉 위 구조에서는 myconf.conf의 내용으로(파일명은 상관 없음, 확장자는 .conf 이어야 함) 원하는 최대 용량을 아래와 같이 지정하고 어플리케이션을 다시 배포하면 해당 문제를 해결 할 수 있다.(0으로 값을 지정하면 무제한으로 설정됨)

client_max_body_size 50M;

Python/Django 쇼핑몰 서비스 스택

본 글은 회사 블로그에 본인이 작성한 글을 다시 갈무리한 것입니다.

이 글은 작년 7월경 본 블로그에 작성 하였던 Python/Django로 쇼핑몰 만들기 와 연결되는 글이며, 해당 글이 인프라와 사용하는 패키지 등이 혼합되어 작성되어 있었던 것과 달리 좀 더 서비스 인프라의 관점에서 작성되었습니다.


안녕하세요. 인테이크에서 시스템 개발과 운영을 담당하고 있는 조영일입니다. 인테이크는 현재 총 2개의 쇼핑몰(인테이크, 라이브스토어)을 운영하고 있으며 국내 대부분의 쇼핑몰과 달리 Cafe24, 고도몰 등의 쇼핑몰 솔루션을 사용하지 않고 프론트엔드 부터 결제 연동 등 백엔드 시스템 까지 모두 100% 자체 개발된 쇼핑몰을 운영하고 있습니다.

이 글은 이러한 쇼핑몰을 구축하면서 저희가 어떠한 도구와 스택을 도입하였는지, 쇼핑몰의 요소별로 설명한 글입니다. 쇼핑몰 기능에 집중하여 설명하므로, 일반적인 서비스 스택과는 다소 다른 구조를 가지고 있음을 참고 부탁 드립니다.

서비스 개발

  • 개발 언어 : Python
    그 유연성과 확장성 덕분에 너무나 많은 서비스들이 선택하고 있는 개발 언어인 Python을 사용 하고 있습니다. 웹 서비스 뿐만 아니라 사내 업무용 챗봇 및 통계 산출 도구 등 90% 이상의 프로젝트가 Python으로 개발 되었습니다.
  • 웹 개발 프레임워크 : Django
    전 세계적으로 가장 유명한 Python 기반 웹 프레임워크인 Django를 사용하고 있습니다. “The web framework for perfectionists with deadlines.”라는 캐치프레이즈에 맞게, Django는 웹 개발에 필요한 매우 풍부한 기능을 기본 탑재하고 있어서(덕분에 때로는 너무 무겁다고 평가 받지만) 필요한 기능을 가장 빠르게, 체계적인 방식으로 개발 할 수 있는 장점이 있습니다.  현재 인테이크 쇼핑몰, 라이브스토어 쇼핑몰, 쇼핑몰 및 외부 채널 주문을 취합 수집하고 물류센터와의 연계를 위한 SCM 시스템, 회사 공식 홈페이지 등 인테이크의 거의 모든 웹 서비스는 Django를 통해 개발 되었습니다.

서비스 호스팅

  • 서비스 호스팅 : AWS
    서비스 초기에는 국내 IDC를 사용하였으나, 서버에 대한 유지보수 이슈가 커짐에 따라 서비스의 개발과 운영에 집중하기 위하여 AWS(Amazon Web Service)를 도입하여 현재는 모든 서비스를 클라우드 상에서 제공하고 있습니다.

배포/오토 스케일링

  • 배포/오토스케일링 : AWS ElasticBeanstalk
    AWS에서 제공하는 어플리케이션 배포 서비스인 AWS ElasticBeastalk를 이용하여 어플리케이션을 배포하고 있습니다. AWS ElasticBeanstalk의 경우 기본적으로 AWS ELB(Elastic Load Balancer)를 통한 로드 밸런싱을 지원하며 git push 만으로도 새로운 버젼의 어플리케이션 배포 작업이 가능하여, 복잡한 배포 절차를 직접 구성 할 필요가 없습니다.AWS ElasticBeanstalk의 경우 배포 과정에서 pip를 통해 repository에 정의된 requirements.txt를 바탕으로 python dependency에 대해 자동으로 설치를 해주지만, 프로젝트에서 요구되는 python 이외의 dependency(리눅스 패키지 등)의 경우 ElasticBeanstalk에서 지원하는 Config Script를 작성하여 배포 단계에서 자동으로 설치되도록 하였습니다.오토스케일링의 경우 위에서 말한대로 AWS ElasticBeanstalk에서 ELB 를 통해 기본 제공하는 기능으로서, AWS 설정 콘솔에서 일정 시간 동안 Network In/Out 발생 할 경우 자동으로 서비스 인스턴스를 늘이고 줄일 수 있도록 하였습니다.

정적 컨텐츠 처리

  • 스토리지 : AWS S3
    인테이크는 기본적인 서비스 인프라를 AWS(아마존 웹 서비스) 상에서 운영하기 때문에 정적 컨첸츠 역시 AWS에서 제공하는 정적 컨텐츠 스토리지인 AWS S3를 통해 관리하고 있습니다.
  • CDN : AWS CloudFront
    정적 컨텐츠는 모든 사용자가 상시적으로 접근 하며, 쇼핑몰의 경우에 일반적으로 그 용량이 크므로(다수의 상품 썸네일, 긴 상품 상세 이미지 등) 컨텐츠를 제공하는 속도를 높이는 노력이 필요 합니다. 일반적으로 컨텐츠를 제공하는 서버와 사용자 사이의 물리적인 거리가 컨텐츠의 전송 속도에 큰 영향을 미칠 수 밖에 없는데, 인테이크는 이 문제를 AWS CloudFront 를 통해서 해결 하고 있습니다.AWS CloudFront는 AWS에서 제공하는 CDN(Contents Delivery Network)로서, S3에 존재하는 원본 정적 컨텐츠를 전 세계에 존재하는 50여개의 CloudFront 엣지 로케이션 서버에 캐싱 하므로서 전 세계의 사용자가 동일한 주소로 이미지를 요청하더라도 그 사용자에게 물리적으로 가장 가까운 엣지 로케이션 서버에서 파일을 제공하므로서 컨텐츠  빠르고 안정적으로 서빙 할 수 있도록 해줍니다.또한 AWS CloudFront를 사용 함으로서 모든 컨텐츠에 대해 사용자 도메인을 적용 할 수 있으며(예를 들면, 인테이크의 모든 컨텐츠는 https://i.intakefoods.kr의 도메인을 가집니다.) SSL 인증서를 적용 하여 모든 컨텐츠를 안전한 HTTPS 프로토콜로 제공 할 수 있습니다.

비동기 작업 처리/작업 스케쥴링

  • 비동기 작업 처리 : Celery
    쇼핑몰에 무슨 비동기 작업이 필요할까 의문을 품을 수 있지만, 실제로는 꽤 많은 비동기 작업에 대한 처리가 필요합니다. 사용자의 액션(주문완료 등)과 함께 작업이 실행되어야 하나 그 작업이 사용자의 경험에 해로운 blocking을 유발할 우려가 있는 작업들(eg. 외부 API와 I/O 작업이 필요한 Email/SMS/카카오 알림톡 발송), 실행 시간이 긴 작업(eg. 회원 대상 대량 SMS 발송) 등을 비동기로 처리하지 않을 경우 쇼핑몰의 매끄러운 운영에 상당한 방해요소가 될 수 있습니다.이러한 비동기 작업을 처리하기 위하여 python에서 가장 유명한 비동기 작업 워커 패키지인 Celery를 도입하였습니다.
  • 작업 스케쥴러 : Celery-beat
    또한 쇼핑몰을 운영하다보면 주기적으로 수행되어야 할 작업들 역시 많습니다. (eg. 매일 자정에 당일 매출 통계를 전 멤버한테 메일로 발송하기, 물류센터 API로 부터 자동으로 운송장 번호 가져오기, 특정 기간 동안 쇼핑몰 사용기록이 없는 회원 휴면 계정 처리하기, 매월 회원들의 구매 기록을 기반으로 회원 등급 평가하기 등)이러한 정기적 작업 스케쥴링을 위해서는 보통 리눅스의 crontab 등을 고려할 수 있지만, 위의 Celery 패키지에 포함된 celery beat가 이러한 작업 스케쥴러 기능을 제공하고 있어 해당 기능을 통해 작업 스케쥴러 기능도 Celery와 유연하게 통합하였습니다.
  • 메세징 큐(브로커) : AWS ElastiCache
    Celery와 같은 비동기 워커를 사용하기 위해선 메세징 큐가 반드시 필요합니다. 워커가 처리할 task들을 쌓아놓고 task의 처리 상태를 공유함으로서 다수의 워커가 중복된 task를 처리하는 일 없이 효과적으로 task를 처리 할 수 있습니다.Celery의 경우 기본적으로 RabbitMQ, Redis Amaon SQS, Zookeeper 등의 메세징 큐를 지원하지만 그 중 현재 가장 Stable 하게 지원되는 것은 RabbitMQ와 Redis 입니다. 저희는 그 중 상대적으로 좀 더 범용성이 있는(메세징 큐 이외에 캐싱 용도로도 효과적으로 사용 가능한) Redis를 선택하여 메세징 큐로 사용하고 있으며, 유지보수의 편의성을 위하여 AWS ElastiCache를 통해 호스팅된 Redis를 사용하고 있습니다.

버그 레포팅/운영

  • 버그 레포팅 : Sentry
    서비스 운영 시 발생하는 예상 밖의 exception이 발생하는 경우에 대비하기 위해 real-time crash reporting 도구인 Sentry 서버를 자체 구축하고, 해당 서버를 통해 live 서버에서 발생하는 exception이 발생할 경우 해당 내역을 자동으로 메일과 사내 커뮤니케이션 도구인 Slack으로 레포팅 되도록 하고 있습니다.
  • ChatOps : Slack
    ChatOps는 DevOps의 방법론 중 하나로, 메신저를 통한 사람의 명령에 따라 운영과 관련된 다양한 처리를 수행하고, 그 응답을 메세지 형태로 제공하는 ChatBot을 서비스 운영에 적극적으로 도입하는 것을 의미합니다.
    인테이크는 사내 업무용 커뮤니케이션을 위한 도구로 Slack을 사용하고 있으며, Slack API를 통하여 사용자의 명령에 응답 하는 ChatBot을 개발하였습니다. ChatBot은 사내 SCM 시스템, BitBucket, 버그 레포팅 시스템 등과 연계 하어 운영과 관련된 다양한 수치를 명령어 하나로 간단히 조회하고, 운영 시 발생하는 각종 이슈들을 해당 서비스에 직접 접속 할 필요 없이 Slack 상에서 담당자가 처리 할 수 있도록 구성 하였습니다.

이상 인테이크 서비스 스택에 대한 설명이었습니다. 이 글에 대한 피드백이나, 저희와 비슷한 환경에서 개발하시는 분들의 질문 등은 언제든 환영이니 아래 메일 주소나, 댓글로 연락 주시면 가능한 빨리 답변 드리도록 하겠습니다. 감사합니다.

dev@intakefoods.kr

Python/Django로 쇼핑몰 만들기

회사에서 한국판 소일런트라고 할 수있는 밀스(Meals) 홍보차 PyCon APAC 2016 후원 논의를 하다 여러가지 사유에서 결국 드랍하였고, 그냥 개인으로 참가 신청을 하였다.

참가 신청을 하고보니, 프로그램 목록에서 Django 로 쇼핑몰을 만들자 라는 발표를 발견했다. 이건 내가 3년째 하고 있는 일인데..?  마침 github에 발표 내용에 대해 분야별로 정리된 내용을 공유 해주셨기에, 이것을 기본으로 하여 스스로 정리차 내가 구축한 Intake(https://www.shopintake.com) 버젼으로 작성해 보았다.

필요 : 혼자 쇼핑몰을 만들어야 하는 상황이었고, 그래서 Python/Django를 골랐다.

이미지 파일 다루기

  • 스토리지 : AWS S3, CDN : AWS CloudFront
  • 이미지 리사이징 / 썸네일 : sorl-thumnail 패키지 활용
  • S3 Browser 통한 이미지 관리

배송 확인

  • 별도의 API를 사용하거나 만들지 않음
  • 대부분의 배송사들이 송장번호 기반의 배송 URL을 제공한다는 점에 착안하여 고객 송장번호를 URL에 삽입하여 해당 배송사 배송 확인 페이지로 바로 연결

이메일 발송

  • 가입, 회원등급 변경, 문의 답변, 결제완료, 마케팅 등에 사용됨
  • Mailchimp와 Mailchimp에서 제공하는 transactional email 서비스인 Mandrill 사용
  • 이메일 클라이언트에서 inline CSS(HTML 태그에 style attribute로 스타일 직접 추가하는 형태) 만을 지원하는 이슈가 있음, 하지만 Mailchimp는 해당 기능을 디폴트로 지원함
  • Mailchimp Automation 기능을 활용하여 쇼핑몰의 회원/구매기록 DB와 Mailchimp list를 연동, 다양한 상황에서 자동화된 메일링 발송(회원등급 변경, 장바구니에만 담기고 완료되지 않은 주문, 주문 후 N일이 지난 고객 등등)

결제

  • Intake(https://www.shopintake.com) 를 최초 구축하던 2년반전에는 아임포트 같은 아름다운 결제 API 서비스가 없었음
  • 당시 PG사들은 SDK로 JSP, ASP, PHP 등 기성 쇼핑몰 개발 주력 언어들만 지원하던 절망적인 상황
  • 이니시스 이니라이트 PHP SDK를 1:1로 Python으로 포팅하여 결제 환경을 구축함, 제한적으로 크로스브라우징 구현
  • 2015년 크롬 NPAPI 지원 중단 이슈로 인하여 이니시스가 기존 결제 플러그인 대신 ‘웹표준 결제 플러그인’을 도입함, 이 역시 이니시스의 난해한 개발 문서 읽으며 1:1로 Python으로 구현
  •  평소 자주 사용하던 서비스 중 하나인 토스(Toss)에서 결제 서비스인 토스페이(TossPay)가 출시되어, 빠르게 도입. 엄청나게 간편한 결제 API에 감탄.
  • 카카오 페이(KakaoPay) 역시 빠르게 도입, 카카오페이의 개발/운영 주관사는 다음카카오가 아닌 LG CNS. LG CNS에서는 Python 예제 코드를 제공하였으나, Java스러운 Python코드로 역시 해독이 어려움. 1:1로 Python 스타일로 라이브러리 변경
  • 아임포트 런칭 초기에 도입을 고려하였으나, 이미 구축한 legacy가 너무 많아 포기. 유지보수 이슈로 추후 도입 고려 중

관리자/통계 기능

  • raw-level의 모델 데이터 CRUD 작업 시에만 django admin 사용. django admin에 grappelli를 얹어서 사용
  • 주문관리, 회원관리, 제품관리, 매출 통계 등의 기능이 제공되는 별도의 관리자용 WebUI 구축
  • 관리자 페이지엔 회원 정보, 주문 정보, 매출 정보 등 민감한 사항이 많은데 간헐적으로 악의적인 로그인 시도가 감지되어, two factor authentication 으로 OTP(One Time Password) 도입. django-otp 패키지 사용 하였고, 구글에서 제공하는 OTP 앱인 google authenticator를 사용하여 관리자 페이지에 접근하는 모든 멤버들이 로그인 시점에 자신의 휴대전화를 통해 OTP를 제공받도록 함
  • django aggregation 활용하여 시간별(일간/주간/월간), 제품별(제품 카테고리/개별 SKU), 주문 유형별(PC, 모바일, 외부채널 등) 매출 통계, 회원 통계를 멤버들이 원하는 조건에 따라 조회 할 수 있도록 구현
  • 그래프의 경우 Google Chart 라이브러리 활용

CMS

  • 운영도 개발자가 할 것이 아니라면 쇼핑몰엔 기본적으로 상품 상세페이지/공지사항/이벤트 등 CMS(Contents Management System)이 필요함(쇼핑몰 운영하는 멤버는 HTML을 이해하지 못한다=.=)
  • Bootstrap 기반의 WYSIWYG 에디터인 summernote(django-summernote) 도입
  • 깔끔하고 사용하기 좋은 UI를 가지고 있지만, summernote가 생성하는 CSS는 깨끗하지 않고-일부 inline CSS가 쇼핑몰 자체의 CSS와 충돌을 일으키며 작성된 결과물이 표시될때 지저분하게 표시되는 문제 발생
  • django model signal 을 이용하여 저장 이후 summernote가 생성한 HTML검사하고 문제의 소지가 있는 inline CSS 들을 제거하는 기능 개발

디자인

  • 쇼핑몰 프론트엔드 디자인은 자체 디자인하여 퍼블리싱만 외주로 작업
  • 반응형 웹은 실제 사용자에게 큰 의미가 없다고 생각하여 PC와 모바일 별도로 디자인함

장바구니

  • 장바구니는 django-carton 패키지를 clone하여 수정 후 사용
  • django-carton은 자신이 구현한 Product Model을 장바구니 item으로 추가할수있고, 장바구니 추가/수량변경/삭제 등 기본적인 장바구니 기능이 구현되어 있음. 장바구니 정보는 session-base로 저장
  • 정보를 session에 저장하기 때문에  삭제 되면 안되는 장바구니 정보(회원 로그인 후 장바구니에 담은 물건) 가 휘발되는 문제 발생, 결제 완료 이전 장바구니정보를 JSON으로 serialize 하여 db에 저장하는 기능 추가 구현
  • 무료 배송,  구매 조건에 따른 사은품 기능 등을 쇼핑몰에서 추가로 요구되는 기능들 추가 구현함

주문 진행

  • 우편번호 검색의 경우 최초에는 우체국에서 제공하는 API를 Wrapping하여 사용하였으나, 한글 인코딩과 관련하여 자주 예상치 못한 변화가 발생하고 API 자체의 상태도 오락가락 하여 문제가 많았음
  • 다음 주소 API 공개 시점에 모든 주소 관련 API 를 다음 주소 API로 변경하였음

주문 모델

  • 카드/실시간계좌이체 의 경우 주문서 작성과 동시에 주문 완료 여부가 결정되므로 주문 완료 시점에 주문, 배송, 결제 정보, 주문 상품 정보가 담긴 모델을 생성
  • 가상계좌입금(무통장)/토스페이 등의 경우 주문 완료 시점과 결제 완료 시점이 다른 문제 발생, 주문 완료 시점에 카드/실시간계좌이체와 동일하게 모든 정보를 저장하되 주문 모델에 주문 상태를 추가하여 ‘입금 대기’상태로 저장함. PG사로 부터 입금 통보가 올 경우 결제 상태를 ‘결제 완료’로 변경하고 배송 시작.

비동기 작업/작업 스케쥴링

  • 쇼핑몰에 필요한 비동기 작업 : 사용자의 액션(주문완료 등)과 함께 작업이 실행되어야 하나 그 작업이 사용자의 경험에서 blocking을 유발할 우려가 있는 작업들(eg. 외부 API를 이용한 Email/SMS/카카오 알림톡 발송), 실행시간이 긴 작업(eg. 회원 대상 대량 SMS 발송)
  • 쇼핑몰에 필요한 작업 스케쥴링 : 주기적으로 수행되어야 할 작업들(eg. 매일 자정에 당일 매출 통계를 전 멤버한테 메일로 발송하기, 물류센터 API로 부터 자동으로 운송장 정보 가져오기, 특정 기간 동안 쇼핑몰 사용기록이 없는 회원 휴면 계정 처리하기, 매월 N일에 회원들 구매 기록 기반으로 회원 등급 평가하기 등)
  • Celery : Python 기반 비동기 Worker, Scheduler를 포함하는 패키지
  • 위 기능들을 Celery를 이용하여 구축하였으며 작업 queue로는 AWS ElastiCache 사용

배포

  • AWS ElasticBeanstalk 를 사용하여 git push 만으로 배포작업이 가능하도록 구성함
  • 기본적으로 특정 트래픽에 도달할 경우 자동으로 인스턴스를 추가하여 auto-scaling이 되도록 구성.
  • ElasticBeanstalk Config Script를 사용하여 새 인스턴스가 시작될때는 python package 이외(python package는 ElasticBeanstalk 이 requirements.txt 기반으로 자동으로 설치해줌) 에 필요한 Linux package를 설치하도록 구성함
  • AWS ElasticBeanstalk로 배포 할 경우 짧은시간(30초 미만) 동안의 서비스 downtime 이 발생함, 쇼핑몰 입장에선 심각한 수준의 downtime은 아님 -> 가급적 낮 시간동안엔 배포 안하지만, 심각한 문제의 경우 그냥 배포 진행
  • 올해 새로 출시된 AWS Certificate Manager 통해서 서버/CDN리소스에 대해 HTTPS 적용

운영

  • real-time crash reporting 도구인 Sentry 서버를 자체 구축하고, 해당 서버를 통해 live 서버에서 예상치 못한 exception이 발생할경우 해당 내역을 자동으로 메일, 슬랙을 통해 전송 하도록 함.
  • 사내 커뮤니케이션 툴로 사용 중인 슬랙에 Bot을 개발하여 연동함, 해당 Bot은 명령어를 통해 실시간으로 매출 통계 조회, 주문조회, 재고 조회 등이 가능하도록 함.

이외에도 자잘한 기능들이 많지만, 큰 맥락에선 이 정도로 정리가 되는 느낌이다. 쇼핑몰을 기반이 전무한 상태에서 직적 구축하다보니 우여곡절이 많았고 내가 이걸 왜 다 직접 만들고있나?(Why reinvent the wheel?)라는 생각도 들었지만, 결과적으로는 잘한 일이라는 생각이든다.  이미 존재하는 쇼핑몰 솔루션을 선택했더라면, 지금의 Intake와 같이 자유도 높은 쇼핑몰 운영은 아마 힘들었을 것이다.

* 글에서 언급한 패키지/서비스들이 참 많은데 아직 링크를 걸지는 못하였다. 차차 글 내용 추가와 함께 업데이트 예정.