급격하게 증가하는 트래픽, 어떻게 대비할까?

김학재
AB-Z
Published in
15 min readJan 21, 2022

ABZ에서 인포크링크를 개발하고 있는 김학재(FE), 박준하(BE)입니다.

인포크링크의 주요 사용자인 인플루언서들은 영향력이 있는 만큼, 우리 서비스로 많은 방문자들을 유입시킵니다. 특히 새해나 발렌타인데이 등 특별한 날에 이벤트를 열어서 팔로워들과 소통을 하기도 합니다.

그러다 보니 예상하지 못한 시점에 갑자기 트래픽이 몰리곤 하는데요, 이를 해결하기 위하여 서버 스택을 이전하는 프로젝트를 진행하게 되었습니다. 이번 글에서는 여러 과정 중에서도 주요한 의사결정과 여러 시행착오에 대해 설명드리려고 합니다.

좀 더 자세한 단계 및 내용에 대해 알고 싶으시다면 ABZ 개발자의 블로그를 방문해 보세요!

김학재 : https://velog.io/@gouz7514/series/AB-Z

기존의 인포크링크는 AWS에서 제공하는 서비스를 활용해 서버는 EB (ElasticBeanstalk), 프론트는 ECS (Elastic Container Service)로 환경을 구축해 개발 및 유지, 보수를 진행중이었습니다. 우리는 여기서 고민이 생겼습니다.

인포크링크는 평소에는 상대적으로 낮은 workload가 부여되지만, 다수의 방문자들이 몰리는 시간대에 빠른 시간 내에 scale-out이 필요한 서비스입니다. 하지만 EB는 scaling의 속도가 느려 즉각적이고 기민한 대응이 힘들다는 단점이 존재했습니다.

“어떻게 하면 더 빠르게 서버를 scale-out할 수 있을까?”

이러한 고민에서 우리의 프로젝트는 시작되었습니다.

이번 프로젝트의 목표는 서버 컨테이너화를 통해 서버 스택을 ECS로 이전하여 auto scaling에 걸리는 시간을 유의미하게 단축하는 것입니다. 다만 이때 유의해야 하는 점이 있습니다. 인포크링크에서는 여러 외부 API가 연동되어 있었기 때문에 기존에 사용하던 외부 API의 사용에도 문제가 없어야 합니다.

완성된 인포크링크 서버 아키텍쳐의 모습

ECS를 사용한 이미지 관리

  • 왜 ECS를 썼을까?

ECS는 AWS가 제공하는 도커 컨테이너를 관리하는 컨테이너 오케스트레이션 서비스입니다.

이번 프로젝트에서 ECS를 선택한 이유는 컨테이너의 관리 및 유지 보수가 굉장히 편리하기 때문입니다. 빌드된 이미지를 통해 태스크를 만들고 서비스로 배포하는 과정을 손쉽게 관리를 할 수 있으며, 실행중인 서비스 및 태스크 관리, auto scaling, 다양한 지표를 통한 모니터링 등 여러 편의 기능을 통해 안정적인 서비스의 제공이 가능합니다.

또한 인포크링크의 프론트 스택은 이미 ECS를 사용중이었기에 서버 스택까지 ECS로의 이전을 통해 좀 더 편리하고 포괄적인 관리를 가능하게 합니다. 현재는 프론트, 서버 스택이 따로 배포되어있지만 더 나아가 하나의 스택으로 관리하는 방향까지 염두에 두었기에 ECS를 통해 컨테이너를 운영하기로 결정했습니다.

  • Reverse proxy : Nginx

컨테이너화 과정을 거친 이미지는 장고 컨테이너 이미지 그리고 장고 앞단에 사용할 nginx 컨테이너 이미지 총 2개입니다.

장고와 함께 nginx를 사용하는 이유는 바로 장고에서 사용하는 runserver 커맨드가 “개발 및 테스트”를 주요 목적으로 하기 때문입니다. 실제로 장고 공식 document에서도 Production 환경에서는 보안, 성능 상의 이슈로 인해 runserver의 사용을 권하지 않고 있습니다.

출처 : https://docs.djangoproject.com/en/2.2/ref/django-admin/#runserver

이러한 이유로 runserver 대신 WSGI(Web Server Gateway Interface)의 일종인 uWSGI를 사용해서 장고를 실행합니다.

  • EC2 vs fargate

생성된 이미지를 레포지토리에 올리기 전 목적에 맞는 클러스터를 생성해야 합니다. 클러스터를 생성할 때 아래 사진처럼 EC2와 Fargate 중 선택을 할 수 있습니다.

AWS console에서 선택할 수 있는 클러스터의 인프라

EC2는 cpu의 사용률이 예측 가능하고 안정적인 시스템에서 더 높은 효율을 발휘하는 반면, fargate는 유동적인 workload가 나타나는 환경에서 높은 효율을 발휘합니다.

앞에서 언급했듯 인포크링크는 scaling의 빈도가 잦고 평소에는 상대적으로 낮은 workload가 부여되기 때문에 낮은 단위의 cpu(0.25)까지 사용할 수 있는 fargate를 최종 선택했습니다.

배포까지의 시행착오

  • m1 칩 기기라면?

보통의 경우라면 다음과 같은 커맨드로 이미지를 빌드할 수 있습니다.

docker build -t 태그명 .

이 단계에서 우리는 예상하지 못한 에러를 맞닥뜨리게 됐습니다.

exec format error

이러한 에러가 발생하는 이유는 바로 ECS를 통해 배포될 task와 m1칩을 통해 빌드된 이미지의 아키텍쳐가 다르기 때문입니다.

ECS task의 아키텍쳐는 아래와 같이 설정할 수 있는 반면

ECS 태스크 아키텍쳐

m1 칩을 통해 빌드된 이미지의 아키텍쳐는 다음과 같습니다.

m1칩을 통해 빌드된 이미지 아키텍쳐

이 문제를 해결하기 위해 우리는 docker buildx커맨드를 사용했습니다.

docker buildx는 docker 명령어의 확장 플러그인으로 멀티 아키텍쳐 이미지를 빌드할 수 있습니다. 또한, 사용자가 원하는 기능을 포함한 커스텀 빌더를 통해 원하는 형식의 이미지를 빌드할 수 있습니다.

docker buildx build --platform linux/amd64 (-t 태그) . (--no-cache) (-f Dockerfile 이름)

이렇게 생성된 이미지는 이제 amd64 의 아키텍쳐를 지니게 되어 ECS의 태스크와 문제 없이 호환되는 것을 확인할 수 있습니다.

이제 생성된 이미지를 통해 본격적으로 배포를 진행할 차례입니다!

  • Task, Service 그리고 Subnet

Task를 정의하는 과정부터 ECS에 서비스가 생성되어 정상적으로 작동하기까지에 많은 시행착오들이 있었습니다. 기존 네트워크나 보안 관련 지식이 많이 부족한 상황이었기에 서비스의 이전은 많은 공부를 필요로 했습니다.

기존 VPC에 사용되고 있는 다른 서비스들 (elastiCache 등)이 이전 이후에도 사용 가능해야 했기 때문에 새로운 VPC를 만드는 대신 이미 많은 리소스들을 포함하고 있는 기존 VPC를 사용했습니다. 그러다 보니 다음과 같은 에러를 마주했습니다.

existing CIDR error

ECS에 배포될 서비스를 위해 5개의 서브넷이 필요했는데, VPC에 할당된 기존의 CIDR block의 대부분이 다른 서브넷에 의해 사용 중이었기에 VPC 설정에서 새로운 CIDR block을 다음과 같이 추가했습니다.

이렇게 새로운 block을 만들어 한 서비스에 필요한 모든 subnets를 해당 block 내에 할당하니 추후 관리에도 더욱 용이했습니다.

만약 CIDR, subnet masking 의 컨셉이 낯설다면 이 영상을 추천드립니다.

  • 보안 그룹

서비스를 생성하고 정상적으로 작동을 완료한 후, 사용한 리소스나 컴포넌트들에 대해 되짚다보니 의문이 들었습니다.

AWS의 설명에 의하면, Security group은 inbound/outbound 트래픽을 컨트롤 해주는 가상 방화벽입니다. 하지만 우리의 ECS service에 사용되고 있는 Security group은 방화벽으로서의 역할을 전혀 해주지 못한다는 것을 깨달았습니다.

출처 : https://www.sladeblog.co.uk/aws-security/what-is-a-security-group/

위 사진과 같이 여러가지 security group으로 나뉘어져 있는 대신, load balancer를 포함한 모든 컴포넌트가 하나의 security group으로 묶여져 있었고 이것은 곧 취약한 보안으로 이어집니다.

따라서 https 접속을 허용하는 load balancer만을 위한 security group을 따로 만들어 주었고, ECS service를 위한 security group에서는 load balancer target groups과의 통신, 그리고 ElastiCache와의 통신을 위한 포트만 열어두었습니다. 이 과정을 통해 모든 인터넷 접속은 load balancer를 통하게 되고 서비스는 외부의 공격으로부터 비교적 안전해집니다.

  • 끝나지 않는 보안 문제
절대 해서는 안될 그 말.. 해치웠나?

안전한 아키텍쳐를 구성하고 “해치웠나" 라는 말을 입 밖으로 꺼낸 순간, 한가지 문제가 눈에 띄었습니다. 우리의 ECS 서비스는 인터넷에 여러가지 요청을 보내고 응답을 받아야 할 일이 많기 때문에 서비스의 subnet의 route table에서 internet gateway를 추가해 주었습니다. 하지만 이렇게 되면 우리 의도와는 다르게 ECS service가 public subnet에서 돌아가게 되는 셈입니다. 이상적인 아키텍쳐의 경우는 다음 사진과 같습니다.

출처 : https://datachef.co/blog/aws-vpc-with-public-and-private-subnets/

이렇게 public subnet에 위치한 NAT Gateway를 한번 거쳐 Internet Gateway로 연결되는 방식이기때문에, private subnet에 위치한 ECS service는 어떤 경우에도 인터넷과 다이렉트한 통신을 할 수 없습니다.

위에서 언급한 security group이 방화벽 역할을 해주지만, 다른 서비스들과의 통신을 위해 열어둔 포트를 통해 인터넷으로부터의 접속이 충분히 가능했고, 이것은 곧 보안문제로 이어지게 됩니다. 이에 대한 대체 방안으로, 모든 인터넷의 해당 포트를 통한 접속을 허락하는 대신, AWS 내 해당 포트를 통해 소통하는 다른 컴포넌트/서비스가 속한 security group으로 부터의 해당 포트만을 통한 접속을 허용했습니다. 이를 통해 어떤 경우에도 인터넷으로부터의 다이렉트한 접속은 security group을 통해 모두 차단되고, 외부의 공격으로부터 한층 안전해집니다.

  • 테스트 그리고 테스트

배포 이후에 과연 우리가 ECS 의 컴퓨팅 파워를 효율적으로 사용하고 있는가에 대한 의문이 들었습니다. ECS Fargate Task를 정의할 때 컨테이너별 cpu와 memory limit 항목을 본 것이 불현듯 생각이 나 바로 시험해 보기로 했습니다.

상대적으로 가벼운 Nginx에 메모리 및 cpu 할당량을 적게 주고 장고 서버 컨테이너의 리소스를 더 확보하면 성능이 향상되지 않을까 하는 기대를 가지고 테스트를 시작했습니다.

아래 사진은 Locust를 활용한 load testing 결과입니다. 인포크링크의 api 서버에 평소 들어오는 요청 수와 비슷하게 테스트 환경을 구축하고 컨테이너별 cpu/memory 수동 할당(attempt #1), 그리고 따로 할당하지 않은 기본 세팅(attempt #2) 으로 진행해보았습니다.

attempt #1 (Nginx 0.15 : django 0.85 의 할당량 설정)
attempt #2 (default settings)
AWS cloudwatch에서 확인한 CPU utilization 그래프

여기서는 유의미한 차이를 찾지 못하여 기본 세팅을 유지하기로 하였습니다.

배포 후

  • stress test

배포가 마무리 되고, 이제는 우리의 가설대로 새로운 서비스가 기존 EB내의 서비스보다 더 빠른 auto-scaling이 가능하고 갑작스럽게 몰리는 트래픽에 대한 대응이 가능한지 테스트 해보기로 했습니다.

유명 인플루언서의 공동구매 등 큰 이벤트가 있을때, 가장 많이 트래픽이 몰리는 곳은 인포크 링크의 방문자 페이지 입니다. 따라서 인포크링크의 방문자 페이지에 접속했을 때 api 서버에 보내는 요청들을 재현해서 Locust를 통한 stress test를 진행하기로 했습니다.

2021년 한해동안 가장 많은 트래픽이 몰렸던 날을 그대로 재현해보기로 했습니다. 평소의 트래픽 양과는 비교도 안 되게 많은 양의 트래픽이 몰렸고, 잠시동안 대부분의 요청을 제대로 핸들링하지 못했던 전례입니다.

기존의 EB는 감당하지 못했던 트래픽을 새로운 서비스가 감당할 수 있을지 로컬 환경에서 테스트를 진행했습니다. Cloudwatch에서 확인한 request count 그래프는 다음과 같습니다.

딱 봐도 그 날이 언제인지 보이시죠?

한 컴퓨터에서 너무 많은 요청을 보내 자꾸 컴퓨터가 꺼지는 일이 발생했고, 결국 모두의 힘을 빌려 약 다섯 대의 컴퓨터로 테스트 상황을 구현했습니다. 반응속도가 조금 느려지나 싶었지만 빠른 scale-out 이후에 곧 안정화가 되었고, 우리의 본래 목적이었던 “급작스러운 트래픽 증가 시 빠른 scale-out과 서버의 안정화” 라는 목표의 달성을 확인 할 수 있었던 감격스러운 순간이었습니다.

위 사진과 같이 우리가 정해놓은 cpu utilization 수치를 넘어가면 여러개의 태스크를 추가해 빠르게 scale-out되며 서버가 안정화됩니다. 이후 너무 많은 태스크로 cpu utilization이 과도하게 낮아지면 리소스를 save 하기 위해 천천히 scale-in 되며 우리가 원하는 cpu utilization range 내의 수치로 조정합니다.

  • CDK

배포는 성공적으로 마무리되었지만 위와 같은 과정을 매번 반복하기에는 한계가 있었습니다. 유지보수하는 개발자가 각 단계를 명확히 알아야 하고 동시에 순서까지 지켜야 하고…

이렇게 복잡한 배포 과정을 한 번에 그리고 코드를 통해 관리할 수 있도록 하는 기능이 바로 AWS CDK 입니다.

AWS의 다양한 리소스들을 CDK를 통해 손쉽게 관리할 수 있으며 Java Python Typescript 등 다양한 언어들을 사용해 개발할 수 있습니다. (인포크링크는 Typescript를 사용해 여러 스택을 한번에 관리합니다)

CDK는 빌드된 스택을 cdk deploy 스택명 커맨드를 통해 AWS의 Cloudformation에 생성합니다. deploy된 스택은 각 단계별로 진행 상황 및 에러 발생 시 어느 부분에서 에러가 발생하는지를 손쉽게 파악할 수 있습니다. 또한 cdk synth 커맨드를 통해 작성중인 코드와 실제 AWS의 리소스와 차이점을 파악해 개발자의 실수를 방지할 수 있습니다.

아쉽게도 현재 기준(2022.1.21) CDK는 codedeploy까지는 완벽하게 지원하지 않아 CDK만을 사용한 완벽한 배포는 불가능합니다. 이를 보완하기 위해 우리는 github action을 사용해 이미지를 푸쉬하고 동시에 codedeploy 과정까지 수행함으로서 CDK 그리고 github action을 사용한 CI/CD 과정을 완성했습니다.

이제 인포크링크의 개발자는 다음 단계만 거친다면 누구라도 손쉽게 서버 이미지를 관리할 수 있게 되었습니다!

1. cdk deploy 스택
2. 서버 이미지 빌드 후 업로드
3. codedeploy 그룹 생성
4. github action을 통한 deploy

이후 새로운 배포가 있을 경우 github action을 통해 Blue/Green deploy

약 3일의 모니터링 기간 동안 서버는 우리의 바램대로 정상적으로 동작했고 동시에 서비스 장애 관련 CS도 발생하지 않았습니다. 하지만 우리의 프로젝트는 이제부터 시작이라고 생각합니다.

예상을 뛰어넘는 방대한 유저 트래픽이 몰릴 시 scaling에 대한 모니터링도 필요할 뿐더러, 아직 서버 스택과 프론트 스택이 따로따로 관리되고 있기 때문에 새로운 기능 배포 시 중단 배포가 불가피한 상황입니다. 만약 하나의 스택만으로 둘 다 관리가 가능하다면 진정한 의미의 무중단 배포를 이루어내지 않을까 기대를 하고 있습니다!

방대한 내용을 담으려고 노력한 긴 글을 읽어주셔서 감사합니다!

글을 마치며

박준하 : ABZ에 백엔드 엔지니어 인턴으로 합류해서 진행한 첫 프로젝트 입니다. 평소 혼자 프로젝트를 진행하면서 크게 신경쓰지 않았던 best practice나 보안 등, 서비스의 완성도에 대한 고민을 하게되는 계기가 되었습니다. 단순한 “coder”로써의 성장 뿐만 아니라, 전체적인 큰 그림을 이해하고 문제를 찾아 해결하는 “engineer”로써의 큰 첫 발걸음 이였다고 생각합니다. 나보다 뛰어난, 언제든 가이드라인을 제시 해 줄 수 있는 동료들과 함께 일할 수 있다는 것은 축복같은 일이라고 느꼈습니다. 나영님, 성문님, 그리고 학재님… 정말 감사하다 👍

김학재 : 평소에 눈에 보이지 않아 막연하게 어려워했던 서버, 인프라에 대해 공부하고 체험해볼 수 있는 프로젝트였습니다. 프론트엔드 개발자로는 접하기 힘들었던 리소스들에 대한 공부를 진행하면서 평소 모토인 “막막해 보일지라도 결국 나는 해내고야 만다”를 또 한번 실천할 수 있었습니다. 인포크링크 서비스의 메인 개발자로 나아가는 첫 걸음을 성공적으로 뗄 수 있어서 새해 시작이 너무 만족스러워요! 같이 고생해주신 테크리드 윤성문님, 백엔드 강나영님, 박준하님 정말 감사해요!

ABZ 팀에 관심 있으신가요? (채용 정보 확인하기)

--

--