Magitech.ca written by Wyatt Zacharias

Managing a Route53 DNS zone with Terraform

As we continue our migration of resources into AWS, one item on the list was our public DNS zone and all of the records within. Many cloud native solutions have a specific dependency on DNS, in that they are otherwise completely dynamic and rely on DNS being automatically updated whenever these dynamic resources are created or destroyed. This is easily accomlished with a service such as Route53, which provides full automation control through the API to adjust DNS records as resources are created or destroyed. This is great when you’re using an automation tool to deploy resources and at the same time create the appropriate DNS records, but what about when you have a mixed environment where you’re still maintaining traditional DNS records that are manually created to correspond to resources and have automatically created records deployed with other resources.

Since we use Terraform at work to dynamically deploy our AWS resources, I was looking for a simple but scalable method for maintaining a set of static DNS records with Terraform. This is actually a bit difficult as Terraform doesn’t handle certain dynamic input sets well. We use the aws_route53_record resource in Terraform to create DNS records in Route53. This resource accepts a list of strings for the data portion of the record, but only accepts a single string for the name of the record. Now we could use the Terraform count directive to create a number of these resources relative to the number of input records that we give it but this is where Terraform falls short in its dynamic data handling. Resources created with a count are assigned to a speicifc index in the state file for that resource. Any changes to the dataset such as an insertion or removal will reorder the resources and cause Terraform to destroy and recreate all DNS records in the data set.

Essentially there is no scalable way to deploy a large set of DNS records with Terraform in a single apply. Instead I decided the best way to deploy the set of resources would be to do so individually per record. This is slightly less efficient as it creates an individual state file for each record, but resolves all of the other issues and makes all of your DNS records independent from one another. What it looks like is instead of a single tfvars files with all the records, we create a subfolder to represent each DNS record, and a tfvars file within that contains input data for only a single record. Then we run an apply-all on the parent folder which will then run the same DNS record module for each tfvars file which creates a single DNS record.

It looks something like this:

env_name/
   region/
    dns_records/
      record1.example.com/
        terraform.tfvars
      record2.example.com/
        terraform.tfvars
      record3.example.com/
        terraform.tfvars

Running an apply-all in the dns_records folder would run each tfvars file in each sub-directory. Or if you were running an apply-all in the top level folder then all other tfvars files in the tree would be run in addition.