Using ROS to Build an SNAT Gateway for VPC Internet Access
Background
In a Virtual Private Cloud (VPC) environment, Elastic Compute Service (ECS) instances cannot directly access the public internet. One method is to assign Elastic IP addresses (EIPs) to ECS instances that need internet access. However, this approach is not feasible when a large number of ECS instances require simultaneous internet access. This article introduces an alternative method using an SNAT gateway, as shown in the diagram below:
::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::0
This method requires provisioning an additional ECS instance bound to an EIP to serve as the SNAT gateway for the VPC network. First, this ECS instance must reside within the specified VPC and have an EIP attached. Next, configure iptables on this ECS instance. Finally, add a route entry in the VPC’s routing table with the next hop pointing to this ECS instance. For detailed manual configuration steps, please refer to here.
Introduction to ROS Resources
The following section explains how to automate the creation and configuration of a VPC SNAT gateway using Resource Orchestration Service (ROS). The template utilizes the following ROS resources:
- InternetChargeType: Billing method, either PayByBandwidth or PayByTraffic.
- Bandwidth: Value range [1–200], unit is Mbps.
Creating an EIP resource.
- AllocationId: The ID of the allocated EIP.
- InstanceId: The ID of the ECS instance to which the EIP will be bound.
This resource binds the EIP to the specified ECS instance.
- RouteId: The ID of the router in the specified VPC.
- RouteTableId: The ID of the route table in the specified VPC.
- DestinationCidrBlock: The destination CIDR block for the route entry.
- NextHopId: The next hop for the route entry.
This resource adds a route to the specified VPC’s route table, with the next hop pointing to the SNAT gateway.
- In this example, the UserData property is primarily used. By specifying a script via UserData, iptables are automatically configured when the ECS instance starts.
This resource creates the SNAT gateway.
Writing the ROS Template
In the template, we first define that the stack requires an EIP resource and one ECS instance, binding the EIP to the ECS resource. Finally, by executing the UserData script passed to the ECS, iptables are automatically configured, and a route entry pointing to the ECS is added to the VPC router’s route table. The final ROS template is as follows:
"ROSTemplateFormatVersion": "2015-09-01",
"Description": "One-click creation of SNAT gateway",
"Parameters": {
"SecurityGroupId": {
"Description": "Security Group ID",
"Type": "String"
},
"RouteTableId": {
"Description": "Router ID in the VPC",
"Type": "String"
},
"RouteId": {
"Description": "Router ID in the VPC",
"Type": "String"
},
"EIPBandwidth": {
"Default": 5,
"MinValue": 1,
"Description": "Rate limit for the Elastic Public IP, default is 5Mbps.",
"Type": "Number",
"MaxValue": 200
},
"ECSZoneId": {
"Description": "Availability Zone, <a href='#/product/cn-shenzhen/list/zoneList' target='_blank'>View Availability Zones</a>",
"Type": "String"
},
"ECSPassword": {
"NoEcho": true,
"MaxLength": 30,
"Description": "Instance password",
"Type": "String",
"ConstraintDescription": "8-30 characters, must include uppercase letters, lowercase letters, numbers, and special symbols.",
"MinLength": 8
},
"VSwitchId": {
"Type": "String"
},
"VpcId": {
"Type": "String"
},
"VSwitchCidrBlock": {
"Description": "The VSwitch CIDR block accessing the internet via the SNAT gateway. This block must belong to the VPC.",
"Type": "String"
},
"EIPInternetChargeType": {
"Default": "PayByTraffic",
"AllowedValues": [
"PayByBandwidth",
"PayByTraffic"
],
"Description": "Billing type for the Elastic Public IP. PayByBandwidth (fixed bandwidth billing), PayByTraffic (traffic-based billing). Fixed bandwidth is billed daily, while traffic-based is billed hourly.",
"Type": "String"
},
"ECSSysDiskSize": {
"Default": 40,
"MinValue": 40,
"Description": "System disk size, between 40-500 GB",
"Type": "Number",
"MaxValue": 500
},
"ECSDiskCategory": {
"Default": "cloud_efficiency",
"AllowedValues": [
"cloud",
"cloud_efficiency",
"cloud_ssd"
],
"Description": "System disk category: Standard Cloud Disk (cloud), Efficient Cloud Disk (cloud_efficiency), or SSD Cloud Disk (cloud_ssd).",
"Type": "String"
},
"ECSInstanceType": {
"Default": "ecs.s2.large",
"AllowedValues": [
"ecs.t1.small",
"ecs.s1.small",
"ecs.s1.medium",
"ecs.s1.large",
"ecs.s2.small",
"ecs.s2.large",
"ecs.s2.xlarge",
"ecs.s2.2xlarge",
"ecs.s3.medium",
"ecs.s3.large",
"ecs.m1.medium",
"ecs.m2.medium",
"ecs.m1.xlarge",
"ecs.c1.small",
"ecs.c1.large",
"ecs.c2.medium",
"ecs.c2.large",
"ecs.c2.xlarge",
"ecs.n1.tiny",
"ecs.n1.small",
"ecs.n1.medium",
"ecs.n1.large",
"ecs.n1.xlarge"
],
"Description": "Instance type, <a href='#/product/cn-shenzhen/list/typeList' target='_blank'>View Instance Types</a>",
"Type": "String"
},
"ECSImageId": {
"Default": "ubuntu1404_64_40G_cloudinit_20160427.raw",
"Description": "Image ID, representing the image resource selected when launching the instance, <a href='#/product/cn-shenzhen/list/imageList' target='_blank'>View Images</a>",
"Type": "String"
},
"ECSTag": {
"Description": "Tag for the ECS instance",
"Type": "String"
}
},
"Resources": {
"ElasticIpAssociation": {
"Type": "ALIYUN::ECS::EIPAssociation",
"Properties": {
"InstanceId": {
"Fn::Select": [
"0",
{
"Fn::GetAtt": [
"ECSSnatGateWay",
"InstanceIds"
]
}
]
},
"AllocationId": {
"Fn::GetAtt": [
"ElasticIp",
"AllocationId"
]
}
}
},
"VRoute": {
"Type": "ALIYUN::ECS::Route",
"Properties": {
"NextHopId": {
"Fn::Select": [
"0",
{
"Fn::GetAtt": [
"ECSSnatGateWay",
"InstanceIds"
]
}
]
},
"RouteId": {
"Ref": "RouteId"
},
"RouteTableId": {
"Ref": "RouteTableId"
},
"DestinationCidrBlock": "0.0.0.0/0"
}
},
"ECSSnatGateWay": {
"Type": "ALIYUN::ECS::InstanceGroup",
"Properties": {
"SecurityGroupId": {
"Ref": "SecurityGroupId"
},
"ImageId": {
"Ref": "ECSImageId"
},
"Password": {
"Ref": "ECSPassword"
},
"MinAmount": "1",
"InternetMaxBandwidthIn": 100,
"UserData": {
"Fn::Replace": [
{
"ros-notify": {
"Fn::GetAtt": [
"ECSSnatGateWayConditionHandle",
"CurlCli"
]
}
},
{
"Fn::Join": [
"",
[
"#!/bin/sh",
"n",
"PostRouting=",
{
"Ref": "VSwitchCidrBlock"
},
"n",
"SourceRouting=`ifconfig eth0|grep inet|awk '{print $2}'|tr -d 'addr:'`",
"n",
"echo 'net.ipv4.ip_forward=1'>> /etc/sysctl.conf n",
"sysctl -p n",
"iptables -t nat -I POSTROUTING -s $PostRouting -j SNAT --to-source $SourceRouting n",
"apt-get updaten",
"apt-get install -y curln",
"ros-notify -d '{"data" : " docker swarm created"}'n"
]
]
}
]
},
"ZoneId": {
"Ref": "ECSZoneId"
},
"VSwitchId": {
"Ref": "VSwitchId"
},
"InternetChargeType": "PayByTraffic",
"VpcId": {
"Ref": "VpcId"
},
"InstanceType": {
"Ref": "ECSInstanceType"
},
"SystemDisk_Category": {
"Ref": "ECSDiskCategory"
},
"IoOptimized": "optimized",
"Tags": [
{
"Value": {
"Ref": "ECSTag"
},
"Key": "ECS_SNAT_GATEWAY"
}
],
"InternetMaxBandwidthOut": 100,
"SystemDisk_Size": {
"Ref": "ECSSysDiskSize"
},
"MaxAmount": "1"
}
},
"ECSSnatGateWayConditionHandle": {
"Type": "ALIYUN::ROS::WaitConditionHandle"
},
"ECSSnatGateWayGroupWaitCondition": {
"Type": "ALIYUN::ROS::WaitCondition",
"DependsOn": "ECSSnatGateWay",
"Properties": {
"Handle": {
"Ref": "ECSSnatGateWayConditionHandle"
},
"Timeout": 600,
"Count": 1
}
},
"ElasticIp": {
"Type": "ALIYUN::ECS::EIP",
"Properties": {
"InternetChargeType": {
"Ref": "EIPInternetChargeType"
},
"Bandwidth": {
"Ref": "EIPBandwidth"
}
}
}
},
"Outputs": {
"ECSSnatGateWay_INNER_IPS": {
"Value": {
"Fn::GetAtt": [
"ECSSnatGateWay",
"PrivateIps"
]
},
"Description": "Inner IP address of the ECS instance."
},
"ECSSnatGateWay_InstanceIds": {
"Value": {
"Fn::GetAtt": [
"ECSSnatGateWay",
"InstanceIds"
]
},
"Description": "The instance ID of the created ECS instance"
},
"EipAddress": {
"Value": {
"Fn::GetAtt": [
"ElasticIp",
"EipAddress"
]
},
"Description": "IP address of the created EIP."
},
"ECSSnatGateWay_ZoneIds": {
"Value": {
"Fn::GetAtt": [
"ECSSnatGateWay",
"ZoneIds"
]
},
"Description": "Zone ID of the created instance."
}
}
}
The previous article summarized building an SNAT gateway using ROS to enable VPC internet access. The main idea was to create an ECS instance within the VPC to act as a NAT gateway, thereby enabling internet access for the VPC. To be honest, this method is relatively complex. Today, we introduce a new ROS resource: NAT Gateway. With it, you can easily achieve mutual access between machines in the VPC and the internet.
NAT Gateway Introduction
The NAT Gateway is an enterprise-level VPC public internet gateway that provides NAT proxying (SNAT, DNAT), forwarding capabilities at the 10 Gbps level, and high availability at the Region level (cross-Availability Zone disaster recovery).
A Shared Bandwidth Package encapsulates the public IP and public bandwidth on the NAT Gateway. A shared bandwidth package consists of a single public bandwidth pool and a set of public IPs that share this bandwidth.
NAT Gateways and Shared Bandwidth Packages must be used together to form a high-performance, flexible enterprise-grade gateway.
The topology of NAT Gateway in a VPC is shown below:
::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::1
Key Features:
DNAT: Destination Network Address Translation (inbound proxy), used for ECS instances within the VPC to provide services to the internet; supports port mapping and IP mapping.
SNAT: Source Network Address Translation (outbound proxy), used for ECS instances within the VPC to access internet content.
Multi-IP Shared Bandwidth: When purchasing public IPs and public bandwidth on the NAT Gateway, they are purchased in the form of a “Shared Bandwidth Package,” where all IPs within a bandwidth package share the bandwidth.
High Performance: NAT Gateways can support forwarding capabilities at the 10 Gbps level per instance.
High Availability: Based on SDN technology, NAT Gateways use a cross-Availability Zone distributed architecture, ensuring each instance can withstand Availability Zone-level failures.
Pay-as-you-go: All fees are billed after usage. Instance specifications, bandwidth size, and the number of public IPs can be adjusted at any time and take effect immediately.
Introduction to ROS NAT Gateway Related Resources
ALIYUN::ECS::NatGateway
This resource declares the creation of a NAT Gateway within a specific VPC. When creating a NAT Gateway, you must specify the VPC ID, VSwitch ID, and at least one bandwidth package. After successful creation, the resource automatically adds a route entry pointing to the NAT Gateway in the VPC’s routing table. Through ROS, you can output the NAT Gateway ID, all bandwidth package IDs, all public IPs declared in all bandwidth packages, Forward Table ID, and SNAT Table ID.
"NatGateway": {
"DependsOn": "VSwitch",
"Properties": {
"BandwidthPackage": [
{
"Bandwidth": 5,
"IpCount": 1,
"ZoneId": "cn-hangzhou-b"
},
{
"Bandwidth": 5,
"IpCount": 1,
"ZoneId": "cn-hangzhou-b"
}
],
"Description": "hhhhh",
"NatGatewayName": "nat_gateway_1",
"Spec": "Middle",
"VSwitchId": {
"Fn::GetAtt": [
"VSwitch",
"VSwitchId"
]
},
"VpcId": {
"Fn::GetAtt": [
"Vpc",
"VpcId"
]
}
},
"Type": "ALIYUN::ECS::NatGateway"
}
ALIYUN::ECS::BandwidthPackage
A bandwidth package can also be created as a standalone resource and then associated with a specific NAT Gateway. Creating a bandwidth package requires specifying the NAT Gateway ID, bandwidth size, and the number of public IPs required.
After resource creation, ROS can output the bandwidth package ID and all IPs applied for in the bandwidth package.
BandwidthPackage": {
"Type": "ALIYUN::ECS::BandwidthPackage",
"Properties": {
"BandwidthPackageName": "pkg_2",
"Description": "new bandwidth package",
"NatGatewayId": "ngw-h1xoxepyf",
"IpCount": 2,
"Bandwidth": 5,
"ZoneId": "cn-beijing-c"
}
}
ALIYUN::ECS::ForwardEntry
This resource adds access rules to the NAT Gateway’s Destination Network Address Translation table. These rules allow external networks to actively access machines within the VPC. Creating a rule requires passing the Forward Table ID, network protocol, internal machine IP, internal machine port, and the external IP and port exposed for access. The external IP refers to the IP resources applied for in the bandwidth package.
After resource creation, ROS can output the ID of this rule.
The following example demonstrates how to configure external access to a machine in the VPC via port 22.
"ForwardEntry": {
"Properties": {
"ExternalIp": {
"Fn::Select": [
"1",
{
"Fn::GetAtt": [
"NatGateway",
"BandwidthPackageIps"
]
}
]
},
"ExternalPort": "22",
"ForwardTableId": {
"Fn::GetAtt": [
"NatGateway",
"ForwardTableId"
]
},
"InternalIp": "192.168.0.1",
"InternalPort": "22",
"IpProtocol": "TCP"
},
"Type": "ALIYUN::ECS::ForwardEntry"
}
ALIYUN::ECS::SNatEntry
This resource configures a rule in the Source Network Address Translation table, allowing machines in the VPC to actively access the external network. Creating a rule requires passing the SNAT Table ID, the VSwitch ID of the VPC machines that will access the internet, and the external IP used for access. This IP is also an IP resource applied for in the bandwidth package. If both active and passive internet access are required, at least two external IPs are needed. That is, the public IPs for the SNAT and Forward tables cannot be the same.
"SNatEntry": {
"Properties": {
"SNatIp": {
"Fn::Select": [
"0",
{
"Fn::GetAtt": [
"NatGateway",
"BandwidthPackageIps"
]
}
]
},
"SNatTableId": {
"Fn::GetAtt": [
"NatGateway",
"SNatTableId"
]
},
"SourceVSwitchId": {
"Fn::GetAtt": [
"VSwitch",
"VSwitchId"
]
}
}
The following is a complete template for creating a VPC network, configuring a NAT Gateway, and enabling mutual access between ECS instances in the VPC and the internet. Download the complete template ->>