1. AWS
Amazon Web Services (AWS) is the world’s most comprehensive and broadly adopted cloud offering, with more than 200 fully featured services available from data centers globally.
I am blogging my learnings on how to setup AWS and automate service instance creation and maintenance using the AWS CLI. This blog will mostly cover setting up a virtual cloud and spinning a new EC2 instance from the CLI. Although, the AWS CLI is powerful enough to do everything that the web console supports provided you have the necessary access. I will also provide some of the automation bash scripts I discovered online and scripts I wrote for my daily use at the end of the blog.
2. Prerequisites
Some of the prerequisites are listed below:
- An active AWS account
- AWS CLI installed in the local machine
- AWS CLI configured with an IAM user with the necessary permission - EC2FullAccess
3. AWS Essentials
I am expecting you already know what AWS EC2 is and how to go about spinning new instances from the web console. Just to summarize, essential steps required to spin a new instance safely include the following
- Create a VPC
- Create an Internet Gateway and attach to the VPC
- Create a Subnet
- Create a Routing Table and required routes
- Create a Security Group
- Launch an Instance
1. Create a VPC
You can read more about what a Virtual Private Cloud (VPC) here. I will be creating a dualstack VPC that supports both ipv4 and ipv6
The AWS command to create a new vpc is:
aws ec2 create-vpc \
--cidr-block 10.10.0.0/24 \
--amazon-provided-ipv6-cidr-block
The options --cidr-block
specifies the ipv4 CIDR block to use and
--amazon-provided-ipv6-cidr-block
specifies the ipv6 CIDR block to use.
The output of the above command can be piped to the jq
command to save the VPC Id which is required by the next step. jq
is an amazing tool to query and parse json outputs and json api responses. You can check its documentation here.
After piping, save the output to a variable called VPCID like this
export VPCID="$(aws ec2 create-vpc \
--cidr-block 10.10.0.0/24 \
--amazon-provided-ipv6-cidr-block \
| jq -r '.Vpc.VpcId')"
You can create a tag for the VPC instance for future references. Tags come in handy when you are spinning multiple instances for your project. I am going to tag my VPC as "First_VPC"
aws ec2 create-tags \
--tags Key=Name, Value="First_VPC" \
--resources "${VPCID}"
You can enable DNS Hostname and DNS Resolution for your VPC by modifying the VPC attributes as follows:
aws ec2 modify-vpc-attribute --enable-dns-hostnames --vpc-id "${VPCID}"
aws ec2 modify-vpc-attribute --enable-dns-support --vpc-id "${VPCID}"
The above commands can finally look this:
export VPCID="$(aws ec2 create-vpc \
--cidr-block 10.10.0.0/24 \
--amazon-provided-ipv6-cidr-block \
| jq -r '.Vpc.VpcId')"
aws ec2 create-tags --tags \
Key=Name, Value="First_VPC" \
--resources "${VPCID}"
aws ec2 modify-vpc-attribute --enable-dns-hostnames --vpc-id "${VPCID}"
aws ec2 modify-vpc-attribute --enable-dns-support --vpc-id "${VPCID}"
2. Create an Internet Gateway and attach to the VPC
An Internet Gateway is required for the VPC to be able to communicate to any devices outside of the Virtual Cloud. You can read about Internet Gateways here.
You can create a new Internet Gateway for your VPC like this:
aws ec2 create-internet-gateway \
| jq -r '.InternetGateway.InternetGatewayId'
The jq
queries the result to return the id of the newly created Internet Gateway.
Like the previous command, you can export this output to save in a variable for a future step such as attaching the Internet Gateway to the VPC like this:
export IGW="$(aws ec2 create-internet-gateway \
| jq -r '.InternetGateway.InternetGatewayId')"
Tag the Internet Gateway like this:
aws ec2 create-tags \
--tags Key=Name, Value="First_IG" --resources "${IGW}"
Attach the Internet Gateway to the VPC like this:
aws ec2 attach-internet-gateway \
--vpc-id="${VPCID}" --internet-gateway-id="{IGW}"
Combining all the above commands can look like this:
export IGW="$(aws ec2 create-internet-gateway \
| jq -r '.InternetGateway.InternetGatewayId')"
aws ec2 create-tags \
--tags Key=Name, Value="First_IG" --resources "${IGW}"
aws ec2 attach-internet-gateway \
--vpc-id="${VPCID}" --internet-gateway-id="{IGW}"
3. Create a Subnet
A subnet is required for host machines to reside in a network. A subnet helps in grouping host machines into a smaller sub network to make networking more efficient by reducing the distance for network traffics. You can read more about Subnets here and here.
To create a Subnet for your VPC you can execute the following command:
aws ec2 create-subnet \
--cidr-block 10.10.0.0/26 \
--vpc-id "${VPCID}" \
| jq -r '.Subnet.SubnetId'
To have your subnet provide both ipv4 and ipv6 support include additional flags and export the SubnetId returned from the jq
command and save it in a variable like this:
export V6CIDR=$(aws ec2 describe-vpcs \
--vpc-id "${VPCID}" \
| jq -r '.Vpcs[].Ipv6CidrBlockAssociationSet[].Ipv6CidrBlock')
export SUBNET="$(aws ec2 create-subnet \
--cidr-block 10.10.0.0/26 \
--ipv6-cidr-block ${V6CIDR%/56}/64 \
--vpc-id "${VPCID}" | jq -r '.Subnet.SubnetId')"
Create a tag for your Subnet like this:
aws ec2 create-tags \
--tags Key=Name,Value=firstSubnet --resources "${SUBNET}"
Allow Subnet to create ipv6 address on creation and map a public ip on creation with the following commands:
aws ec2 modify-subnet-attribute \
--subnet-id "${SUBNET}" \
--assign-ipv6-address-on-creation`
`aws ec2 modify-subnet-attribute \
--subnet-id "${SUBNET}" \
--map-public-ip-on-launch
All the above commands, when combined will look like this:
export V6CIDR=$(aws ec2 describe-vpcs \
--vpc-id "${VPCID}" \
| jq -r '.Vpcs[].Ipv6CidrBlockAssociationSet[].Ipv6CidrBlock')
export SUBNET="$(aws ec2 create-subnet \
--cidr-block 10.10.0.0/26 \
--ipv6-cidr-block ${V6CIDR%/56}/64 \
--vpc-id "${VPCID}" \
| jq -r '.Subnet.SubnetId')"
aws ec2 create-tags --tags Key=Name,Value=dualstack --resources "${SUBNET}"
aws ec2 modify-subnet-attribute \
--subnet-id "${SUBNET}" \
--assign-ipv6-address-on-creation
aws ec2 modify-subnet-attribute \
--subnet-id "${SUBNET}" \
--map-public-ip-on-launch
4. Create a Route Table and add Routes
A route table is required for the hosts in the subnet to be discoverable and communicate to other hosts. You can read more about Route tables here
To create a route table and to add routes, create a route table and save the Id of the route table resource for future use. Also tag the route table for better management. Run the following commands:
export RTB="$(aws ec2 create-route-table \
--vpc-id "${VPCID}" | jq -r '.RouteTable.RouteTableId')"
aws ec2 create-tags \
--tags Key=Name,Value=FirstRouteTable --resources "${RTB}"
Associate your route table with the subnet like this:
aws ec2 associate-route-table \
--route-table-id "${RTB}" \
--subnet-id "${SUBNET}" | jq '.AssociationId'
To create routes, you can run the following:
aws ec2 create-route \
--route-table-id "${RTB}" \
--destination-ipv6-cidr-block ::/0 \
--gateway-id "${IGW}"
aws ec2 create-route \
--route-table-id "${RTB}" \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id "${IGW}"
Combining all the above commands should look like this:
export RTB="$(aws ec2 create-route-table \
--vpc-id "${VPCID}" \
| jq -r '.RouteTable.RouteTableId')"
aws ec2 create-tags \
--tags Key=Name,Value=FirstRouteTable --resources "${RTB}"
aws ec2 associate-route-table \
--route-table-id "${RTB}" --subnet-id "${SUBNET}" \
| jq '.AssociationId'
aws ec2 create-route \
--route-table-id "${RTB}" \
-destination-ipv6-cidr-block ::/0 \
--gateway-id "${IGW}"
aws ec2 create-route \
--route-table-id "${RTB}" \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id "${IGW}"
5. Create a Security Group
EC2 instances require a Security Group to configure and manage inbound and outbound network traffic by configuring certain ports, etc. You can read more about AWS Security Groups here.
To create a Security Group and to save the security group id for future use, run the below command:
export SG="$(aws ec2 create-security-group \
--description "Default security group for dualstack instances" \
--group-name "dualstack" \
--vpc-id "${VPCID}" | jq -r '.GroupId')"
Create a tag for the Security Group:
aws ec2 create-tags \
--tags Key=Name,Value=FirstSecurityGroup --resources "${SG}"
Allow all inbound and outbound traffic, run:
aws ec2 authorize-security-group-ingress \
--group-id "${SG}" \
--ip-permissions '[{"IpProtocol": "-1", "IpRanges" : [{"CidrIp": "0.0.0.0/0"}], "Ipv6Ranges" : [{"CidrIpv6": "::/0"}]}]'
Combining all the above commands, we get:
export SG="$(aws ec2 create-security-group \
--description "Default security group for dualstack instances" \
--group-name "dualstack" \
--vpc-id "${VPCID}" | jq -r '.GroupId')"
aws ec2 create-tags \
--tags Key=Name,Value=FirstSecurityGroup --resources "${SG}"
aws ec2 authorize-security-group-ingress \
--group-id "${SG}" \
--ip-permissions '[{"IpProtocol": "-1", "IpRanges" : [{"CidrIp": "0.0.0.0/0"}], "Ipv6Ranges" : [{"CidrIpv6": "::/0"}]}]'
6. Launch a new instance of EC2
Finally, to launch a new instance of EC2 with a specific Amazon AMI (FreeBSD in this case), you can run the below command. We are saving the instance ID to a variable to describe and monitor the instance.
export ID=$(aws ec2 run-instances \
--image-id ami-0de268ac2498ba33d \
--instance-type t2.micro \
--count 1 \
--security-group-ids "${SG}" \
--subnet-id "${SUBNET}" | jq -r '.Instances[].InstanceId')
The below command describes the instance that is just created.
aws ec2 describe-instances \
--instance-id "${ID}" \
| jq -r '.Reservations[].Instances[] | "\(.PublicDnsName) \(.PublicIpAddress) \(.NetworkInterfaces[].Ipv6Addresses[].Ipv6Address)"'
Note: It can take a few minutes for the EC2 instance to spin up.
Combining all the above commands, we get:
export ID=$(aws ec2 run-instances \
--image-id ami-0de268ac2498ba33d \
--instance-type t2.micro \
--count 1 \
--security-group-ids "${SG}" \
--subnet-id "${SUBNET}" \
| jq -r '.Instances[].InstanceId')
aws ec2 describe-instances \
--instance-id "${ID}" \
| jq -r '.Reservations[].Instances[] | "\(.PublicDnsName) \(.PublicIpAddress) \(.NetworkInterfaces[].Ipv6Addresses[].Ipv6Address)"'
7. Bonus Scripts
I combined the above commands and created bash functions and aliases that helps me automate and launch any infrastructure with any configuration I like. I will continue to update this script as I learn to configure the script in a more efficient way. More about it in a new blog.