인프라를 관리하다보면 인프라가 얼마나 안정적(Stable)이고 확장 가능(Scalable)한지 구축하는 것도 중요하지만, 비용적으로 효율적(Cost Optimal)인가 고려하는 것도 아주 중요하다.
인프라 관리만 하는 것이 아니라, 프론트/백엔드까지 관리해야 하는 입장으로서 항상 신경쓰고 있기란 현실적으로 힘든 일이기도 하고, 스펙이 바뀌면서 리소스 사용량이 크게 달라지면 인프라 배치도 새로 고려해야 한다.
또한 EKS · EC2 · CloudWatch · Amplify · Load Balancer 같은 AWS 리소스들은 아무 생각 없이 두면 매달 비용이 자연스럽게 누적된다.
이번 작업의 목표는 단순했다.
서비스 안정성을 해치지 않으면서, 구조적으로 줄일 수 있는 비용은 모두 줄이는 것이다.
이 글에서는 실제로 우리가 적용했던 AWS Cost Optimization 항목들과, 각각 어떤 판단으로 비용을 줄였는지를 정리한다.
1. EBS 볼륨 크기 줄이기
Karpenter Node Pool 설정에 백엔드와 데이터베이스는 같은 Node Pool에서 노드를 할당받는다.
현재 노드가 생성될 때 EBS는 아래 설정에 따라 128GiB로 생성된다.
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 128Gi
volumeType: gp3
deleteOnTermination: true하지만 백엔드 서버는 Stateless 이기 때문에 내부 캐싱도 필요 없기 때문에 큰 저장 공간이 필요 없었다.
심지어 여러 개의 서버가 있고, 서버마다 3개의 Pod이 배포되어있기 때문에 EBS 낭비가 심하게 되고 있는 상태였다.
- 우리는 1 Node 1 Pod 원칙으로 관리하고 있기 때문에 서버 당 3개의 노드로 관리되고 있다.
정리하자면 데이터베이스를 제외한 모든 노드가 비교적 넉넉한 스토리지를 할당받고 있었고, 이는 실제 사용량 대비 오버 프로비저닝이다.
따라서 백엔드는 다른 Node Pool을 참조하도록 해서 EBS Storage를 10GB로 축소시켰다.
어차피 컨테이너 자체는 Stateless하고, 로그는 외부 Grafana로 모이기 때문에 지장이 없을 것이라고 생각하였다.
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 10Gi # 백엔드는 10GiB
volumeType: gp3
deleteOnTermination: true2. CloudWatch 비용 줄이기
CloudWatch는 켜두면 편한데, 켜둔 만큼 그대로 비용이 나가는 서비스다.
클러스터 로깅 관련 설정을 보니 EKS Control Plane Logging이 전부 활성화되어 있었고, 실제 운영에서는 거의 보지 않는 로그들이 계속 쌓이고 있었다.
따라서 필요 없는 Control Plane Logging을 전부 꺼주었다.
3. 프론트 빌드 및 이미지 최적화하기
몇몇 무한스크롤 페이지에서 next/image 컴포넌트를 사용하고 있지 않았다.
그렇게 되면 같은 캐릭터의 이미지를 여러 번 로드할 필요가 없는데 매번 CDN에서 이미지를 가져오게 된다.
따라서 Cloudfront Cache Hit 비용이 추가로 나가게 된다.next/image 적용 시 Next.js 서버 자체에 캐싱되기에 Cloudfront에 접근하기 전에 처리가 된다.
추가적으로 Amplify 빌드 시 필요 이상으로 노드 리소스를 확장하고 있었는데, 다시 기본값으로 돌렸다.
우리는 핫픽스/기능 개발로 프론트 빌드가 하루에도 5번 이상 일어나는데, Amplify는 빌드 노드 리소스 만큼 비용을 부과하기 때문에 비용 절감에 도움이 될 것이라고 생각하였다.
4. 불필요하게 할당된 Public IP 없애기
기존 Karpenter 설정 상 Subnet Selector를 넣지 않아서 일정 확률로 Public Subnet에 노드가 생기면 Public IP를 할당받는 문제가 있었다.
Karpenter의 Node Class에 internal-elb를 넣어서 Private Subnet에만 노드가 생길 수 있도록 하였다.
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: app-cluster
kubernetes.io/role/internal-elb: "1"5. Kubernetes 노드/인스턴스 구조 최적화하기
우리는 데이터베이스를 t4g.large 인스턴스에서 운영하고 있다.
3개의 데이터베이스 Pod이 각각 t4g.large 인스턴스에서 실행되고 있는 구조다.
t4g.large 인스턴스는 8GiB 메모리로, 평상 시에 1.5-2GiB 정도의 메모리를 쓰는 상황에서는 과하다고 생각했다.
따라서 t4g.large -> t4g.medium으로 노드 스펙을 낮추기로 하였다.
아래와 같이 데이터베이스 노드는 모두 t4g.medium으로 다운그레이드 시켰고, 문제없이 동작함을 확인하였다.
그리고 인퍼런스(Inference) 서버는 Python + Prisma로 구현돼 있어 다른 서버보다 메모리를 상당히 많이 먹는다.
평상 시에도 메모리를 3GiB 정도 먹고 있기 때문에 상당히 많은 리소스를 필요로 하는 서버다.
그리고 채팅 기능과 직결돼 있기에 인퍼런스 서버는 건들지 않기로 결정하였다.
백엔드 서버는 현재 Node 기반의 Nest.js로 구현돼 있어 매우 가볍게 운영되는 서버이다.
평상 시에도 메모리 사용량이 500MiB를 넘지 않아 t4g.medium에서 t4g.small로 낮춰도 될 것이라고 생각하였다.
t4g.small은 2GiB 메모리이기 때문에 그렇게 해도 20% 정도 밖에 사용하지 않는다.
그리고 현재 정책이 On-demand 1개, Spot 2개였는데, Spot을 1개로 줄여서 서버 당 노드 하나를 더 줄였다.
인퍼런스나 다른 백엔드 서버나 노드의 20% 수준밖에 리소스를 사용하지 못하고 있기에, 40-50% 수준까지는 끌어올려야 알차게 리소스를 사용한다고 생각이 들었다.
아래는 실제 개선 결과이다. (하루 기준)
표/그래프에는 포함되지 않은 인프라 비용이 많아서 절대적인 값보단 감소량을 참조하면 좋을 것 같다.
개선 전


개선 후


하루 비용은 $43.75 -> $28.19로 약 35% 정도 감소했다.
특히 노드/인스턴스 구조를 최적화해서 EC2 가격과 프론트 이미지/빌드 최적화로 Amplify 비용이 크게 줄어든 것을 확인할 수 있다.
Cost Optimization 역시 인프라 설계와 마찬가지로, 현재 서비스의 상황을 충분히 고려한 상태에서 이루어져야 한다.
비용을 줄이는 것이 목적이 되어 무조건적인 축소를 선택하는 것은 오히려 위험할 수 있다. 특히 서비스의 지속성과 안정성이 중요한 영역에서는 조금 더 비용을 지불하더라도 유지해야 할 부분은 분명히 존재한다.
중요한 것은 얼마나 줄였느냐가 아니라, 어디를 줄이고, 어디는 지켰느냐다.
반대로, 사용하지 않는 리소스, 구조적으로 의미 없는 중복, 단순히 방치되어 비용만 발생하는 요소들은 반드시 정리되어야 한다.
이런 부분을 정리하는 것만으로도 서비스 안정성을 해치지 않으면서 상당한 비용을 절감할 수 있다.
결국 Cost Optimization은 그저 비용만을 아끼는 작업이 아니라, 현재 서비스 단계에 맞게 리소스를 재배치하는 과정에 가깝다고 느꼈다.
개발자는 그저 서비스를 돌아가게 하는 사람이 아니라 잘 돌아가게 하는 사람이기 때문에 성능과 비용 관련 고민도 끊임없이 해야 한다.

답글 남기기