Create AWS VPC Infrastructure with Cloudformation

Stefano Monti
AWS Infrastructure
Published in
5 min readDec 25, 2022

Prerequisites

You must have an AWS account and aws-cli installed on your machine. Here you can find instructions to satisfy the prerequisites for this tutorial.

At this point, after the initialization, you should have the following folder structure:

The file template.yaml should contain the following lines:

AWSTemplateFormatVersion: 2010-09-09

Description: STW network template

VPC Resources

Let’s start adding all the AWS resources needed to provide a VPC!!

From now on every resource must be declared inside the resource block:

Resources:

First of all, maybe could look obvious, but the first resource to create is a vpc 😅

#============ VPC =============

STWVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: STW vpc

Now we’ve created the container for all the network resources we are going to provide.

Next, let’s create the internet gateway and internet gateway attachment for permitting the communication with public internet and link it to the vpc already created.

#============ INTERNET GATEWAY =============

STWInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: STW

STWVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref STWInternetGateway
VpcId: !Ref STWVpc

Time for creating subnets!! 🤩

We will create different subnets spread over 2 different availability zones (if you don’t know what an availability zone is, check this).


#============ SUBNETS =============

STWSubnetPublic1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.101.0/24
MapPublicIpOnLaunch: true
VpcId: !Ref STWVpc
AvailabilityZone: "eu-west-1a"
Tags:
- Key: Name
Value: STW

STWSubnetPublic2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.102.0/24
MapPublicIpOnLaunch: true
VpcId: !Ref STWVpc
AvailabilityZone: "eu-west-1b"
Tags:
- Key: Name
Value: STW

STWSubnetPrivate1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: false
VpcId: !Ref STWVpc
AvailabilityZone: "eu-west-1a"
Tags:
- Key: Name
Value: STW

STWSubnetPrivate2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: false
VpcId: !Ref STWVpc
AvailabilityZone: "eu-west-1b"
Tags:
- Key: Name
Value: STW

As you can see we have declared 4 resources of type AWS::EC2::Subnet in the region eu-west-1 (Ireland, the same region where has been declared also the vpc) across 2 different AZ (eu-west-1a and eu-west-1b).

Now we have created all the subnets where our computing resources will be placed but, something is missing 🧐. For example, how can we instruct our system to route the traffic based on the requested address? Here come routing tables!

Let’s begin with the public subnets!

We will create a route table and routes with the following rules:
— if a request is directed to the vpc cidr (10.0.0.0/16) forward it inside the vpc (is implicit in the resource definition);
— if a request is directed to any other address (0.0.0.0/0) forward it to the internet gateway in order to permit the request to reach the public internet.

  STWRouteTablePublic:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref STWVpc
Tags:
- Key: Name
Value: STW

STWVPCRoutePublic1:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref STWInternetGateway
RouteTableId: !Ref STWRouteTablePublic

Now is the time to assign the created route table to the public subnets with the terraform resource called aws_route_table_association.

  STWSubnetRouteTableAssociationPublic1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref STWRouteTablePublic
SubnetId: !Ref STWSubnetPublic1

STWSubnetRouteTableAssociationPublic2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref STWRouteTablePublic
SubnetId: !Ref STWSubnetPublic2

Ok, the public subnets are done! Unfortunately is not possible to do the same thing with the private subnets because the ec2 won’t have a public ip and so they are not able to make public internet request via the internet gateway 😓.
The solution is to provide a nat gateway with a public ip associated 🤯.
The aim of the nat gateway is to forward every request made by any resource inside the private network to the internet using his public ip.
Obviously, the nat gateway must be provisioned inside a public subnet; the result will be that a machine inside a private subnet can communicate with the internet but it will not be available outside of the vpc (this could be useful for databases).

#============ NAT GATEWAY =============

STWNatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt STWEIP.AllocationId
ConnectivityType: public
SubnetId: !GetAtt STWSubnetPublic1.SubnetId
Tags:
- Key: Name
Value: STW

STWEIP:
Type: AWS::EC2::EIP
Properties:
Tags:
- Key: Name
Value: STW

Similarly to the public subnet, we must instruct our resources inside the private subnets to route the requests based on the target:
— if a request is directed to the vpc cidr (10.0.0.0/16) forward it inside the vpc (is implicit in the resource definition);
— if a request is directed to any other address (0.0.0.0/0) forward it to the nat gateway in order to permit the request to reach the public internet via the internet gateway (the public route table will be used).
The route table must be associated with the private subnets.

  STWRouteTablePrivate:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref STWVpc
Tags:
- Key: Name
Value: STW

STWVPCRoutePrivate1:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref STWNatGateway
RouteTableId: !Ref STWRouteTablePrivate

STWSubnetRouteTableAssociationPrivate1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref STWRouteTablePrivate
SubnetId: !Ref STWSubnetPrivate1

STWSubnetRouteTableAssociationPrivate2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref STWRouteTablePrivate
SubnetId: !Ref STWSubnetPrivate2

You can find the final version of this template on my GitHub profile at this link: cloudformation-infrastructure-aws-vpc.

Let’s deploy the template!!

In order to deploy the declared resources, you must run the following command for the first upload:

$ aws cloudformation create-stack --template-body file://template.yaml --stack-name stw-vpc

Remember to add the parameter --profile <profile name> in case your profile name is different from default.

You can follow all the creation events in the cloudformation dashboard inside your AWS console.

if you need to update an already deployed template you must run the following command:

$ aws cloudformation update-stack --template-body file://template.yaml --stack-name stw-vpc

If you want to destroy all resources:

$ aws cloudformation delete-stack --stack-name stw-vpc

Bye 😘

--

--