IT/AWS

NAT Gateway 청구서가 갑자기 3.2배로 뛴 날

gfrog 2026. 5. 13. 03:40
반응형

월요일 아침에 출근하니 재무팀에서 슬랙이 와있었다. "지난달 AWS 비용 한 번만 확인 부탁드려요." 평소엔 무시할 만한 변동이었는데 이번엔 NAT Gateway 라인 하나만 전월 대비 3.2배가 찍혀 있었다. 다른 항목은 거의 그대로였다. 보자마자 멘탈이 살짝 흔들렸다. 우리 팀은 분기 비용 가이드라인이 있어서 한 항목이 갑자기 튀면 그게 곧 회고 거리다.

여기에 진단부터 해결까지 일주일 동안 삽질한 기록을 남긴다. 결론부터 말하면 범인은 단순했고, 우리가 그동안 NAT Gateway 트래픽 구성을 너무 안 들여다본 게 진짜 문제였다.

첫 번째 가설: 누가 풀데이터를 빨아 가나

처음엔 누군가 새 워크로드 띄우면서 외부 데이터셋이라도 받아오는 줄 알았다. 그런데 워크로드 추가된 거 없었다. 노드 수도 그대로(82대), 트래픽 그래프도 P99 평소 수준. 근데 NAT Gateway의 BytesOutToDestination 메트릭만 보면 거의 두 배가 돼 있었다.

VPC Flow Logs를 Athena로 펴서 destination IP/포트 기준 top N을 뽑았다. 이때부터 좀 의외였는데, 1위가 S3 IP 대역이었다. 2위가 ECR Public, 3위가 CloudWatch Logs 엔드포인트 IP였다. 외부 트래픽이라고 생각했던 게 전부 AWS 내부 서비스로 가는 트래픽이었다.

SELECT
  CASE
    WHEN regexp_like(dstaddr, '^52\.21[6-9]\.') THEN 'S3-like'
    WHEN regexp_like(dstaddr, '^3\.5\.') THEN 'ECR-public-like'
    ELSE 'other'
  END AS bucket,
  SUM(bytes) / 1024 / 1024 / 1024 AS gb
FROM vpc_flow_logs
WHERE day = '2026-04-28'
  AND action = 'ACCEPT'
  AND srcaddr LIKE '10.%'
GROUP BY 1
ORDER BY 2 DESC;

S3로 가는 트래픽이 전체의 절반 가까이였다. NAT Gateway는 GB당 $0.045 데이터 처리 요금이 붙는데, S3로 향하는 그 모든 바이트가 그 요금을 거쳐서 다시 S3로 가고 있었다. 더 어이없는 건 S3 Gateway Endpoint는 무료라는 거다. 그냥 라우팅 테이블에 엔트리 하나만 박으면 됐던 것을.

왜 갑자기 늘었나

여기서 한 번 더 의문이 들었다. S3 트래픽이 늘 NAT 통해 나가던 거였다면 청구서가 갑자기 튈 이유가 없잖아? 평소에도 비쌌어야지.

git log 뒤지다가 한 PR이 눈에 띄었다. 4월 초에 데이터 플랫폼 팀에서 Iceberg 테이블 마이그레이션을 시작했는데, 그 일환으로 새 Spark 잡이 매일 새벽에 돌면서 raw 데이터를 S3에서 읽어다 변환하고 다시 S3에 쓰고 있었다. 입출력 합치면 일평균 800GB 정도였다. 한 달이면 24TB. NAT 데이터 처리 요금만 약 $1,080. 거기에 EKS 노드 50대 가까이가 ECR에서 이미지 풀 받을 때마다 또 NAT 거쳐서 나가는 트래픽이 있었다. Karpenter 도입한 뒤로 노드 회전이 잦아져서 이미지 풀 횟수도 늘었다.

상황 정리하면 NAT Gateway 청구서는 두 가지 원인의 합이었다:

  1. 데이터 플랫폼의 새 S3 워크로드 (예상 못 한 신규 트래픽)
  2. Karpenter 도입 후 ECR 풀 트래픽 증가 (이건 우리가 자초)

임시 조치와 진짜 조치

먼저 S3 Gateway Endpoint부터 박았다. 이건 정말로 5분이면 된다.

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

  policy = jsonencode({
    Statement = [{
      Effect    = "Allow"
      Principal = "*"
      Action    = "*"
      Resource  = "*"
    }]
  })
}

이걸 적용한 다음 날 NAT Gateway BytesOutToDestination이 그 즉시 절반 가까이 떨어졌다. 정확히는 47% 감소. Athena 쿼리에서 본 그 S3 비중과 거의 정확히 매칭됐다. 솔직히 너무 깨끗하게 떨어져서 좀 허무하기까지 했다.

그 다음 주에 ECR 인터페이스 엔드포인트를 박았다. ECR은 인터페이스 타입이라 GB당 $0.01이 붙긴 하지만, NAT 통과 $0.045 대비 22% 수준. 시간당 엔드포인트 요금이 AZ마다 $0.01씩 붙는 게 단점인데, 우리 트래픽 수준에선 손익분기점이 한참 아래였다.

resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.ap-northeast-2.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpce.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.ap-northeast-2.ecr.api"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpce.id]
  private_dns_enabled = true
}

ECR 엔드포인트 추가할 때 한 번 더 삽질했다. private_dns_enabled = true로 했으면 그냥 도메인 그대로 해석되니까 별 변경 없어도 될 줄 알았는데, 이미 떠 있던 노드의 일부가 DNS 캐시 때문에 한동안 기존 public IP로 계속 붙더라. 결국 노드 한 번씩 돌려서 정리했다. 새벽에 했어야 했는데 그 날 점심시간에 했다가 빌드 파이프라인 일부가 1-2분 흔들렸다. 미안.

한 달 뒤 청구서

다음 청구서가 나왔을 때 NAT Gateway 라인은 원래의 110% 수준이었다. 이전 청구서 대비 65% 감소. 데이터 플랫폼 워크로드는 계속 돌고 있는 상태였는데도. S3 트래픽이 통째로 빠져나간 효과가 압도적이었다.

여기서 더 줄일 수 있는 항목들도 같이 적어둔다. 우리도 아직 다 손은 못 댔다:

  • CloudWatch Logs 엔드포인트 (Logs 트래픽이 의외로 많음)
  • STS 엔드포인트 (IRSA 켜고 나면 토큰 갱신 트래픽이 NAT 타고 나감)
  • SQS / SNS 엔드포인트 (워크로드에 따라)

회고에서 나온 이야기

분기 회고에서 두 가지 합의가 있었다.

첫째, NAT Gateway BytesOutToDestination 메트릭에 CloudWatch 알람을 걸기로 했다. 일별 + 주별 임계치. 비용 청구서를 기다릴 게 아니라 트래픽 자체에 알람을 걸어야 빨리 알아챈다.

둘째, 신규 VPC 만들 때 S3 Gateway Endpoint는 기본으로 박는다. Terraform 모듈 안에 default로 넣어버렸다. 무료인 걸 굳이 안 넣을 이유가 없다.

근데 사실 더 본질적인 교훈은 이거였다. 우리는 트래픽 경로를 너무 안 들여다보고 있었다. NAT Gateway는 그냥 "있는 거"였고 매달 비용이 얼마나 나오는지도 안 봤다. 인프라가 작동만 하면 됐지 비용 라인을 안 본다. 이게 한두 달 누적되면 결국 재무팀이 먼저 본다. 그리고 우리는 부끄러워진다.

VPC Flow Logs는 진작에 켜놨었는데 한 번도 제대로 쿼리해본 적이 없었다. 이번 일 이후로 월 1회 NAT 트래픽 top destination 리포트를 자동화해서 슬랙으로 쏘게 만들었다. 처음 이걸 봤을 때 팀원들 반응이 재밌었다. "어 우리 이걸로 이만큼 돈 쓰고 있었어?" 누구도 안 보던 영역이었다.

혹시 비슷한 경험 있으신 분들, NAT 트래픽에서 의외로 큰 비중 차지하던 destination 뭐 있었는지 댓글 남겨주시면 좋을 것 같다. 우리는 Slack webhook이 의외로 꽤 차지하고 있어서 그것도 다음에 정리할 예정이다.

반응형