通过 iptables 的 SNAT 规则实现私有实例轮询使用多公网 IP 访问公网
日期: 2023-05-11

 

背景说明

 

众所周知,业务服务器是企业用以提供服务实现盈利的核心,绝大多数情况业务服务器都部署于私有网络中,以保证安全。

在私有网络中的业务服务器提供业务功能时,对网络的诉求会分成两种类型:第一种是经由互联网被访问,可以借助负载均衡器进行服务暴露实现;第二种则是主动访问互联网。

对于第二种类型,如果需要的公网 IP 数量少于或等于 8 个,可以使用 Amazon NAT Gateway 实现;若需要的公网 IP 数量大于 8 个,则需要更加灵活的方案。

本文将介绍两种方案,用于满足“需要的公网 IP 数量大于 8 个”的场景,并在文章的最后,对这两种方案进行优劣势比较。

 

 

─────◆─────

 

 

a. 架构图

 

 

 

 

b. 环境搭建(执行命令的机器需要有足够的权限)

 

1) 创建网络资源

# 0. 进入screen模式,以保证环境变量不会因为退出Session而消失
screen# 1. 创建VPC
ningxia_vpc_id=$(aws ec2 create-vpc –cidr-block 10.0.0.0/16 –region cn-northwest-1 –query “Vpc.VpcId” –output text)# 2. 创建Subnet
# 在AZ1中创建ningxia_subnet_az1_id1(私有子网1)
ningxia_subnet_az1_id1=$(aws ec2 create-subnet –cidr-block 10.0.0.0/24 –vpc-id $ningxia_vpc_id –region cn-northwest-1 –availability-zone cn-northwest-1a –query “Subnet.SubnetId” –output text)
# 在AZ1中创建ningxia_subnet_az1_id2(公有子网)
ningxia_subnet_az1_id2=$(aws ec2 create-subnet –cidr-block 10.0.1.0/24 –vpc-id $ningxia_vpc_id –region cn-northwest-1 –availability-zone cn-northwest-1a –query “Subnet.SubnetId” –output text)# 3. 创建路由表
# 创建ningxia_route_table_id1(私有子网1路由表)
ningxia_route_table_id1=$(aws ec2 create-route-table –vpc-id $ningxia_vpc_id –region cn-northwest-1 –query “RouteTable.RouteTableId” –output text)
# 创建ningxia_route_table_id2(公有子网路由表)
ningxia_route_table_id2=$(aws ec2 create-route-table –vpc-id $ningxia_vpc_id –region cn-northwest-1 –query “RouteTable.RouteTableId” –output text)# 4. 更改子网路由表
aws ec2 associate-route-table –route-table-id $ningxia_route_table_id1 –subnet-id $ningxia_subnet_az1_id1 –region cn-northwest-1
aws ec2 associate-route-table –route-table-id $ningxia_route_table_id2 –subnet-id $ningxia_subnet_az1_id2 –region cn-northwest-1# 5. 修改安全组
# 获取宁夏的groupid
groupid_ningxia=$(aws ec2 describe-security-groups –region cn-northwest-1 –filter “Name=vpc-id,Values=$ningxia_vpc_id” –query “SecurityGroups[].GroupId” –output text)
# 基于获取的groupid,修改安全组
aws ec2 authorize-security-group-ingress –group-id “$groupid_ningxia” –protocol all –port all –cidr “0.0.0.0/0” –region cn-northwest-1# 6. 创建并附加Internet Gateway
internet_gateway_id_ningxia=$(aws ec2 create-internet-gateway –region cn-northwest-1 –query “InternetGateway.InternetGatewayId” –output text)
aws ec2 attach-internet-gateway –region cn-northwest-1 –internet-gateway-id $internet_gateway_id_ningxia –vpc-id $ningxia_vpc_id# 7. 修改路由表
aws ec2 create-route –destination-cidr-block 0.0.0.0/0 –gateway-id $internet_gateway_id_ningxia –region cn-northwest-1 –route-table-id $ningxia_route_table_id1
aws ec2 create-route –destination-cidr-block 0.0.0.0/0 –gateway-id $internet_gateway_id_ningxia –region cn-northwest-1 –route-table-id $ningxia_route_table_id2

 

 

2) 创建实例资源

# 8. 创建业务服务器1
business_instance_id=$(aws ec2 run-instances –region cn-northwest-1 –image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 –instance-type t3.micro –key-name id_rsa_test.pub –security-group-ids $groupid_ningxia –subnet-id $ningxia_subnet_az1_id1 –associate-public-ip-address –query ‘Instances[].InstanceId’ –output text)
# 等待实例创建完成
aws ec2 wait instance-status-ok –instance-ids $business_instance_id –region cn-northwest-1
# 获得business_instance_ip
business_instance_ip=$(aws ec2 describe-instances –instance-ids “$business_instance_id” –region cn-northwest-1 –query ‘Reservations[0].Instances[0].PublicIpAddress’ –output text)# 9. 创建转发服务器
proxy_instance_id=$(aws ec2 run-instances –region cn-northwest-1 –image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 –instance-type t3.medium –key-name id_rsa_test.pub –security-group-ids $groupid_ningxia –subnet-id $ningxia_subnet_az1_id2 –associate-public-ip-address –query ‘Instances[].InstanceId’ –output text)
# 等待实例创建完成
aws ec2 wait instance-status-ok –instance-ids $proxy_instance_id –region cn-northwest-1
# 获得proxy_instance_ip
proxy_instance_ip=$(aws ec2 describe-instances –instance-ids “$proxy_instance_id” –region cn-northwest-1 –query ‘Reservations[0].Instances[0].PublicIpAddress’ –output text)# 创建好转发服务器,需要修改业务服务器1的路由表,将路由条目0.0.0.0/0的下一跳指向转发服务器的主网卡,这样所有流量都将流向转发服务器。

 

 

 

 

c. 配置转发服务器

 

1) 原理解释

 

iptables 中提供两种模式实现四层负载均衡,分别是 random(随机)和 nth(轮询)。

 

random 模式基于概率实现负载均衡,请参考如下示例说明:

iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 –dport 27017 -m statistic –mode random –probability 0.33 -j DNAT –to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 –dport 27017 -m statistic –mode random –probability 0.5 -j DNAT –to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 –dport 27017 -j DNAT –to-destination 10.0.0.4:1234

 

 

rules 说明:

第一条规则中,指定 –probability 0.33 ,则说明该规则有 33% 的概率会命中。

第二条规则也有 33% 的概率命中,因为规则中指定 –probability 0.5。 则命中的概率为:50% * (1 – 33%)=0.33。

第三条规则中,没有指定 –probability 参数,因此意味着当匹配走到第三条规则时,则一定命中,此时走到第三条规则的概率为:1 – 0.33 -0.33 ≈ 0.33。

由上可见,可以通过修改 –probability 参数调整规则命中率。

 

 

probability 计算规则说明:

假设有 n 个 server,则可以设定 n 条 rule 将流量均分到 n 个 server 上,其中 –probability 参数的值可通过以下公式计算得到:p=1/(n−i+1)

# 其中 i 代表规则的序号(第一条规则的序号为1)

# n 代表规则/server的总数

# p 代表第 i 条规则中 –probability 的参数值

 

nth 模式基于轮询实现负载均衡,请参考如下示例说明:

# every:每n个包匹配一次规则
# packet:从第p个包开始iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 –dport 27017 -m statistic –mode nth –every 1 –packet 0 -j DNAT –to-destination 10.0.0.1:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 –dport 27017 -m statistic –mode nth –every 2 –packet 0 -j DNAT –to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 –dport 27017 -m statistic –mode nth –every 3 –packet 0 -j DNAT –to-destination 10.0.0.3:1234

 

rules 说明:

第一条规则是从第 0 个包开始计算,匹配第 1 个包

第二条规则是从第 0 个包开始计算,匹配第 2 个包

第三条规则是从第 0 个包开始计算,匹配第 3 个包

第 4 个包回到原点,再次重新匹配,以此反复循环

 

 

2) 部署转发服务器(这次实验采用 nth 方式进行)

为了实现多公网 IP 轮询,首先需要转发服务器拥有多个公网 IP,可以将 Elastic IP 与主网卡的多个 Secondary Private IP 进行绑定实现。又由于路由的原因,无法使用 Secondary Network Interface。所以,转发服务器可以拥有多少个公网 IP,将由实例的主网卡可以拥有多少个 Secondary Private IP 决定。这次实验使用的是 t3.medium,此实例类型主网卡可以拥有最多 6 个私网地址,去掉已经使用的 Primary Private IP,总计有 5 个 Secondary Private IP。具体哪种实例可以有多少 IP,请参考如下链接:Elastic network interfaces – Amazon Elastic Compute Cloud

 

# 10. 创建Secondary Private IP并绑定EIP
# 获取主网卡id
network_interface_id=$(aws ec2 describe-instances –region cn-northwest-1 –instance-ids $proxy_instance_id –query “Reservations[].Instances[].NetworkInterfaces[].NetworkInterfaceId” –output text)
# 分配私网地址
private_ip_list=$(aws ec2 assign-private-ip-addresses –region cn-northwest-1 –network-interface-id $network_interface_id –secondary-private-ip-address-count 5 –query “AssignedPrivateIpAddresses[].PrivateIpAddress” –output text)
# 申请并绑定EIP
count=1; for i in $private_ip_list;
do
allocation_id=$(aws ec2 allocate-address –region cn-northwest-1 –query “AllocationId” –output text);
allocation_id_list[$count]=$allocation_id;
association_id=$(aws ec2 associate-address –region cn-northwest-1 –allocation-id $allocation_id –network-interface-id $network_interface_id –private-ip-address $i –output text)
association_id_list[$count]=$association_id
((count++));
done

 

 

创建完 Secondary Private IP 并绑定 EIP 后,下一步就是关闭实例的 source/destination check 以及登录到转发服务器进行 iptables nth 规则的设置。

 

# 关闭source/destination check
aws ec2 modify-instance-attribute –region cn-northwest-1 –no-source-dest-check –instance-id $proxy_instance_id
# 登录转发服务器(以下命令通过root账号执行)
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -I POSTROUTING -m statistic –mode nth –every 1 –packet 0 -j SNAT –to-source 10.0.1.184
iptables -t nat -I POSTROUTING -m statistic –mode nth –every 2 –packet 0 -j SNAT –to-source 10.0.1.177
iptables -t nat -I POSTROUTING -m statistic –mode nth –every 3 –packet 0 -j SNAT –to-source 10.0.1.9
iptables -t nat -I POSTROUTING -m statistic –mode nth –every 4 –packet 0 -j SNAT –to-source 10.0.1.252
iptables -t nat -I POSTROUTING -m statistic –mode nth –every 5 –packet 0 -j SNAT –to-source 10.0.1.46
iptables -t nat -I POSTROUTING -m statistic –mode nth –every 6 –packet 0 -j SNAT –to-source 10.0.1.63
yum install iptables-services -y
service iptables save # 持久化规则

 

 

d. 测试

 

登录到业务服务器 1,通过 curl ip.sb 获取访问公网的 IP 地址,可以看到是以轮训的方式获取到了不同的 IP 地址,而这些 IP 地址就是在转发服务器上绑定的 EIP。

 

 

 

 

 

e. 环境清理

 

下载环境清理:

 

# 删除资源,对应# 10
# 解绑并释放EIP
count=1; for i in $private_ip_list;
do
aws ec2 disassociate-address –association-id ${association_id_list[$count]} –region cn-northwest-1
aws ec2 release-address –allocation-id ${allocation_id_list[$count]} –region cn-northwest-1
((count++));
done
# 删除私网地址
aws ec2 unassign-private-ip-addresses –region cn-northwest-1 –network-interface-id $network_interface_id –private-ip-addresses $private_ip_list# 删除资源,对应# 8, 9
aws ec2 terminate-instances –region cn-northwest-1 –instance-ids $proxy_instance_id
aws ec2 wait instance-terminated –region cn-northwest-1 –instance-ids $proxy_instance_id
aws ec2 terminate-instances –region cn-northwest-1 –instance-ids $business_instance_id
aws ec2 wait instance-terminated –region cn-northwest-1 –instance-ids $business_instance_id# 删除资源,对应#2. 创建Subnet
aws ec2 delete-subnet –region cn-northwest-1 –subnet-id $ningxia_subnet_az1_id1
aws ec2 delete-subnet –region cn-northwest-1 –subnet-id $ningxia_subnet_az1_id2# 删除资源,对应# 3. 创建路由表
for i in $(aws ec2 describe-route-tables –region cn-northwest-1 –filter “Name=vpc-id,Values=$ningxia_vpc_id” –query “RouteTables[].RouteTableId” –output text); do aws ec2 delete-route-table –region cn-northwest-1 –route-table-id $i; done# 删除资源,对应# 6. 创建并附加Internet Gateway
aws ec2 detach-internet-gateway –internet-gateway-id $internet_gateway_id_ningxia –vpc-id $ningxia_vpc_id –region cn-northwest-1
aws ec2 delete-internet-gateway –internet-gateway-id $internet_gateway_id_ningxia –region cn-northwest-1# 删除资源,对应# 1. 创建VPC
aws ec2 delete-vpc –region cn-northwest-1 –vpc-id $ningxia_vpc_id