Published on

AWS NAT Gateway 비용 폭탄, VPC Flow Logs로 추적

Authors

서버 비용을 보다가 가장 당황스러운 순간 중 하나가 NAT Gateway가 갑자기 상위권에 올라오는 경우입니다. NAT Gateway는 구조적으로 "나가는 트래픽"에 비용이 붙고(처리 시간 + 데이터 처리량), 프라이빗 서브넷의 기본 egress 경로로 자주 쓰이기 때문에, 한 번 새는 트래픽이 생기면 짧은 시간에도 비용이 크게 튈 수 있습니다.

이 글은 "NAT Gateway 비용 폭탄" 상황에서 감으로 추측하지 않고, VPC Flow Logs로 누가(어떤 ENI/인스턴스/파드), 어디로(목적지 IP/포트), 얼마나(바이트/패킷) 나갔는지 추적하는 방법을 단계별로 정리합니다. 마지막에는 자주 나오는 원인 패턴과 바로 적용 가능한 절감 처방도 함께 제공합니다.

NAT Gateway 비용이 튀는 대표 시나리오

NAT Gateway 비용은 크게 두 축으로 나뉩니다.

  • 시간당 비용: NAT Gateway가 떠 있는 시간
  • 처리 데이터 비용: NAT를 통과한 바이트(대개 GB 단위)

즉 "조금씩 계속" 나가도 비용이 쌓이고, "짧게 폭발"해도 비용이 튈 수 있습니다. 실무에서 자주 만나는 원인은 다음과 같습니다.

  1. 컨테이너 이미지 대량 Pull
    • EKS 노드가 외부 레지스트리에서 대량 다운로드
    • 노드 스케일아웃 시 동시 Pull 폭증
  2. 로그/메트릭의 외부 전송
    • SaaS 로그 수집기로 대량 전송
    • 디버그 로그 폭발로 전송량 증가
  3. S3, DynamoDB 같은 AWS 서비스로의 접근이 NAT를 타는 구성
    • VPC Endpoint 미구성으로 퍼블릭 엔드포인트로 나감
  4. 외부 API 재시도 폭주
  5. 의도치 않은 대역폭 사용
    • 패키지 미러/OS 업데이트가 프라이빗 서브넷 전체에서 동시 실행
    • 크론 작업이 시간대에 몰림

핵심은 "어떤 리소스가 egress를 만들었는지"를 빨리 특정하는 것입니다. 여기서 VPC Flow Logs가 가장 빠르고 범용적인 단서가 됩니다.

추적 전략: NAT 자체가 아니라 "프라이빗 ENI"를 잡아라

NAT Gateway는 여러 프라이빗 리소스의 트래픽이 합쳐지는 지점입니다. 그래서 NAT Gateway ENI만 보면 "합계"는 보이지만 "범인"을 특정하기 어렵습니다.

실전에서는 이렇게 접근합니다.

  1. 비용이 뛴 시간대를 Cost Explorer에서 확인
  2. 그 시간대에 VPC Flow Logs에서 egress 바이트가 큰 "소스 ENI"를 찾기
  3. 소스 ENI를 EC2, EKS 노드, RDS 프록시, Lambda ENI 등으로 매핑
  4. 목적지 IP/포트, 바이트 상위 흐름을 보고 원인 패턴 분류

이를 위해서는 Flow Logs가 "바이트/패킷"과 "소스/목적지"를 충분히 담고 있어야 하고, 조회가 쉬워야 합니다. 가장 많이 쓰는 조합은 S3로 저장하고 Athena로 쿼리하는 방식입니다.

VPC Flow Logs 설정: S3 + Athena 분석을 전제로

1) Flow Logs 생성 범위 선택

권장 우선순위는 아래와 같습니다.

  • VPC 전체: 가장 포괄적이지만 로그량이 많음
  • 서브넷 단위: NAT를 사용하는 프라이빗 서브넷만 선택 가능
  • ENI 단위: 특정 의심 리소스만 찍을 때 유리

처음에는 "프라이빗 서브넷" 또는 "VPC 전체"로 켜고, 원인이 잡히면 범위를 줄이는 방식이 운영 부담이 적습니다.

2) 로그 필드(포맷) 선택 포인트

Flow Logs에서 꼭 보고 싶은 필드는 다음입니다.

  • interface-id
  • srcaddr, dstaddr, srcport, dstport, protocol
  • bytes, packets
  • action (ACCEPT/REJECT)
  • log-status
  • start, end

가능하면 VPC Flow Logs v5 이상에서 tcp-flags, pkt-srcaddr, pkt-dstaddr 같은 확장 필드도 도움이 됩니다(특히 프록시/로드밸런서 뒤에서 실제 IP를 보고 싶을 때).

3) S3로 내보내기

S3로 내보내면 Athena/Glue로 분석하기 쉬워집니다.

  • S3 버킷은 로그 전용으로 분리
  • 수명주기 정책으로 7일, 30일 등 보관기간 설정
  • 파티션 프리픽스는 AWS 기본 형식을 사용해도 충분

CloudWatch Logs로 보내는 방식도 가능하지만, 대량 분석/집계는 Athena 쪽이 비용과 편의가 좋습니다.

Athena로 "누가 얼마나 나갔는지" 10분 안에 찾기

아래는 S3에 저장된 Flow Logs를 Athena로 분석하는 전형적인 흐름입니다.

1) 테이블 생성 예시

S3 경로와 포맷에 따라 DDL이 달라집니다. AWS 문서 예시를 기반으로, 가장 흔한 텍스트 기반 로그에 대한 예시를 제공합니다.

CREATE EXTERNAL TABLE IF NOT EXISTS vpc_flow_logs (
  version int,
  account_id string,
  interface_id string,
  srcaddr string,
  dstaddr string,
  srcport int,
  dstport int,
  protocol int,
  packets bigint,
  bytes bigint,
  start bigint,
  end bigint,
  action string,
  log_status string
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ' '
LOCATION 's3://your-bucket/AWSLogs/your-account-id/vpcflowlogs/ap-northeast-2/'
TBLPROPERTIES ('skip.header.line.count'='0');

운영에서는 Glue Crawler로 스키마를 자동 생성하고, 날짜 파티션을 붙여 쿼리 비용을 줄이는 편이 일반적입니다.

2) 시간대 필터링(비용 폭증 구간만)

Cost Explorer에서 확인한 폭증 시간대에 맞춰 start/end를 좁힙니다. Flow Logs의 start, end는 epoch 초 단위입니다.

-- 특정 시간 구간(예: 2026-02-25 01:00:00~02:00:00 UTC)을 epoch로 변환해 사용
SELECT
  from_unixtime(start) AS start_time,
  interface_id,
  srcaddr,
  dstaddr,
  dstport,
  SUM(bytes) AS total_bytes
FROM vpc_flow_logs
WHERE action = 'ACCEPT'
  AND start BETWEEN 1771981200 AND 1771984800
GROUP BY 1,2,3,4,5
ORDER BY total_bytes DESC
LIMIT 50;

이 쿼리 하나로 "상위 50개의 대화 흐름"이 나옵니다. 여기서 srcaddrinterface_id가 사실상 1차 범인 후보입니다.

3) "소스 ENI" 기준으로 egress 상위 찾기

목적지보다 "누가"가 더 중요할 때가 많습니다.

SELECT
  interface_id,
  srcaddr,
  SUM(bytes) AS egress_bytes,
  COUNT(*) AS flow_records
FROM vpc_flow_logs
WHERE action = 'ACCEPT'
  AND start BETWEEN 1771981200 AND 1771984800
GROUP BY 1,2
ORDER BY egress_bytes DESC
LIMIT 30;

여기서 상위 interface_id를 확보한 뒤, AWS 콘솔 또는 CLI로 ENI 소유자를 추적합니다.

4) ENI를 실제 리소스로 매핑

CLI로 ENI의 AttachmentDescription, RequesterManaged 등을 보면 대개 감이 옵니다.

aws ec2 describe-network-interfaces \
  --network-interface-ids eni-0123456789abcdef0 \
  --query 'NetworkInterfaces[0].{ENI:NetworkInterfaceId,Desc:Description,Status:Status,Attachment:Attachment.InstanceId,RequesterManaged:RequesterManaged,PrivateIp:PrivateIpAddress,Groups:Groups[*].GroupId,SubnetId:SubnetId,VpcId:VpcId}' \
  --output table
  • Attachment.InstanceId가 있으면 EC2 인스턴스(또는 EKS 워커 노드)일 확률이 큽니다.
  • DescriptionAWS Lambda VPC ENI 같은 힌트가 들어가기도 합니다.
  • EKS의 경우 노드 인스턴스를 찾은 뒤, 해당 노드에서 어떤 파드가 대량 egress를 만들었는지까지 내려가려면 CNI/노드 로그, 프록시 로그, 애플리케이션 로그를 추가로 봐야 합니다.

EKS 트러블슈팅 흐름은 네트워크/타임아웃 이슈와 같이 엮이는 경우가 많아, EKS ALB Ingress에서 504 Idle timeout만 반복될 때 같은 글의 "관측 지점 늘리기" 접근도 함께 참고하면 좋습니다.

목적지 분석: "어디로" 나갔는지 패턴화

범인 리소스를 찾았으면, 다음은 목적지 패턴을 봅니다.

1) 목적지 포트 상위

SELECT
  dstport,
  SUM(bytes) AS total_bytes
FROM vpc_flow_logs
WHERE action = 'ACCEPT'
  AND start BETWEEN 1771981200 AND 1771984800
GROUP BY 1
ORDER BY total_bytes DESC
LIMIT 20;
  • 443이 압도적이면 HTTPS 기반 외부 통신(레지스트리, API, SaaS)
  • 80이면 패키지 미러/레거시 HTTP
  • 특정 포트(예: 9200, 6379)면 외부로 열려 있는 데이터스토어 접근 가능성도 의심

2) 목적지 IP 상위

SELECT
  dstaddr,
  SUM(bytes) AS total_bytes
FROM vpc_flow_logs
WHERE action = 'ACCEPT'
  AND start BETWEEN 1771981200 AND 1771984800
GROUP BY 1
ORDER BY total_bytes DESC
LIMIT 50;

여기서 상위 dstaddr를 WHOIS로 확인하거나, 사내에서 사용하는 SaaS/레지스트리의 IP 대역과 대조합니다.

주의할 점은, 대형 서비스는 IP가 자주 바뀌고 CDN을 타기 때문에 "도메인"으로 바로 매핑이 어려울 수 있다는 것입니다. 이때는 애플리케이션 레벨 로그(예: HTTP client 로그, 프록시 로그)와 함께 상관관계를 잡는 편이 빠릅니다.

자주 나오는 "진짜 원인" 6가지와 처방

1) S3 트래픽이 NAT를 탐

프라이빗 서브넷에서 S3에 접근하면서 Gateway VPC Endpoint를 안 붙이면, S3 퍼블릭 엔드포인트로 나가며 NAT 비용이 발생할 수 있습니다.

  • 처방: S3 Gateway Endpoint 추가, 라우트 테이블 연결
  • 추가로: DynamoDB도 Gateway Endpoint 대상

2) ECR/레지스트리 이미지 Pull 폭증

노드가 스케일아웃되거나, 이미지 태그를 자주 바꾸는 배포로 인해 대량 Pull이 발생할 수 있습니다.

  • 처방
    • ECR을 쓴다면 VPC Endpoint(Interface Endpoint) 검토
    • 이미지 레이어 최적화, 캐시 전략, 노드 로컬 캐시
    • 배포 시 동시성 제한

3) 외부 API 재시도 루프

타임아웃이나 429에서 무한 재시도하면 egress가 폭발합니다.

  • 처방
    • 지수 백오프 + 지터
    • 최대 재시도 횟수
    • 서킷 브레이커
    • 큐잉으로 평탄화

재시도 설계는 비용뿐 아니라 장애 확산을 막는 핵심이라, OpenAI 429/Rate Limit 대응 - 재시도·백오프·큐잉 같은 패턴을 그대로 적용해도 효과가 큽니다.

4) 프라이빗 DNS/라우팅 실수로 내부 트래픽이 외부로 샘

같은 VPC 또는 피어링/Transit Gateway로 갈 수 있는 트래픽이, DNS 설정이나 라우팅 실수로 인터넷 방향으로 나가 NAT를 탈 수 있습니다.

  • 처방
    • 라우트 테이블 점검(0.0.0.0/0이 NAT로만 향하는지)
    • Private Hosted Zone 우선순위
    • 서비스 디스커버리 구성 재점검

5) 로그/메트릭 과다 전송

디버그 레벨 로그가 켜지거나, 특정 에러로 로그가 폭증하면 전송량이 크게 늘어납니다.

  • 처방
    • 로그 레벨/샘플링
    • 배치 전송, 압축
    • 내부 수집기(예: VPC 내부)로 모은 뒤 외부로 나가는 단일 경로 설계

6) 백그라운드 업데이트/크론 작업 시간대 집중

OS 업데이트, 패키지 업데이트, 데이터 동기화가 같은 시간에 몰리면 NAT가 급증합니다.

  • 처방
    • 크론 분산(랜덤 지연)
    • 미러 서버를 VPC 내부에 두거나 캐시 프록시 운영

비용 절감 체크리스트(바로 적용용)

  • VPC Endpoint로 NAT 우회
    • S3/DynamoDB는 Gateway Endpoint
    • ECR, CloudWatch, STS 등은 Interface Endpoint 검토
  • 멀티 AZ NAT 구성 점검
    • AZ마다 NAT를 두는 것이 가용성에는 좋지만, 라우팅이 꼬이면 크로스 AZ 트래픽이 늘 수 있음
  • 프라이빗 서브넷의 기본 경로(0.0.0.0/0) 검증
  • EKS라면
    • 노드 스케일아웃 시 이미지 Pull 동시성
    • 파드별 egress 관측(프록시/네트워크 폴리시)
  • 재시도/백오프/서킷 브레이커 적용

마무리: "합계"가 아니라 "상위 ENI"를 찾으면 끝이 보인다

NAT Gateway 비용은 대부분 "특정 시간대에 특정 소스가 대량 egress"를 만든 결과입니다. VPC Flow Logs를 S3로 모아 Athena로 상위 interface-idbytes를 뽑아보면, 원인 규명은 생각보다 빨리 끝납니다.

마지막으로 운영 팁을 하나 더 적으면, Flow Logs는 사고가 나서 켜면 이미 늦는 경우가 많습니다. 로그량/비용이 걱정된다면 VPC 전체 상시 수집 대신, 프라이빗 서브넷만 상시 수집 + 보관기간을 짧게(예: 7일) 두고, 사건 발생 시 기간만 늘리는 방식이 현실적인 타협점입니다.