Automating DNS with Terraform or Ansible

📋 Topic Synopsis
No excerpt available
Managing DNS records manually works fine when you have just a few domains, but what happens when you're responsible for hundreds of domains with thousands of records? Or when you need to make the same DNS changes across multiple environments consistently?
That's where DNS automation comes in. Tools like Terraform and Ansible let you manage DNS as code, making your infrastructure more reliable, repeatable, and scalable.
In this topic on DNS server, we'll explore how to automate DNS management using two popular tools, even if you've never automated anything before.
1. Why Automate DNS?
IaC Principles (Infrastructure as Code)
Infrastructure as Code means managing your IT resources through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.
For DNS, this means:
- Storing DNS configurations in version-controlled files
- Making changes through code reviews and pull requests
- Having a complete history of all DNS changes
- Enabling rollbacks when something goes wrong
Benefits of DNS as Code
Transformative advantages of treating DNS as code:
- Version Control: Track every change with detailed commit messages
- Collaboration: Multiple team members can safely work on DNS
- Reproducibility: Exactly recreate environments anywhere
- Disaster Recovery: Quickly restore DNS configurations
- Compliance: Audit trails for regulatory requirements
- Testing: Validate changes before deployment
Repeatability
Manual DNS management is prone to human error. Ever made a typo in an IP address or forgotten to update a record? With automation:
- Every change follows the same tested process
- Records are always created consistently
- Complex multi-record changes happen atomically
- Environments stay in sync automatically
Error Reduction Through Automation
How automation eliminates common DNS errors:
- Syntax Validation: Automated checking catches typos
- Consistency Enforcement: Templates ensure uniform records
- Atomic Operations: All-or-nothing changes prevent partial updates
- Rollback Capability: Instant recovery from failed changes
Scalability and Efficiency
Automation enables management of complex DNS infrastructures:
- Bulk Operations: Update hundreds of records simultaneously
- Template-Based Creation: Standardize record creation patterns
- Cross-Environment Sync: Keep dev, staging, and prod consistent
- Self-Service: Enable non-experts to make safe DNS changes
2. Comprehensive Terraform for DNS
Terraform is an infrastructure provisioning tool that uses declarative configuration files to manage resources across multiple providers.
Providers: AWS, Cloudflare, Azure, Google Cloud
Terraform supports DNS automation across major providers through specific providers:
AWS Route 53 Provider:
provider "aws" {
region = "us-east-1"
# Use AWS credentials from environment or ~/.aws/credentials
}
# Configure provider with specific credentials
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
}
Cloudflare Provider:
provider "cloudflare" {
email = "[email protected]"
api_key = "your-global-api-key"
# Or use API tokens (recommended)
api_token = "your-api-token"
}
Azure Provider:
provider "azurerm" {
features {}
subscription_id = "your-subscription-id"
tenant_id = "your-tenant-id"
}
Google Cloud Provider:
provider "google" {
project = "your-project-id"
region = "us-central1"
}
Creating DNS Zones
Create a DNS zone with Terraform:
Route 53 Example:
resource "aws_route53_zone" "primary" {
name = "example.com"
tags = {
Environment = "production"
Owner = "dns-team"
CreatedBy = "terraform"
}
}
# Private hosted zone
resource "aws_route53_zone" "private" {
name = "internal.example.com"
vpc {
vpc_id = aws_vpc.main.id
}
tags = {
Environment = "production"
Visibility = "private"
}
}
Cloudflare Example:
resource "cloudflare_zone" "example" {
zone = "example.com"
plan = "free"
lifecycle {
prevent_destroy = true
}
}
Azure Example:
resource "azurerm_dns_zone" "example" {
name = "example.com"
resource_group_name = azurerm_resource_group.main.name
tags = {
environment = "production"
}
}
Google Cloud Example:
resource "google_dns_managed_zone" "example" {
name = "example-zone"
dns_name = "example.com."
description = "Managed zone for example.com"
dnssec_config {
state = "on"
}
}
Creating Records
Once you have a zone, create DNS records:
Simple A Record:
resource "aws_route53_record" "www" {
zone_id = aws_route53_zone.primary.zone_id
name = "www.example.com"
type = "A"
ttl = "300"
records = ["192.0.2.1"]
lifecycle {
create_before_destroy = true
}
}
MX Record:
resource "aws_route53_record" "mx" {
zone_id = aws_route53_zone.primary.zone_id
name = "example.com"
type = "MX"
ttl = "300"
records = [
"10 mail.example.com",
"20 backup-mail.example.com"
]
}
Multiple Records:
resource "aws_route53_record" "web_servers" {
zone_id = aws_route53_zone.primary.zone_id
name = "example.com"
type = "A"
ttl = "300"
records = [
"192.0.2.1",
"192.0.2.2",
"192.0.2.3"
]
}
Complex Record Types:
# CNAME Record
resource "aws_route53_record" "cdn" {
zone_id = aws_route53_zone.primary.zone_id
name = "cdn.example.com"
type = "CNAME"
ttl = "300"
records = ["example-cloudfront.cloudfront.net"]
}
# TXT Record for SPF
resource "aws_route53_record" "spf" {
zone_id = aws_route53_zone.primary.zone_id
name = "example.com"
type = "TXT"
ttl = "300"
records = ["v=spf1 include:_spf.google.com ~all"]
}
# SRV Record
resource "aws_route53_record" "sip" {
zone_id = aws_route53_zone.primary.zone_id
name = "_sip._tcp.example.com"
type = "SRV"
ttl = "300"
records = ["10 100 5060 sipserver.example.com"]
}
Advanced Terraform DNS Patterns
Module-Based Approach:
# modules/dns-zone/main.tf
variable "domain_name" {
description = "The domain name for the DNS zone"
type = string
}
variable "environment" {
description = "Environment tag"
type = string
}
resource "aws_route53_zone" "zone" {
name = var.domain_name
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
output "zone_id" {
value = aws_route53_zone.zone.zone_id
}
# Usage
module "production_dns" {
source = "./modules/dns-zone"
domain_name = "example.com"
environment = "production"
}
Dynamic Record Creation:
variable "web_servers" {
description = "Map of web server names to IP addresses"
type = map(string)
default = {
web1 = "192.0.2.1"
web2 = "192.0.2.2"
web3 = "192.0.2.3"
}
}
resource "aws_route53_record" "web_servers" {
for_each = var.web_servers
zone_id = aws_route53_zone.primary.zone_id
name = "${each.key}.example.com"
type = "A"
ttl = "300"
records = [each.value]
}
Terraform State Management
Remote State Configuration:
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "dns/production.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
State Locking:
# Prevent concurrent state modifications
terraform {
backend "azurerm" {
storage_account_name = "terraformstatesa"
container_name = "tfstate"
key = "dns.production.tfstate"
}
}
3. Comprehensive Ansible for DNS
Ansible is an automation tool that uses YAML playbooks to configure systems and manage resources.
Using DNS Modules
Ansible has specific modules for different DNS providers:
Route 53 Module:
- name: Create DNS record in Route 53
route53:
state: present
zone: example.com
record: www.example.com
type: A
ttl: 300
value: 192.0.2.1
overwrite: yes
wait: yes
Cloudflare Module:
- name: Create DNS record in Cloudflare
cloudflare_dns:
zone: example.com
record: www
type: A
value: 192.0.2.1
account_api_token: "{{ cloudflare_api_token }}"
proxied: yes
Azure DNS Module:
- name: Create DNS record in Azure
azure_rm_dnsrecordset:
resource_group: myResourceGroup
zone_name: example.com
relative_name: www
record_type: A
records:
- entry: 192.0.2.1
time_to_live: 300
Advanced Ansible DNS Playbooks
Complex Record Management:
---
- name: Manage DNS Records
hosts: localhost
gather_facts: no
vars:
dns_records:
- name: www
type: A
value: 192.0.2.1
- name: mail
type: A
value: 192.0.2.2
- name: "@"
type: MX
value: "10 mail.example.com"
tasks:
- name: Create DNS records
route53:
state: present
zone: example.com
record: "{{ item.name }}.example.com"
type: "{{ item.type }}"
ttl: 300
value: "{{ item.value }}"
loop: "{{ dns_records }}"
when: item.name != "@"
- name: Create root records
route53:
state: present
zone: example.com
record: "example.com"
type: "{{ item.type }}"
ttl: 300
value: "{{ item.value }}"
loop: "{{ dns_records }}"
when: item.name == "@"
Updating Records Programmatically
Dynamic Inventory Example:
- name: Update DNS with server IPs
route53:
state: present
zone: example.com
record: "{{ item.name }}.example.com"
type: A
ttl: 300
value: "{{ item.ip }}"
loop:
- { name: "web1", ip: "192.0.2.1" }
- { name: "web2", ip: "192.0.2.2" }
Integration with Dynamic Inventory:
- name: Update DNS from dynamic inventory
route53:
state: present
zone: example.com
record: "{{ inventory_hostname }}.example.com"
type: A
ttl: 300
value: "{{ ansible_default_ipv4.address }}"
delegate_to: localhost
Ansible Roles for DNS Management
Role Structure:
roles/dns_manager/
├── tasks/
│ ├── main.yml
│ ├── create_zone.yml
│ └── create_records.yml
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
└── templates/
└── zone_file.j2
Main Tasks File:
# roles/dns_manager/tasks/main.yml
---
- name: Include zone creation tasks
include_tasks: create_zone.yml
when: create_zone is defined and create_zone
- name: Include record creation tasks
include_tasks: create_records.yml
when: dns_records is defined
4. CI/CD Integration
Auto-Updating DNS on Deployment
GitHub Actions Example:
name: Deploy and Update DNS
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
TF_VERSION: 1.0.0
AWS_REGION: us-east-1
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Terraform fmt
run: terraform fmt -check
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -no-color
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
deploy:
needs: validate
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Terraform Init
run: terraform init
- name: Terraform Apply
run: terraform apply -auto-approve
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Post-deployment DNS validation
run: |
# Wait for DNS propagation
sleep 60
# Validate DNS records
dig www.example.com +short
GitLab CI Example:
stages:
- validate
- deploy
- verify
variables:
TF_ROOT: ${CI_PROJECT_DIR}/terraform/dns
validate:
stage: validate
script:
- cd ${TF_ROOT}
- terraform fmt -check
- terraform init
- terraform validate
- terraform plan -no-color
only:
- merge_requests
- main
deploy:
stage: deploy
script:
- cd ${TF_ROOT}
- terraform init
- terraform apply -auto-approve
only:
- main
environment:
name: production
verify:
stage: verify
script:
- sleep 60
- dig www.example.com +short | grep -q "192.0.2.1"
only:
- main
Validation Steps
Always validate DNS changes:
# Check that records were created
terraform show
# Test DNS resolution
dig www.example.com
# Verify record types
dig example.com MX
# Check TTL values
dig example.com | grep "TTL"
# Validate DNSSEC (if enabled)
dig +dnssec example.com
# Test from multiple locations
for resolver in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo "Testing $resolver:"
dig @$resolver www.example.com +short
done
Automated Testing
Unit Testing with Terratest:
# Test DNS record creation
func TestRoute53Records(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/dns",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
# Verify A record
record := GetRoute53Record(t, "example.com", "www.example.com", "A")
assert.Equal(t, "192.0.2.1", record.Value)
}
5. DNS Version Control Best Practices
Repository Structure
Organize your DNS code logically:
dns-infrastructure/
├── production/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── staging/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── development/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
├── modules/
│ └── dns-zone/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── scripts/
│ ├── validate-dns.sh
│ └── monitor-propagation.py
└── README.md
State Management
Use remote state for collaboration:
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "dns/production.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
# State locking to prevent corruption
workspace_key_prefix = "dns"
}
}
Change Management
Implement proper change management:
- Branch for changes: Create feature branches for DNS modifications
- Peer review: Have teammates review DNS changes
- Automated testing: Validate syntax and plan changes
- Gradual rollout: Apply to staging first, then production
- Post-deployment validation: Verify changes worked as expected
Pull Request Template
## DNS Change Request
### Description
[Brief description of the DNS changes]
### Records Affected
- [ ] A records
- [ ] CNAME records
- [ ] MX records
- [ ] TXT records
### Impact Assessment
- [ ] No user impact
- [ ] Temporary downtime expected
- [ ] Performance improvements
### Testing Plan
- [ ] Validate in staging environment
- [ ] Check DNS propagation
- [ ] Verify application functionality
### Rollback Plan
[Steps to revert these changes if needed]
Security Best Practices
Credential Management:
# Use variables for sensitive data
variable "cloudflare_api_token" {
description = "Cloudflare API token"
type = string
sensitive = true
}
# Never hardcode credentials
# Use environment variables or secure vaults
Access Control:
# Restrict state access
terraform {
backend "gcs" {
bucket = "terraform-state-bucket"
prefix = "dns/"
# Use service account with minimal permissions
}
}
6. Monitoring and Alerting
DNS Health Monitoring
# Ansible playbook for DNS monitoring
- name: Monitor DNS Records
hosts: localhost
tasks:
- name: Check DNS resolution
shell: dig {{ item }} +short
register: dns_result
failed_when: dns_result.stdout == ""
loop:
- www.example.com
- mail.example.com
- api.example.com
- name: Alert on DNS failures
slack:
token: "{{ slack_token }}"
msg: "DNS resolution failed for {{ item.item }}"
when: item.failed
loop: "{{ dns_result.results }}"
Performance Monitoring
#!/bin/bash
# Monitor DNS query performance
resolvers=("8.8.8.8" "1.1.1.1" "9.9.9.9")
domain="example.com"
for resolver in "${resolvers[@]}"; do
start_time=$(date +%s%N)
dig @$resolver $domain >/dev/null 2>&1
end_time=$(date +%s%N)
duration=$((($end_time - $start_time) / 1000000))
echo "Resolver $resolver: ${duration}ms"
done
7. Summary & Key Takeaways
DNS automation transforms DNS management from a manual, error-prone process into a reliable, scalable infrastructure component. Here are the essential points to remember:
- Infrastructure as Code: Treat DNS configurations as version-controlled code
- Tool Selection: Choose Terraform for declarative management or Ansible for procedural automation
- Provider Integration: Leverage native cloud provider integrations
- CI/CD Pipeline: Integrate DNS changes into deployment workflows
- Version Control: Maintain organized repositories with proper state management
- Security: Implement secure credential handling and access controls
- Monitoring: Continuously monitor DNS health and performance
- Best Practices: Follow established patterns for reliability and collaboration
Whether you choose Terraform for its declarative approach or Ansible for its procedural flexibility, automating your DNS management pays dividends in reliability and operational efficiency.
As organizations grow and DNS complexity increases, automation becomes not just beneficial but essential. By implementing these practices, you can ensure your DNS infrastructure scales with your organization while maintaining the reliability and security required for modern digital operations.
The investment in DNS automation pays off through reduced errors, faster deployments, improved security, and better collaboration among team members. Start small with basic record management and gradually expand to more sophisticated automation patterns as your comfort and requirements grow.