Automatically Updating Route53 with Public IP Address from EC2 Instance

So when you're trying to bootstrap a SaaS offering, and your wife has access to the credit card statements, sometimes you go to extreme lengths to shave costs. (And end up going down a very deep yak shaving hole.)

In my case, rather than paying for a hosted AuthN solution, I'm self-hosting on EC2 instances. (Because one day my product will be immensely popular and have millions of users and I don't want to pay the $.02 per MAU when I get there.) And because this is my dev environment, I'll be shutting down my instances at the end of the day so I'm not paying the $0.0168 per instance-hour that I'm not using them.

As I said: extreme lengths to shave costs.

In any case, when an EC2 instance is restarted, AWS will assign a new public IP address to the instance. Which means that any Route53 record sets for the host will need to be repopulated with the instance's new public IP address.

If you search the Internet, you'll find several solutions for doing this, but everything I found out there was too complex for my tastes. This really is a simple requirement, and it turns out that meeting this requirement really is pretty simple. (At least on Amazon Linux, where the aws CLI is preinstalled.)

The solution is to use the AWS CLI to update the DNS record. The command looks like this:

aws route53 change-resource-record-sets --hosted-zone-id <ZoneID> --change-batch file://<filename>

The command parses a JSON file with the changeset data in it. To update a single A record with a single IP address, the file looks like this:

{
    "Changes": [
        {
            "Action" : "UPSERT",
            "ResourceRecordSet": {
                "Name": "<hostname>.your.domain.com",
                "Type": "A",
                "TTL": 60,
                "ResourceRecords": [
                    {"Value": "<ip>"}
                ]
            }
        }
    ]
}

The placeholders for the hostname and the IP address should be obvious there. Save this file as route53update.template.

So to execute the aws CLI command above, we need a script to retrieve three values:

  • The DNS Zone ID
  • The hostname
  • The public IP address

There are two ways to get the Route53 Zone ID: either hardcode it into the script, or put it in a tag on the instance. If you put the Zone ID in a tag, ensure that "Allow tags in instance metadata" is enabled for the instance. To get the value of the tag from inside the instance, run curl http://169.254.169.254/latest/meta-data/tags/instance/DNSZONE.

Getting the hostname is easy: use the hostname command. (The hostname can be set with sudo hostnamectl set-hostname myhostname and rebooting.)

The public IP address can be found with curl http://169.254.169.254/latest/meta-data/public-ipv4.

Tying it all together, updating the public IP address in Route53 requires us to:

  1. Create a .json file from the template, replacing the IP address and hostname placeholders, and then
  2. Run the aws cli to update the Route53 record, feeding it the newly created .json file

Step 1:

sed 's/<hostname>/'"$(hostname)"/ route53update.template | sed 's/<ip>/'"$(curl http://169.254.169.254/latest/meta-data/public-ipv4)"/ > route53update.json

Step 2:

aws route53 change-resource-record-sets --hosted-zone-id $(curl http://169.254.169.254/latest/meta-data/tags/instance/DNSZONE) --change-batch file://route53update.json

So create a shell script with those two lines in it, updateroute53.sh:

#!/bin/bash
cd "$(dirname "$0")"
sed 's/<hostname>/'"$(hostname)"/ /full/path/to/route53update.template | sed 's/<ip>/'"$(curl http://169.254.169.254/latest/meta-data/public-ipv4)"/ > route53update.json
aws route53 change-resource-record-sets --hosted-zone-id $(curl http://169.254.169.254/latest/meta-data/tags/instance/DNSZONE) --change-batch file://route53update.json

Run chmod +x updateroute53.sh to make the script executable.

The final piece of the puzzle is giving your EC2 instance permissions to update records in the Route53 hosted zone. Create an IAM role for EC2, and in the inline policy, grant the route53:ChangeResourceRecordSets permission on your hosted zone. This can be done in the visual editor, or use this JSON:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/<YourZoneID>"
        }
    ]
}

Note that this does give your instance permissions to update any record in the zone, so be aware of this security issue. Use conditionals on the Allow grant to limit the scope of what the role can do if you'd like.

Attach the new IAM role to your instance and run your script to ensure it works.

Lastly, to make this script run every time the instance boots. Cloud-init will automatically run any scripts in /var/lib/cloud/scripts/per-boot/ on every boot. So either put your script in that directory, or create a symlink there.

One thought on “Automatically Updating Route53 with Public IP Address from EC2 Instance”

Leave a Reply

Your email address will not be published.


*