DNSControl 5.7 配置参数详解
DNSControl v5.7.0 配置体系实测详解:dnsconfig.js 顶层函数/记录函数/TTL 三级体系/NO_PURGE 与 IGNORE_NAME 接管边界/SPF与DMARC构建器/--variable 变量注入/creds.json 的 TYPE、$ENV 与 !命令动态凭证,附生产双 provider 模板。
DNSControl 5.7 配置参数详解
目标:系统讲清 DNSControl v5.7.0 的两份配置文件——
dnsconfig.js(声明式 JS DSL)与creds.json(provider 凭证/参数)——的全部常用参数:顶层函数、记录函数、TTL 体系、域修饰符、构建器、变量注入与全局命令行参数。全部示例与回显已在 Ubuntu 22.04 + DNSControl v5.7.0 + BIND 9.18.39 环境实测通过(2026-10-25)。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS,x86_64
- 软件版本:DNSControl v5.7.0;验证用权威 DNS:BIND 9.18.39-0ubuntu0.22.04.7
- 配置路径:
/etc/dnscontrol/dnsconfig.js、/etc/dnscontrol/creds.json;zone 文件目录/srv/zones/primary/(主)、/srv/zones/secondary/(备) - 演示域名:
example.org(双 provider 主备演示)、var.example.net(变量注入演示) - 安装方式见 《Ubuntu 22.04 安装 DNSControl 5.7 指南》,本篇每个示例命令均可独立执行
二、前置条件
已安装 DNSControl v5.7.0(dnscontrol version 输出 v5.7.0),并准备好最小工作目录:
mkdir -p /etc/dnscontrol /srv/zones/primary /srv/zones/secondary
cd /etc/dnscontrol
三、配置体系总览
3.1 两份文件的分工
| 文件 | 角色 | 内容 | 纳管建议 |
|---|---|---|---|
dnsconfig.js | 期望状态(源码) | 域名、记录、provider 挂载关系 | 进 Git,走评审 |
creds.json | 凭证与 provider 参数 | 各 provider 实例的 TYPE、密钥、目录等 | 权限 600,不进 Git |
执行时 DNSControl 先跑 dnsconfig.js 生成中间表示(IR),再按 creds.json 初始化 provider 并逐一比对差异。两份文件默认取当前工作目录下的这两个文件名,可用全局参数 --config / --creds 改路径。
3.2 配置的查看与校验方式
改完配置先用两个 debug 类子命令自检,不触碰任何 provider:
dnscontrol check
实测回显:
No errors.
print-ir --pretty 查看校验+规范化后的 IR(这是 push 真正使用的"事实"):
dnscontrol print-ir --pretty | head -30
实测回显(节选):
{
"registrars": [
{
"name": "none",
"type": "-"
}
],
"dns_providers": [
{
"name": "bind",
"type": "-",
"meta": {
"default_soa": {
"expire": 604800,
"master": "ns1.example.io.",
"mbox": "hostmaster.example.io.",
"minttl": 300,
"refresh": 3600,
"retry": 600
}
}
}
],
⚠️ 注意:
print-ir --raw跳过校验与规范化,输出的是 DSL 裸执行结果——TTL 默认值、SOA 合成等都还没应用。排查"配置最终长什么样"要用不带--raw的默认形态。
四、dnsconfig.js:顶层声明函数
4.1 NewRegistrar() 与 NewDnsProvider()
每个域名要声明"注册商谁管"和"DNS 记录谁托管"。自建场景注册商用 none:
var REG_NONE = NewRegistrar("none");
var DSP_BIND = NewDnsProvider("bind", {
"default_soa": {
"master": "ns1.example.org.",
"mbox": "hostmaster.example.org.",
"refresh": 3600,
"retry": 600,
"expire": 604800,
"minttl": 3600
}
});
NewRegistrar("none"):注册商不在 DNSControl 管理内(内网域名/已固定委派的场景)。NewDnsProvider("bind", {...}):第一个参数"bind"是creds.json里的实例名;第二个可选参数是实例级覆盖配置,BIND provider 支持default_soa(zone 新建时的 SOA 模板)与default_ns(默认 NS 列表)。- 同一 provider 类型可起多个实例,如
"bind"(主)与"bind_dr"(备),对应creds.json里两个不同directory。
4.2 D() 域名声明
D() 是核心:声明一个域名及其全部期望记录。最小形态:
D("example.org", REG_NONE, DnsProvider(DSP_BIND),
NAMESERVER("ns1.example.org."),
A("ns1", "192.168.1.41"),
A("@", "192.168.1.41")
);
参数顺序:域名 → 注册商 → 任意数量的 provider 挂载/修饰符/记录。主机名 @ 表示域顶点。
4.3 DnsProvider() 挂载:一域多写
一个 D() 可挂多个 DNS provider,push 时同一批记录写入每一处(主备 DNS 同构的关键):
var DSP_BIND_DR = NewDnsProvider("bind_dr", {
"default_soa": { "master": "ns1.example.org.", "mbox": "hostmaster.example.org.",
"refresh": 3600, "retry": 600, "expire": 604800, "minttl": 3600 }
});
D("example.org", REG_NONE, DnsProvider(DSP_BIND), DnsProvider(DSP_BIND_DR), DefaultTTL("3600"),
NAMESERVER("ns1.example.org."),
SOA("@", "ns1.example.org.", "hostmaster.example.org.", 3600, 600, 604800, 3600),
A("ns1", "192.168.1.41"),
A("@", "192.168.1.41"),
AAAA("@", "2001:db8::41"),
MX("@", 20, "mail.example.org."),
TXT("_acme-challenge", "challenge-token-0001"),
SRV("_imap._tcp", 0, 0, 143, "mail.example.org."),
CAA("@", "issue", "letsencrypt.org")
);
双 provider 的 push 实测回显(节选首尾,两套 zone 文件各写一次):
******************** Domain: example.org
13 corrections (bind)
#1: + CREATE example.org SOA ns1.example.org. hostmaster.example.org. X 3600 600 604800 3600 ttl=300
...
WRITING ZONEFILE: "/srv/zones/primary/example.org.zone" (serial 2026102500)
SUCCESS!
13 corrections (bind_dr)
...
WRITING ZONEFILE: "/srv/zones/secondary/example.org.zone" (serial 2026102500)
SUCCESS!
Done. 28 corrections.
4.4 NAMESERVER()
声明域名的权威 NS。配合 BIND provider 会写进 zone 的 NS 记录;push 回显中的 + CREATE example.org NS ns1.example.org. ttl=300 即由它产生。若域名 NS 由注册商侧管理而不想纳入比对,可在 D() 加元数据 {no_ns: "true"}。
4.5 D_EXTEND() 向已声明域名追加记录
D_EXTEND("域名", 记录...) 把记录追加到前面 D() 声明的域名上,用于按业务拆文件、各团队维护各自主机:
D_EXTEND("example.org",
A("api", "192.168.1.42", TTL("600")),
CNAME("docs", "api.example.org.")
);
实测 push 回显中对应两行:
+ CREATE api.example.org A 192.168.1.42 ttl=600
+ CREATE docs.example.org CNAME api.example.org. ttl=3600
五、记录函数与 TTL 体系
5.1 常用记录函数速查
本篇示例用到的记录函数及参数顺序(与 zone 文件写法一一对应):
| 函数 | 参数 | 生成记录示例(实测 zone 文件) |
|---|---|---|
A(label, ip) | 主机名, IPv4 | api 600 IN A 192.168.1.42 |
AAAA(label, ip6) | 主机名, IPv6 | @ IN AAAA 2001:db8::41 |
CNAME(label, target.) | 别名, 规范名(带点结尾) | docs IN CNAME api.example.org. |
MX(label, prio, target.) | 主机名, 优先级, 邮件主机 | @ IN MX 20 mail.example.org. |
TXT(label, "text") | 主机名, 文本 | _acme-challenge IN TXT "challenge-token-0001" |
SRV(label, prio, weight, port, target.) | 服务名, 优先级, 权重, 端口, 目标 | _imap._tcp IN SRV 0 0 143 mail.example.org. |
CAA(label, "tag", "value") | 主机名, 标签, 值 | @ IN CAA 0 issue "letsencrypt.org" |
NS(label, target.) | 主机名, NS 主机 | @ IN NS ns1.example.org. |
SOA("@", master., mbox., refresh, retry, expire, minttl) | 域顶点, 主 NS, 邮箱, 4 个计时器 | @ 300 IN SOA ns1.example.org. hostmaster.example.org. 2026102500 3600 600 604800 3600 |
规则要点:目标是"主机名"的一律写 FQDN 并带点结尾(mail.example.org.);CAA 的 flag 由工具置 0。记录的实际下发值可用 print-ir 的 comparablev3 字段预览:
"type": "MX",
"ttl": 3600,
"name": "@",
"comparablev3": "20 mail.example.org.",
5.2 TTL() 记录级修饰
任意记录函数末尾可挂 TTL() 修饰符覆盖本条记录的 TTL,支持秒数或带单位时长(如 "1h"):
A("api", "192.168.1.42", TTL("600"))
实测 dig 验证(TTL 确为 600,未修饰记录为域默认 3600):
api.example.org. 600 IN A 192.168.1.42
docs.example.org. 3600 IN CNAME api.example.org.
5.3 DefaultTTL() 域级默认 TTL
DefaultTTL("3600") 写在 D() 参数里,给整个域的记录设默认 TTL(未单独挂 TTL() 的记录都用它)。规范化后的 IR 实测:
"type": "A",
"ttl": 3600,
"name": "ns1",
"comparablev3": "192.168.1.41",
生效优先级:记录级 TTL() > 域级 DefaultTTL() > 内置默认(300)。上文 api 记录因挂 TTL("600") 保持 600,其余记录被 DefaultTTL("3600") 统一为 3600。
5.4 SOA() 与 default_soa 的关系
- 新建 zone 的 SOA 默认值取自 provider 实例的
default_soa(4.1 节); D()里显式写SOA(...)可覆盖 refresh/retry/expire/minttl 四项计时器;- serial 号始终由 BIND provider 自动按
YYYYMMDDHH日期序号生成(实测2026102500、变更后为2026102501),不由配置指定;需要可复现 serial 时可用 push 的--bindserial参数钉死。
生成 zone 文件头实测:
$TTL 3600
; generated with dnscontrol 2026-10-25T14:06:34Z
@ 300 IN SOA ns1.example.org. hostmaster.example.org. 2026102500 3600 600 604800 3600
300 IN NS ns1.example.org.
($TTL 3600 取自 SOA minttl;SOA 行自身 ttl=300 为内置域顶点默认。)
六、域修饰符:接管边界控制
DNSControl 默认"域名全量纳管":线上存在而配置里没声明的记录会被删除。两个修饰符用来收窄接管边界。
6.1 NO_PURGE:只增改、不删除
无 NO_PURGE 时 preview 实测(zone 里两条手工记录被判死刑):
******************** Domain: example.org
3 corrections (bind)
#1: - DELETE legacy.example.org A 192.168.1.99 ttl=3600
- DELETE legacy.example.org TXT "legacy-keep" ttl=3600
- DELETE manual.example.org A 192.168.1.98 ttl=3600
Done. 3 corrections.
在 D() 参数里加上 NO_PURGE 后同一配置再 preview:
******************** Domain: example.org
Done. 0 corrections.
push 后 zone 文件尾部实测(手工记录原样保留,纳管记录照常生成):
api 600 IN A 192.168.1.42
docs IN CNAME api.example.org.
ns1 IN A 192.168.1.41
manual IN A 192.168.1.98
legacy IN A 192.168.1.99
legacy IN TXT "legacy-keep"
适用:渐进接管存量域名、与其他系统(如手工 DDNS)共存过渡期。
6.2 IGNORE_NAME:按名单豁免特定主机
比 NO_PURGE 精细:只豁免匹配的记录,其余未声明记录照样删除。配置:
D("example.org", REG_NONE, DnsProvider(DSP_BIND), /* ... */,
IGNORE_NAME("legacy", "A,TXT"),
// ...其余记录
);
preview 实测回显:
******************** Domain: example.org
1 correction (bind)
#1: 2 records not being deleted because of IGNORE*():
A("legacy.example.org.", 192.168.1.99),
TXT("legacy.example.org.", "legacy-keep"),
- DELETE manual.example.org A 192.168.1.98 ttl=3600
Done. 1 corrections.
legacy 主机的 A/TXT 被豁免并点名列出,同为手工记录的 manual 仍被删除——豁免边界精确到名单。
七、记录构建器
7.1 SPF_BUILDER
把 SPF 记录拆成参数拼装,避免手写一长串 TXT 出错:
SPF_BUILDER({
label: "@",
parts: ["v=spf1", "ip4:192.168.1.41", "-all"]
})
push 实测生成的就是标准 TXT 记录:
+ CREATE example.org TXT "v=spf1 ip4:192.168.1.41 -all" ttl=3600
7.2 DMARC_BUILDER
DMARC_BUILDER({
policy: "none",
rua: ["mailto:dmarc@example.org"]
})
push 实测:
+ CREATE _dmarc.example.org TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.org" ttl=3600
dig 验证两条构建器产物:
example.org. 3600 IN TXT "v=spf1 ip4:192.168.1.41 -all"
_dmarc.example.org. 3600 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.org"
💡 说明:构建器只是"参数化生成 TXT"的语法糖,IR 里看到的就是普通 TXT 记录;此外还有
DKIM_BUILDER、LOC_BUILDER等同类构建器,用法见官方文档站 Language Reference。
八、变量注入与模板化(--variable)
dnsconfig.js 是真实 JavaScript,可用变量;变量值在命令行用 --variable/-v 注入,实现一套配置渲染多环境。配置示例:
var DSP = NewDnsProvider("bind");
D("var.example.net", NewRegistrar("none"), DnsProvider(DSP),
NAMESERVER("ns1.example.net."),
A("ns1", TARGET),
A("@", TARGET),
TXT("@", "env=" + ENV_TAG)
);
不传变量直接报错(实测):
executing dnsconfig.js: ReferenceError: 'TARGET' is not defined
注入两个变量后校验通过:
dnscontrol check --variable TARGET=192.168.1.61 --variable ENV_TAG=prod
No errors.
print-ir 确认渲染结果(实测 JSON 节选):
{"typenum":16,"type":"TXT","ttl":300,"name":"@","comparablev3":"\"env=prod\"","rdata":{"Txt":["env=prod"]}}
九、creds.json 参数详解
9.1 结构与 TYPE
{
"bind": {
"TYPE": "BIND",
"directory": "/srv/zones/primary"
},
"bind_dr": {
"TYPE": "BIND",
"directory": "/srv/zones/secondary"
}
}
- 顶层键 = provider 实例名,与
NewDnsProvider("bind")第一个参数一一对应; TYPE必填,取值是 provider 官方类型名(自建 zone 文件场景固定为BIND);类型写错会按错误的插件初始化并报凭证类错误(排障见安装篇常见问题 9.4);- 其余键为该 provider 的私有参数。BIND provider 的
directory指定 zone 文件读写目录,目录必须预先存在(zone 文件本身会自动创建,父目录不会)。
9.2 $ENV 环境变量引用
creds.json 里以 $ 开头的值会被当作环境变量名展开,适合把路径/密钥外移:
{
"bind": {
"TYPE": "BIND",
"directory": "$BIND_DIR"
}
}
实测(设置变量后凭证校验列出 zone 清单;未设置则初始化失败):
BIND_DIR=/srv/zones/primary dnscontrol check-creds --creds creds_env.json bind
example.com
example.org
failed GetZone LZ: directory "zones" does not exist
9.3 !命令 动态凭证
--creds 的值以 ! 开头时,DNSControl 执行该外部程序并把其 stdout 当作 creds JSON,便于对接密钥管理系统。准备脚本 creds.sh:
#!/bin/sh
cat /etc/dnscontrol/creds.json
chmod +x creds.sh
dnscontrol preview --creds='!/etc/dnscontrol/creds.sh'
实测回显(与直接读文件效果一致):
******************** Domain: var.example.net
1 correction (bind)
#1: Ensuring zone "var.example.net" exists in "bind"
...
Done. 1 corrections.
⚠️ 注意:
!后不能带参数/空格——--creds='!cat creds.json'会被整体当成一个文件名而报Config file "!cat creds.json" does not exist. Skipping.。需要参数时包成脚本。
十、全局命令行参数
dnscontrol --help 实测的全局参数(所有子命令通用):
GLOBAL OPTIONS:
--debug Enable debug logging
--allow-fetch Enable JS fetch(), dangerous on untrusted code!
--disableordering Disables update reordering
--no-colors Disable colors
--help, -h show help
--version, -V print the version
逐项说明:
--debug:打印配置读取、IR 构建、PHASE 1/2/3 各阶段细节,排障首选;--allow-fetch:允许 DSL 里用 JSfetch()联网取数。危险,只对完全可信的自有配置开启,默认关闭;--disableordering:关闭变更重排序(默认会把删除/新建按依赖重排),一般不动;--no-colors:关闭彩色输出,CI 日志里建议加(回显中的[32m...[0m颜色转义即被去掉);--version/-V:输出版本号。
另有两个高频子命令级公共参数:--config(默认 dnsconfig.js)与 --creds(默认 creds.json),在 preview/push/check/print-ir 等子命令上均可指定路径。
十一、生产配置模板
适用场景:16GB 内存以下的普通 x86_64 服务器、自建 BIND9 主备双机、单域名 50 条以内记录的内网/小团队权威 DNS。要点:双 provider 同写、NO_PURGE 保护过渡期手工记录、DefaultTTL 统一 1 小时、api 类低 TTL 记录单独修饰。
// /etc/dnscontrol/dnsconfig.js —— 生产模板(example.org 主备双写)
var REG_NONE = NewRegistrar("none");
var SOA_TPL = {
"default_soa": { "master": "ns1.example.org.", "mbox": "hostmaster.example.org.",
"refresh": 3600, "retry": 600, "expire": 604800, "minttl": 3600 }
};
var DSP_BIND = NewDnsProvider("bind", SOA_TPL); // creds.json: /srv/zones/primary
var DSP_BIND_DR = NewDnsProvider("bind_dr", SOA_TPL); // creds.json: /srv/zones/secondary
D("example.org", REG_NONE, DnsProvider(DSP_BIND), DnsProvider(DSP_BIND_DR),
NO_PURGE, DefaultTTL("3600"),
NAMESERVER("ns1.example.org."),
SOA("@", "ns1.example.org.", "hostmaster.example.org.", 3600, 600, 604800, 3600),
A("ns1", "192.168.1.41"),
A("@", "192.168.1.41"),
AAAA("@", "2001:db8::41"),
MX("@", 20, "mail.example.org."),
IGNORE_NAME("legacy", "A,TXT"),
SPF_BUILDER({ label: "@", parts: ["v=spf1", "ip4:192.168.1.41", "-all"] }),
DMARC_BUILDER({ policy: "none", rua: ["mailto:dmarc@example.org"] })
);
// 业务组按文件追加:
D_EXTEND("example.org",
A("api", "192.168.1.42", TTL("600")),
CNAME("docs", "api.example.org.")
);
配套 creds.json(9.1 节)与权限:
chmod 600 /etc/dnscontrol/creds.json
dnscontrol check && dnscontrol preview
十二、常见问题
12.1 check 通过但 preview 报 ReferenceError
变量未注入:executing dnsconfig.js: ReferenceError: 'TARGET' is not defined。给 preview/push/check/print-ir 全部加上同一组 --variable,或把变量收敛到 CI 的统一步骤里。
12.2 DefaultTTL 没生效?
检查是否对单条记录挂了 TTL()——记录级优先于域级;确认你看的是规范化后 IR(print-ir 默认形态),--raw 输出不含默认值应用。
12.3 CNAME 与其他记录共存报错
同一主机名 CNAME 与 A/TXT 等不能共存,校验阶段即报错终止——这是 DNS 协议约束,不是工具缺陷;把冲突记录改名(如 docs CNAME 指向 api,A 只留在 api 上)。
12.4 creds.json 里 !命令 报 "does not exist"
见 9.3:! 后不能带空格与参数,包一层脚本解决。
十三、参考链接
- 官方 DSL 参考:
https://docs.dnscontrol.org/language-reference - 官方命令参考:
https://docs.dnscontrol.org/commands - 《DNSControl 5.7 介绍与选型指南》
- 《Ubuntu 22.04 安装 DNSControl 5.7 指南》