DNSControl 5.7 配置参数详解

DNSControl v5.7.0 配置体系实测详解:dnsconfig.js 顶层函数/记录函数/TTL 三级体系/NO_PURGE 与 IGNORE_NAME 接管边界/SPF与DMARC构建器/--variable 变量注入/creds.json 的 TYPE、$ENV 与 !命令动态凭证,附生产双 provider 模板。

DNSControlDNSControlDNS服务器网络自动化运维2026/10/26

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)主机名, IPv4api 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 里用 JS fetch() 联网取数。危险,只对完全可信的自有配置开启,默认关闭;
  • --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:! 后不能带空格与参数,包一层脚本解决。

十三、参考链接