Amazon EC2 and Route53 - one click setup

How to deploy an EC2 instance integerated with Route53. The solution can be extended to deploy custom artefacts via user-data script.

I have been using AWS services at my workplace and on my side projects for several years. While the AWS console provides a quick way to jumpstart, it does not entirely integrate with the typical software development lifecycle.

At my workplace, we have been using Terraform to provision the infrastructure, but for a side project or working on a proof of concept, I am usually looking for a quicker solution without much overhead. AWS provides a fully functional command line toolset that is easy to configure and integerates well with AWS APIs.

In this post, I will share my workflow to provision an EC2 instance with a user-data script integrated with a Route53 DNS name. Depending on the mapping criteria, one can use a CNAME (EC2 public DNS) or an A (EC2 IPv4 address) record for the set up.

Pre-requisite

The following is a list of pre-requisites for this set up:

  1. An AWS account with access to AWS CLI.
  2. A registered domain name within Route 53. A hosted zone is also required with the same name server configured for the domain name. If you have deleted the default hosted zone for your domain name and created a new one, chances are that the newly created hosted zone will have a different NS than the domain name, so make sure to sync those in that case.
  3. A user-data script to set up a service on the EC2 instance.
  4. Security group to allow inbound traffic.
  5. Instance type (small, micro, etc.) and AMI_ID to uniquely identify an image to be used.
  6. A key pair to enable access to the EC2 instance.

Setup

The following script functions can be executed in the order to replicate this setup.

EC2 Instance Creation

The first step is to create an EC2 instance. Once the instance is created successfully, we wait for it to be started so that we can query the INSTANCE_ID. The --user-data flag is used to specify the script we can use to set up the actual deployment candidate on the machine.

# REGION    : AWS region where the instance is to be launched
# AMI_ID    : Image identifier
# KEY_NAME  : Key-pair to be used for accessing machine
# $1        : User-Data script
function createInstanceWithUserData(){
    # Create EC2 instance in default subnet
    # of default vpc in the given region
    echo "launching instance with user data from: " $1
    INSTANCE_ID=$(aws ec2 run-instances \
        --count 1 \
        --region ${REGION} \
        --instance-type ${INSTANCE_TYPE} \
        --image-id ${AMI_ID} \
        --security-group-ids ${SECURITY_GROUP_ID} \
        --key-name ${KEY_NAME} \
        --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=${TAG_NAME}}]" \
        --user-data file://$1 \
        --output text --query 'Instances[0].InstanceId')

    # Wait for the instance to be running
    echo "waiting for the instance launch to complete"
    echo "EC2 instance created successfully!"
    aws ec2 wait instance-running --region $REGION --instance-ids $INSTANCE_ID
}

Query Instance Details

The next step is to query the newly generated instance based on the region and instance_id we received in the last step. On a high level, we are looking for the following details:

  1. Public IP address.
  2. Public DNS name.
  3. The route53 hosted zone in which the domain name is mapped.
# INSTANCE_ID: Unique identifier for the newly created instance
#              This is logged on console in the createInstanceWithUserData
#              function.
function fetchIpAddress(){
    # Get public IP address of the instance 
    echo "fetching ip-address: "
    PUBLIC_IP=$(aws ec2 describe-instances \
        --region ${REGION} \
        --instance-ids ${INSTANCE_ID} \
        --query 'Reservations[0].Instances[0].PublicIpAddress' \
        --output text)

    PUBLIC_DNS=$(aws ec2 describe-instances \
        --region ${REGION} \
        --instance-ids ${INSTANCE_ID} \
        --query 'Reservations[0].Instances[0].PublicDnsName' \
        --output text)

    # the output is /hostedzone/xxxxxxxxxx
    # so remove first 12 characters
    ZONE=$(aws route53 list-hosted-zones-by-name \
        --dns-name xxxxxxxxx.org \
        --query "HostedZones[].Id" \
        --output text | cut -c 13-)               

    echo "Instance ID: ${INSTANCE_ID}"
    echo "Public IP: ${PUBLIC_IP}"
    echo "Public DNS: ${PUBLIC_DNS}"
    echo "ZONE: ${ZONE}"
}

Map DNS

The final step is to create a new record in the hosted zone. The record can be a CNAME or an A record.

# PREFIX      : subdomain for xxxxx.org
# PUBLIC_DNS  : autogenerated dns name from fetchIpAddress
#               function
function registerDNS(){
    echo "Registering DNS with route53 entry"
    aws route53 change-resource-record-sets \
      --hosted-zone-id ${ZONE} \
      --change-batch '
      {
        "Comment": "record set created via cli for xxxxxxxxx.org and ec2",
        "Changes": [{
          "Action"              : "CREATE",
          "ResourceRecordSet"  : {
            "Name"              : "'"$PREFIX"'.xxxx.org",
            "Type"             : "CNAME",
            "TTL"              : 120,
            "ResourceRecords"  : [{
                "Value"         : "'"$PUBLIC_DNS"'"
            }]
          }
        }]
      }
    '
}

The above functions can be combined and executed as a single script to quickly set up an EC2 machine mapped to a domain name. On similar lines, we can write a cleanup script to clear the same once we finish the POC. Please drop me an email if you need the cleanup script; I’ll be happy to share the details.

References

  1. Routing traffic to an Amazon EC2 instance
  2. Create Hosted Zone
  3. Change Resource Record Set

That is all for this post. If you want to share any feedback, please drop me an email, or contact me on any social platforms. I’ll try to respond at the earliest. Also, please consider subscribing feed for regular updates.

Be notified of new posts. Subscribe to the RSS feed.