목차
- AWS로 옮긴 진짜 이유 – 표준이 아니라 영업이었다
- 1개월차 – EKS 클러스터를 세우면서 만난 IAM 미로
- 2개월차 – 첫 청구서를 받은 날
- 비용을 뜯어본 결과 – 새고 있던 구멍들
- 3개월차 – 되돌리기로 결정한 시점
- GCP + Cloudflare 조합으로 돌아온 후
- AWS를 다시 쓰게 될 조건
- 떠나기 전에 했어야 할 것들
AWS는 Amazon이 운영하는 종합 클라우드 플랫폼이다. EC2(컴퓨팅), S3(스토리지), RDS(관리형 DB)를 비롯해 작성 시점(2026년 5월) 기준 240개 이상의 서비스를 묶어서 판매한다(출처: AWS 공식 서비스 페이지, 2026-05 기준). 일반적으로 글로벌 리전 확장, 규제 산업 컴플라이언스, 엔터프라이즈 영업 채널이 필요할 때 후보로 올라온다.
또한, 이 글은 그 AWS를 3개월 쓰다가 다시 GCP로 돌아간 회고다. 처음부터 잘못 고른 케이스라고 단정하긴 어렵다. 사업 방향이 바뀌면서 정답도 바뀌었다는 표현이 더 정확하다. EKS 클러스터를 어떻게 세웠고, 어디서 비용이 새었으며, 어느 시점에 "되돌리자"고 결정했는지를 시간순으로 풀어본다.
그러나, 배경부터 잠깐 정리해두자. 사내 워크로드는 원래 GCP에 있었다. GKE Autopilot, Cloud SQL Postgres, Cloud Run, Cloudflare CDN 조합이었다. 어느 시점에 북미 고객 대응과 SOC 2 Type II 관련 요청이 늘면서 "AWS도 같이 운영해보자"는 결정이 있었다. 그래서 일부 서비스를 EKS로 옮겼다. 결과부터 말하면 3개월 뒤 원래 자리로 돌아왔다.
AWS로 옮긴 진짜 이유 – 표준이 아니라 영업이었다
표면적 이유는 "북미 고객사가 AWS를 선호한다"였다. 정확히는 보안 실사 문서에 AWS Region이 적혀 있어야 통과되는 RFP가 두 건 있었다. SOC 2 보고서에는 인프라 공급자가 명시되는데, 거기에 AWS가 들어가 있는 편이 영업적으로 유리하다는 판단이었다.
예를 들어, 기술적 이유도 있긴 했다. ap-northeast-2(서울)에서 일부 매니지드 서비스의 지연이 GCP asia-northeast3보다 낮게 측정된다는 사내 벤치마크가 있었다(자체 측정, 2025년 11월, ICMP 기준 평균 4ms 차이). 다만 이 차이가 실서비스에 유의미한 영향을 줄 수치인지는 처음부터 의심스러웠다.
이전한 워크로드는 다음과 같다.
- 메인 API 서버 (FastAPI + Postgres)
- 배치 잡 3종 (Python, 하루 4회 실행)
- 정적 자산 CDN (이미지, JS 번들)
- 내부 어드민 (Next.js)
DB는 RDS Postgres 15.5로, 컴퓨팅은 EKS 1.29로, CDN은 CloudFront로 옮긴다는 그림이었다.
1개월차 – EKS 클러스터를 세우면서 만난 IAM 미로
첫 2주는 EKS 클러스터 구축에 다 썼다. eksctl로 한 줄에 클러스터가 뜨긴 했다. 문제는 그 다음부터다.
그래서, IRSA(IAM Roles for Service Accounts)를 처음 본 사람은 거의 다 같은 곳에서 막힌다. Pod가 S3에 쓰려면 Service Account → IAM Role → IAM Policy → OIDC Provider 연결이 다 맞아야 한다. 하나만 어긋나면 다음 에러가 뜬다.
An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:sts::123456789012:assumed-role/eksctl-prod-nodegroup-NodeInstanceRole
is not authorized to perform: s3:PutObject on resource:
"arn:aws:s3:::my-bucket/path/file.json" because no identity-based policy
allows the s3:PutObject action
게다가, 이 에러를 받으면 보통 NodeInstanceRole에 정책을 더 붙이는 실수를 한다. 그러면 동작은 하지만 Pod 단위가 아니라 노드 단위로 권한이 열린다. 보안 실사 때 지적받기 딱 좋은 형태다. IRSA로 다시 잡으려면 OIDC provider 등록부터 다시 가야 한다.
# Terraform - IRSA용 OIDC provider 등록
data "tls_certificate" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
그런데, GKE에서 Workload Identity는 iam.gke.io/gcp-service-account 어노테이션 한 줄로 끝난다. AWS는 같은 일에 OIDC provider, IAM Role trust policy, ServiceAccount 어노테이션, Pod spec까지 네 군데를 손대야 한다. 이게 더 안전한 설계라는 주장도 일리가 있다. 다만 1인 운영 팀에서 다섯 군데를 매번 동기화하는 비용은 만만치 않다.
VPC 설계에서 한 번 더 걸렸다
기본 VPC를 그대로 쓰면 안 된다는 건 알고 있었다. 그래서 직접 VPC를 만들었다. Private subnet에 노드 그룹을 두고, 인터넷 접근은 NAT Gateway를 통해 나가게 했다. 멀티 AZ 권장을 따라 AZ 3개에 NAT을 각각 하나씩 두는 구성으로 시작했다.
결국, 이 구성이 한 달 뒤 청구서에 어떻게 찍히는지는 다음 섹션에서 다룬다.
2개월차 – 첫 청구서를 받은 날
그래서, 월말 청구서가 도착했을 때 가장 먼저 든 의문은 "NAT Gateway 항목이 왜 이렇게 큰가"였다. 컴퓨팅(EKS Node + Fargate)보다 NAT이 더 비쌌다.
원인은 두 가지였다. NAT Gateway가 AZ별로 3개 떠 있었고, 시간당 요금 외에 처리량(GB) 요금이 별도로 붙는다는 점을 과소평가했다. 배치 잡이 S3로 데이터를 올리는데, VPC Endpoint를 안 만들어둬서 그 트래픽이 전부 NAT을 통과하고 있었다. 하루에 80~120GB가 NAT을 거쳐 S3로 갔다.
# Cost Explorer CLI - 서비스별 비용 조회
aws ce get-cost-and-usage \
--time-period Start=2026-01-01,End=2026-02-01 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[?Metrics.UnblendedCost.Amount>`100`]' \
--output table
이 명령으로 항목을 뜯어봤더니 분포가 의외였다.
| 서비스 | 1월 비용(USD) | 비중 | 비고 |
|---|---|---|---|
| EC2 + EKS 노드 | 1,180 | 28% | t3.large × 6 + m6i.large × 2 |
| NAT Gateway | 980 | 23% | AZ 3개 + 데이터 처리 |
| RDS Postgres | 720 | 17% | db.t3.medium Multi-AZ |
| CloudWatch Logs | 540 | 13% | 약 1.2TB/월 인제스트 |
| Data Transfer Out | 410 | 10% | CloudFront 우회 트래픽 일부 |
| S3 + 기타 | 370 | 9% | – |
| 합계 | 4,200 | 100% | – |
특히, CloudWatch Logs가 13%를 차지한 것도 예상 밖이었다. EKS control plane 로깅을 전부 켜둔 게 화근이었다. audit 로그가 분당 수천 줄씩 쌓이고 있었다. 인제스트 단가는 GB당 0.76 USD(서울 리전 기준, 2026-01 시점)인데, 한 달치를 곱하니 무시할 금액이 아니었다.
:::stats
- 청구서 4,200 USD / 월
- 이전 GCP 동등 워크로드 1,800 USD / 월 (실측)
- 차이의 주범 TOP 3: NAT Gateway, CloudWatch Logs, Multi-AZ RDS :::
GCP 시절 같은 워크로드는 월 1,800 USD 선이었다. 두 배 넘게 찍힌 셈이다. 물론 동일 비교는 아니다. EKS는 Control Plane 비용이 0.10 USD/h 따로 붙고, RDS는 GKE 쪽 Cloud SQL과 옵션이 미세하게 달랐다. 그럼에도 격차가 너무 컸다.
비용을 뜯어본 결과 – 새고 있던 구멍들
청구서를 보고 바로 마이그레이션을 되돌린 건 아니다. 우선 줄일 수 있는 만큼 줄여봤다.
NAT Gateway는 VPC Endpoint와 single-AZ로
게다가, S3, ECR, CloudWatch는 전부 Gateway Endpoint 또는 Interface Endpoint로 빠지게 했다. 이걸로 NAT 통과 트래픽의 70% 정도가 빠졌다. 멀티 AZ NAT은 single-AZ로 줄였다. 가용성이 살짝 떨어지지만 NAT은 어차피 한 AZ 죽으면 다른 AZ로 우회시키는 라우팅을 따로 만들어야 한다. 그 라우팅 자체를 안 했으니, 비용만 쓰고 가용성은 못 얻은 상태였다.
# Terraform - S3 Gateway VPC Endpoint
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.ap-northeast-2.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
# S3 트래픽을 NAT 우회시키는 핵심 한 줄
}
CloudWatch Logs 보존 기간 조정
실제로, 기본값이 "Never expire"라는 사실을 처음 알았다. 모든 로그 그룹의 보존 기간을 7일로 설정하고, EKS audit/authenticator 로그는 끈 뒤 필요한 컴포넌트만 다시 켰다. 이 작업으로 월 540 USD가 약 180 USD로 줄었다.
RDS는 그대로 두기로
Multi-AZ RDS는 줄이지 않았다. 운영 DB의 가용성을 흥정할 자신은 없었다. 물론 인스턴스를 t3.medium에서 t4g.medium(Graviton)으로 바꿔 약 18% 인하 효과를 봤다. ARM 호환성은 PostgreSQL 클라이언트단에서 문제없었다.
이 모든 조치 후 청구서가 4,200에서 2,900 USD로 떨어졌다. 여전히 GCP 대비 60% 이상 비싼 수치였다.
3개월차 – 되돌리기로 결정한 시점
반면, 비용만 봤다면 끝까지 버텼을지도 모르겠다. 운영 시간이 문제였다.
그러나, EKS 클러스터 업그레이드(1.28 → 1.29) 작업에 이틀이 들어갔다. add-on 호환성, kubelet 버전, CNI 플러그인을 다 맞춰야 했다. 같은 작업을 GKE Autopilot에서는 했던 기억이 거의 없다. 컨트롤 플레인을 구글이 알아서 올려준다.
그러나, IRSA 권한 추가 요청이 한 주에 두세 건씩 들어왔다. "이 Pod에서 SQS 보내고 싶다", "저 잡에서 SES로 메일 보내고 싶다" 같은 요청이다. 매번 IAM Policy → Role trust → ServiceAccount → Pod spec 네 단계를 손대야 한다. 자동화 안 한 내 잘못이긴 한데, 그 자동화를 만들 시간이 없어서 매번 수동으로 했다.
결정타는 RFP 두 건이 결국 다른 이유로 무산된 것이다. AWS가 적혀 있어야 한다는 그 RFP다. 영업 명분이 사라지자 "이걸 왜 운영하고 있나"라는 질문에 답이 어려워졌다.
3개월째 마지막 주에 되돌리기로 결정했다. 마이그레이션 자체는 GitOps로 잘 추상화해뒀던 덕에 2주가 걸렸다.
# 마이그레이션 되돌릴 때 쓴 helmfile 한 줄
helmfile -e gcp-prod sync
# kustomize overlay만 바꿔서 GKE로 다시 배포
GCP + Cloudflare 조합으로 돌아온 후
원래 자리로 돌아오면서 구성을 살짝 손봤다. CDN은 CloudFront 대신 Cloudflare로 일원화했다. GCP 워크로드 앞단에 Cloudflare를 두는 구성이 운영 단순성 면에서 가장 깔끔했다.
예를 들어, 작성 시점(2026년 5월) 기준 청구서는 다음과 같다.
| 항목 | 월 비용(USD) |
|---|---|
| GKE Autopilot | 740 |
| Cloud SQL Postgres (HA) | 520 |
| Cloud Storage | 80 |
| Cloud Logging (30일 보존) | 110 |
| Egress | 230 |
| Cloudflare Pro + Workers | 120 |
| 합계 | 1,800 |
따라서, AWS 시절 최적화 후 2,900 USD와 비교하면 1,100 USD 차이가 난다. 1년이면 13,200 USD. 작은 팀 입장에서 무시할 수치는 아니다.
성능 차이는 체감되지 않았다. p95 응답 지연이 양쪽 다 비슷한 구간(약 180~210ms)에 머물렀다. 자체 측정이라 일반화는 어렵지만, 우리 워크로드에서는 그랬다.
AWS를 다시 쓰게 될 조건
반면, 이번 경험으로 "AWS는 안 쓴다"는 결론에 도달한 건 아니다. 다음 세 가지 중 하나라도 해당된다면 다시 검토할 생각이다.
예를 들어, 첫째, 영업적으로 AWS가 진짜 필요한 RFP가 들어오는 경우다. 이번에는 무산됐지만 그런 계약이 다시 오면 거절 비용이 더 클 수 있다.
그러나, 둘째, SageMaker, Bedrock 같은 AWS-only 서비스를 메인 워크로드에 써야 하는 경우다. GCP에 Vertex AI가 있긴 하지만 모델 라인업과 가격 구조가 다르다. 2026년 들어 Bedrock의 모델 카탈로그가 빠르게 늘었다(출처: AWS Bedrock 공식 모델 카탈로그, 2026-05 기준).
특히, 셋째, 멀티 리전 운영이 필수가 되는 경우다. AWS는 리전 수와 사이즈에서 여전히 앞서 있는 것으로 보인다(2026년 상반기 기준).
떠나기 전에 했어야 할 것들
그래서, 회고하면서 정리한, 다시 AWS를 쓰게 됐을 때 첫 주에 무조건 할 항목 세 가지다.
그러나, 1) NAT Gateway 보기 전에 VPC Endpoint부터 정의한다. S3, ECR, CloudWatch, STS는 무조건 Endpoint로 빼라. 이걸 한 달 뒤에 알면 청구서가 이미 폭발한 뒤다.
따라서, 2) CloudWatch Logs 보존 기간을 모든 로그 그룹에 적용한다. Terraform으로 aws_cloudwatch_log_group 리소스를 만들 때 retention_in_days를 빼먹지 않는다. 기본값이 무한이라는 점을 잊지 않는다.
이처럼, 3) IRSA 추가를 자동화한다. Terraform 모듈이나 CDK 컨스트럭트로 "서비스 어카운트 + Role + Policy + trust" 네 가지를 한 번에 만드는 추상화를 1주차에 만들어둔다. 수동으로 하면 3개월 뒤 운영자의 인내심이 먼저 떨어진다.
세 가지 다 떠나고 나서야 분명해진 것들이다. 처음 옮길 때부터 알았다면 청구서가 60%는 덜 찍혔을 거라고 본다. 물론 그 60%를 줄였다고 해서 돌아오지 않았을지는 모르겠다. 결정타가 비용만은 아니었기 때문이다.
관련 글
- reCAPTCHA 다음 단계, Google Cloud Fraud Defense 실전 봇 방어 비교 – reCAPTCHA 다음 세대로 묶인 Google Cloud Fraud Defense는 단순 캡차가 아니라 계정 보호와 부정 결제 차단까지 …
- GitHub Actions 캐시 설정 비교 — actions/cache vs registry cache vs 셀프러너 – node_modules 설치 2분, Docker 빌드 4분. PR마다 9분씩 기다리는 게 지겨워서 캐시 전략 세 개를 직접 비교했다. 무엇…
- Redis 캐시 전략 비교 — LRU·LFU·allkeys로 maxmemory 다루기 – 프론트엔드에서 백엔드로 넘어온 지 2년. Redis OOM 에러를 만나고서야 maxmemory-policy를 제대로 본다. LRU와 LFU…