Post

Kickstart Your AWS EC2 Ubuntu Instance with a Custom VPC Using AWS CloudShell

Image

Setting up an AWS EC2 instance involves configuring various networking components to ensure your instance is accessible and secure. This blog post will guide you through a comprehensive Bash script designed to automate the setup of an Ubuntu EC2 instance with its own Virtual Private Cloud (VPC), including custom CIDR blocks, subnets, route tables, and security groups. We’ll also walk you through running this script in AWS CloudShell, making the process seamless and efficient.

Prerequisites

  • An active AWS account with necessary permissions to create VPCs, subnets, security groups, and EC2 instances.
  • A key pair (e.g., bruce_pub_key) created in the desired AWS region.
  • A local machine with SSH access if you plan to connect to your EC2 instance after creation.

Understanding the Script

The provided Bash script automates the following tasks:

  • Checks for an existing VPC with a specified CIDR block; if none exists, it creates a new one.
  • Checks for an existing subnet within the VPC; if none exists, it creates a new subnet.
  • Checks for an existing security group; if none exists, it creates a new one and configures necessary inbound rules.
  • Launches an EC2 instance within the specified subnet and security group.

This approach ensures idempotency, meaning you can run the script multiple times without creating duplicate resources, as it reuses existing ones when possible.

The Complete Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/bin/bash

## Set your variables
VPC_CIDR="10.0.0.0/16"
SUBNET_CIDR="10.0.1.0/24"
REGION="ap-southeast-2"
AMI_ID="ami-001f2488b35ca8aad" 
INSTANCE_TYPE="t2.micro"
KEY_NAME="bruce_pub_key"
SEC_GROUP_NAME="ubuntu-sec-group"

## Check for existing VPC with the same CIDR
echo "Checking for existing VPC with CIDR block $VPC_CIDR..."
EXISTING_VPC_ID=$(aws ec2 describe-vpcs --filters "Name=cidr-block,Values=$VPC_CIDR" --query 'Vpcs[0].VpcId' --output text --region $REGION)

if [ "$EXISTING_VPC_ID" != "None" ]; then
  echo "Existing VPC found: $EXISTING_VPC_ID"
  VPC_ID=$EXISTING_VPC_ID
else
  echo "No existing VPC found. Creating new VPC..."
  VPC_ID=$(aws ec2 create-vpc --cidr-block $VPC_CIDR --region $REGION --query 'Vpc.VpcId' --output text)

  ## Enable DNS support and DNS hostnames
  aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support
  aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames

  ## Create Internet Gateway
  echo "Creating Internet Gateway..."
  IGW_ID=$(aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text)
  aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID

  ## Create Route Table and Add Route to Internet Gateway
  echo "Creating Route Table and adding route to Internet Gateway..."
  ROUTE_TABLE_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
  aws ec2 create-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
fi

## Check if Subnet exists
echo "Checking for existing Subnet with CIDR block $SUBNET_CIDR..."
EXISTING_SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=cidr-block,Values=$SUBNET_CIDR" --query 'Subnets[0].SubnetId' --output text --region $REGION)

if [ "$EXISTING_SUBNET_ID" != "None" ]; then
  echo "Existing Subnet found: $EXISTING_SUBNET_ID"
  SUBNET_ID=$EXISTING_SUBNET_ID
else
  echo "No existing Subnet found. Creating new Subnet..."
  SUBNET_ID=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block $SUBNET_CIDR --region $REGION --query 'Subnet.SubnetId' --output text)

  ## Associate Subnet with Route Table
  aws ec2 associate-route-table --subnet-id $SUBNET_ID --route-table-id $ROUTE_TABLE_ID
fi

## Check if Security Group exists
echo "Checking for existing Security Group named $SEC_GROUP_NAME..."
EXISTING_SEC_GROUP_ID=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" "Name=group-name,Values=$SEC_GROUP_NAME" --query 'SecurityGroups[0].GroupId' --output text --region $REGION)

if [ "$EXISTING_SEC_GROUP_ID" != "None" ]; then
  echo "Existing Security Group found: $EXISTING_SEC_GROUP_ID"
  SEC_GROUP_ID=$EXISTING_SEC_GROUP_ID
else
  echo "No existing Security Group found. Creating new Security Group..."
  SEC_GROUP_ID=$(aws ec2 create-security-group --group-name $SEC_GROUP_NAME --description "Allow SSH, HTTP, and HTTPS" --vpc-id $VPC_ID --query 'GroupId' --output text)

  ## Allow inbound traffic on ports 22, 80, and 443
  aws ec2 authorize-security-group-ingress --group-id $SEC_GROUP_ID --protocol tcp --port 22 --cidr 0.0.0.0/0
  aws ec2 authorize-security-group-ingress --group-id $SEC_GROUP_ID --protocol tcp --port 80 --cidr 0.0.0.0/0
  aws ec2 authorize-security-group-ingress --group-id $SEC_GROUP_ID --protocol tcp --port 443 --cidr 0.0.0.0/0
fi

## Create EC2 Instance
echo "Launching EC2 Instance..."
INSTANCE_ID=$(aws ec2 run-instances --image-id $AMI_ID --count 1 --instance-type $INSTANCE_TYPE --key-name $KEY_NAME --security-group-ids $SEC_GROUP_ID --subnet-id $SUBNET_ID --associate-public-ip-address --query 'Instances[0].InstanceId' --output text)

#echo "EC2 Instance $INSTANCE_ID is launching. Waiting for instance to become running..."
aws ec2 wait instance-running --instance-ids $INSTANCE_ID

## Get Public IP of the Instance
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)

echo "Instance is running. You can connect to it via SSH using the following command:"
echo "ssh -i /path/to/your/private-key.pem ubuntu@$PUBLIC_IP"

Step-by-Step Breakdown

1. Setting Variables

The script begins by defining essential variables that determine the configuration of your VPC, subnet, EC2 instance, and security group.

  • VPC_CIDR: The CIDR block for your VPC (e.g., 10.0.0.0/16).
  • SUBNET_CIDR: The CIDR block for your subnet within the VPC (e.g., 10.0.1.0/24).
  • REGION: AWS region where resources will be created (e.g., ap-southeast-2).
  • AMI_ID: The Amazon Machine Image ID for the Ubuntu instance.
  • INSTANCE_TYPE: The type of EC2 instance (e.g., t2.micro).
  • KEY_NAME: The name of your SSH key pair.
  • SEC_GROUP_NAME: The name of the security group.

2. Checking for an Existing VPC

The script checks if a VPC with the specified CIDR block already exists in the chosen region.

1
2
EXISTING_VPC_ID=$(aws ec2 describe-vpcs --filters "Name=cidr-block,Values=$VPC_CIDR" --query 'Vpcs[0].VpcId' --output text --region $REGION)

If found, it reuses the existing VPC; otherwise, it creates a new one.

3. Creating a New VPC (If Necessary)

If no existing VPC matches the specified CIDR, the script creates a new VPC and configures DNS settings.

1
2
3
4
5
VPC_ID=$(aws ec2 create-vpc --cidr-block $VPC_CIDR --region $REGION --query 'Vpc.VpcId' --output text)

aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames

4. Setting Up the Internet Gateway and Route Table

The script creates an Internet Gateway (IGW) and attaches it to the VPC. It then creates a route table and adds a route to the IGW, enabling internet access.

1
2
3
4
5
6
IGW_ID=$(aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID

ROUTE_TABLE_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID

5. Checking for an Existing Subnet

The script verifies if a subnet with the specified CIDR block exists within the VPC. If it does, the existing subnet is used; otherwise, a new subnet is created and associated with the route table.

1
2
3
4
5
6
7
8
9
EXISTING_SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=cidr-block,Values=$SUBNET_CIDR" --query 'Subnets[0].SubnetId' --output text --region $REGION)

if [ "$EXISTING_SUBNET_ID" != "None" ]; then
  SUBNET_ID=$EXISTING_SUBNET_ID
else
  SUBNET_ID=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block $SUBNET_CIDR --region $REGION --query 'Subnet.SubnetId' --output text)
  aws ec2 associate-route-table --subnet-id $SUBNET_ID --route-table-id $ROUTE_TABLE_ID
fi

6. Checking for an Existing Security Group

The script checks if a security group with the specified name exists within the VPC. If it does, the existing security group is used; otherwise, a new one is created with rules to allow SSH (port 22), HTTP (port 80), and HTTPS (port 443) traffic.

1
2
3
4
5
6
7
8
9
10
11
12
EXISTING_SEC_GROUP_ID=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" "Name=group-name,Values=$SEC_GROUP_NAME" --query 'SecurityGroups[0].GroupId' --output text --region $REGION)

if [ "$EXISTING_SEC_GROUP_ID" != "None" ]; then
  SEC_GROUP_ID=$EXISTING_SEC_GROUP_ID
else
  SEC_GROUP_ID=$(aws ec2 create-security-group --group-name $SEC_GROUP_NAME --description "Allow SSH, HTTP, and HTTPS" --vpc-id $VPC_ID --query 'GroupId' --output text)

  aws ec2 authorize-security-group-ingress --group-id $SEC_GROUP_ID --protocol tcp --port 22 --cidr 0.0.0.0/0
  aws ec2 authorize-security-group-ingress --group-id $SEC_GROUP_ID --protocol tcp --port 80 --cidr 0.0.0.0/0
  aws ec2 authorize-security-group-ingress --group-id $SEC_GROUP_ID --protocol tcp --port 443 --cidr 0.0.0.0/0
fi

7. Launching the EC2 Instance

With the networking components in place, the script launches an EC2 instance using the specified AMI, instance type, key pair, security group, and subnet. It also associates a public IP address to the instance.

1
2
INSTANCE_ID=$(aws ec2 run-instances --image-id $AMI_ID --count 1 --instance-type $INSTANCE_TYPE --key-name $KEY_NAME --security-group-ids $SEC_GROUP_ID --subnet-id $SUBNET_ID --associate-public-ip-address --query 'Instances[0].InstanceId' --output text)

The script then waits until the instance is running:

1
2
aws ec2 wait instance-running --instance-ids $INSTANCE_ID

8. Retrieving the Public IP

Finally, the script fetches the public IP address of the newly launched EC2 instance and provides an SSH command for connecting to it.

1
2
3
4
5
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)

echo "Instance is running. You can connect to it via SSH using the following command:"
echo "ssh -i /path/to/your/private-key.pem ubuntu@$PUBLIC_IP"

Running the Script in AWS CloudShell

AWS CloudShell is a browser-based shell that makes it easy to manage, explore, and interact with your AWS resources. Here’s how to run the script:

1. Access AWS CloudShell

  1. Log in to your AWS Management Console.
  2. Click on the CloudShell icon in the top navigation bar (it looks like a terminal).
  3. If it’s your first time, AWS will provision the CloudShell environment, which may take a few moments.

2. Create the Script File

  1. In the CloudShell terminal, use a text editor like nano to create a new script file:
1
2
nano setup_ec2.sh

  1. Paste the complete script (as shown above) into the editor.
  2. Save and exit the editor (for nano, press CTRL + O, then ENTER, and CTRL + X).

3. Make the Script Executable

Change the script’s permissions to make it executable:

1
2
chmod +x setup_ec2.sh

4. Execute the Script

Run the script by executing:

1
2
./setup_ec2.sh

The script will output the progress of each step, informing you whether it’s using existing resources or creating new ones. Once the EC2 instance is running, it will display the SSH command to connect to your instance.

Connecting to Your EC2 Instance

After the script completes, use the provided SSH command to connect to your Ubuntu EC2 instance:

1
2
ssh -i /path/to/your/private-key.pem ubuntu@PUBLIC_IP

Ensure that:

  • You replace /path/to/your/private-key.pem with the actual path to your SSH private key.
  • Your key pair has the correct permissions set (e.g., chmod 400 your-key.pem).

Conclusion

This script serves as an excellent starting point for automating the deployment of EC2 instances within a custom VPC setup. By checking for existing resources before creation, it ensures efficient resource management and prevents duplication. Running this script in AWS CloudShell streamlines the process, eliminating the need for local configurations and providing a secure, browser-based environment to manage your AWS infrastructure.

Important: Always ensure that your security groups and key pairs are configured correctly to prevent unauthorized access. Regularly review your AWS resources and clean up any unused components to maintain a secure and cost-effective environment.

This post is licensed under CC BY 4.0 by the author.