[회고] AWS 기반 신규 서비스 설계 및 구축


입사 후 처음으로 처음부터 끝까지 직접 담당하게 된 프로젝트였습니다.

보안 회사 S사는 신규 서비스를 준비하면서, 기존에 사용하던 인프라 환경이 아닌 AWS 상에 새로운 환경을 구축하는 것을 목표로 하고 있었습니다.
저는 해당 프로젝트에 Solutions Architect로 참여하여, 전체 아키텍처 설계와 AWS 인프라 구축을 담당하게 되었습니다.

고객사 측에서 최초로 전달한 요구사항은 다음과 같습니다.

고객사 데이터는 VPN을 통해 수신되며, 하루 평균 약 5TB 규모이고 모든 트래픽은 UDP 프로토콜을 사용한다.
신규 서버 5대를 기본으로 구성하되, Auto Scaling을 통해 최대 10대까지 scale-out이 가능해야 하며, 트래픽 감소 시 자동으로 scale-in 되어야 한다.
서버로 수신된 데이터는 파일 스토리지에 저장한 후, 각 서버에서 압축 과정을 거쳐 IDC와 AWS S3로 전송되어야 한다.
이 모든 과정에서 트래픽 유실은 절대 발생하지 않아야 한다.
개발 환경과 운영 환경은 서로 분리하되, 동일한 구조로 구성해야 한다.

요구사항을 처음 접했을 당시에는, 아키텍처 자체가 매우 복잡한 구조는 아니었기 때문에 비교적 수월하게 진행할 수 있을 것이라 생각했습니다. 핵심은 대용량 UDP 트래픽을 안정적으로 처리할 수 있는 성능과 확장성을 확보하는 것이었고, 해당 부분만 적절히 설계한다면 큰 문제는 없을 것이라 예상했습니다.

하지만 실제 프로젝트를 진행하면서, 제가 예상했던 것보다 훨씬 더 많은 고려사항과 예상치 못한 이슈들을 마주하게 되었습니다.


첫 번째 이슈

UDP 트래픽의 로드밸런싱

여기서 제가 아직 초보라는 것을 많이 느꼈습니다. AWS NLB는 UDP 트래픽의 로드밸런싱이 TCP와는 다르게 이뤄진다는 것을 미쳐 고려하지 못하고 아키텍처를 설계하려했던 것이죠.
AWS NLB는 UDP 트래픽에 대하여 5-tuple(출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트, 프로토콜)기반의 해시 알고리즘만 지원하며, TCP와 달리 라운드로빈 등의 알고리즘을 선택할 수 없었습니다. 이로 인해 동일한 클라이언트에서 발생하는 UDP 트래픽은 항상 동일한 타겟 서버로만 라우팅되어 부하가 특정 서버로만 편중되고 제대로 분산되지 않게됩니다.
이를 해결하기 위해 NLB 앞단에 Nginx 서버를 배치하여 UDP 패킷의 소스 포트를 통적으로 변경함으로써 해시 결과값이 달라지도록 했습니다. 이렇게 하면 트래픽이 여러 타겟 서버들에 고르게 분산될 수 있었습니다.


두 번째 이슈

자꾸만 추가되는 새로운 상황

프로젝트 전반에 걸쳐 가장 큰 어려움은 지속적으로 변경되는 고객사의 요구사항이었습니다.
초기에 합의된 요구사항이 계속해서 바뀌면서 아키텍처 설계에 걸림돌이 되었습니다.
주요 변경 사항은 다음과 같습니다.

1. 고객사 데이터는 VPN뿐 아니라 인터넷망을 통해서 들어오는 데이터도 많다.
2. 데이터가 모두 UDP인 것은 아니고 TCP도 존재한다.
3. 서버로 들어온 데이터들을 저장할때 블록 스토리지를 사용해야 한다.
4. 환경 구성에서 비용 문제로 개발환경과 운영환경을 똑같이 구성할 수 없다.
5. 트래픽을 독립적으로 분리해서 처리해야하는 경우가 하나 있다.

결과적으로, 초기 회의롤 통해 설계했던 아키텍처는 전부 쓸 수 없게 되었고 다시 설계해야했습니다.
VPN과 인터넷망 트래픽을 분리하여 수신하도록 LB를 수정해야 했고, AWS에서 TCP와 UDP의 부하분산 처리 방식이 다르기 때문에 LB를 추가로 구성해야 했습니다. 또한 블록 스토리지로 서버 간 데이터를 공유하는 경우, Scale-Out 후 Scale-In된 새 서버에 기존 스토리지를 재연결하는 과정도 고려해야 했습니다. 트래픽을 독립적으로 분리하기 위한 LB와 서버가 추가되면서 비용 제약으로 인해 개발 환경을 운영 환경과 동일하게 구성하기 어려워졌습니다. 이에 따라 개발 환경의 서버 수를 줄이고 최소한의 구성으로 테스트할 수 있도록 전반적인 변경이 필요했습니다.


세 번째 이슈

블록 스토리지 사용으로 인한 데이터 유실

기존 AWS EFS(Elastic File Storage)를 사용하기로 했던 설계에서 고객사 측의 요구로 EBS(Elastic Block Storage)로 변경하게 되었습니다. 이유는 EFS가 속도가 느리다는 이유였습니다.
이에 따라 스케일링 되는 각 서버들 간 데이터 정합성이 일치하지 않게 되는 문제에 봉착하게 되었습니다. 이 문제를 해결하기 위해 어플리케이션 개발사 측에 해당 내용을 요청했습니다.

오토스케일링 환경에서 Scale-In 이벤트 발생 시 인스턴스의 블록 스토리지에 잔류하는 데이터 유실 문제 해결을 위하여, 
Java의 소멸자 패턴을 활용한 데이터 보존 프로세스를 구현 할 것
JVM 종료 시점에 실행되는  Runtime.addShutdownHook()을 활용해 인스턴스 종료 직전 정리 로직이 트리거 되도록 구성

이를 통해 리소스 해제 시점에 블록 스토리지 내 데이터를 수집하고, 이를 압축 후 Amazon S3 버킷과 IDC로 전송하는 파이프라인을 구축할 수 있었습니다.
Scale-In 시 AWS EC2 인스턴스는 Lifecycle Hook을 통해 종료 전 대기 시간을 확보할 수 있으며, 이 유예 시간 내에 Shutdown Hook이 실행되어 데이터 전송이 완료될 수 있도록 타임아웃 및 전송 완료 여부를 검증하는 로직을 함께 구현하였습니다.
결과적으로, Scale-In 발생 시 블록 스토리지 데이터의 유실 없이 S3와 IDC로 안전하게 이관함으로써 데이터 정합성을 유지하고, 인스턴스 비용 절감과 데이터 보존이라는 두 가지 목표를 동시에 달성하였습니다.


네 번째 이슈

개발사 측 사정으로 인한 일정 지연

여러 가지 이슈가 있었지만, 결과적으로는 주어진 WBS 일정에 맞춰 아키텍처 설계와 인프라 구축을 모두 완료할 수 있었습니다. 특히 아키텍처 설계 단계부터 Terraform 코드를 사전에 준비해두었기 때문에, 인프라 구축을 빠르게 진행할 수 있었습니다.
그러나 테스트를 진행하기 위해서는 어플리케이션이 먼저 완성되어야 했고, 개발사 측의 내부 사정으로 인해 예정된 날짜까지 어플리케이션이 준비되지 못했습니다. 해당 서비스는 대규모 트래픽이 유입되는 환경이었기 때문에 사전 테스트가 필수적이었고, 반복되는 일정 지연으로 인해 점점 더 촉박한 상황에 놓이게 되었습니다.
결국 서비스 오픈 일정이 다가오면서, 기존 계획을 그대로 기다리기보다는 테스트를 진행할 수 있는 다른 방안을 마련해야 했습니다.
따라서 다음과 같은 방식으로 테스트를 진행하기로 결정했습니다.

운영 환경의 Fortigate를 대신하여 임시로 Nginx Proxy 서버를 구성하고,
개발 환경에 배포된 어플리케이션과 연결하여 기능 테스트를 먼저 수행한다.
이후 어플리케이션 테스트가 완료되면, 실제 운영 환경과 동일한 인프라 구조로 다시 전환한 뒤
Auto Scaling이 정상적으로 동작하는지 대규모 트래픽을 발생시켜 검증한다.

해당 테스트를 완료한 이후, 개발사 측의 개발 및 배포가 모두 마무리되었고, 최종적으로 서비스를 안정적으로 오픈할 수 있었습니다.


마무리하며

이 외에도 글로 모두 담지 못한 여러 이슈들이 있었습니다. 첫 프로젝트였던 만큼 예상하지 못한 상황들도 많았고, 그 과정에서 다양한 고민과 판단이 필요했습니다. 사수님의 문제 해결 방식과 회의에서의 의사결정 방식을 현장에서 직접 보고 배우면서, 프로젝트를 어떻게 바라보고 대응해야 하는지 실무적인 접근 방법을 배울 수 있었습니다. 이번 경험은 앞으로 수행하게 될 프로젝트들을 위한 중요한 밑거름이 되었다고 생각합니다.