Adding private subnets

Now we need to add two private subnets under this VPC:

"PrivateSubnet1": { 
  "Type": "AWS::EC2::Subnet", 
    "Properties": { 
    "VpcId": { 
      "Ref": "VPC" 
    }, 
    "CidrBlock": "10.0.1.0/24", 
    "AvailabilityZone": { 
      "Fn::Select": [ "0", 
        { 
          "Fn::GetAZs": { 
            "Ref": "AWS::Region" 
          } 
        } 
      ] 
    }, 
    "Tags": [ 
      { 
        "Key": "Name", 
        "Value": { 
          "Fn::Sub": "${AWS::StackName}: Private Subnet 1" 
         } 
      } 
    ] 
  } 
}, 
"PrivateSubnet2": { 
  "Type": "AWS::EC2::Subnet", 
  "Properties": { 
    "VpcId": { 
      "Ref": "VPC" 
    }, 
    "CidrBlock": "10.0.3.0/24", 
    "AvailabilityZone": { 
      "Fn::Select": [ 
        "1", 
        { 
          "Fn::GetAZs": { 
            "Ref": "AWS::Region" 
          } 
        } 
      ] 
    }, 
    "Tags": [ 
      { 
        "Key": "Name", 
        "Value": { 
          "Fn::Sub": "${AWS::StackName}: Private Subnet 2" 
        } 
      } 
    ] 
  } 
} 

Here we tell the VPC to create subnets. For the first subnet, we select a CIDR block that starts from 10.0.1.0 and ends at 10.0.1.255. For the second one we select a block from 10.0.3.0 to 10.0.3.255. Each subnet has 256 available IP addresses.

Note the usage of the Fn::GetAZs method. For both subnets, we get the first and second availability zones of the region, so we distribute our IP pool among two different availability zones.

Although availability zones are physical zones, they are independently mapped identifiers for each account. For example, your Availability Zone us-east-1a might not be the same location as us-east-1a for another account. So for every account, the first and second availability zones will be different. Here, we only create two subnets because every region has a minimum two availability zones. If you are sure that you are installing your stack in a region where more than two availability zones exist, you can increase the number of subnets.

With this configuration, we ensure that our Lambda functions, when we configure them to operate in VPC, will get one of those IP addresses automatically. On the other hand, we have to remember that we have a important limit: as our available IP address count is 512 (256 for each availability zone). Our maximum Lambda execution count is also limited to 512. To increase this number, you have to adjust the subnets so they have sufficient IP addresses for parallel executions.

After the private subnets, we also create two public subnets. While Lambda functions are in private subnets, NAT Gateways will be placed in public subnets. Public subnets are also routed to the internet, so any resource that you place in a public subnet, can also have a internet IP address and thus access to outer Internet. Moreover, they are also able to connect to any subnet in the same VPC. It means, when we place NAT Gateway into a public gateway, they can access internet and Lambda functions. That's why NAT Gateways are able to act as a NAT device and provide internet connectivity to Lambda functions while Lambda functions are not accessible from outside internet.

Let's now create public subnets. Actually their configuration is same as private subnet, but they are routed to Internet Gateway, which makes them public subnet. First, let's create them. After that we will see how to connect them to internet:

"PublicSubnet1": { 
  "Type": "AWS::EC2::Subnet", 
    "Properties": { 
      "VpcId": { 
        "Ref": "VPC" 
      }, 
      "CidrBlock": "10.0.0.0/24", 
      "AvailabilityZone": { 
        "Fn::Select": [ 
          "0", 
          { 
            "Fn::GetAZs": { 
              "Ref": "AWS::Region" 
            } 
          } 
        ] 
      }, 
      "Tags": [ 
        { 
          "Key": "Name", 
          "Value": { 
            "Fn::Sub": "${AWS::StackName}: Public Subnet 1" 
          } 
        } 
      ] 
    } 
  }, 
  "PublicSubnet2": { 
    "Type": "AWS::EC2::Subnet", 
    "Properties": { 
      "VpcId": { 
        "Ref": "VPC" 
      }, 
      "CidrBlock": "10.0.2.0/24", 
      "AvailabilityZone": { 
        "Fn::Select": [ 
          "1", 
          { 
            "Fn::GetAZs": { 
              "Ref": "AWS::Region" 
            } 
          } 
        ] 
      }, 
      "Tags": [ 
        { 
          "Key": "Name", 
          "Value": { 
            "Fn::Sub": "${AWS::StackName}: Public Subnet 2 " 
          } 
        } 
     ] 
   } 
} 

Before we deal with public subnets, let's create two route tables for private subnets and associate them with private subnets:

"PrivateRouteTable1": { 
  "Type": "AWS::EC2::RouteTable", 
  "Properties": { 
    "VpcId": { 
      "Ref": "VPC" 
    }, 
    "Tags": [ 
      { 
        "Key": "Name", 
        "Value": { 
          "Fn::Sub": "${AWS::StackName}: Route Table for Private Subnet 1 " 
        } 
      } 
    ] 
  } 
}, "PrivateRouteTable2": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" }, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${AWS::StackName}: Route Table for Private Subnet 2" } } ] } }, "PrivateSubnetRouteTableAssociation1": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "PrivateSubnet1" }, "RouteTableId": { "Ref": "PrivateRouteTable1" } } }, "PrivateSubnetRouteTableAssociation2": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "PrivateSubnet2" }, "RouteTableId": { "Ref": "PrivateRouteTable2" } } }

The syntax here is not very complicated. Route tables are rule tables for routing a subnet. When we create our NAT Gateways, we will add rules to these tables, so any outgoing traffic originating from these subnets are routed to NAT Gateways.

In the same way, let's create a single route table for both public subnets and associate subnets with it in the same way:

"PublicRouteTable": { 
  "Type": "AWS::EC2::RouteTable", 
    "Properties": { 
      "VpcId": { 
        "Ref": "VPC" 
      }, 
      "Tags": [ 
        { 
          "Key": "Name", 
          "Value": { 
            "Fn::Sub": "${AWS::StackName}: Route Table for Public Subnet" 
          } 
        } 
      ] 
    } 
}, 
"PublicSubnetRouteTableAssociation1": { 
  "Type": "AWS::EC2::SubnetRouteTableAssociation", 
  "Properties": { 
    "SubnetId": { 
      "Ref": "PublicSubnet1" 
    }, 
    "RouteTableId": { 
      "Ref": "PublicRouteTable" 
    } 
  } 
}, 
"PublicSubnetRouteTableAssociation2": { 
  "Type": "AWS::EC2::SubnetRouteTableAssociation", 
  "Properties": { 
    "SubnetId": { 
      "Ref": "PublicSubnet2" 
    }, 
    "RouteTableId": { 
      "Ref": "PublicRouteTable" 
    } 
  } 
},  

We did not need two route tables here because in this table we will only have one rule, which routes the outgoing traffic to Internet Gateway.

Port traffic in VPC is controlled via Access Control Lists (ACLs). With ACL, you can limit the network traffic at a lower level, without having to deal with security groups. In our case, we will open all the traffic for all ports.

Now we create a ACL and associate it with public subnets:

"PublicNetworkAcl": { 
  "Type": "AWS::EC2::NetworkAcl", 
  "Properties": { 
    "VpcId": { 
      "Ref": "VPC" 
    }, 
    "Tags": [ 
      { 
        "Key": "Name", 
        "Value": { 
          "Fn::Sub": "${AWS::StackName}: Public Network ACL" 
        } 
      } 
    ] 
  } 
}, 
 
"PublicSubnetNetworkAclAssociation1": { 
  "Type": "AWS::EC2::SubnetNetworkAclAssociation", 
  "Properties": { 
    "SubnetId": { 
      "Ref": "PublicSubnet1" 
    }, 
    "NetworkAclId": { 
     "Ref": "PublicNetworkAcl" 
    } 
  } 
}, 
"PublicSubnetNetworkAclAssociation2": { 
  "Type": "AWS::EC2::SubnetNetworkAclAssociation", 
  "Properties": { 
    "SubnetId": { 
      "Ref": "PublicSubnet2" 
    }, 
    "NetworkAclId": { 
      "Ref": "PublicNetworkAcl" 
    } 
  } 
}