이번엔 기술적인 이야기를 해볼려고 한다. 2년동안 스타트업에서 서버를 호스팅하면서 아키텍처가 점점 어떤 식으로 발전하였는지
아주 초기에는 AWS 클라우드 위에 서버를 임대하여 호스팅하였다. 루트계정을 하나 만들어서 사용했고 매우 단순한 구조였다.
AWS에서 기본으로 제공해주는 VPC 안에 PUBLIC Subnet에 EC2 인스턴스를 하나 빌려서 고정 Public IP를 하나 할당해두고, 도메인 호스팅 업체에서 도메인을 하나 산 뒤 route53에 네임서버를 하나 만들고 구매 호스팅 업체 네임서버에 내가 AWS에서 만든 네임서버로 변경해주었다. 그리고 서브 도메인 주소를 하나 만들고 SpringBoot로 만든 API를 연동했고, 프론트는 Vue.js로 만들어서 빌드 된 파일을 S3 버킷에 올려 정적 웹사이트 호스팅을 하였다.
이후에는 RDS도 하나 AWS에서 임대하고 프론트용 정적파일을 올려둔 Bucket을 cloudFront에 붙이고 호스팅하였다. 그리고 Backend API는 Target Group을 만들고 ALB를 하나 만들어서, 이 녀석을 중간에 두고 통신하도록 세팅하였다. 업무 요구사항에 HTTPS가 있고, 이걸 어떻게 붙일까 찾다보니, ACM이라는 녀석을 알게 되고, 얘를 어떻게 붙일까 고민하다 보니 어찌어찌 이런 구조를 짜게 되었다. 이때만 해도 뭘 잘 몰랐기 때문에, 와일드 카드 도메인과 ALB 리스너 룰이라는 개념도 몰라서 서브도메인마다 ALB를 하나씩 생성해서 1대1로 붙이고, ACM도 1대 1 로 하나씩 계속 만들었다.
위의 상태로 꽤 오래 유지했다. 개발진행단계였고, 딱히 트래픽도 없었기에 뭐가 문제인지 파악을 못했다. 그러다가 AWS Summit Seoul 에 참석하게 되었는데 참석명단을 보고 AWS 파트너사 중 한 곳이 우리에게 컨택을 했다. 그로 인해 MSP라는 개념에 대해서 알게 되었고, 상담결과 계약을 맺기로 하고, 몇가지 조치를 취했다. 이때부터 보안에 대해서 경각심이 들었다.
꽤나 많이 바꼈다. 이메일 전송 기능을 위해서 AWS의 SES api를 이용했고, 단일 서버에서 역할을 분리해 서버를 하나 더 만들었다. 그리고 파이썬 개발자 한분이 만든 API와 통신할 인스턴스도 하나 만들고 세팅했다. file을 업로드하는 기능을 위해서 File용 버킷이 하나 생기고, DB도 AWS가 권장하는 자동으로 레플리카 옵션이 달려진 벤더사에서 엔진 자체 제작한 Aurora Mysql로 변경했다. 웹에서 오디오 및 비디오 파일 스트리밍을 위해서 업로드 된 파일을 HLS 프로토콜로 컨버팅하기 위해, MediaConvert API를 이용했고, mediaConvert 결과를 callback 받기 위해 EventBridge로 리스너를 선정해뒀다. DB도 private Subnet 안에 위치하게 두어, 혹여 계정이 탈취당하더라도 Vpn을 통하지 않는 한 외부에서 접근 못하도록 세팅하였다. Private EC2 에서도 패키지를 인터넷에서 땡겨와 설치할 수 있도록 Private Subnet에게 NAT GateWay를 달아줬고 인스턴스 안의 API간 통신은 모두 ALB을 중간에 두고 통하도록 만들었다.
이때부터 단순 개발만 아니라, 개발 업무에 관련된 모든 할 수 있는 일에 대해서 찾아보기 시작했다. 혜택을 얻을 수 있는 경로는 모두 신청했던 것 같다. 이 시기에서 제일 짜증났던 것은 이런 인프라 구축과 행정적인 신청과정이 아니라, 결제를 위해 PG사를 고르는 것이었다. 전에 결제모듈을 구현한 경험이 없다 보니, 어떤 PG사를 컨택하고 API를 연동할지에 대해서 고민이 많았고, 특히 중국쪽과 연동된 해외결제 API를 제공해주는 PG사는 뭐가 있는지 파악하는 게 골머리를 썩였다. 결국 찾은 PG사는 문서가 엉망에 Form 방식의 옛날 통신 규격을 가지고 있어서, 몇 번 연동하다가 뒤늦게 특정 조건에만 지원 해주는 걸 파악하고 다시 고치는 과정이 스트레스였다. 덕분에 이 부분은 코드가 예쁘지가 않고 엉망으로 구현된 게 좀 있다. 만약 후임자가 넘겨받는다면 이 부분은 미안하게 생각한다.
이때부터 무중단 배포에 대해서 신경을 쓰기 시작했다. 시중에 나와있는 글이 대부분 NGINX 기준으로 무중단 배포 설정을 설명하고 있어서, 로드밸런서 기준으로 어떻게 설정하는지 찾느라 꽤 삽질을 했다. CodeDeploy와 AutoScalling Group을 이용한 블루 그린 배포로 설정하였고, 깃허브 액션을 통해 트리거하도록 파이프라인을 짰다. 여기서 삽질을 진짜 많이 했다. 여러개의 서버에서 지표를 한군데 통합해서 보기 위해 하나의 모니터링용 서버 인스턴스를 두고 로그 및 지표를 수집하도록, 그라파나와 프로메테우스 로키 서버를 연동했다. 그러고도 cloudWatch를 통해 로깅해서 버킷에 기록하도록 만들었다. 그리고 불필요한 외부 인터넷 망을 타서 통신하는 비용을 줄이기 위해, Private subnet 안의 API끼리 통신할 때는 내부용 로드밸런서를 하나 만들어서 이걸 통해 통신하도록 세팅했다. 파이썬 서버와 통신할 때는, 네트워크 로드밸런서를 하나 생성해서 통신하도록 만들었는데, 별다른 이유는 없고, 이렇게 한 번 만들어보고 싶어서 했다. 더 빠르지 않을까 하는 기대심리도 있고. JMeter를 통해 부하테스트를 진행해서 ASG 스케일 아웃 기준 지표가 잘 통하는지 테스트해보았다. 알리바바 클라우드 POC 인프라 구축 및 서버 복제도 이와 동시에 진행을 하였다.
서버를 하나만 운영하는 것보다 중간에 프록시 서버를 하나 두고 여러개의 복제된 서버가 번갈아서 요청받는 구조일 때, 신경써야 될 문제점들이 태어나는데, 대표적인 게 세션 같은 문제점이다. 여기서는 스케줄링 함수의 중복이랑, SSE Event를 정상적으로 못 받는 장애가 나타났는데, 이를 Redis의 Pub/sub 기능과 shedlock 기능을 활용해 해결했다. 캐시 구현체로 레디스를 쓴 것은 아니다. 애초에 만든 서버가 캐싱을 적용하기 애매한 부분이 있어서 적극적인 캐싱전략을 하지 못하는 상태이기도 했다. 그리고 Backend Api는 따로 캐싱서버가 필요한 게 아니라 매번 상태를 정확히 보고받아야 되기 때문에 일부러 CloudFront를 통해서 호스팅하지 않고, ALB만 앞단에 놓았는데, 여러가지 서칭결과, ALB도 CDN을 먼저 앞단에 뒤에 놓는게 여러모로 성능상 이점이 있다는 것을 깨달았다. 아무런 캐싱을 하지 않더라도, ALB는 일반 ISP 회선망을 통해 전송되는데 CDN을 통하면 CDN 전용 회선망을 통해 빠르게 네트워크 전송이 가능하고 그 외 여러가지 혜택이 있단다. 그래서 인터넷에 노출되는 주소는 전부 CloudFront를 통해 일괄적으로 호스팅하도록 설정하고, WAF도 붙여서, 개발 테스트용 서버는 허용된 IP 외에 외부에서 접속 못하도록 세팅하였다.
내가 생각하기에 Simple is Best이다. 사실 해보고 싶었던 것은 많았다. EKS도 해보고 싶고, 카프카도 써보고 싶고, 하지만 지금의 경우는 오버엔지니어링이며 비용 및 관리 대비 효율이 안 나온다. 이것도 최대한 보안을 챙기고 비용을 아끼면서 최대한 단순하도록 아키텍처를 짤려고 노력한 결과이다. 아 또 뭐 할 얘기가 많은데.. 일단 귀찮아서 여기에 끊겠다.