摘要:本文对比了 NAT 网关与 NAT 实例的费用结构,基于 EC2 网络带宽机制和多流流量规则,分析了中国宁夏区域(cn-northwest-1)中所有当前代实例类型的可用带宽与成本,筛选出各流量档位性价比最优的实例类型,并提供了基于 Auto Scaling Group 和 CloudFormation 的高可用一键部署方案。
1. 背景
在亚马逊云科技中,私有子网中的实例可以通过 NAT 访问互联网。亚马逊云科技提供了托管的 NAT 网关和自建的 NAT 实例两种方案,两者在可用性、运维成本和费用结构上各有差异。除出站流量收取数据传输费外,NAT 网关还额外对双向流量收取数据处理费。选择 NAT 实例时,需要了解不同实例类型的实际可用带宽,才能进行合理选型。
以某客户在亚马逊云科技部署的开发测试环境为例,其 VPC 中的私有子网需要访问互联网以拉取软件包、下载容器镜像和调用外部 API。以入站流量为主,前期预估每月约 10 TB,并持续增加。希望在满足带宽需求的前提下尽可能降低成本。
本文分析了亚马逊云科技中国(宁夏)区域(cn-northwest-1)中所有当前代 EC2 实例类型的网络带宽数据,计算了作为 NAT 实例时的实际可用带宽和成本,并提供了基于 CloudFormation 的一键部署方案。
NAT 网关 vs NAT 实例
参考:比较 NAT 网关和 NAT 实例
选择建议
- 选择 NAT 网关:追求免运维、对可用性要求高。例如生产环境中运行关键业务,无法接受任何中断时间,NAT 网关提供单 AZ 内自动冗余,无需关注实例健康状态、OS 补丁和故障恢复逻辑
- 选择 NAT 实例:成本敏感,适用于开发测试环境、非关键生产环境,或业务关键路径不依赖出站互联网访问的场景。能接受分钟级故障恢复时间。需要安全组控制流量、需要端口转发或堡垒机功能
| 属性 |
NAT 网关 |
NAT 实例 |
| 可用性 |
高度可用,单 AZ 内冗余实施 |
无内置高可用,需自行实现故障转移 |
| 带宽 |
自动扩展到 100 Gbps |
取决于实例类型 |
| 维护 |
亚马逊云科技托管,无需维护 |
自行管理 OS 更新、补丁、NAT 配置 |
| 性能 |
软件经过 NAT 优化 |
通用 AMI,需自行配置 |
| 费用 |
按小时 + 双向数据处理费 + 出站流量费 |
实例费用 + 出站流量费 |
| 安全组 |
不支持关联安全组 |
支持安全组控制流量 |
| 端口转发 |
不支持 |
支持自定义配置 |
| 堡垒服务器 |
不支持 |
可兼做堡垒机 |
| IP 分段 |
仅支持 UDP 分段转发 |
支持 UDP/TCP/ICMP 分段重组 |
| 超时行为 |
返回 RST 数据包 |
发送 FIN 数据包关闭连接 |
2. 名词解释
- 基准带宽(BaselineBandwidthInGbps):实例可持续稳定使用的网络带宽
- 峰值带宽(PeakBandwidthInGbps):通过网络 I/O 积分机制可短暂突增到的最大带宽(持续 5-60 分钟,取决于实例大小)
- NAT 可用带宽:NAT 实例的流量经过互联网网关(IGW),受多流流量规则限制:
- vCPUs < 32:带宽限制为基准带宽或 5 Gbps,以较小者为准
- vCPUs ≥ 32:带宽限制为通过互联网网关的流量的 50%,或者限制为 5 Gbps,以较大者为准
3. 实例类型建议
以下数据表列出了亚马逊云科技中国(宁夏)区域(cn-northwest-1)筛选后的 NAT 实例类型,按流量从小到大排列。根据实际月流量需求,在表中选择满足流量需求且价格最低的实例类型即可。
数据说明
- 价格单位为人民币(CNY)。NAT 实例价格为 Linux 按需价格。NAT 网关费用:¥0.325/小时 + ¥0.325/GB 数据处理费
- 入站/出站流量费:本文场景以入站流量为主(入站免费),出站流量相对较小(¥0.933/GB,可忽略不计)
- 流量为理论最大值:按 730 小时满载计算,实际使用中不可能 24/7 跑满带宽
- 突增带宽不可持续:小规格实例(≤ 16 vCPUs)的峰值带宽依赖网络 I/O 积分,持续高流量时会回落到基准带宽
- 实例购买选项:NAT 实例价格基于查询时的亚马逊云科技Pricing API 数据,可能随时变动。使用 Savings Plans 或 Reserved Instances 可再降 30-60%
- 数据时效:截止至 2026 年 5 月,仅包含当前代(current-generation)实例类型,主要包括:
- 通用型:m5a、m5d、m6g、m7g、m8g
- 计算优化型:c5a、c5d、c6g、c6gd、c6i、c6in、c7g、c8g、c8gd、c8gn
- 内存优化型:r5a、r5d、r6g、r6gd、r7g、r8g、r8gd、x1、x2idn、x2iedn、z1d、u-6tb1
- 存储优化型:i3、i3en、i4i、i7ie
- 加速计算型:g4dn、g5、inf1
- 突增型:t2、t3、t3a、t4g
| 实例类型 |
vCPUs |
网络性能 |
峰值带宽 (Gbps) |
基准带宽 (Gbps) |
NAT 可用带宽 (Gbps) |
入站流量 (TB/月) |
NAT 实例价格 (¥/月) |
NAT 网关价格 (¥/月) |
| t4g.nano |
2 |
Up to 5 Gigabit |
5.0 |
0.032 |
0.032 |
10.5 |
13.5 |
3,650 |
| t4g.micro |
2 |
Up to 5 Gigabit |
5.0 |
0.064 |
0.064 |
21.0 |
27.0 |
7,062 |
| t4g.small |
2 |
Up to 5 Gigabit |
5.0 |
0.128 |
0.128 |
42.0 |
53.9 |
13,887 |
| t4g.medium |
2 |
Up to 5 Gigabit |
5.0 |
0.256 |
0.256 |
84.1 |
108.0 |
27,570 |
| c8g.medium |
1 |
Up to 12.5 Gigabit |
12.5 |
0.52 |
0.52 |
170.8 |
142.9 |
55,747 |
| c8g.large |
2 |
Up to 12.5 Gigabit |
12.5 |
0.937 |
0.937 |
307.8 |
285.9 |
100,272 |
| c8gn.medium |
1 |
Up to 25 Gigabit |
25.0 |
3.125 |
3.125 |
1,026.6 |
338.4 |
333,882 |
| c8gn.large |
2 |
Up to 30 Gigabit |
30.0 |
6.25 |
5.0 |
1,642.5 |
676.9 |
534,050 |
| c8g.8xlarge |
32 |
15 Gigabit |
15.0 |
15.0 |
7.5 |
2,463.8 |
4,573.8 |
800,972 |
| c8g.12xlarge |
48 |
22.5 Gigabit |
22.5 |
22.5 |
11.25 |
3,695.6 |
6,860.7 |
1,201,307 |
| c8gn.8xlarge |
32 |
100 Gigabit |
100.0 |
100.0 |
50.0 |
16,425.0 |
10,830.0 |
5,338,362 |
| c8gn.12xlarge |
48 |
150 Gigabit |
150.0 |
150.0 |
75.0 |
24,637.5 |
16,244.9 |
8,007,425 |
| c8gn.16xlarge |
64 |
200 Gigabit |
200.0 |
200.0 |
100.0 |
32,850.0 |
21,659.9 |
10,676,487 |
| c8gn.24xlarge |
96 |
300 Gigabit |
300.0 |
300.0 |
150.0 |
49,275.0 |
32,489.9 |
16,014,612 |
4. 数据分析过程
NAT 转发的核心操作是修改 IP 报文头,主要消耗 CPU,并不依赖大内存或高速存储。因此整体来看计算优化型(c 系列)性价比最高。以 medium 规格为例,c/m/r 三者网络带宽相同,但 c8g 在大流量场景下性价比反而最优,t4g 系列则更适合小流量场景。
| 实例类型 |
系列 |
基准带宽 (Gbps) |
月价 (¥) |
| c8g.medium |
计算优化 |
0.52 |
142.9 |
| m8g.medium |
通用 |
0.52 |
196.4 |
| r8g.medium |
内存优化 |
0.52 |
255.9 |
具体分析过程如下:
步骤一:查询所有实例类型的网络带宽信息
参考:Amazon EC2 实例网络带宽 – 介绍实例网络带宽规格、基准/突增机制、describe-instance-types 查询方法
使用 describe-instance-types API 查询目标区域中所有实例类型的网络性能数据(含 vCPUs、网络性能、峰值带宽、基准带宽)。
aws ec2 describe-instance-types \
--filters "Name=current-generation,Values=true" \
--query "InstanceTypes[].[InstanceType, VCpuInfo.DefaultVCpus, NetworkInfo.NetworkPerformance, NetworkInfo.NetworkCards[0].PeakBandwidthInGbps, NetworkInfo.NetworkCards[0].BaselineBandwidthInGbps]" \
--output json
步骤二:计算 NAT 实例可用带宽
参考:Amazon EC2 实例网络带宽 – 【多流流量】及【可用实例带宽】部分
NAT 实例流量经过互联网网关(IGW),根据文档【多流流量】规则计算:
- vCPUs < 32:限制为 5 Gbps,即 min(基准带宽, 5 Gbps)
- vCPUs ≥ 32:限制为基准带宽的 50%,或 5 Gbps,以较大者为准,即 max(基准带宽 × 50%, 5 Gbps)
步骤三:计算月流量
基于 NAT 可用带宽,按每月 730 小时满载计算理论最大月流量,新增 入站流量 (TB/月) 列。
公式:流量 (TB) = NAT 可用带宽 (Gbps) × 730 × 3600 / 8 / 1000
步骤四:查询 NAT 实例价格
使用 AWS Pricing API 查询 Linux 共享租赁按需实例价格,按每月 730 小时换算为月费,新增 NAT 实例价格 列。
aws pricing get-products \
--service-code AmazonEC2 \
--filters \
'Type=TERM_MATCH,Field=location,Value=China (Ningxia)' \
'Type=TERM_MATCH,Field=operatingSystem,Value=Linux' \
'Type=TERM_MATCH,Field=tenancy,Value=Shared' \
'Type=TERM_MATCH,Field=capacitystatus,Value=Used' \
'Type=TERM_MATCH,Field=preInstalledSw,Value=NA' \
--output json
步骤五:筛选实例类型
在当前客户场景下,流量以入站方向为主,因此筛选的核心指标是:以最低的实例价格获得最大的可用带宽(即入站流量吞吐能力)。
1)规则筛选
将数据按流量升序、价格升序排列,从最后一行(流量最大)向前扫描,记录已见过的最低价格。如果当前行价格大于已见最低价,说明后面存在流量更大且价格更低的实例类型,当前行予以删除。如果价格相同,保留流量更大的,删除流量更小的。
此外,部分实例类型在 NAT 场景下可用带宽相同(如被 IGW 5 Gbps 封顶)且价格相近,仅保留流量更大或价格更低的一个。
2)手动筛选
自动筛选后仍存在价格几乎相同但流量差距明显的情况,手动去除:
- c7g.16xlarge(¥9147.6/月,4928 TB)vs c8gn.8xlarge(¥10830/月,16425 TB):差 ¥1682,流量多 233%
- c6in.8xlarge(¥10363/月,8212 TB)vs c8gn.8xlarge(¥10830/月,16425 TB):差 ¥467,流量多 100%
5. 基于 Auto Scaling Group 的高可用方案
参考:NAT 实例教程 – 介绍 NAT 实例的创建、配置 iptables、禁用源/目标检查、更新路由表等完整流程
NAT 实例为单实例部署,需要自行实现故障恢复。以下基于 Amazon EC2 Auto Scaling Group(ASG)实现高可用方案,并使用 CloudFormation 模板实现一键部署。
5.1 架构概览
[图1] |
核心服务
- Auto Scaling Group(min=max=1):始终维持一台 NAT 实例运行,故障时自动替换
- Elastic IP:固定出口 IP,新实例启动后自动接管
- Launch Template:定义实例配置和 UserData 自动化脚本
- IAM Role:授予实例调用 EC2 API 的权限(接管 EIP、修改路由表等)
- Lambda Custom Resource:自动获取 VPC ID 和配置安全组规则(仅在部署时调用一次)
5.2 部署方案
输入参数
| 参数 |
说明 |
| InstanceType |
NAT 实例类型 |
| KeyPairName |
EC2 密钥对,用于 SSH 访问 |
| PublicSubnetId |
NAT 实例部署的公有子网 |
| PrivateSubnetIds |
需要 NAT 的私有子网,支持多选 |
核心资源
| 资源 |
类型 |
说明 |
| NatLaunchTemplate |
Launch Template |
定义实例配置(AMI、实例类型、安全组、UserData) |
| NatAutoScalingGroup |
Auto Scaling Group |
管理实例生命周期,min=max=1 确保始终运行一台实例 |
| NatEip |
Elastic IP |
固定出口 IP,便于下游白名单配置 |
| NatSecurityGroup |
Security Group |
控制 NAT 实例的入站和出站流量 |
| NatInstanceRole |
IAM Role |
授予实例调用 EC2 API 的权限 |
辅助资源(Lambda Custom Resource)
| 资源 |
说明 |
| GetVpcIdFunction |
从公有子网自动获取 VPC ID,用于创建安全组 |
| AddSecurityGroupRulesFunction |
为每个私有子网 CIDR 动态添加安全组入站规则 |
安全组规则
| 方向 |
协议 |
端口 |
来源/目标 |
说明 |
| 入站 |
TCP |
80 |
私有子网 CIDR |
允许 HTTP 流量 |
| 入站 |
TCP |
443 |
私有子网 CIDR |
允许 HTTPS 流量 |
| 出站 |
TCP |
80 |
0.0.0.0/0 |
允许 HTTP 到互联网 |
| 出站 |
TCP |
443 |
0.0.0.0/0 |
允许 HTTPS 到互联网 |
故障恢复流程
- ASG 检测到实例终止(硬件故障、手动终止、状态检查失败)
- ASG 自动启动新实例
- 新实例执行 UserData 脚本:
- 配置 iptables NAT 转发
- 接管 EIP(
--allow-reassociation)
- 禁用源/目标检查
- 更新私有子网路由表指向新实例
- 私有子网恢复互联网访问(中断时间约 2-3 分钟)
5.2.1 UserData 脚本
以下脚本已嵌入 CloudFormation 模板的 Launch Template 中,部署时无需单独执行。每次 ASG 启动新实例时自动运行,并按以下脚本执行顺序完成 NAT 接管。
配置 NAT 转发
安装 iptables,启用 IP 转发,配置 MASQUERADE 规则。这是 NAT 的核心功能,必须最先完成。
# 安装 iptables
sudo yum install iptables-services -y
sudo systemctl enable iptables
sudo systemctl start iptables
# 启用 IP 转发
sudo tee /etc/sysctl.d/custom-ip-forwarding.conf << EOF
net.ipv4.ip_forward=1
EOF
sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf
# 获取默认路由网卡,配置 MASQUERADE
IFACE=$(ip route show default | awk '{print $5}' | head -1)
sudo /sbin/iptables -t nat -A POSTROUTING -o "$IFACE" -j MASQUERADE
sudo /sbin/iptables -F FORWARD
sudo service iptables save
获取实例元数据
通过 IMDSv2(实例元数据服务)获取当前实例 ID,后续步骤均依赖此变量。
# 获取 IMDSv2 Token
TOKEN=$(curl -s -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" -X PUT "http://169.254.169.254/latest/api/token")
# 通过 Token 获取当前实例 ID
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
接管 EIP
将预分配的 EIP 绑定到当前实例。--allow-reassociation 确保即使 EIP 仍关联在旧实例上也能接管。
# 接管 EIP,--allow-reassociation 允许从旧实例接管
aws ec2 associate-address --allocation-id ${NatEip.AllocationId} --instance-id $INSTANCE_ID --allow-reassociation
禁用 SourceDestCheck
NAT 实例需要转发非自身的流量,必须禁用源/目标检查。Launch Template 不支持此属性,只能通过 API 设置。
# 禁用源/目标检查,允许转发非自身流量
aws ec2 modify-instance-attribute --no-source-dest-check --instance-id $INSTANCE_ID
更新路由表
遍历所有私有子网,将其路由表的 0.0.0.0/0 指向当前实例。此步骤放在最后,确保 NAT 功能就绪后再切换流量,避免路由黑洞。
for SUBNET in ${PrivateSubnets}; do
# 查找子网显式关联的路由表
RT=$(aws ec2 describe-route-tables --filters "Name=association.subnet-id,Values=$SUBNET" --query 'RouteTables[0].RouteTableId' --output text)
# 未找到则说明子网使用主路由表,通过 VPC ID 查找
if [ "$RT" = "None" ] || [ -z "$RT" ]; then
VPC_ID=$(aws ec2 describe-subnets --subnet-ids $SUBNET --query 'Subnets[0].VpcId' --output text)
RT=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" "Name=association.main,Values=true" --query 'RouteTables[0].RouteTableId' --output text)
fi
# 替换已有路由,若不存在则创建
aws ec2 replace-route --route-table-id $RT --destination-cidr-block 0.0.0.0/0 --instance-id $INSTANCE_ID || \
aws ec2 create-route --route-table-id $RT --destination-cidr-block 0.0.0.0/0 --instance-id $INSTANCE_ID
done
5.3 CloudFormation 部署模版
AWSTemplateFormatVersion: "2010-09-09"
Description: NAT Instance
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Instance Configuration
Parameters:
- InstanceType
- KeyPairName
- PublicSubnetId
- Label:
default: Network Configuration
Parameters:
- PrivateSubnetIds
ParameterLabels:
InstanceType:
default: Instance Type
KeyPairName:
default: Key Pair
PublicSubnetId:
default: Public Subnet
PrivateSubnetIds:
default: Private Subnets
Parameters:
InstanceType:
Type: String
Default: t4g.nano
AllowedValues:
- t4g.nano
- t4g.micro
- t4g.small
- t4g.medium
- c8g.medium
- c8g.large
- c8gn.medium
- c8gn.large
- c8g.8xlarge
- c8g.12xlarge
- c8gn.8xlarge
- c8gn.12xlarge
- c8gn.16xlarge
- c8gn.24xlarge
Description: Select a recommended NAT instance type in cn-northwest-1
KeyPairName:
Type: AWS::EC2::KeyPair::KeyName
Description: Select a key pair for SSH access
PublicSubnetId:
Type: AWS::EC2::Subnet::Id
Description: Select a public subnet to deploy the NAT instance
PrivateSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Select private subnets that need NAT
Resources:
GetVpcIdRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Policies:
- PolicyName: DescribeVpcSubnets
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:DescribeSubnets
Resource: "*"
GetVpcIdFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.14
Handler: index.handler
Role: !GetAtt GetVpcIdRole.Arn
Timeout: 60
Code:
ZipFile: |
import boto3
import cfnresponse
import json
def handler(event, context):
try:
# Skip processing on stack deletion
if event['RequestType'] == 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
ec2 = boto3.client('ec2')
public_subnet_id = event['ResourceProperties']['PublicSubnetId']
# Get VPC ID from subnet
response = ec2.describe_subnets(SubnetIds=[public_subnet_id])
vpc_id = response['Subnets'][0]['VpcId']
cfnresponse.send(event, context, cfnresponse.SUCCESS, {
'VpcId': vpc_id
})
except Exception as e:
print(f"Error: {str(e)}")
cfnresponse.send(event, context, cfnresponse.FAILED, {})
GetVpcId:
Type: Custom::GetVpcId
Properties:
ServiceToken: !GetAtt GetVpcIdFunction.Arn
PublicSubnetId: !Ref PublicSubnetId
NatSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for NAT instance
VpcId: !GetAtt GetVpcId.VpcId
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: Allow HTTP to internet
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: Allow HTTPS to internet
AddSecurityGroupRulesRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Policies:
- PolicyName: SecurityGroupOperations
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:DescribeSubnets
- ec2:AuthorizeSecurityGroupIngress
Resource: "*"
AddSecurityGroupRulesFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.14
Handler: index.handler
Role: !GetAtt AddSecurityGroupRulesRole.Arn
Timeout: 60
Code:
ZipFile: |
import boto3
import cfnresponse
def handler(event, context):
try:
ec2 = boto3.client('ec2')
# Skip processing on stack deletion
if event['RequestType'] == 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
sg_id = event['ResourceProperties']['SecurityGroupId']
private_subnet_ids = event['ResourceProperties']['PrivateSubnetIds']
# Get CIDR blocks for all private subnets
response = ec2.describe_subnets(SubnetIds=private_subnet_ids)
# Add ingress rules for each private subnet CIDR
for subnet in response['Subnets']:
cidr = subnet['CidrBlock']
ec2.authorize_security_group_ingress(
GroupId=sg_id,
IpPermissions=[
{
'IpProtocol': 'tcp',
'FromPort': 80,
'ToPort': 80,
'IpRanges': [{'CidrIp': cidr, 'Description': f'HTTP from {cidr}'}]
},
{
'IpProtocol': 'tcp',
'FromPort': 443,
'ToPort': 443,
'IpRanges': [{'CidrIp': cidr, 'Description': f'HTTPS from {cidr}'}]
}
]
)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
print(f"Error: {str(e)}")
cfnresponse.send(event, context, cfnresponse.FAILED, {})
AddNatSecurityGroupRules:
Type: Custom::SecurityGroupRules
Properties:
ServiceToken: !GetAtt AddSecurityGroupRulesFunction.Arn
SecurityGroupId: !Ref NatSecurityGroup
PrivateSubnetIds: !Ref PrivateSubnetIds
NatInstanceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: NatInstancePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:ModifyInstanceCreditSpecification
- ec2:AssociateAddress
- ec2:ModifyInstanceAttribute
- ec2:DescribeRouteTables
- ec2:DescribeSubnets
- ec2:ReplaceRoute
- ec2:CreateRoute
Resource: "*"
NatInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref NatInstanceRole
NatLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
ImageId: "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64}}"
InstanceType: !Ref InstanceType
KeyName: !Ref KeyPairName
NetworkInterfaces:
- DeviceIndex: "0"
AssociatePublicIpAddress: true
Groups:
- !Ref NatSecurityGroup
IamInstanceProfile:
Arn: !GetAtt NatInstanceProfile.Arn
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
set -ex
# Install and enable iptables
sudo yum install iptables-services -y
sudo systemctl enable iptables
sudo systemctl start iptables
# Enable IP forwarding
sudo tee /etc/sysctl.d/custom-ip-forwarding.conf << EOF
net.ipv4.ip_forward=1
EOF
sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf
# Configure NAT masquerade on default interface
IFACE=$(ip route show default | awk '{print $5}' | head -1)
sudo /sbin/iptables -t nat -A POSTROUTING -o "$IFACE" -j MASQUERADE
sudo /sbin/iptables -F FORWARD
sudo service iptables save
# Get instance ID using IMDSv2
TOKEN=$(curl -s -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" -X PUT "http://169.254.169.254/latest/api/token")
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
# Set CPU credit specification to standard
aws ec2 modify-instance-credit-specification --instance-credit-specifications InstanceId=$INSTANCE_ID,CpuCredits=standard
# Associate EIP with this instance
aws ec2 associate-address --allocation-id ${NatEip.AllocationId} --instance-id $INSTANCE_ID --allow-reassociation
# Disable source/destination check for NAT
aws ec2 modify-instance-attribute --no-source-dest-check --instance-id $INSTANCE_ID
# Update route tables for private subnets
for SUBNET in ${PrivateSubnets}; do
# Find route table explicitly associated with subnet
RT=$(aws ec2 describe-route-tables --filters "Name=association.subnet-id,Values=$SUBNET" --query 'RouteTables[0].RouteTableId' --output text)
# If not found, use main route table
if [ "$RT" = "None" ] || [ -z "$RT" ]; then
VPC_ID=$(aws ec2 describe-subnets --subnet-ids $SUBNET --query 'Subnets[0].VpcId' --output text)
RT=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" "Name=association.main,Values=true" --query 'RouteTables[0].RouteTableId' --output text)
fi
# Replace or create default route pointing to this instance
aws ec2 replace-route --route-table-id $RT --destination-cidr-block 0.0.0.0/0 --instance-id $INSTANCE_ID || \
aws ec2 create-route --route-table-id $RT --destination-cidr-block 0.0.0.0/0 --instance-id $INSTANCE_ID
done
- PrivateSubnets: !Join [ " ", !Ref PrivateSubnetIds ]
NatAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref NatLaunchTemplate
Version: !GetAtt NatLaunchTemplate.LatestVersionNumber
VPCZoneIdentifier:
- !Ref PublicSubnetId
DesiredCapacity: "1"
MinSize: "1"
MaxSize: "1"
NatEip:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
5.4 使用说明
进入 CloudFormation 控制台,选择创建堆栈,然后选择 CloudFormation 部署脚本。根据输入参数提示,依次选择要使用的实例类型、实例密钥、要部署的目标公网,要转发的私有子网列表等信息。
[图2] |
提交并等待当前堆栈创建完成后,在堆栈的详情中,查看已自动化部署的资源。
[图3] |
6. 总结
本文从带宽机制、成本对比和高可用三个维度分析了 EC2 NAT 实例方案:
- 带宽机制:EC2 实例的实际可用带宽受多种因素影响,如多流流量规则、vCPU 数量、网络 I/O 积分等。本文梳理了这些因素如何共同作用于 NAT 实例场景,明确了基准带宽、峰值带宽与可用带宽三者之间的关系
- 成本与选型:以亚马逊云科技中国(宁夏)区域(cn-northwest-1)为例,基于亚马逊云科技 API 获取所有当前代实例的网络带宽和价格数据,结合 NAT 带宽规则计算实际可用流量,筛选出每个流量档位的实例类型
- 高可用部署:Auto Scaling Group 是实现 NAT 实例故障恢复的方案之一,本文提供了基于 ASG 的 CloudFormation 模板部署示例。此外还可通过 Lambda 监听 CloudWatch 告警、多 AZ 主备切换等方式实现
NAT 网关和 NAT 实例各有所长,适合的方案取决于业务对成本、可用性和运维复杂度的权衡。希望本文对带宽机制的梳理、成本数据的分析以及高可用方案的实践,能为您的选型决策提供参考。
➡️ 下一步行动:
相关产品:
相关文章:
*前述特定亚马逊云科技生成式人工智能相关的服务目前在亚马逊云科技海外区域可用。亚马逊云科技中国区域相关云服务由西云数据和光环新网运营,具体信息以中国区域官网为准。
本篇作者
2026 亚马逊云科技中国峰会
探索多 Agent 架构、MCP 与 A2A 协议,掌握安全治理与评测体系,全面解锁生产级 Agent 的关键能力与前沿趋势。

|
 |