6 분 소요

초기 프로젝트를 진행할 때는 불필요한 비용과 오버 엔지니어링을 방지하기 위해 서비스 규모와 SLO(Service Level Objective)를 적절하게 예측해야 합니다. 이미 대중에 널리 알려진 브랜드 IP를 보유한 회사가 새로운 프로젝트를 할 경우 상대적으로 높은 사양의 인프라를 초기 구축해야 하지만, 그렇지 않을 경우 단순한 구조로 시작하여 점차 Architecture를 업그레이드할 수 있습니다.

본 포스팅에서는 제가 웹 쇼핑몰 인프라 운영을 담당하면서 호스팅 사이트와 활성 사용자 수 증가에 따라 초기 2-tier Architecture에서 3-tier Architecture까지 전환한 경험에 대해서 소개해 드리려고 합니다.

초기 2-tier 구성

loading-ag-1044

웹 서버 - DB 서버로 구성된 2-tier 아키텍쳐입니다. WEB 대역과 DB 대역이 별도의 LAN으로 분리되어 있고, 라우팅 및 방화벽 정책을 UTM이라고 하는 장비에서 중앙 관리합니다. 웹 서버가 다수의 개인 혹은 중소 규모의 쇼핑몰 사이트들을 호스팅하기 때문에 1 ~ 2TB 용량의 데이터베이스 한 대에 모든 데이터를 저장하되, 사이트 아이디인 서브 도메인과 고객명, 상품명 등 컬럼을 키로 지정하고 인덱스 또한 적절하게 설정해 주었습니다.

또한 그림에는 나오지 않았지만 쇼핑몰 이미지와 DB 데이터를 백업하기 위한 백업 서버 또한 배치했습니다. 모니터링 또한 빼놓을 수 없는 부분이기 때문에 Glances + InfluxDB + Grafana 스택의 모니터링 시스템을 구축하였습니다.

Load Balancer 및 Redis 도입

loading-ag-1106

더 많은 사이트와 사용자 트래픽을 수용하기 위해서는 단순히 서버 대수를 늘리는 것이 아닌 한층 더 개선된 구조의 Architecture가 필요했습니다. 따라서 많은 쇼핑몰의 트래픽을 여러 대의 서버에 분산하도록 L4 LoadBalancer를 도입한 새로운 인프라를 증설하였습니다. 이를 통해 하나의 공인 VIP로 유입되는 사용자 트래픽을 3대의 서버에 적절하게 분산하고, 하나의 서버가 다운되더라도 자연스럽게 다른 2대의 서버에 forwarding할 수 있는 구조로 고가용성을 보장했습니다. 또한 자주 불러오는 데이터를 cache하기 위해 Redis를 도입하고 분산된 서버에서 이미지 데이터를 저장하고 불러올 수 있도록 NFS 서버를 같은 서브넷에 설치하였습니다.

서버 Scale up 및 수량 최적화로 안정적인 운영과 비용 절감

기존의 CentOS7가 EOS를 앞두고 있어 최신 버그/보안 패치를 적용할 수 있는 신규 Downstream OS인 Rocky Linux로의 이전이 필요했습니다. 그래서 팀 내부적으로 신규 OS로 전환하면서 동시에 memory 크기와 CPU core 수를 Scale up한 서버로 사이트 도메인을 하나씩 이전하여 늘어나는 사이트와 사용자 트래픽도 함께 대비하는 것으로 의견을 모았습니다.

기존에는 12 Core와 Memory 64G의 서버가 주를 이뤘다면 이번 작업에서는 24 Core와 Memory 128G의 서버들을 증설하고 32 Core와 Memory 256G의 서버를 신규로 도입했습니다. 시스템 사양을 높여 서버 대수를 줄인 덕분에 IDC 여유 공간을 25% 확보하여 운영 비용을 절감할 수 있었습니다.

부하 및 트래픽 최적화에 대한 고민

앞선 방법들을 통해 시스템 아키텍쳐를 꾸준히 개선해 왔지만 Grafana에서는 여전히 높은 수치의 네트워크 트래픽을 보여주고 있었습니다. 기존의 2-tier 구조에서 웹 서버가 외부 http(s) 트래픽과 DB mysql 트래픽을 하나의 회선에서 처리하고 있었기 때문에 최대 500Mbps까지 웃돌기도 했습니다. 많은 패킷을 단일 회선에서 처리하기 때문에 특정 이벤트 시 각종 처리 지연이 발생하고 시스템 자원 사용량 또한 증가하기 때문에 웹 트래픽과 DB 트래픽을 분리하는 방안을 고민하게 되었습니다.

loading-ag-1115

처음 생각했던 방안은 그림과 같습니다. 웹 서버에 두개의 이더넷에 회선을 연결하여 하나는 외부 http(s) 트래픽을 처리하고 다른 하나는 데이터베이스에 연결하는 방식입니다. 그림대로 물리적 인프라를 구축하고 DB 트래픽이 eth1 회선을 타도록 라우팅 테이블을 구성한 다음, telnet을 통해 라우팅 및 mysql 접속 상태를 확인하는 식으로 연결 테스트를 진행했습니다. 이제 실제 서비스 테스트와 운영 환경에의 적용만 남았다고 생각했는데 또 하나 고려해야 할 부분이 생겼습니다.

앞단에서 데이터베이스에 직접 접근할 수 있다고?

당시 ISMS 인증 심사 컨설팅을 함께 받고 있었는데, 사용자 인터페이스를 다루는 서버가 데이터베이스에 직접 접근할 수 있는 구조가 보안상 위험할 수 있다는 점을 전달받았습니다. 최소한의 허용 정책만 추가한다는 방침 아래 시스템 방화벽을 UTM과 서버 firewalld를 통해 이중으로 운영하고 있었지만 보다 높은 보안성을 위해 데이터베이스에 접근할 수 있는 애플리케이션 레이어를 구분해야 한다는 권고였습니다.

3-tier Architecture

loading-ag-282

그림처럼 데이터베이스와 Backup 네트워크를 별도로 구성하고, 같은 스위치에 두 개의 VLAN을 놓고 WEB과 WAS tier를 분리합니다. WAS 레이어에 NFS Storage와 Redis를 함께 배치하고 UTM과 firewalld 방화벽을 통해 꼭 필요한 상호 접근 정책만을 허용해 줍니다. 그리고 외부 트래픽의 전달과 데이터베이스로의 트래픽 전달 속도를 개선하기 위해 UTM과 데이터베이스 서브넷, L4 그리고 WEB/WAS tier의 L4와 L2간 회선을 광케이블로 연결해 주었습니다.

그리고 웹 서버에서 WAS에 요청을 forwarding할 수 있도록 nginx에 ‘proxy_pass’ 구문을 담은 location을 추가했고, sticky session이 적용되도록 ‘ip_hash’ 를 설정했습니다.

location ~ \.php {
    proxy_pass   http://backend;
}
upstream backend {
    ip_hash;

    server was1:80;
    server was2:80;
    server was3:80;
}

WEB Layer는 외부 http 요청만을, Application Layer는 백엔드 로직을 처리하는데 집중하여 시스템 부담을 대폭 완화할 수 있는 3-tier 아키텍쳐의 장점을 몸소 체감할 수 있었던 작업이었습니다. 이벤트 진행 빈도가 많고 평균적으로 요청 수가 가장 많은 사이트 한 개를 3-tier 아키텍쳐에 이전하고 모니터링한 결과, 전체적인 시스템 처리 속도가 개선되어 이벤트를 진행할 때도 LoadAvg가 2 밑으로 안정적으로 유지되고 네트워크 트래픽 수치 또한 60% 절감할 수 있었습니다.

더욱 성능이 좋은 Architecture를 만들기 위해서는

앞선 작업들을 거치면서 Redis, Load Balancing, 3-tier Architecture 등 서비스 인프라 성능 개선을 위한 다양한 기술적 구조와 노하우들을 경험할 수 있었습니다. 하지만 더 많은 방법들을 생각하고 실행에 옮기지 못한 것에 대한 아쉬움도 느꼈는데요. On-Premise 인프라 특성상 자원을 손쉽게 scale out할 수 없어 처음부터 최대 트래픽을 고려한 설계를 해야 했고, 이중화되지 않은 데이터베이스를 포함하여 재해 복구 전략을 Backup & Restore 방식에만 의존하다 보니 상당한 시스템 Downtime이 발생할 우려 또한 있었습니다. 높은 생산성을 바탕으로 서비스를 한계없이 성장시키기 위해서는 더 효율적인 여러가지 방식들을 고려해야 합니다.

클라우드 인프라로의 이전

AWS, GCP, Azure 등 퍼블릭 클라우드 환경으로 이전하면 손쉬운 서버 scale out이 가능하기 때문에 처음부터 대형 자원을 선정할 필요가 없습니다. 평상시의 트래픽을 소화할 수 있는 최소한의 자원을 운용하다가 피크 타임에 동일한 자원을 프로비저닝하는 Auto Scaling 정책을 생성할 수 있어요. EFS, S3와 같이 무제한에 가깝게 확장할 수 있는 스토리지 서비스와 RDS, Aurora, ElastiCache 같은 데이터베이스 서비스, CloudFront 같은 CDN 서비스 또한 제공합니다. 부가적인 기능을 추가하기 위한 서버리스 Lambda나 API Gateway를 사용할 수도 있고, 특정 레이어에서 장애가 확산되지 않도록 도입할 수 있는 SQS 같은 큐 서비스 또한 제공하고요. 거의 모든 CSP에서 아키텍쳐를 설계하고 구축하는데 안되는 게 없을 정도의 많은 서비스를 제공하기 때문에 이제는 사용하지 않을 수가 없다고 생각됩니다.

제가 담당했던 쇼핑몰 서비스에서 상품을 결제할 때 사용했던 자체 PG 서비스 혹은 금융 관련 서비스의 경우 데이터 거버넌스나 규정 준수 문제에 부딪혀 클라우드로의 이전이 어려울 수 있습니다. 이럴 경우 프론트엔드만 클라우드로 이전하는 방식으로 퍼블릭 클라우드와 온프레미스 환경을 상호 연동하는 하이브리드 클라우드 환경을 고려해볼 수 있습니다.

IaC 기반 인프라 형상 관리

이전에는 Shell Script를 통해 서버를 빠르게 구성하였지만 멱등성을 보장하지 못한다는 점에서 한계가 있다는 생각이 들었습니다. 그래서 IaC를 고려하게 되었는데, 대표적으로 terraform과 ansible, packer 등이 언급됩니다. 이러한 IaC 도구를 사용하면 작성한 코드대로 서비스 인프라를 휴먼 에러 없이 프로비저닝할 수 있다는 장점이 있습니다. 여러 차례 반복 실행해도 특별한 변경 사항이 없다면 처음에 의도한 대로 인프라를 유지할 수 있고요. 이러한 코드와 state 파일은 Github 같은 형상 관리 도구에 저장하고 여러 사람에게 공유할 수 있습니다.

컨테이너 도입

애플리케이션과 의존성 파일을 하나의 컨테이너 이미지로 빌드하여 배포할 수 있습니다. 여러 대의 서버를 직접 일일이 구성하다 보면 오래 걸릴 뿐더러 빌드가 잘못되거나 의존성이 누락될 수있는 단점이 있는데요. 이를 컨테이너 이미지로 묶어서 보관하였다가 새로운 시스템을 프로비저닝하면 해당 이미지를 pull한 다음 컨테이너를 생성하여 배치하면 더욱 빠르고 정확하게 서버를 구성할 수 있습니다. 각각의 컨테이너는 서로 다른 네임스페이스로 격리되어 특정 워크로드의 장애가 다른 워크로드나 노드에 영향을 미치지 않습니다.

대표적인 컨테이너 런타임에는 Docker가 있고, Docker로 생성한 이미지를 containerd나 cri-o 런타임에서 컨테이너를 실행하고 오케스트레이션할 수 있는 쿠버네티스를 도입해볼 수 있겠습니다. 하지만 쿠버네티스는 많은 학습량을 요구하는 데다 대규모 클러스터에 서비스를 효율적으로 분산하기 위한 기술이므로 자칫 잘못 도입하게 되면 운영 관리가 어려운 오버엔지니어링이 될 수 있습니다. 중소 규모의 프로젝트에서는 오케스트레이션 기술 없이 컨테이너 런타임만 도입해도 충분한 효과를 볼 수 있습니다.

컨테이너 기술을 사용하게 되면 성능 Metric 수집을 노드 수준에서만 할 수 있는 glance가 아닌 Prometheus를 사용하여 노드 뿐만 아니라 파드와 컨테이너 단위로도 Metric을 수집하여 모니터링할 수 있도록 구성해야 한다고 생각됩니다.

CI/CD 도입

Jenkins, Github Action, Argo CD와 같은 CI/CD 도구를 도입하면 수동으로 진행하던 빌드, 테스트, 배포 작업을 빠르게 자동화하여 소프트웨어 개발 생산성을 높일 수 있습니다. 개발자가 형상 관리 도구에 소스를 commit하고 push하면 자동으로 Docker 이미지를 생성하고 테스트할 수 있는 코드(yml)를 작성하여 설정할 수 있고, 환경 변수를 안전하게 저장하였다가 배포 시 적용할 수 있습니다. CI/CD를 통해 개발자는 정해진 시간 안에 더 많은 커밋을 테스트할 수 있고 버그나 오류를 빠르게 수정하고 배포할 수 있습니다.

댓글남기기