AWS Serverless 어플리케이션 빌드 & 배포 구축 후기

(주)하프스

안녕하세요 HALFZ에서 개발팀 리딩을 맡고 있는 Mark입니다. 첫 팀블로그 주제로 AWS Serverless 어플리케이션 빌드 및 배포 구축에 관련된 내용을 써보려합니다 :)

들어가며

Elastic Beantalk를 사용하여 돌아가던 Node.JS + React 기반 서버를 API Gateway + Lambda로 옮겨며 겪은 경험담입니다.

넥스트유니콘의 경우 Real Phase에서 사용하기에는 무리가 있다 판단, Alpha Phase에서 실험적으로 이용중입니다.

추후 Serverless Application을 제작하시는 분들에게 조금이나마 도움이 되었으면 합니다.

Serverless 란

서버-less라는 단어는 어폐가 있어보입니다.

서버는 어디엔가는 존재합니다 (…) 다만, 우리가 관리하지 않을 뿐…

AWS Lambda 같은 경우, 플랫폼 코드 혹은 AWS Linux에서 돌아가는 Binary 및 Platform 코드를 올리면, AWS에서 알아서 서버를 관리해줍니다.

Request가 많으면, 많은 Request를 동시에 핸들링 할 수 있는 구성으로

반대로 Reqeust가 없으면 자원을 아예 할당하지 않다가, On-Demand로 할당

Beta나 Rc등 테스트용도의 서버라면, 요금이 꽁짜라고 느껴질 정도로 저렴합니다.

기존 AWS 유저의 경우 일반적인 셋팅하에, 월 10~100만건의 request가 free-tier Request 당 비용 청구

공식문서에서는…

서버관리가 불필요합니다.

유연한 확장성이 있습니다.

종량제 요금

자동화된 고가용성

결론적으로, 코드가 어딘가의 공유 컴퓨터에서 마이크로 VM상에서 알아서 잘 돌아가게 설계되어있고, 그에 따른 제약이 있다 라고 이해해봅니다 (...)

내부적으로는 docker 등의 가상화 기술을 사용했을 것으로 보여집니다.

Serverless 구성

구성

Permission 리소스를 간소화 해 표현하였습니다 (Cloudformation으로 디자인)

EC2 -> Lambda Function

기존의 EC2에서 하던 컴퓨팅 역할을 Lambda function이 대체하게 됩니다.

Api Gateway -> Load Balancer

정확히 같은 역할은 아니나, Api Gateway에서 SSL 관리 및 Routing을 담당합니다.

모든 Api Gateway Request를 Lambda function으로 재전송

CloudFront를 함께 사용하려했으나, CloudFront를 연결하는 과정에서 Api Gateway가 Host Header를 확인하는 과정에서 503 status를 내보냈습니다.

Api Gateway에서 Custom Domain name 설정이 있는 것을 확인하고 해당 기능을 켬으로써 해결.

Lambda의 내부 및 외부 리소스 접근을 동시헤 하기 위해서 VPC + Nat 구성이 필요했습니다.

내부 리소스 접근을 위해, VPC안에 Lambda를 넣었어야했습니다.

이 경우 Lambda가 외부 리소스 접근을 못하게 되는게, 해결 방안으로 VPC + Nat 구성이 필요했습니다.

배포방법

Lambda의 update-function-code와 CloudFormation을 비교한 끝에 CloudFormation으로 진행하였습니다.

초기 고민 당시, Rc와 Real Phase 모두 Serverless로 대체하고 싶어, CloudFormation을 이용하였으나, aws cli상의 update-func-code 명령 역시 충분히 효과적일 것이라 예상합니다.

CloudForamtion 예제

Lambda

간단한 Function의 경우, 웹상에 많은 문서들이 있었습니다.

하지만, 우리는 express application을 통째로 lambda로 옮겨가고 싶었기에 (…) 조금더 검색을 진행하였고, 결론적으로 aws-serverless-express를 사용해 해결 하였습니다.

AWS Serverless Express

express를 내부의 랜덤 소캣으로 띄웁니다. 
Labmda request가 들어오면, http request로 전환, 미리 열어둔 express 소캣으로 보냅니다. 
express 서버에서 response를 보내면, 그대로 lambda response로 보내버립니다.

Api Gateway

Api Gateway는 AWS에서 제공하는 서비스로 API Endpoint를 설정하고, 해당 endpoint로 request가 들어왔을 경우, lambda, http reverse proxy, 다른 AWS service 호출, VPC 내부의 리소스 proxying 등이 가능합니다.

우리는 Api Gateway의 모든 request를 제작한 lambda function으로 redirection 시키게됩니다.

Troubleshooting

Lambda ColdStart 문제

Lambda의 경우 On-demand로 Code를 어디엔가 올리고 실행하는 과정을 수행합니다. 따라서 Code가 어디론가 올라가는 과정에 시간이 필요합니다.

특히, VPC를 사용하는 경우 Cold Start는 10초 넘게 소요되기도 했습니다.

내부 실험 결과, Lambda는 동시에 요청된 Request 개수에 따라, Lambda의 실행 Unit이 증가하는 것으로 보여지며, 실행 Unit이 증가하는 때에 Cold Start에 따른 문제가 일어납니다.

참고

Cold Starts in AWS Lambda

해결책

Lambda Warmer를 사용해 해결했습니다.

5분마다 3개의 Lambda Request를 동시에 날리게 됩니다. 따라서 적어도 3개의 Request는 동시에 handling 할 수 있는 상태로 Lambda를 유지시키게 됩니다.

이 경우 Request가 갑자기 몰릴 경우, Cold Start가 일어날 수 있습니다.

외부 리소스 접근 문제

위에서 언급한 것과 같이, Lambda가 VPC상의 리소스와 외부 리소스를 동시에 접근하려면, NAT 구성이 필요합니다.

해결책

Nat 구성은 AWS Lambda: Enable Outgoing Internet Access within VPC를 참조하였습니다.

네트워크에 대한 이해 부족으로 많은 고생을 했던 부분입니다 (…)

Custom Domain 문제

위에 언급과 같이 처음에는 CloudFront를 사용하려 했습니다.

CloudFront를 pop server 처럼 쓰기 위해서는 모든 header를 전부 origin server로 보내야합니다.

header의 Host가 Api Gateway의 domain과 맞지 않아, 503 Error 발생 …

해결책

Api gateway를 regional 에서 edge로 변경하고, custom domain name 설정을 하였습니다.

해당 기능은 Api gateway에 연결된 cloudfront를 자체적으로 생성한다고합니다.

Lambda Async 문제

Lambda의 경우 종량제로 실행되기 때문에, 요청의 시작점과 끝점이 명확해야합니다.

기본 설정의 경우 Lambda에서 reponse가 오는 순간을 끝점으로 간주합니다.

callback 방식의 handler를 구현하면, event queue가 빌때까지 기다리나, 사용하고 있는 MySql engine이 계속 event를 물고 있어, 사용하지 못했습니다.

따라서, Express로 부터 response가 간 이후의 작업들은 수행되지 않습니다. (혹은 추후 다른 request가 들어왔을 경우 실행된다.)

해결책

근본적인 해결책은 Async로 해결해야하는 문제가 있는 경우, 새로운 Lambda를 호출 하는 것으로 보여집니다. 하지만 그 경우 … 수많은 추가작업이 필요하기에 …

response.end function을 override 하여, 모든 Async 작업이 끝나고 response를 return하게 수정하였습니다.

Real Phase에서 serverless를 사용하고 싶다면, 해당 문제에 따른 job queue 및 async용 lambda function이 필요 할 것으로 보여집니다.

Binary Module 문제

사용하고 있는 모듈 중, binary 빌드가 필요한 package가 있었습니다.

Lambda는 내부적을 aws linux2에서 돌고 있기 때문에, 해당 package는 aws linux2에서 빌드되어야했습니다.

해결책

사용하고 있는 Jenkins에 docker를 띄우고, aws lunux2 이미지를 사용해 빌드하였습니다.

사용한 Dockerfile

이 외에도 위의 Dockerfile을 사용해 aws codebuild를 활용한 작업을 시도했었습니다.

하지만, 위의 경우 aws의 codecommit, codedeploy, cloudformation 까지 전부 설정해야했으므로 … Pass

내부 Lambda 이해 문제

Troubleshooting이라기 보다는 이해에 관련된 문제였습니다.

고전적인 서버 개발시에 당연하다 생각하던 개념들

DB connection이 어떻게 유지되는가, singleton으로 존재하는 service는 어떻게 관리되는가 등의 질문이었습니다.

해결책

Lambda의 경우 request가 continuous하게 오는 경우, code가 어딘가의 메모리에 계속 존재하게 됩니다. 따라서, db connection이나 singleton 객체 등은 별도의 초기화 없이 지속적으로 유지되게 됩니다.

RDS사용시 문제

AWS RDS의 경우 Tier 별로 connection 갯수에 제약이 있습니다.

따라서 Lambda instance가 많이 늘어날 경우 RDS에 connection을 연결하지 못하는 문제가 생깁니다.

해결책

사실상 마땅한 해결책은 없을 것 같습니다. RDS를 사용하지 않는 것이 맞는 것 같습니다 (…)

저희 Beta 구성의 경우 동시에 수행되는 lambda수를 10개 이하로 제약하므로써 해당 문제를 해결하였습니다

프로덕션에서는 사용하지 못하는 기법일 것 같습니다 (…)

결론

기존 ElasticBeanstalk 상의 Node.JS + React 어플리케이션을 Serverless로 옮기면서 수많은 Troubleshooting이 있었습니다.

위의 Troubleshooting 중 ColdStart 및 Async 문제를 해결하기 위해서는 별도의 작업이 더 필요했으며, 예상치 못한 문제가 일어날 수 있다 판단하여, Real Phase에서 Serverless를 도입하는 것은 보류하기로 했습니다.

다만, Serverless application 및 Serverless application 구성을 위한 Cloudformation, lambda상의 코드를 빌드하기위한 AWS Linux + Docker 구성까지 경험해 봄으로써, 추후에 필요한 Micro Service를 별도의 서버구성 없이 Lambda 상에서 구현할 수 있을 것이라 생각됩니다.

기업문화 엿볼 때, 더팀스

로그인

/