DNS is one of the cornerstones of the internet, translating words we know as domain names into the combination of numbers we know as IP addresses. The process of registering a domain, pointing it to your chosen DNS providers’ nameservers and then setting up your DNS records has remained mostly the same since inception. Without DNS we’d be stuck remembering combinations of IP addresses to visit our favourite websites.
In 2010, AWS introduced their flavour on DNS by releasing a service called Route 53. Route 53 includes all the usual DNS functionality, along with more advanced features such as alias records, failover and geographical routing (just to name a few), and a 100% uptime SLA. I almost exclusively use and recommend Route 53 for DNS for its reliability, flexibility and ease of use.
For developers, DNS is one of those components which can be messy, difficult to track and generally a manual process. On AWS, when your app gets to the point where you start splitting environments or services across multiple AWS accounts (this helps place boundaries around environments/services and limits the blast radius of failures), things start to get even complicated with DNS. Questions get asked such as, Who owns DNS? Who can make changes? How do changes get made? When you have multiple teams who want to make changes to the DNS zone’s records, and everyone is granted access, it’s not uncommon for free for all of changes to occur, usually with little awareness across teams, no accountability or version control.
One solution which I often see utilized is a centralized DevOps or SRE team as gatekeepers of the DNS zone. The issues I have with this method are, changes can be delayed depending on the workload of the team, or introduce human error between the party requesting the change and the party making the change. What if these problems could be solved by automating the process as part of a deployment? How do we do this without granting access to the entire DNS zone in a single AWS account? We can use delegation.
In a similar way to delegating nameservers for an entire domain, it’s possible to delegate parts of our domain to other Route 53 zones, eg. a delegation for each service or environment in their respective AWS accounts. Let’s say we have domain.com in one AWS account and we want to the development team to be able to make changes to any subdomain of foo.domain.com, but in the foo AWS account? Using delegation this is possible. For example (assuming we had this in place already), if a user was to lookup api.foo.domain.com, initially, the domain.com nameservers will respond to the request and find anything under foo.domain.com has been delegated to another set of nameservers. DNS will then forward the request to the nameservers for foo.domain.com, which ultimately resolves the IP address for any record under the delegated parts of the domain.
Note: To get a good grasp on how this process works cross-account, I’d recommend using at least 2 AWS accounts. If you don’t have two accounts (and don’t want to sign up for a second one), you can use a single account. I’d also advise testing this on a non-production domain.
Let’s Get Hands-On
I’m going to take you through how this can be set up in Route 53 using the Console, but tools like CloudFormation, Terraform etc can be used as well.
In AWS Account 1, create a new public hosted zone in Route 53 — I’m going to refer to the AWS documentation at this point as it’s relatively straight forward:
Once created, you’ll need to update the nameservers in your domain registrar to point to the ones created by your Route 53 zone. Each domain registrar is slightly different so you will need to follow their instructions if you’re not sure how to do this. The nameservers generated by Route 53 should something like this:
ns-xxxx.awsdns-xx.net ns-xxxx.awsdns-xx.org ns-xxxx.awsdns-xx.com ns-xxxx.awsdns-xx.co.uk
I’ve also added a few initial records to my domain’s zone, gavinlewis.cloud, like A and MX records to mimic what a real zone might look like (private IP addresses are illustrative purposes only).
Doing a dig on my domain shows that it’s working, the root apex record is resolving to 192.168.0.1 as expected.
Now let’s pretend the foo team wants to create and manage their DNS entries as part of the services (eg. an API) running in their AWS account. In AWS Account 2 a new public hosted zone for the domain foo.gavinlewis.cloud will need to be created, and for testing later I’ve also created two A records, foo.gavinlewis.cloud and api.foo.gavinlewis.cloud.
Take note of the entries in foo.gavinlewis.cloud, then log in again to AWS Account 1 and navigate to your DNS zone in Route 53. In the gavinlewis.cloud zone, I’ll create a nameserver (NS) record for foo.gavinlewis.cloud and will use the nameserver records generated when I created foo.gavinlewis.cloud in AWS Account 2 as the values.
To check if it’s working, we can dig foo.gavinlewis.cloud to see what the response is — great, we have a response and it corresponds to the same IP address as in the zone.
What about api.foo.gavinlewis.cloud? We have success! It also corresponds to the same IP address which was configured in the foo.gavinlewis.cloud zone.
Building upon both of the AWS Account strategies earlier, they would look something more like the below diagrams when we start to delegate parts of our primary zone to the other AWS Accounts. This method means management of each part of the domain is completely segregated in the services scenario, and almost completely segregated the environments scenario. The reason for this is, you may want your production environment to manage records in the root of the domain, eg. app.domain.com. There are some other approaches to this problem, but I’ll leave them for another time.
Although this example was demonstrated using the console, in the real world I recommend to use CLI scripts, a tool like CloudFormation or Terraform and retain change history in a version control system like git. This way we can ensure changes are automated and should a change ever need to be reverted or referred to, we have a log of all changes.
One thing to note, although the cost of a Route 53 zone is inexpensive (starts at $0.50 per zone), if you had 10 services this works out to be a cost of $5.50 per month, as you have a zone for each of the services plus the primary domain. You’ll also likely see increased queries as your requests to delegated records are forwarded. These additional costs aren’t going to break the bank — just something to be aware of.