Thursday, May 15, 2014

Have your AWS server set its own DNS on boot

One of the huge advantages of using Amazon's Web Services is the fact that you can turn virtual machines on and off, and only pay for the time that the instance is running.  This can be really cool if you need a staging server of some sort, and only need it to be running during the work day, or even just on the days you are working on a particular project.  One huge disadvantage to  this, though, is that you get a new IP address every time you boot an instance. Sure, you could use an elastic IP address, or you could manually set your DNS record each time you boot, but why pay for the IP address when you're not using it (Amazon charges you by the hour that it's not connected to an instance)? And why go through the trouble of setting your own DNS?  You have a server at your fingertips, let it grow up and take care of itself!

Unfortunately, there is not a Route 53 implementation in the command line tools that are automatically installed on the Amazon AMI.  There is, however, a great application by Barnaby Gray called cli53 which is on github here: https://github.com/barnybug/cli53.

Installation is painless via the python pip package management system.  Here is an install on a fresh Amazon AMI instance:

First, I'm installing pip (because it doesn't come automatically):

$ sudo yum install python-pip

Now we can install cli53:

$ sudo pip install cli53

That's it, it is now installed. In order for pip to run it needs to have either the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables set, or they need to be placed in a file located in ~/.boto. I'm using the boto method, so using your favorite text editor create a file that contains the following:

[Credentials]
aws_access_key_id = XXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX

You can also set up your server in a role that has access to Route 53, and you won't have to manually set these credentials.

The next step is to install the script that will set your DNS entry for the server.  Amazon provides several URLs that you can request from your server that gives you information about your server.  We'll be using http://instance-data.ec2.internal/latest/meta-data/public-hostname which returns our public hostname entry.  We'll use this to add a CNAME entry to our Route53 DNS.


#!/bin/bash

# This script will set the DNS record for this server.  Run from rc.local to set on boot.
# Requires cli53 https://github.com/barnybug/cli53

# This is the domain we are updating
DOMAIN=example.com
# This is the subdomain
SUBDOMAIN=staging

# Obtain our public host name
PUBLIC_HOST_NAME="`wget -q -O - http://instance-data.ec2.internal/latest/meta-data/public-hostname || die \"wget public-hostname has failed: $?\"`"
test -n "$PUBLIC_HOST_NAME" || die 'cannot obtain public-hostname'

echo Setting $SUBDOMAIN.$DOMAIN to $PUBLIC_HOST_NAME
echo

cli53 rrcreate $DOMAIN $SUBDOMAIN CNAME $PUBLIC_HOST_NAME --ttl 300 --replace

Let me go over this line by line.  First we have two variables that we're setting: $DOMAIN and $SUBDOMAIN.  $DOMAIN is a domain that you have in Route53; it's the primary domain name, not the subdomain.  The $SUBDOMAIN is the actual subdomain element that you are wanting to set the CNAME for.  In my case, I'm using example.com and setting the subdomain staging, so my server will receive the DNS entry staging.example.com.

The next part queries http://instance-data.ec2.internal/latest/meta-data/public-hostname to get our public hostname.  This is the long public DNS entry that looks like ec2-X-X-X-X.compute-1.amazonaws.com, and we'll set our CNAME to this value.  We could just as easily get the IP address, but using the subdomain has the advantage of resolving to an internal IP address from another AWS instance, making communication between AWS instances available on the internal network.

Lastly, the cli53 command replaces the CNAME entry for this hostname. This will only work if you don't have an existing setting for this subdomain, or if the existing entry is already a CNAME; i.e. it  will fail if you have an A record.

Once you have the script ready, run it from the command line. You should see an output something like this:


Setting staging.example.com to ec2-X-X-X-X.compute-1.amazonaws.com

Success
ChangeInfo:
  Status: PENDING
  SubmittedAt: 2014-05-15T15:51:11.975Z
  Id: /change/C3LPBE20HC9ITN

If you now go into your Route53 console, you should see the new entry with a TTL of 5 minutes.

The next step is to get this script to execute every time the machine boots. Enter cron, which has a handly option to execute on boot.

Using your text editor, create a file at /etc/cron.d/set_route_53 with the following contents:


EC2_HOME=/opt/aws/apitools/ec2
JAVA_HOME=/usr/lib/jvm/jre

@reboot [user] /usr/local/bin/set_route_53

where [user] is the username you installed the .boto file in.  The environment variables at the top are required to have the environment variables accessible to the script, and those are variables that are used in the AWS command line utilities.

Now, reboot your machine.  If you have mail configured for the user running the cron job, you'll get an e-mail with the output of the command.  If you stop and then start your instance, you should see the Route53 entry update.  With the TTL set to 5 minutes, you should always be able to access your instance within 5 minutes of its boot being complete, and you don't have to lift another finger!

No comments:

Post a Comment