NAT Gateway 有两笔费用:一笔按网关存在的小时数收,一笔按过它的字节数收。两笔都可能白花,其中每 GB 那笔的口径最容易看错,Amazon VPC 定价页的原话是:

Data processing charges apply for each gigabyte processed through the NAT gateway regardless of the traffic’s source or destination

regardless of the traffic's source or destination:目的地在同一个可用区、同一个区域,还是地球另一端,单价一样。所以只要一个包从私有子网绕出去,这笔钱就照收,跟它本来有近路可走无关。

IPv4 的出公网流量(调外部 API、拉公网镜像)没有别的走法,该走 NAT 还是走 NAT,这部分每 GB 的钱省不掉。IPv6 多一条路,给实例 IPv6 地址、路由指向 egress-only internet gateway,出向就不用过 NAT:这个网关只往外走、不让公网主动连进来,本身不收钱,过它的字节按 EC2 的公网数据传输计费。

冤枉钱来自三件事:一件出在小时费上,两件出在每 GB 那笔上,各自有不同的成因和修法。

浪费 怎么产生的 解法 实测
网关空转 NAT 建完就没人拆,小时费按个数一直计 确认没流量后删网关,连 EIP 一起释放 8 个网关,每月 400 多美元,另加 29 美元的 EIP
到 AWS 服务的流量绕公网 私有子网里 0.0.0.0/0 指向 NAT,S3 和 ECR 镜像层都从那儿出去 建 gateway endpoint(S3、DynamoDB 免费)或按流量算回本点的 interface endpoint 一个 crash loop 每天 412 GB、38 美元
SaaS 流量照走公网 SaaS 给的是公网域名,peering 建好也没人走 对端给私有端点后,用 private hosted zone 把域名解析过去 peering 只服务按私有 IP 直连的调用

下面单价一律按东京区列,都是 2026-09-13 从 AWS 定价接口查的,机制照 AWS 官方文档核对过。账单上那几个数字不在东京区,出现的时候单独标。圣保罗区的两笔都是 $0.093,比东京贵一档,按东京区算出来的回本点换到别的区,得拿本区那两个数重算。

8 个空转的 NAT 网关#

东京区用 Pricing API 查 EC2 这个 offer code,两笔数字是一样的:

aws pricing get-products --service-code AmazonEC2 --region us-east-1 \
  --filters 'Type=TERM_MATCH,Field=location,Value=Asia Pacific (Tokyo)' \
            'Type=TERM_MATCH,Field=productFamily,Value=NAT Gateway' \
  --output json | jq -r '.PriceList[] | fromjson
    | .terms.OnDemand[].priceDimensions[]
    | "\(.pricePerUnit.USD)  \(.description)"'
0.0620000000  $0.062 per GB Data Processed by NAT Gateways
0.0620000000  $0.062 per NAT Gateway Hour

小时费按网关个数走,不看用量,东京区 $0.062 一小时约合一个月 45 美元。这 8 个挂在几个没有工作负载的 VPC 里,30 天 BytesOutToDestinationActiveConnectionCount 都是 0,每月加起来 400 多美元;它们不在东京区,各区小时价不同,别拿 45 美元去乘 8。

证明「没人用」,NAT 那几个字节指标随便挑一个都行,反正都是 0;要按计费口径加出总量的时候才需要挑对是哪两个,那是后面那节的事。指标之外还可以看 VPC 里除了 NAT 自己那几张 ENI 还有没有别的:

aws ec2 describe-network-interfaces --filters Name=vpc-id,Values=vpc-xxxxxxxx \
  --query 'NetworkInterfaces[].[InterfaceType,Description]' --output text

删 NAT 时把 Elastic IP 一起释放。这 8 个 EIP 现在就在收费:2024-02-01 之后公网 IPv4 不分挂没挂在资源上都收,PublicIPv4:InUseAddressPublicIPv4:IdleAddress 各区都是 $0.005 每地址每小时,8 个就是每月 29 美元,删掉网关只是把这笔钱从 InUse 挪到 Idle,单价不变,release-address 之后才停。(这个账号拿不到 resource 级的 Cost Explorer,这条是拿账单地址数跟 EIP 库存卡出来的。圣保罗区八月的 InUse 折合 44 个地址;这个区非 NAT 的公网 IP 最多凑得出 39 个,43 个已关联 EIP 减去在 NAT 上的 7 个,再加 3 个自带公网 IP 的实例。差的 5 个只能是 NAT 的,另外两个区也对不上。这是按数量卡出来的,不是逐个资源的归属。)

到 S3 和 ECR 的流量绕了公网#

一个包要不要按 GB 付钱,取决于应用连的是哪个地址:

应用连的地址 走哪条路 每 GB 单价(东京区)
公网域名解析出的公网 IP 私有子网 → NAT → IGW $0.062,另加对应的数据传输费
VPC peering 对端的私有 IP 私有子网 → peering 同 AZ 不收,跨 AZ 进出各 $0.01
interface endpoint 的 ENI 私有子网 → endpoint ENI $0.01(首 1 PB),另加 $0.014 每 endpoint 每 AZ 每小时
gateway endpoint(到 2026-09-13 只有 S3 和 DynamoDB) 私有子网 → 路由表里的 prefix list 0

单价来源:NAT 的 $0.062 和 peering 跨 AZ 的 $0.01 都在 VPC 定价页;endpoint 那两个数字在 AmazonVPC 这个 offer code 的定价清单里(aws pricing get-products --service-code AmazonEC2 查的是另一个清单,抓不到这两个),东京区的 usagetype 是 APN1-VpcEndpoint-Hours($0.014)和 APN1-VpcEndpoint-Bytes($0.01)。跨 AZ 那笔只有 peering 那行有,2022-04-01 起走 interface endpoint 的跨 AZ 流量 AWS 不另收,表里的 $0.01 就是全部。gateway endpoint 免费同样是定价页的原话:

There are no data processing or hourly charges for using Gateway Type VPC endpoints.

这个 0 只在本区成立:gateway endpoint 靠路由表里那条本区 prefix list 生效,发往别的区同一个服务的包不匹配它,照旧落回 0.0.0.0/0 走 NAT。这条和另外几条非价格的分界,展开在两种 endpoint 怎么选那篇里。

免费这一档值得先做完,因为它没有回本点要算。最明显的一例是 ECR 拉镜像,ECR 的 VPC endpoint 文档写得很直接:

For your Amazon ECS tasks to pull private images from Amazon ECR, you must create a gateway endpoint for Amazon S3. The gateway endpoint is required because Amazon ECR uses Amazon S3 to store your image layers.

同一页还列了各平台版本要哪几个 endpoint:

Amazon ECS tasks hosted on Fargate using platform version 1.4.0 or later require both Amazon ECR VPC endpoints and the Amazon S3 gateway endpoints.

ecr.apiecr.dkr 这两个 interface endpoint 只管住 API 调用(取 token、拿 manifest)。镜像层在 prod-<region>-starport-layer-bucket 这个 S3 桶里,没有 S3 gateway endpoint 就仍旧从 NAT 出去再回来。出问题的那个 VPC 就是这个配法:两个 ECR endpoint 建了,PrivateDnsEnabled 也是 true,S3 的没建,562 MB 的层一个字节都没少走。

flowchart TD T["Fargate 任务起来,拉镜像"] T -- "取 token、拿 manifest" --> API["ecr.api / ecr.dkr
interface endpoint"] API --> ECR[("Amazon ECR")] T -- "下载镜像层 562 MB" --> RT{"子网路由表"} RT -- "没建 S3 gateway endpoint
落到 0.0.0.0/0" --> NAT["NAT Gateway
按 GB 收费"] RT -. "建了走 prefix list,不收钱" .-> GW["S3 gateway endpoint"] NAT --> S3[("starport-layer-bucket")] GW -.-> S3

任务每重启一次就重拉一次镜像:Fargate 的任务跑在按需新起的执行环境里,不像 EC2 那样有上一任务留下的本地镜像缓存,一起来就重新下载一遍(这条我没找到 AWS 文档的正面说明,是从重启次数和流量对得上反推的)。任务 ENI 所在子网的路由表里,默认路由指向 NAT,这 562 MB 每次都要过一遍收费口。412 GB 除以 562 MB 差不多是每天 750 次,跟那个循环的重启次数对得上。

建 gateway endpoint 有两点容易漏。一是它靠路由表生效,创建时要把每一张私有子网的路由表都勾上,漏掉的那张照旧走 NAT;以后新加的子网带的是新路由表,同样不会自动带上这条,悄悄退回 NAT。二是 ECR 文档这条:

If your containers have existing connections to Amazon S3, their connections might be briefly interrupted when you add the Amazon S3 gateway endpoint.

已有的 S3 长连接会断一下。AWS 文档给的解法是另建一个带 gateway endpoint 的 VPC 再把集群迁过去;这个成本太高的话,挑个低峰时段直接加也行,风险是有状态的 S3 传输可能要重试一次。

gateway endpoint 覆盖不到的 AWS 服务,interface endpoint 要算回本点#

AWS 自己在 NAT gateway 的计费说明页把两种 endpoint 并列给出来:

If most traffic through your NAT gateway is to AWS services that support interface endpoints or gateway endpoints, consider creating an interface endpoint or gateway endpoint for these services.

S3 和 DynamoDB 之外的 AWS 服务要走私有路径只能用 interface endpoint,而它有小时费。

东京区一个 interface endpoint 铺 3 个 AZ,$0.014 乘 3 乘 730 小时是 30.7 美元。每 GB 从 $0.062 降到 $0.01,每 GB 省 $0.052,两边相除得出月流量大约 600 GB 以上才开始省钱,再往上线性增长:一个月 10 TB 的服务,NAT 是 $635,interface endpoint 是 $102 加 $31。

流量不到这个量级就别建,付了小时费省不下多少。peering 没有小时费,不用算回本点:跨 AZ 每 GB 进出各 $0.01,已经比 NAT 的 $0.062 低一档;资源跟对端放同一个 AZ,这两笔也都省掉。

SaaS 给的公网域名:peering 建好了流量也不走过去#

第三方账号里的托管服务(Confluent Cloud、ClickHouse Cloud 这类)给的连接串是公网域名。哪怕实例就开在你同一个区,DNS 给出来的是公网 IP,单价就落到表格第一行。自己 VPC 里的 RDS、MSK 不在这条路径上:DB 实例和 broker 的 ENI 建在你的子网里,端点解析回私有 IP,本来也不过 NAT。

应用连的如果还是那个公网域名,DNS 解析回公网 IP,包照样出 NAT,peering 在旁边闲着。要确认得从子网里实际解析一次,或者看账号里有没有 private hosted zone 覆盖这个域名:

aws route53 list-hosted-zones --query 'HostedZones[].[Name,Config.PrivateZone]' --output text

这个账号里一个覆盖 SaaS 域名的私有 zone 都没有,那 peering 就只服务于按私有 IP 直连的那部分调用。要让 SaaS 流量走 peering,得补上这条域名解析,而它能不能成,取决于对端给不给你按私有 IP 访问的端点。

从账单查到具体网关#

上面三件事都是从账单倒推出来的。先把这笔钱在账单里框出来:NAT 的两笔费用在 Cost Explorer 里是 EC2 - Other 下的两个 usage type:

aws ce get-cost-and-usage \
  --time-period Start=2026-08-01,End=2026-09-01 --granularity MONTHLY \
  --metrics UnblendedCost UsageQuantity \
  --group-by Type=DIMENSION,Key=USAGE_TYPE \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["EC2 - Other"]}}' \
  --query 'ResultsByTime[0].Groups[?contains(Keys[0],`NatGateway`)]'

出来的是 <区域前缀>-NatGateway-Bytes-Hours 两行。-Hours 的 UsageQuantity 除以当月小时数正好是网关个数,可以用它跟 describe-nat-gateways 的数量对一下账,两边对不上通常意味着这个月有网关新建或删除过。

框出来之后要落到具体网关。AWS/NATGateway 下有四个字节指标,容易数重:

  • BytesInFromSource:实例发给 NAT 的
  • BytesOutToDestination:NAT 发往目的地的
  • BytesInFromDestination:目的地回给 NAT 的
  • BytesOutToSource:NAT 回给实例的

去程在前两个里各计一次,回程在后两个里各计一次。我按 BytesInFromSourceBytesInFromDestination 取数,每个字节只数一次。我没找到 AWS 文档把计费和这四个指标对应起来的地方,要跟账单对齐,就拿这两个指标的和跟 Cost Explorer 里 -NatGateway-Bytes 的 UsageQuantity 对一次。单看回程那个指标:

aws cloudwatch get-metric-statistics --namespace AWS/NATGateway \
  --metric-name BytesInFromDestination \
  --dimensions Name=NatGatewayId,Value=nat-xxxxxxxxxxxxxxxxx \
  --start-time 2026-08-13T00:00:00Z --end-time 2026-09-12T00:00:00Z \
  --period 86400 --statistics Sum \
  --query 'sort_by(Datapoints,&Timestamp)[].[Timestamp,Sum]' --output text

开头那个 crash loop 就是这样撞见的:按天拉出来,某一天从 0.1 GB 跳到 196 GB,之后七天稳定在 412 GB 上下,循环停掉当天回到 0。这个 VPC 在圣保罗区,412 GB 按那里的 $0.093 算下来是每天 38 美元。任务起来就退是数据库那边的问题,这篇不碰;这笔账单是每次重启重拉那 562 MB 攒出来的,两边各有各的修法。

这两步能回答「哪个网关在烧钱」,回答不了「这些字节发给谁」:Cost Explorer 没有目的地维度,CloudWatch 也没有,要归因只有 VPC Flow Logs。我不建议在 VPC 级别直接打开并写进 CloudWatch Logs,日志费可能比省下的流量费还贵;给 NAT 自己那几张 ENI 单独建,投到 S3,开一两天,用 Athena 按 pkt-dstaddr 聚合出 top 20 然后关掉。建 Flow Log 时记得选自定义格式:pkt-dstaddr 是 version 3 字段,默认格式只有 version 2 的那批,里面的 dstaddr 在 NAT 的 ENI 上记的是 NAT 自己那一跳,聚出来看不到真实目的地。拿到那张表再决定哪些目的地值得建 endpoint 或 peering。

小结#

  • 三笔里只有第一笔现在就能算出总账:8 个空转网关每月 400 多美元的小时费,加上它们 EIP 的 29 美元,删干净就没了。另外两笔不能直接加上去:crash loop 那 38 美元一天是数据库那边修好之后停的;gateway endpoint 省下来的是平时拉镜像那部分流量,得按稳态的量另算;SaaS 那笔要等对端给出私有端点。
  • 顺序上先删空转网关,再给 S3 建 gateway endpoint(不要钱,顺带解决 ECR 镜像层),然后按 600 GB 这个量级判断要不要 interface endpoint,最后再考虑 SaaS 的私有域名解析。
  • 删网关时连 EIP 一起释放。公网 IPv4 这笔钱删网关省不掉,只是从 InUse 挪到 Idle,单价一样。
  • 建 endpoint 之前先做归因。没有 Flow Logs,账单只能说出哪个网关贵,说不出这些字节发给谁,很容易把 endpoint 建在没什么流量的目的地。