Launching an AWS EC2 instance using CloudFormation Template
AWS CloudFormation is a service which gives us the flexibility to manage and provision our AWS resources. It gives us the option to choose sample templates or to design our custom templates to launch and provision the resources. It also gives the option of AWS CloudFormation Designer using which the templates are visualized.
I have come across a scenario where I have to set up reproducible staging environment for a project. The environment consists of an EC2 instance which is in public subnet. The AWS CloudFormation template has been designed to achieve launch this reproducible environment. The following is the stack which is launched and configured by the CloudFormation template:
- Custom VPC
- Public subnet in that VPC
- Internet Gateway
- Route table
- Network ACL Rules
- Security Group Rules
- EC2 instances
The following is the architecture diagram of the staging environment:
The following is the description of CloudFormation template which I am using:
{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "TTND AWS CloudFormation template to launch first instance", "Parameters" : { "KeyName" : { "Description" : "EC2 Key Pair for SSH Access "Default" : "sample", "MinLength": "1", "MaxLength": "64", "AllowedPattern" : "[-_ a-zA-Z0-9]*", "ConstraintDescription" : "can contain only alphanumeric characters, spaces, dashes and underscores." }, "InstanceType" : { "Description" : "Instance1 EC2 instance type", "Type" : "String", "Default" : "c4.xlarge", "AllowedValues" : [ "t2.micro","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","m3.medium","m3.xlarge","c4.xlarge","c4.4xlarge","m3.2xlarge","c1.medium","c1.xlarge","cc1.4xlarge","c4.large","cc2.8xlarge","cg1.4xlarge"], "ConstraintDescription" : "must be a valid EC2 instance type." }, },
Here, in the above block, version is defined, and description is given of the AWS CloudFormation template. Three parameters are taken which consist of EC2 key pair to launch the instance, Instance1 EC2 type, Instance2 EC2 type:
"Mappings" : { "AWSInstanceMapping" : { "t2.micro" : { "Arch" : "64" }, "t2.small" : { "Arch" : "64" }, "t2.medium" : { "Arch" : "64" }, "t2.large" : { "Arch" : "64" }, "m3.medium" : { "Arch" : "64" }, "m4.large" : { "Arch" : "64" }, "m4.xlarge" : { "Arch" : "64" }, "m4.2xlarge" : { "Arch" : "64" } }, "InstanceAMI" : { "us-east-1" : { "64" : "ami-09ca8e1e" } },
Here above, we are mapping the instances and passing the AMI id of the instance. The instance will be launched using this AMI every time a new environment is launched using the template.
"Resources" : { "VPC" : { "Type" : "AWS::EC2::VPC", "Properties" : { "CidrBlock" : "10.0.0.0/16", "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} }, { "Key": "Name", "Value": "Project_VPC"}, {"Key" : "Network", "Value" : "Public" } ] } }, "PublicSubnet" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "CidrBlock" : "10.0.0.0/24", "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} }, {"Key" : "Network", "Value" : "Public" }, { "Key": "Name", "Value": "Project_Public_Subnet"} ] } }, "InternetGateway" : { "Type" : "AWS::EC2::InternetGateway", "Properties" : { "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} }, {"Key" : "Network", "Value" : "Public" }, { "Key": "Name", "Value": "Project_Internetgateway"} ] } }, "AttachGateway" : { "Type" : "AWS::EC2::VPCGatewayAttachment", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "InternetGatewayId" : { "Ref" : "InternetGateway" } } },
Here above, a custom VPC is created with CIDR 10.0.0.0/16. A public subnet is created with CIDR 10.0.0.0/24; an Internet Gateway is created for internet access, and the gateway is attached to the custom VPC. The tags are also defined with the respective resources.
"PublicRouteTable" : { "Type" : "AWS::EC2::RouteTable", "Properties" : { "VpcId" : {"Ref" : "VPC"}, "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} }, {"Key" : "Network", "Value" : "Public" }, { "Key": "Name", "Value": "cloudwords_public_routetable"} ] } }, "PublicRoute" : { "Type" : "AWS::EC2::Route", "DependsOn" : "AttachGateway", "Properties" : { "RouteTableId" : { "Ref" : "PublicRouteTable" }, "DestinationCidrBlock" : "0.0.0.0/0", "GatewayId" : { "Ref" : "InternetGateway" } } }, "PublicSubnetRouteTableAssociation" : { "Type" : "AWS::EC2::SubnetRouteTableAssociation", "Properties" : { "SubnetId" : { "Ref" : "PublicSubnet" }, "RouteTableId" : { "Ref" : "PublicRouteTable" } } }, "PublicNetworkAcl" : { "Type" : "AWS::EC2::NetworkAcl", "Properties" : { "VpcId" : {"Ref" : "VPC"}, "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} }, {"Key" : "Network", "Value" : "Public" }, {"Key": "Name", "Value": "cloudwords_publicNetworkacl"} ] } },
Here above a Public Route Table is created in the custom VPC. The Public route is defined to route all traffic outside through Internet Gateway. “DependsOn: AttachGateway” is used so that public route is only defined when the gateway is attached. A Public Network ACL is also created.
"InboundHTTPPublicNetworkAclEntry" : { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "NetworkAclId" : {"Ref" : "PublicNetworkAcl"}, "RuleNumber" : "100", "Protocol" : "6", "RuleAction" : "allow", "Egress" : "false", "CidrBlock" : "0.0.0.0/0", "PortRange" : {"From" : "0", "To" : "65535"} } }, "InboundDynamicPortsPublicNetworkAclEntry" : { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "NetworkAclId" : {"Ref" : "PublicNetworkAcl"}, "RuleNumber" : "101", "Protocol" : "6", "RuleAction" : "allow", "Egress" : "false", "CidrBlock" : "0.0.0.0/0", "PortRange" : {"From" : "1024", "To" : "65535"}, "PortRange" : {"From" : "22", "To" : "22"} } }, "OutboundHTTPPublicNetworkAclEntry" : { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "NetworkAclId" : {"Ref" : "PublicNetworkAcl"}, "RuleNumber" : "100", "Protocol" : "6", "RuleAction" : "allow", "Egress" : "true", "CidrBlock" : "0.0.0.0/0", "PortRange" : {"From" : "80", "To" : "80"}, "PortRange" : {"From" : "22", "To" : "22"} } }, "OutBoundDynamicPortPublicNetworkAclEntry" : { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "NetworkAclId" : {"Ref" : "PublicNetworkAcl"}, "RuleNumber" : "101", "Protocol" : "6", "RuleAction" : "allow", "Egress" : "true", "CidrBlock" : "0.0.0.0/0", "PortRange" : {"From" : "1024", "To" : "65535"} } }, "PublicSubnetNetworkAclAssociation" : { "Type" : "AWS::EC2::SubnetNetworkAclAssociation", "Properties" : { "SubnetId" : { "Ref" : "PublicSubnet" }, "NetworkAclId" : { "Ref" : "PublicNetworkAcl" } } },
Here above different ACL rules are defined, and the public subnet is associated with Public Network ACL.
"InstanceSecurityGroup" : { "Type" : "AWS::EC2::SecurityGroup", "Properties" : { "GroupDescription" : "Enable HTTP access", "VpcId" : { "Ref" : "VPC" }, "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0"}, { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"} ], "Tags" : [ {"Key": "Name", "Value": "Project_EC2_sg"} ] }}, "Instance" : { "Type" : "AWS::EC2::Instance", "Properties" : { "KeyName" : { "Ref" : "KeyName" }, "InstanceType" : { "Ref" : "InstanceType" }, "ImageId" : { "Fn::FindInMap" : [ "InstanceAMI", { "Ref" : "AWS::Region" }, { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }, "NetworkInterfaces" : [ { "DeviceIndex" : "0", "AssociatePublicIpAddress" : "true", "DeleteOnTermination" : "true", "SubnetId" : { "Ref" : "PublicSubnet" }, "GroupSet" : [ { "Ref" : "InstanceSecurityGroup" } ] } ], "Tags" : [ {"Key": "Name", "Value": "Project_Instance"} ], "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ "#!/bin/bash -ex\n", "apt-get update" ]]}} }}, "Outputs" : { "InstanceId" : { "Description" : "InstanceId of the newly created Mysql instance", "Value" : { "Ref" : "Instance" } }, "WebUrl" : { "Description" : "This is the endpoint of application.", "Value" : { "Fn::Join" : [ "", [ "https://", { "Fn::GetAtt" : [ "Instance", "PublicIp"] } , ":8444" ] ] } }
Here above the Instance security group is defined. The inbound and outbound rules are defined. The instance is launched using the parameters defined above. The user data is defined and also the tags are created.
In the output block, the instance IP address is showed. Also, the web URL is generated in the output.
The given above is the CloudFormation template to launch an EC2 instance. You can tune and modify the template according to the architectural requirement.
Thank you for the template. When I tried to use the same to create an ec2, it throws an error saying “Template validation error: Invalid template property or properties [InstanceAMI] “. Could you please help me to resolve the same.
Can you please share the code,, I got a error with “Every Resource must have a type”. Its due to the mismatching of braces.
Please share the code.