DEV Community

Kanani Nirav
Kanani Nirav

Posted on

5 2

Launch an EC2 Instance with Ruby installation Using AWS CloudFormation

We are creating an EC2 Instance with Ruby installation Using AWS CloudFormation. Using this we can set up web-application architecture in the AWS cloud. you can add more steps according to requirements like( git install, clone project from git, run deployment script, Nginx setup, docker install, and many more...)

Using this we can save time and money. Easy to setup new server.

What is AWS Cloudformation?

AWS CloudFormation is a service that gives developers and businesses an easy way to create a collection of related AWS and third-party resources, and provision and manages them in an orderly and predictable fashion. We will be using Mappings, Resources, and Outputs for this project.

Here are the core components that we are going to create:

  • Web Server Security Group

  • EC2 instance with Ubuntu 20.04 image and 8Gb Ebs Volume and Ruby installation.

For creating VPC and Subnet please check the below article, in this article we will be creating an EC2 instance with Ruby Installation.
Create a VPC with private and public subnets using CloudFormation

Mappings:

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

"Mappings": {
"RegionMap": {
"ap-northeast-1": {
"AMI": "ami-0a3eb6ca097b78895"
}
}
}
view raw cfn-ec2.json hosted with ❤ by GitHub

Parameters:

Use the optional Parameters section to customize your templates. Parameters enable you to input custom values to your template each time you create or update a stack.

"Parameters": {
"KeyName": {
"Description" : "The name of an existing EC2 keypair for this instance",
"Type": "AWS::EC2::KeyPair::KeyName",
"MinLength": "1",
"MaxLength": "255",
"AllowedPattern" : "[\\x20-\\x7E]*",
"ConstraintDescription" : "can contain only ASCII characters.",
"Default": "First_EC2"
},
"SSHLocation" : {
"Description" : "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "0.0.0.0/0",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
}
view raw cfn-ec2.json hosted with ❤ by GitHub

Change your key name.( First_EC2 => Your_key_name )

Resources:

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

  1. For WebserverSecurityGroup, the template is creating a security group with an inbound rule allowing all traffic on HTTP.
    "WebServerSecurityGroup": {
    "Type": "AWS::EC2::SecurityGroup",
    "Properties": {
    "GroupName": "WebServerSecurityGroup",
    "SecurityGroupIngress": [
    {
    "IpProtocol": "tcp",
    "FromPort": 22,
    "ToPort": 22,
    "CidrIp": {
    "Ref": "SSHLocation"
    },
    "Description": "For EC2 connection with SSH"
    },
    {
    "IpProtocol": "tcp",
    "FromPort": 80,
    "ToPort": 80,
    "CidrIp": {
    "Ref": "SSHLocation"
    },
    "Description": "For traffic from Internet Http"
    }
    ],
    "GroupDescription": "Security Group for demo server",
    "VpcId": {
    "Ref": "VPC"
    }
    }
    }
    view raw cfn-ec2.json hosted with ❤ by GitHub
    1. 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 one region we use the FindInMap function to reference our Mappings.

  • UserData: This section is used to install Ruby using CloudFormation::Init

    AWS::CloudFormation::Init
    Use the AWS::CloudFormation::Init type to include metadata on an Amazon EC2 instance

EC Instance Template

"EC2Instance": {
"Type": "AWS::EC2::Instance",
"Metadata" : {
"Comment" : "Install Ruby On EC2 using CloudFormation::Init",
"AWS::CloudFormation::Init" : {
"configSets" : {
"full_install" : [ "install_cfn", "install_ruby_3" ]
},
"install_cfn" : {
"files" : {
"/etc/cfn/cfn-hup.conf" : {
"content" : { "Fn::Join" : ["", [
"[main]\n",
"stack=", { "Ref" : "AWS::StackId" }, "\n",
"region=", { "Ref" : "AWS::Region" }, "\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
"content": { "Fn::Join" : ["", [
"[cfn-auto-reloader-hook]\n",
"triggers=post.update\n",
"path=Resources.EC2Instance.Metadata.AWS::CloudFormation::Init\n",
"action=/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref" : "AWS::StackName" },
" --resource EC2Instance ",
" --configsets full_install ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"runas=root\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/lib/systemd/system/cfn-hup.service" : {
"content": { "Fn::Join" : ["", [
"[Unit]\n",
"Description=cfn-hup daemon\n",
"[Service]\n",
"Type=simple\n",
"ExecStart=/opt/aws/bin/cfn-hup\n",
"Restart=always\n",
"[Install]\n",
"WantedBy=multi-user.target\n"
]]}
}
},
"commands": {
"01enable_cfn_hup": {
"command": "systemctl enable cfn-hup.service"
},
"02start_cfn_hup": {
"command": "systemctl start cfn-hup.service"
}
}
},
"install_ruby_3": {
"files": {
"/tmp/install_ruby": {
"content": {
"Fn::Join": [
"\n",
[
"#!/bin/bash",
"curl -sSL https://rvm.io/mpapis.asc | sudo gpg --import -",
"curl -sSL https://rvm.io/pkuczynski.asc | sudo gpg --import -",
"curl -sSL https://get.rvm.io | sudo bash -s stable --ruby",
"source /usr/local/rvm/scripts/rvm",
"rvm gemset create own_gemset_name"
]
]
},
"mode": "000500",
"owner": "root",
"group": "root"
}
},
"commands": {
"01_install_ruby": {
"command": "/tmp/install_ruby > /var/log/install_ruby.log"
}
}
}
}
},
"Properties": {
"ImageId": { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]},
"InstanceType": { "Ref" : "InstanceType" },
"SubnetId" : { "Ref" : "PublicSubnet" },
"KeyName": { "Ref" : "KeyName" },
"SecurityGroupIds" : [ { "Ref": "WebServerSecurityGroup" } ],
"Tags": [
{ "Key" : "EC-2", "Value" : { "Fn::Sub": "${AWS::StackName}-EC2" } }
],
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sda1",
"Ebs": {
"DeleteOnTermination": "true",
"VolumeSize": "8",
"VolumeType": "gp2"
}
}
],
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash\n",
"apt-get update\n",
"apt-get install -y python-setuptools\n",
"mkdir -p /opt/aws/bin\n",
"apt-get install -y wget\n",
"wget https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz\n",
"python3 -m easy_install --script-dir /opt/aws/bin aws-cfn-bootstrap-py3-latest.tar.gz\n",
"/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref" : "AWS::StackId" },
" --resource EC2Instance ",
" --configsets full_install ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"/opt/aws/bin/cfn-signal -e $? ",
" --stack ", { "Ref" : "AWS::StackId" },
" --resource EC2Instance ",
" --region ", { "Ref" : "AWS::Region" }, "\n"
]
]
}
}
}
}
view raw cfn-ec2.json hosted with ❤ by GitHub

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" : {
"InstanceId" : {
"Description" : "InstanceId of the newly created EC2 instance",
"Value" : { "Ref" : "EC2Instance" }
},
"AZ" : {
"Description" : "Availability Zone of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "AvailabilityZone" ] }
},
"PublicDNS" : {
"Description" : "Public DNSName of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicDnsName" ] }
},
"PublicIP" : {
"Description" : "Public IP address of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicIp" ] }
}
}
view raw cfn-ec2.json hosted with ❤ by GitHub

Complete CloudFormation Template:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "This template deploys EC2 with a VPC and pair of public and private subnets. It deploys an Internet Gateway, with a default route on the public subnets and NAT gateway and route for private subnet.\n",
"Parameters": {
"InstanceType" : {
"Description" : "The EC2 instance type",
"Type" : "String",
"Default" : "t2.micro",
"AllowedValues" : [ "t2.nano","t2.micro","t2.small","t2.medium","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","m3.medium","m3.large","m3.xlarge","m3.2xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"],
"ConstraintDescription" : "Must be a valid EC2 instance type."
},
"VPCCIDR" : {
"Type" : "String",
"Description" : "Please enter the IP range (CIDR notation) for this VPC",
"MinLength": "9",
"MaxLength": "18",
"Default": "10.22.0.0/16",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},
"PublicSubnetCIDR" : {
"Type" : "String",
"Description" : "Please enter the IP address range for the VPC subnet",
"MinLength": "9",
"MaxLength": "18",
"Default": "10.22.0.0/24",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},
"PrivateSubnetCIDR" : {
"Type" : "String",
"Description" : "Please enter the IP address range for the VPC subnet",
"MinLength": "9",
"MaxLength": "18",
"Default": "10.22.1.0/24",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},
"KeyName": {
"Description" : "The name of an existing EC2 keypair for this instance",
"Type": "AWS::EC2::KeyPair::KeyName",
"MinLength": "1",
"MaxLength": "255",
"AllowedPattern" : "[\\x20-\\x7E]*",
"ConstraintDescription" : "can contain only ASCII characters.",
"Default": "First_EC2"
},
"SSHLocation" : {
"Description" : "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "0.0.0.0/0",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
},
"Mappings": {
"RegionMap": {
"ap-northeast-1": { "AMI" : "ami-0a3eb6ca097b78895" }
}
},
"Resources": {
"VPC": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": { "Ref" : "VPCCIDR" },
"EnableDnsSupport": true,
"EnableDnsHostnames": true,
"InstanceTenancy": "default",
"Tags": [
{ "Key" : "Name", "Value" : { "Fn::Sub": "${AWS::StackName}-VPC" } }
]
}
},
"InternetGateway": {
"Type": "AWS::EC2::InternetGateway",
"Properties": {
"Tags": [
{ "Key" : "Name", "Value" : { "Fn::Sub": "${AWS::StackName}-IG" } }
]
}
},
"GatewayToInternet": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"InternetGatewayId": { "Ref": "InternetGateway" },
"VpcId": { "Ref": "VPC" }
}
},
"PublicSubnetRouteTable": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": { "Ref": "VPC" },
"Tags": [
{ "Key" : "Name", "Value" : { "Fn::Sub": "${AWS::StackName}-public" } }
]
}
},
"PublicSubnetRoute": {
"Type": "AWS::EC2::Route",
"DependsOn": "GatewayToInternet",
"Properties": {
"RouteTableId": { "Ref": "PublicSubnetRouteTable" },
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": { "Ref": "InternetGateway" }
}
},
"PublicSubnet": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": { "Ref": "VPC" },
"CidrBlock": { "Ref" : "PublicSubnetCIDR" },
"MapPublicIpOnLaunch": true,
"Tags": [
{ "Key" : "Name", "Value" : { "Fn::Sub": "${AWS::StackName}-public-subnet" } }
]
}
},
"PublicSubnetRouteTableAssociation": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"RouteTableId": { "Ref" : "PublicSubnetRouteTable" },
"SubnetId": { "Ref" : "PublicSubnet" }
}
},
"PrivateSubnetRouteTable": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": { "Ref": "VPC" },
"Tags": [
{ "Key" : "Name", "Value" : { "Fn::Sub": "${AWS::StackName}-private" } }
]
}
},
"PrivateSubnet": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": { "Ref": "VPC" },
"CidrBlock": { "Ref" : "PrivateSubnetCIDR" },
"MapPublicIpOnLaunch": false,
"Tags": [
{ "Key" : "Name", "Value" : { "Fn::Sub": "${AWS::StackName}-private-subnet" } }
]
}
},
"PrivateSubnetRouteTableAssociation": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"RouteTableId": { "Ref" : "PrivateSubnetRouteTable" },
"SubnetId": { "Ref" : "PrivateSubnet" }
}
},
"WebServerSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupName": "WebServerSecurityGroup",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"CidrIp": { "Ref" : "SSHLocation"},
"Description": "For traffic from Internet"
},
{
"IpProtocol": "tcp",
"FromPort": 80,
"ToPort": 80,
"CidrIp": { "Ref" : "SSHLocation"},
"Description": "For traffic from Internet Http"
}
],
"GroupDescription": "Security Group for demo server",
"VpcId": { "Ref": "VPC" }
}
},
"EC2Instance": {
"Type": "AWS::EC2::Instance",
"Metadata" : {
"Comment" : "Install Ruby On EC2 using CloudFormation::Init",
"AWS::CloudFormation::Init" : {
"configSets" : {
"full_install" : [ "install_cfn", "install_ruby_3" ]
},
"install_cfn" : {
"files" : {
"/etc/cfn/cfn-hup.conf" : {
"content" : { "Fn::Join" : ["", [
"[main]\n",
"stack=", { "Ref" : "AWS::StackId" }, "\n",
"region=", { "Ref" : "AWS::Region" }, "\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
"content": { "Fn::Join" : ["", [
"[cfn-auto-reloader-hook]\n",
"triggers=post.update\n",
"path=Resources.EC2Instance.Metadata.AWS::CloudFormation::Init\n",
"action=/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref" : "AWS::StackName" },
" --resource EC2Instance ",
" --configsets full_install ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"runas=root\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/lib/systemd/system/cfn-hup.service" : {
"content": { "Fn::Join" : ["", [
"[Unit]\n",
"Description=cfn-hup daemon\n",
"[Service]\n",
"Type=simple\n",
"ExecStart=/opt/aws/bin/cfn-hup\n",
"Restart=always\n",
"[Install]\n",
"WantedBy=multi-user.target\n"
]]}
}
},
"commands": {
"01enable_cfn_hup": {
"command": "systemctl enable cfn-hup.service"
},
"02start_cfn_hup": {
"command": "systemctl start cfn-hup.service"
}
}
},
"install_ruby_3": {
"files": {
"/tmp/install_ruby": {
"content": {
"Fn::Join": [
"\n",
[
"#!/bin/bash",
"curl -sSL https://rvm.io/mpapis.asc | sudo gpg --import -",
"curl -sSL https://rvm.io/pkuczynski.asc | sudo gpg --import -",
"curl -sSL https://get.rvm.io | sudo bash -s stable --ruby",
"source /usr/local/rvm/scripts/rvm",
"rvm gemset create own_gemset_name"
]
]
},
"mode": "000500",
"owner": "root",
"group": "root"
}
},
"commands": {
"01_install_ruby": {
"command": "/tmp/install_ruby > /var/log/install_ruby.log"
}
}
}
}
},
"Properties": {
"ImageId": { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]},
"InstanceType": { "Ref" : "InstanceType" },
"SubnetId" : { "Ref" : "PublicSubnet" },
"KeyName": { "Ref" : "KeyName" },
"SecurityGroupIds" : [ { "Ref": "WebServerSecurityGroup" } ],
"Tags": [
{ "Key" : "EC-2", "Value" : { "Fn::Sub": "${AWS::StackName}-EC2" } }
],
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sda1",
"Ebs": {
"DeleteOnTermination": "true",
"VolumeSize": "8",
"VolumeType": "gp2"
}
}
],
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash\n",
"apt-get update\n",
"apt-get install -y python-setuptools\n",
"mkdir -p /opt/aws/bin\n",
"apt-get install -y wget\n",
"wget https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz\n",
"python3 -m easy_install --script-dir /opt/aws/bin aws-cfn-bootstrap-py3-latest.tar.gz\n",
"/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref" : "AWS::StackId" },
" --resource EC2Instance ",
" --configsets full_install ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"/opt/aws/bin/cfn-signal -e $? ",
" --stack ", { "Ref" : "AWS::StackId" },
" --resource EC2Instance ",
" --region ", { "Ref" : "AWS::Region" }, "\n"
]
]
}
}
}
}
},
"Outputs" : {
"InternetGateway" : {
"Description" : "A reference to the IG",
"Value" : { "Ref" : "InternetGateway" }
},
"VPC" : {
"Description" : "A reference to the created VPC",
"Value" : { "Ref" : "VPC" }
},
"PublicSubnet" : {
"Description" : "A reference to the public subnet",
"Value" : { "Ref" : "PublicSubnet" }
},
"PrivateSubnet" : {
"Description" : "A reference to the private subnet",
"Value" : { "Ref" : "PrivateSubnet" }
},
"InstanceId" : {
"Description" : "InstanceId of the newly created EC2 instance",
"Value" : { "Ref" : "EC2Instance" }
},
"AZ" : {
"Description" : "Availability Zone of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "AvailabilityZone" ] }
},
"PublicDNS" : {
"Description" : "Public DNSName of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicDnsName" ] }
},
"PublicIP" : {
"Description" : "Public IP address of the newly created EC2 instance",
"Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicIp" ] }
}
}
}
view raw cfn-ec2.json hosted with ❤ by GitHub

Final Output

Image description

RVM and Ruby Default installation output.

Image description

Note, With the DeletionPolicy the attribute you can preserve, and in some cases, backup a resource when its stack is deleted. You specify a DeletionPolicy attribute for each resource that you want to control. If a resource has no DeletionPolicy attribute, Amazon CloudFormation deletes the resource by default. If you Want to keep your all resource then add DeletionPolicy: Retain in each resource
For more details: AWS doc

Conclusion

Using this template you can create VPC with the public, and private subnets, Web Server Security Group and launch EC2 instance with Ruby installation, using AWS CloudFormation. It builds a private networking environment in which you can securely run AWS resources, along with related networking resources.

If this guide has been helpful to you and your team please share it with others!

Top comments (0)