Adding EC2 instances to Route53

Today’s nifty bit of code is a startup/shutdown script for linux that allows you to add an EC2 instance to Amazon’s Route53 DNS automatically when you start up the instance, and remove it when the instance is knocked down. This script also allows you to add the instance to a weighted round-robin group.

This makes use of the very useful Python-based boto tool which is available in both Yum and Debian repositories under the package name python-boto.

Create this script in /etc/init.d and make it executable, and then add it to the requisite rcX.d directory for startup/shutdown.


#!/bin/bash
#
#       /etc/rc.d/init.d/<servicename>
#
#       Registers DNS with Route 53
#
# chkconfig 345 20 80
#

# Source function library.
. /etc/init.d/functions

# Best practice here is to create an IAM user/group with access to only 
# Route53 and use this keypair.
export AWS_ACCESS_KEY_ID=<Access Key ID Here>
export AWS_SECRET_ACCESS_KEY=<Secret Key ID here>

# Use the domain you have configured in Route 53
export DOMAIN=ianbeyer.com

# This is the hostname of the Weighted Round Robin Group.
export RR=wrr

# Gets the current public hostname - you don't want to use an A record with 
# the IP because the CNAME works in conjunction with Amazon's internal DNS
# to correctly resolve instances to their internal address
export PUB_HOSTNAME=`curl -s --fail http://169.254.169.254/latest/meta-data/public-hostname`

# Gets the Route 53 Zone ID
export ZONEID=`route53 ls | awk '($2 == "ID:"){printf "%s ",$3;getline;printf "%s\n",$3}' | grep $DOMAIN | awk '{print $1}'`

start() {
        echo -n "Registering host with Route 53 : "

# This is the base name of the host you want to use. It will increase the index
# number until it finds one that's not in use, and then use that. 

        export HOST=amz
        export HOSTINDEX=1

        INUSE=`route53 get $ZONEID | grep ${HOST}${HOSTINDEX}\.${DOMAIN} | wc -l`
        while [[ $INUSE > 0 ]]
        do
            HOSTINDEX=$((HOSTINDEX + 1))
            INUSE=`route53 get $ZONEID | grep ${HOST}${HOSTINDEX}\.${DOMAIN} | wc -l`
        done
        if [[ "$HOSTINDEX" == "1" ]]; then
            FQDN="${HOST}${HOSTINDEX}.${DOMAIN}"
        else
            # set the new fqdn hostname and shortname
            FQDN="${HOST}${HOSTINDEX}.${DOMAIN}"
            SHORTNAME="${HOST}${HOSTINDEX}"
        fi

        # Set the instance hostname -- If you want to make sure that bash
        # updates the prompt, run "exec bash" after the script. If you do so
        # in the script, bad stuff happens on startup.

        hostname $FQDN
        echo -n $FQDN


        # Add the DNS record
        RESULT=`route53 add_record $ZONEID $FQDN CNAME $PUB_HOSTNAME | grep "PENDING"`
        if [[ "$RESULT" == "" ]]; then
                echo "... failed.";
        else
                echo "... success.";
        fi

        # Add the CNAME record
        echo -n "Adding host to round-robin group...";

        # Checking to make sure it's not already there. 
        CNAME=`route53 get $ZONEID | grep $RR | grep $PUB_HOSTNAME | wc -l`
        if [[ $CNAME = 0 ]]; then
                RESULT=`route53 add_record $ZONEID $RR.$DOMAIN CNAME $PUB_HOSTNAME 60 ${HOST}${HOSTINDEX} 1 | grep "PENDING"`
                if [[ "$RESULT" == "" ]]; then
                        echo "... failed.";
                else
                        echo "... success.";
                fi
        else
                echo "already exists, ignoring";
        fi

}


stop() {
        echo -n "Deregistering host with Route 53 : "
        HOST=`hostname | cut -f1 -d.`
        FQDN=`hostname`

        # check to make sure it exists in Route53
        CNAME=`route53 get $ZONEID | grep $FQDN | grep $PUB_HOSTNAME | wc -l`
        if [[ $CNAME > 0 ]]; then
                RESULT=`route53 del_record $ZONEID $FQDN CNAME $PUB_HOSTNAME | grep "PENDING"`
                if [[ "$RESULT" == "" ]]; then
                        echo "... failed.";
                else
                        echo "... success.";
                fi
        else
                echo "... not found, ignoring";
        fi

        echo -n "Deregistering host from RR CNAME..."

        # Checking to make sure it exists
        CNAME=`route53 get $ZONEID | grep $RR | grep $PUB_HOSTNAME | wc -l`
        if [[ $CNAME > 0 ]]; then
                RESULT=`route53 del_record $ZONEID $RR.$DOMAIN CNAME $PUB_HOSTNAME 60 ${HOST} 1 | grep "PENDING"`
                if [[ "$RESULT" == "" ]]; then
                        echo "... failed.";
                else
                        echo "... success.";
                fi
        else
                echo "... not found, ignoring";
        fi

        # resets the hostname to default
        hostname `curl -s --fail http://169.254.169.254/latest/meta-data/hostname`


}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo "Usage: <servicename> {start|stop|restart}"
        exit 1
        ;;
esac
exit $?

Many thanks to Dave McCormick for his blog post on the subject from which I borrowed heavily.

10 Comments On “Adding EC2 instances to Route53”

  1. If I understand this correctly, this script will add the server to Route 53 when it starts up, and remove on a shutdown, but how do you handle the case where a server crashes (or otherwise does not shutdown gracefully), you would really still want to remove it from the weighted round robin, otherwise requests will still resolve to the offline server.

    Reply

    • correct – you could do this with an external monitor that checks for ping and service availability once a server is up,  and removes the server if it goes down. 

      Reply

  2. Pingback: Adding EC2 instances to Route53 | Pinehead.tv

  3. Pingback: Add EC2 to Route53 « 0ddn1x: tricks with *nix

  4. At line 77 you try to create a RR of CNAMEs. Is that right? You can’t do that. You can only create RR of MX, NS and A records, not CNAMEs.

    Reply

    • Is that a recent change to the API? Because when I wrote this, it had no problem creating CNAMEs. That’s the Best Practice for anything within Amazon’s cloud, as communication between the boxes resolves to the internal IP address when using the CNAME.

      Cloudfront also requires the use of CNAMEs or Aliases (internal to Route53). 

      Reply

      • A Round robin of CNAMEs is illegal according to rfc2181:
        “[…] That is, for any label in the DNS (any domain name)
        exactly one of the following is true:
        + one CNAME record exists, optionally accompanied by SIG, NXT, and
        KEY RRs, […]”

        I just tried to create a round robin of CNAMEs on Route53 and got the expected response:

        “RRSet of type CNAME with DNS name testcname.mydomain. does not contain exactly one resource record.”

        Either use a RR of IP addresses or do a weighted round robin of CNAMEs, all with the same weight (see http://docs.amazonwebservices.com/Route53/latest/DeveloperGuide/WeightedResourceRecordSets.html for more info on Weighted records).

        Your script tried to create a “simple” (i.e. regular) round robin and therefore it fails.
        I just replaced “CNAME” with “A” at line 77 and public-hostname with public-ipv4 at line 27 to make it work.

        Reply

        • That’s partly why Route53 does Aliases. When doing a round-robin, you need to make it weighted. 

          Reply

          •  Just a word of advice: I was fooled into thinking that the script doesn’t work because when resolving a weighted round robin, Route53 only returns one CNAME (in rotation), not the whole set.
            So don’t be surprised if you start N instances and when you try to resolve the round robin you get only 1 IP address at a time.
            If you want to have all the IPs in a single record you have to use an A record.

          • That’s a good point. If you want it to return all the records and have the client pick, you’ll definitely need an A record. 

            The latency-weighted stuff is pretty cool for geographical load balancing, too. 

Leave a Reply

Your email address will not be published. Required fields are marked *