Zettelkasten

NAT 게이트웨이는 원래 무료인 인터넷 수신에도 GB당 처리요금을 받는다

·수정 1회

요약

  • AWS는 인터넷에서 들어오는 데이터 전송을 받지 않는다. 그런데 NAT 게이트웨이 처리요금은 방향을 가리지 않고 통과 바이트에 붙는다. 그래서 수신 트래픽에 NAT을 쓰면 요금 전액이 얻는 것 없는 낭비다.
  • 수신 우세 트래픽을 IGW 직결로 바꾸면 전송 비용의 100%가 사라지지만, 송신 우세면 32%만 줄어든다. 절감 판단은 트래픽 양이 아니라 방향에서 갈린다.
  • 어느 출구를 쓸지는 목적지가 정한다. 라우트 테이블에서 그 목적지가 어느 줄에 걸리는지부터 확인한다.

본문

요금 구조가 방향에 따라 비대칭이다

서울 리전 단가 기준:

방향 NAT 경유 IGW 직결 절감 폭
수신 $0.059/GB $0 100%
송신 0.126+0.126 + 0.059 $0.126/GB 32%

근거 두 가지가 겹쳐서 이렇게 된다.

  1. 인터넷 수신은 무료다. AWS 문서 표현으로 "There is no charge for inbound data transfer across all services in all Regions."
  2. NAT 처리요금은 방향을 안 가린다. VPC 요금 페이지는 "regardless of the traffic's source or destination", NAT 요금 문서는 "each gigabyte of data that it processes"로 쓴다.

그래서 수신 트래픽에 NAT을 끼우면 공짜 트래픽에 GB당 요금이 새로 생긴다.

송신 쪽은 사정이 다르다. NAT 처리요금은 표준 데이터 전송료에 얹히는 것이라("you also incur standard AWS data transfer charges for all data transferred via the NAT gateway"), 경로를 IGW로 바꿔도 $0.126은 그대로 낸다. 32%만 깎이는 이유다.

여기서 흔한 오판이 나온다. NAT 비용을 줄이려고 퍼블릭 서브넷으로 옮길 때 "인터넷 송신 요금도 없어진다"고 계산하면 절감액을 과대평가한다. 없어지는 건 처리요금뿐이다.

목적지가 출구를 정한다

한 태스크가 여러 곳과 통신하고, 목적지마다 나가는 출구가 따로 정해진다. 서비스 단위로 출구 하나를 고르는 게 아니다.

프라이빗 서브넷 라우트 테이블 예시:

172.31.0.0/16   → local            ← VPC 내부
pl-xxxxxxxx     → vpce-xxxxxxxx    ← S3 게이트웨이 엔드포인트
0.0.0.0/0       → nat-xxxxxxxx     ← 나머지 전부

라우팅은 더 구체적인 줄이 이긴다.

  • S3로 가는 패킷은 S3 IP 목록인 prefix list에 걸려 엔드포인트로 빠진다 → NAT을 안 타고 무료
  • 제3자 인터넷으로 가는 패킷은 걸릴 줄이 없어 0.0.0.0/0으로 폴백한다 → NAT을 타고 GB당 과금

이래서 하루 수백 GB를 S3에 올리는 서비스도 NAT 청구서에는 안 잡힐 수 있다. 이미 무료 출구가 있기 때문이다. 트래픽 총량이 아니라 목적지별로 쪼개서 봐야 어디서 돈이 새는지 보인다.

출구 선택 순서

위에서부터 해결되는 만큼 걷어내고, 마지막에 남는 것만 IGW를 고민한다.

  1. S3·DynamoDB → 게이트웨이 엔드포인트. 같은 리전이면 데이터 전송료가 없고("VPC gateway endpoints allow communication to Amazon S3 and Amazon DynamoDB without incurring data transfer charges within the same Region"), 서브넷을 프라이빗으로 유지한다. 가장 먼저 확인할 것.
  2. 인터페이스 엔드포인트가 있는 AWS 서비스 → PrivateLink. 단 시간 요금과 데이터 전송료가 붙으므로("This type of endpoint incurs hourly service charges and data transfer charges") NAT과 GB당 단가를 비교해서 정한다. 무료가 아니다.
  3. 자사 VPC·온프렘 → 피어링, Transit Gateway.
  4. 제3자 인터넷인데 목적지가 IPv6로 접근 가능 → egress-only 인터넷 게이트웨이. 게이트웨이 자체는 무료고("There is no charge for an egress-only internet gateway") 퍼블릭 IPv4 요금도 없다. 인터넷 송신 데이터 전송료는 그대로 낸다. 인터넷에서 먼저 연결을 걸 수 없다는 보장도 유지된다.
  5. 제3자 인터넷, IPv4만 → 그때 IGW 직결을 검토한다.

제3자 서비스에 사설 경로를 뚫으려면 그 업체가 자기 서비스를 AWS PrivateLink 서비스로 등록해 뒀어야 한다. 자체 글로벌 네트워크를 공용 인터넷에 두고 운영하는 업체는 등록하지 않은 경우가 많고, 그러면 피어링이나 PrivateLink는 원리적으로 불가능하다. 선택지가 NAT 아니면 IGW 둘로 좁혀진다.

퍼블릭 IPv4 요금이 태스크당 손익분기를 만든다

IGW 직결은 태스크마다 퍼블릭 IP를 요구한다. 사용 중이든 유휴든 시간당 $0.005다.

$0.005/시 × 730시 = 태스크당 월 $3.65
$3.65 ÷ $0.059/GB ≈ 62 GB/월 ≈ 태스크당 하루 2 GB

태스크당 하루 2GB를 넘으면 IGW가 이긴다 (서울 리전 단가 기준, 리전마다 다르다). 트래픽은 적고 태스크는 많은 서비스에서는 뒤집힌다. 태스크 200대에 트래픽이 별로 없으면 IP 요금만 월 $730이다.

여기서 태스크 수는 desired_count가 아니라 실제 평균 실행 수로 잡아야 한다. 오토스케일링이 붙어 있으면 최소값만 보고 계산했을 때 크게 어긋난다. ECS/ContainerInsights의 RunningTaskCount 30일 평균으로 확인한다.

NAT 시간 요금은 대체로 안 줄어든다

처리요금과 별도로 NAT은 시간 요금을 받는다. 이건 게이트웨이를 삭제해야 사라지는데, 보통 다른 서비스들이 같은 NAT을 계속 쓰기 때문에 트래픽 하나를 빼내는 것만으로는 줄지 않는다. 절감액 계산에 넣지 말 것.

보안 대가는 조건부로만 감당된다

프라이빗 서브넷은 "인터넷에서 먼저 연결을 걸 수 없다"는 구조적 보장을 준다. 퍼블릭 서브넷 + 퍼블릭 IP로 옮기면 그 보장이 사라지고 전적으로 보안그룹에 의존하게 된다.

감당 가능한 조건:

  • 보안그룹 인바운드에 CIDR 개방이 없다 (소스가 다른 보안그룹으로만 지정돼 있다)
  • 태스크가 항상 먼저 연결을 건다. 외부에서 걸어오는 연결이 필요 없다

인바운드 규칙이 느슨한 서비스에는 같은 판단을 적용하면 안 된다.

옮겨도 태스크의 사설 IP는 그대로 유지되므로, VPC 내부에서 사설 IP로 찾아오는 경로(Cloud Map 같은 사설 DNS 서비스 디스커버리 포함)는 영향받지 않는다.

NAT은 AZ마다 두는 게 맞다

프라이빗 서브넷을 유지하기로 결정하더라도, NAT이 AZ 하나에만 있으면 다른 AZ의 리소스는 AZ를 건너 그 NAT으로 간다. AWS NAT 요금 문서가 이걸 직접 권고한다: "If your AWS resources send or receive a significant volume of traffic across Availability Zones, ensure that the resources are in the same Availability Zone as the NAT gateway. Alternatively, create a NAT gateway in each Availability Zone with resources."

관찰된 사례

제3자 RTC 제공업체에서 오디오 스트림을 받아 녹음하는 태스크를 프라이빗 서브넷에서 퍼블릭 서브넷으로 옮기고 전후를 측정했다. 목적지가 그 업체의 자체 글로벌 네트워크라 피어링·PrivateLink가 불가능했고, 도메인에 AAAA 레코드가 없어 IPv6 경로도 막혀 있었다.

컷오버 전후 21시간 연속 창 비교(이전은 직전 7일 평균):

방향 이전 이후 감소
수신 169.0 GB/일 11.2 −93.4%
송신 91.6 GB/일 62.3 −32.0%

측정에서 배운 두 가지.

측정 시점이 결과를 왜곡한다. 컷오버 직후 하루 중 가장 한산한 시간대에서 재니 수신 86%·송신 16% 감소로 나왔다. 녹음 트래픽은 통화량에 비례해 야간에 몰리는데 나머지 트래픽은 상대적으로 평탄해서, 한산한 시간엔 녹음 몫의 비중이 작게 잡힌다. 피크를 포함한 창으로 다시 재야 93%·32%가 나왔다.

계단 변화로 원인을 귀속하면 과소 추정된다. 청구서가 튄 날짜를 잡아 그 증가분만 절감액으로 계산했는데, 실제 절감은 그보다 컸다. 이전을 걷어낸 뒤 남은 수신이 서비스 가동 전 기준선보다도 낮았기 때문이다. 서비스는 그 계단보다 몇 달 전부터 이미 돌고 있었고, 튄 날짜는 신규 가동이 아니라 증량 시점이었다. 계단 변화 귀속법은 구조적으로 증가분만 잡는다.

측정할 때 CloudWatch CLI가 돌려주는 타임스탬프는 로컬 타임존(+09:00)이다. UTC로 착각하면 컷오버 시점이 일별 버킷 경계에 걸려 전후 비교가 어긋난다.

관련 노트

참고