DNS解析记录看似简单,却是线上服务稳定运行的第一道入口。一旦某条A记录被误删或CNAME指向了错误地址,用户访问会立刻受影响。传统做法是在云厂商控制台手工维护,记录一多就难以追踪谁改了什么、什么时候改的。Terraform把DNS基础设施当作代码来管理,所有解析记录写进配置文件,纳入Git版本控制,变更前可以预览、变更后可以回滚,从根本上解决了配置漂移和审计困难的问题。本文将以Cloudflare和阿里云DNS为例,完整讲解Terraform管理DNS的实践方法。

一、项目初始化与Provider配置
使用Terraform管理DNS的第一步是选择合适的provider。主流方案有两类:一是云厂商DNS服务,例如Cloudflare、AWS Route53、阿里云云解析;二是自建DNS服务器,例如BIND,可以通过terraform-provider-bind这样的社区provider管理。选择时主要考虑你的DNS解析服务托管在哪里,绝大多数团队使用的是云厂商方案。
以Cloudflare为例,先创建一个工作目录,编写main.tf文件声明provider和认证信息。认证凭据推荐使用环境变量注入,避免把Token硬编码进代码仓库:
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "cloudflare" {
# 推荐通过环境变量 CLOUDFLARE_API_TOKEN 传入
# export CLOUDFLARE_API_TOKEN="your-token-here"
}初始化并验证连接是否正常:
terraform init terraform plan
如果plan没有报认证错误,说明provider配置成功。阿里云用户则可以使用alibabacloudprovider中的alicloud_dns_record资源,认证方式同样支持AccessKey环境变量。无论选择哪家厂商,初始化阶段的关键是把凭据管理与代码分离,这一点在多人协作的仓库中尤其重要。
二、编写常见DNS解析记录
Terraform中每条DNS记录对应一个resource。以Cloudflare为例,一个典型的域名解析配置包含A记录、CNAME和MX记录。A记录将主机名指向IPv4地址,CNAME用于别名跳转,MX记录定义邮件服务器优先级。
variable "zone_id" {
description = "Cloudflare Zone ID"
type = string
}
# 网站主记录,指向负载均衡地址
resource "cloudflare_record" "www" {
zone_id = var.zone_id
name = "www"
type = "A"
value = "203.0.113.10"
ttl = 300
proxied = false
}
# API服务通过CNAME指向网关
resource "cloudflare_record" "api" {
zone_id = var.zone_id
name = "api"
type = "CNAME"
value = "gateway.example-ipipp.com"
ttl = 300
}
# 邮件服务器MX记录
resource "cloudflare_record" "mail" {
zone_id = var.zone_id
name = "@"
type = "MX"
value = "mx1.example-ipipp.com"
priority = 10
}上面的配置体现了声明式的核心思想:你只描述最终状态,Terraform负责计算差异并执行变更。执行terraform apply前,plan输出会清楚列出将要新增、修改还是删除哪些记录,这对DNS这种影响面大的基础设施非常关键——多看一眼plan,就能避免误删线上解析。
当记录数量增多时,逐条手写resource会非常冗长。这时可以配合for_each批量生成:
locals {
a_records = {
"web" = "203.0.113.10"
"db" = "203.0.113.11"
"cache"= "203.0.113.12"
}
}
resource "cloudflare_record" "batch" {
for_each = local.a_records
zone_id = var.zone_id
name = each.key
type = "A"
value = each.value
ttl = 300
}使用for_each的好处是新增一条记录只需在map里加一行,并且每条资源都有稳定的资源地址,删除某一项不会影响其他记录。
三、多环境管理与模块化复用
实际项目中通常有dev、staging、production多套环境,每套环境的DNS记录各不相同。推荐使用目录分离加模块复用的方式组织代码:把通用的记录定义封装成module,各环境只传入不同的参数。
module "dns_records" {
source = "./modules/dns-records"
zone_id = var.zone_id
env = "production"
web_ip = "203.0.113.10"
}每个环境使用独立的state文件,可以通过-backend-config指定不同的远程后端路径。远程state强烈建议放在对象存储中,例如S3或OSS,并开启版本控制。这样团队成员共享同一份状态,避免本地state与线上实际配置不一致,同时state的历史版本还能在误操作后用来恢复。
另一个实践是CI/CD集成。在Git流水线中执行plan作为代码评审环节,apply操作则限制为特定人员审批后触发。这样任何DNS变更都有对应的代码提交记录,审计时一目了然。DNS变更的审批链路越清晰,出事故时定位责任人和恢复速度就越快。
四、存量记录导入与运维注意事项
已有大量手工创建的DNS记录时,不必重写全部配置。Terraform提供了import命令可以把存量资源纳入管理:
terraform import cloudflare_record.www <zone_id>/<record_id>
import只读取状态,不生成配置代码,导入后需要手动补写对应的resource块,再执行一次plan确认没有差异。如果记录条数很多,也可以考虑使用terraformer这类工具自动批量生成配置。导入完成后务必在DNS控制台只读操作,后续所有变更都走Terraform,否则会出现配置漂移。
运维层面还有三点需要注意。第一,state文件中包含敏感信息,远程后端要开启加密和访问控制,绝不能提交到Git仓库。第二,TTL值的调整要谨慎,计划做机房迁移或切换线路时,提前把TTL调低到60秒左右,切换完成后再调回,可以缩短全球递归DNS缓存的生效延迟。第三,删除记录要格外小心,DNS解析的生效范围是全球性的,plan阶段看到-> destroy标记时一定要逐条确认原因。
总结来说,Terraform让DNS管理从手工操作升级为可版本化的代码工程:plan预览降低误操作风险,模块化提升复用效率,CI集成保障变更可审计。从一条A记录开始逐步迁移,就能平滑地把整个域名的解析体系纳入基础设施即代码的管理轨道。