AWS CloudFormation, an Infrastructure as Code service, includes a template made up of nine sections. Although made up of nine sections, the Resources section is the only one required. For this project we will be using Mappings, Resources, and Outputs.

Mappings

The Mappings section is basically a lookup table using key: value relationships. In this template the Amazon Machine Image is being mapped to its respective Region. For this project, I am using only have two regions mapped so the template is restricted to these two regions and would fail if launched outside of these regions.

Mappings:
  RegionMap:
    us-east-1:
     "AMI": "ami-04902260ca3d33422"
    us-west-2:
     "AMI": "ami-0142f6ace1c558c7d"

Resources

The Resources section includes all the AWS resources that you want to create in the stack.

  1. For VPC, the template is assigning a CIDR of 10.0.0.0/16 and a tag of Name: LUIT Project.
  2. For InternetGateway, the template is creating the IGW and assigning a tag of Name: LUIT Project.
  3. For InternetGatewayAttachment, the template is attaching the IGW to the VPC. This is the first time in the template that we are using the intrinsic function Ref, which returns the value of the specified parameter or resource.
Resources:
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16 
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
        - Key: Name
          Value: LUIT Project

  InternetGateway:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: LUIT Project

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

4. For PublicSubnet1, the template is creating a public subnet.

  • For VpdId it’s using the Ref function to reference the VPC.
  • For AvailabilityZone it’s using the Select function to select a single object from a list. In this case it is returning the first Availability Zone using the GetAZ function.
  • CidrBlock assigns this subnet a CIDR of 10.0.1.0/24
  • MapPublicIpOnLaunch assigns a public IP on launch when set to true
  • Tags assigns a tag to this subnet.

5. For PublicSubnet2, the template everything is similar to PublicSubnet1 except for:

  • AvailabilityZone the template is now selecting the second AZ using the GetAZ function.
  • CidrBlock is set to 10.0.2.0/24
  • Tags

6. PrivateSubnet1 & PrivateSubnet2 follow the same structure as the public subnets with some differences.

  • AvailabilityZones will match respectively with their public counterparts. This ensures we have a public and private subnet in the same AZ.
  • CidrBlock will be different to ensure the subnets don’t overlap. I’ve chosen to use 10.0.11.0/24 and 10.0.12.0/24 to clearly differentiate the public subnets CIDR from the Private CIDR.
  • MapPublicIpOnLaunch is set to false. Since these are private subnets we don’t want to assign a public IP.
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: LUIT Project Public Subnet (AZ1)

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs  '' ]
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: LUIT Project Public Subnet (AZ2)

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: 10.0.11.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: LUIT Project Private Subnet (AZ1)

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs  '' ]
      CidrBlock: 10.0.12.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: LUIT Project Private Subnet (AZ2)

7. For PublicRouteTable, the template is creating a public route table named LUIT Project Public Routes referencing VPC for the VpcId.

8. For DefaultPublicRoute, the template is creating a route to the Internet Gateway for 0.0.0.0/0.

  • This is the first time in the template we use the DependsOn attribute. This specifies that the resource should follow the creation of another. In this case, the DefaultPublicRoute should follow the creation of the PublicRouteTable.
  • RouteTableId references the PublicRouteTable.
  • The GatewayId references the InternetGateway.

9. For PublicSubnet1RouteTableAssociation, the template is associating PublicSubnet1 to the PublicRouteTable.

10. For PublicSubnet2RouteTableAssociation, the template is associating PublicSubnet2 to the PublicRouteTable.

You’ll notice that the template has not created a default route table or associated the two private subnets. This is because a default route table is created with the VPC and all subnets are automatically associated unless otherwise specified. For this reason we don’t need to add them to the template since its the default.

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: LUIT Project Public Routes

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

11. For WebserverSecurityGroup, the template is creating a security group with an inbound rule allowing all traffic on HTTP.

  WebServerSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable HTTP from 0.0.0.0/0
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0
      VpcId: !Ref VPC

12. For EC2Instance, the template is creating a single EC2 Instance with the following properties:

  • ImageID: You could directly put in the AMI-Id for this property, but since we want the ability to use the template in two regions we use the FindInMap function to reference our Mappings.
  • UserData: This section is used to bootstrap an instance. This particular template is running an update, installing an Apache web server and creating a simple web page. It uses the Sub function to update the AWS::Region pseudo parameter for the actual region the stack is created in. It uses the Base64 function to pass encoded data to EC2 instances.
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
      InstanceType: t2.micro
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds: 
        - !Ref WebServerSecurityGroup
      Tags:
       - Key: Name
         Value: LUIT Project EC2     
      UserData:
        Fn::Base64: !Sub |
            #!/bin/bash
            yum install httpd -y
            service httpd start  
            echo "<html><body><h1>Hello Cloud Gurus from Region ${AWS::Region}<h1></body></html>" > /var/www/html/index.html

Outputs

The Outputs section prints information to the Outputs tab in CloudFormation after the stack is created and can also be used to import information into other stacks.

Outputs:
  PublicIp:
    Description: EC2 Instance Public Ip
    Value: !GetAtt EC2Instance.PublicIp    

Uploading file to CloudFormation:

  1. Login to AWS and navigate to CloudFormation.
  2. Select Create stack and then select ‘Create template in Designer’ option.

3. Upload your template code in the space provide using yaml or json format and validate for any error. If every thing is okay you will be navigated to create your stack name and keep following the steps by leaving everything as default. You should end up in the review stage as shown below.

4. Next step in to click on create stack button and you should see as below.

5. Final result is to check webpage view:

Thank you for reading the article. I recommend you to review the AWS CloudFormation User Guide. This resource has been crucial for me while diving into CloudFormation.

Complete CloudFormation Template

The full source code on Github link.

AWSTemplateFormatVersion: 2010-09-09
Description: >-
  This template deploys a VPC in either us-east1 or us-west-2, with a pair of public and private subnets spread
  across two Availability Zones. It deploys an internet gateway, with a default
  route on the public subnets, a Linux t2.micro Instance with Security Group with open HTTP access. 
Mappings:
  RegionMap:
    us-east-1:
     "AMI": "ami-04902260ca3d33422"
    us-west-2:
     "AMI": "ami-0142f6ace1c558c7d"
Resources:
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16 
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
        - Key: Name
          Value: LUIT Project

  InternetGateway:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: LUIT Project

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: LUIT Project Public Subnet (AZ1)

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs  '' ]
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: LUIT Project Public Subnet (AZ2)

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: 10.0.11.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: LUIT Project Private Subnet (AZ1)

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs  '' ]
      CidrBlock: 10.0.12.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: LUIT Project Private Subnet (AZ2)

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: LUIT Project Public Routes

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2
      
  WebServerSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable HTTP from 0.0.0.0/0
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0
      VpcId: !Ref VPC

  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
      InstanceType: t2.micro
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds: 
        - !Ref WebServerSecurityGroup
      Tags:
       - Key: Name
         Value: LUIT Project EC2     
      UserData:
        Fn::Base64: !Sub |
            #!/bin/bash
            yum install httpd -y
            service httpd start  
            echo "<html><body><h1>Hello Cloud Gurus from Region ${AWS::Region}<h1></body></html>" > /var/www/html/index.html
Outputs:
  PublicIp:
    Description: EC2 Instance Public Ip
    Value: !GetAtt EC2Instance.PublicIp

Leave a Reply

Your email address will not be published. Required fields are marked *