How To Import Your Aws Route 53 Records To Terraform


Until recently we used the web-interface of Route 53, the AWS DNS service, to manage the DNS records for our startup, ParrotPolls.com. Making manual changes to such a critical piece of infrastructure always made me nervous. I knew there was a better way, but I had never taken the time to set this up.

The incident which got me to set up terraform (a command-line tool for managing cloud infrastructure) was when I realized I was about to lose track of which DNS records serve which purpose. We had been trying a couple of different service providers, and in particular the email-providers usually require setting up TXT and CNAME records for DKIM and SPF. If I had used an infrastructure-as-code-approach, like terraform, I could have just looked at the history of the git-repo. That’s what I want!

So I sat down to figure it out.

The Goal

“Importing” an existing set of DNS records from AWS Route 53 “into” terraform, so that I can check this configuration in form of tf-files into a git-repo.

⚠️ Please be careful ⚠️

I am not responsible if you accidentally spin-up hundreds of EC2 instances and incur a huge bill for your company, when you follow this step-by-step guide.

While the steps in this tutorial should be safe (I followed them myself), I cannot guarantee that. If you are completely new to terraform, do yourself a favor and create a new AWS account that you can use as a playground.

Prerequisites

You will need the following software packages. Please follow their respective instructions to install them:

Also, as a terraform-beginner I found this list of best-practices helpful.

Step-By-Step Guide

  1. Configure the AWS provider by creating a provider.tf-file with the following content:

    provider "aws" {
    version = "~> 2.0"
    region = "eu-west-1"
    shared_credentials_file = "~/.aws/credentials"
    profile = "default"
    }
    

    (Assumptions: You are running OSX or Linux. You have the AWS-CLI installed and configured with a default profile.)

  2. Run terraform init to install the AWS provider plugin. (Note: This will have installed the plugin for terraform. You will still need to have installed this plugin for terraformer separately. The relevant sentence from their documentation is: “Copy your Terraform provider’s plugin(s) to folder ~/.terraform.d/plugins/{darwin,linux}_amd64/, as appropriate.”)

    $ terraform init
    
  3. Run terraform import, which will create a ./generated-folder containing a set of *.tf-files:

    $ terraformer import aws --resources=route53 --connect=true --regions=eu-west-1 --profile=default
    
    2020/04/16 22:10:29 aws importing default region
    2020/04/16 22:10:29 aws importing... route53
    2020/04/16 22:10:34 Refreshing state... aws_route53_record.tfer--ZK97EJ5LKJOBM_parrotpolls-002E-com-002E-_A_
    2020/04/16 22:10:34 Refreshing state... aws_route53_zone.tfer--ZK97EJ5LKJOBM_parrotpolls-002E-com
    ...
    2020/04/16 22:10:36 aws Connecting.... 
    2020/04/16 22:10:36 aws save route53
    2020/04/16 22:10:36 aws save tfstate for route53
    
  4. Verify that the exported tf-files work as expected

    $ terraform plan -out=plan.txt ./generated/aws/route53/
    ...[lot's of output omitted]...
    ------------------------------------------------------------------------
    This plan was saved to: plan.txt
    
    To perform exactly these actions, run the following command to apply:
    terraform apply "plan.txt"
    

Let’s print the content of the plan.txt file:

terraform show plan.txt 

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_route53_zone.tfer--ZK97EJ5LKJOBM_parrotpolls-002E-com will be updated in-place
  ~ resource "aws_route53_zone" "tfer--ZK97EJ5LKJOBM_parrotpolls-002E-com" {
      + comment       = "Managed by Terraform"
        force_destroy = false
        id            = "ZK97EJ5LKJOBM"
        name          = "parrotpolls.com."
        name_servers  = [
            "ns-140.awsdns-17.com",
            "ns-1426.awsdns-50.org",
            "ns-1772.awsdns-29.co.uk",
            "ns-761.awsdns-31.net",
        ]
        tags          = {}
        zone_id       = "ZK97EJ5LKJOBM"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Make sure this shows something that you would expect. In particular, you should see the 0 to add, 1 to change, 0 to destroy.

  1. Remove unused files

Unless you want to import more settings using terraformer, you can delete the copy of the AWS provider plugin from ~/.terraform/plugin/....

What I did next

I moved all files from the generated-folder into a git-repository, including terraform.tfstate, which contains the state, which seems to be important. (If you work in a team, you’ll want to use remote-state. See the docs for details.). I then checked everything (except the .terraform-subfolder) into git.

After doing that I started commenting all the records in my route53_record.tf file, one comment for each record. Here is an example:

# MX records   <--- This is the comment 🤓
resource "aws_route53_record" "tfer--ZK97EJ5LKJOBM_parrotpolls-002E-com-002E-_MX_" {
  name    = "parrotpolls.com"
  records = ["5 alt2.aspmx.l.google.com.", "5 alt1.aspmx.l.google.com.", "1 aspmx.l.google.com."]
  ttl     = "600"
  type    = "MX"
  zone_id = aws_route53_zone.tfer--ZK97EJ5LKJOBM_parrotpolls-002E-com.zone_id
}

My new workflow for making changes to DNS records

  • make the changes in the tf-files
  • run terraform plan -out=plan.txt
  • check if the plan looks like what I would expect
  • if the plan looks ok, I commit the changed tf-files to git (not the plan.txt)
  • run terraform apply plan.txt to apply the changes
  • commit the changed state (terraform.tfstate) to git
  • delete plan.txt