5.9 DNS 服务搭建——dnsmasq / BIND / CoreDNS

预计阅读时间:14 分钟

📖 目录

学习目标

学完本章后,你将能够:

  • 理解 DNS 递归查询与迭代查询的完整流程
  • 区分 A、AAAA、CNAME、MX、NS、TXT、SRV、PTR 等记录类型
  • 使用 dnsmasq 搭建轻量内网 DNS 服务
  • 配置 BIND 主从架构,理解区域传输与 Serial 机制
  • 编写 CoreDNS Corefile,理解插件化设计
  • 用 dig、nslookup 排查 DNS 故障

核心知识

  • DNS(Domain Name System)——将人类易记的域名转为机器可读 IP 地址的分布式数据库系统
  • 递归查询(Recursive Query)——DNS 解析器代客户端完成完整解析过程,返回最终结果
  • 迭代查询(Iterative Query)——DNS 服务器指引客户端向下一级 DNS 服务器查询,逐层推进
  • 权威 DNS 服务器(Authoritative DNS)——持有域名实际记录的服务器,负责返回最终答案
  • DNS 缓存(DNS Cache)——解析器将查询结果缓存一段时间(由 TTL 控制),减少重复查询
  • Zone 文件(Zone File)——DNS 服务器存储域名记录的文件,遵循 RFC 标准格式
  • 区域传输(Zone Transfer)——主 DNS 将 zone 数据复制到从 DNS 的过程
  • Split-horizon(分视域 DNS)——根据客户端来源网段返回不同解析结果

知识关联

原理讲解

为什么 DNS 要设计成分布式层级结构

DNS 的分布式层级结构(根→TLD→权威)是为了解决三个核心问题:可扩展性(单一服务器无法承载全球数十亿域名的查询)、可用性(单点故障不应导致整个互联网瘫痪)、性能(就近查询减少延迟)。如果 DNS 是集中式的,每次查询都要跨越半个地球到中央服务器,延迟不可接受;且中央服务器一旦故障,整个互联网将瘫痪。分布式层级设计让每个 DNS 服务器只负责自己的区域,查询逐级向下推进,缓存机制进一步减少了重复查询。这种设计也带来了管理上的好处:每个域名的管理员可以独立管理自己的 DNS 记录,无需中央协调。

递归查询 vs 迭代查询:为什么要区分

递归查询和迭代查询的区分是 DNS 性能与责任分离的关键。客户端(如你的浏览器)向本地解析器发起递归查询——"帮我查 www.example.com 的 IP,我只要最终结果"。本地解析器(如 systemd-resolved 或运营商 DNS)则向根、TLD、权威服务器发起迭代查询——每级只告知下一级地址,解析器自己逐步推进。这种设计将"完整解析的复杂性"集中在少数解析器上,而权威服务器只需回答自己负责的区域,负担极轻。同时,解析器可以缓存结果,后续相同查询直接返回缓存,大幅降低权威服务器的压力。

dnsmasq vs BIND vs CoreDNS:为什么推荐不同场景用不同方案

三种 DNS 方案的设计哲学和适用场景截然不同。dnsmasq 是"极简主义"——单文件配置、自带 DHCP、资源占用极低(<5MB 内存),适合内网小规模场景(家庭实验室、开发环境),但不支持 split-horizon 和主从同步。BIND 是"全功能主义"——支持所有 DNS 特性(split-horizon、TSIG、DNSSEC、动态更新),是生产级权威 DNS 的事实标准,但配置复杂、资源消耗中等。CoreDNS 是"插件化主义"——每个功能是一个插件,需要什么加什么,原生支持 Kubernetes 服务发现,适合云原生环境。选型原则:内网小规模用 dnsmasq,生产权威 DNS 用 BIND,K8s 环境用 CoreDNS。

1. DNS 解析流程:递归与迭代

当你在浏览器输入 www.example.com,背后经历了一条完整的解析链:

  1. 本地缓存检查——浏览器和操作系统先检查本地 DNS 缓存
  2. /etc/hosts 优先级最高——系统在发起网络 DNS 查询前先读此文件,可覆盖 DNS
  3. 递归查询到本地解析器——如 systemd-resolved 或 dnsmasq 代你完成全部查询
  4. 迭代查询遍历 DNS 树——本地解析器从根 DNS 开始:根 → .com TLD → example.com 权威服务器
  5. 返回并逐级缓存——权威服务器返回 A/AAAA 记录,每级缓存结果后返回给客户端
步骤谁在做动作
1浏览器/OS检查本地缓存和 /etc/hosts
2本地解析器接收递归查询请求
3根 DNS 服务器返回 .com TLD 服务器地址
4.com TLD 服务器返回 example.com 权威服务器地址
5权威 DNS 服务器返回最终 A/AAAA 记录
# /etc/hosts 示例
127.0.0.1 localhost
127.0.1.1 myhost
192.168.1.10 dev-server.local

# /etc/resolv.conf(DNS 解析器配置)
nameserver 127.0.0.53     # 本地 systemd-resolved
nameserver 8.8.8.8        # Google 公共 DNS(备用)
nameserver 1.1.1.1        # Cloudflare(备用)
search example.com        # 自动补全搜索域
options rotate            # 轮询 nameserver
options timeout:2         # 每次查询超时 2 秒
options attempts:2        # 重试次数
💡 systemd-resolved 特殊路径 Ubuntu 22.04+ 的 /etc/resolv.conf 通常指向 127.0.0.53(systemd-resolved)。用 resolvectl status 查看完整配置,用 resolvectl query example.com 直接查询。

2. DNS 记录类型

记录类型含义示例
A域名 → IPv4 地址www.example.com. IN A 93.184.216.34
AAAA域名 → IPv6 地址www.example.com. IN AAAA 2606:2800:220:1:248:1893:25c8:1946
CNAME域名别名(指向另一个域名)www.example.com. IN CNAME example.com.
MX邮件交换器(含优先级)example.com. IN MX 10 mail.example.com.
NS权威 DNS 服务器example.com. IN NS ns1.example.com.
TXT文本记录(SPF/DKIM/验证)example.com. IN TXT "v=spf1 include:_spf.google.com ~all"
SRV服务定位(含端口+权重)_sip._tcp.example.com. IN SRV 10 60 5060 sip.example.com.
PTR反向记录(IP → 域名)34.216.184.93.in-addr.arpa. IN PTR www.example.com.
# dig 命令快速查询各记录类型
dig A example.com
# 输出: ;; ANSWER SECTION: example.com. 1234 IN A 93.184.216.34
dig AAAA example.com
dig MX example.com
dig NS example.com
dig TXT example.com
dig ANY example.com    # 获取所有记录(注意:很多 DNS 服务器会抑制 ANY)
⚠️ 尾点(.) zone 文件中域名末尾的 . 表示"绝对域名"。不加 . 会自动补上 @域名。这是 DNS 配置中最常见的错误之一。
⚠️ CNAME 限制 CNAME 记录不能与其他记录类型(如 MX、NS)共存于同一域名。如果 example.com 有 CNAME,则不能同时有 MX 记录。通常只对子域名(如 www)使用 CNAME。

示例代码

1. dnsmasq 轻量搭建

dnsmasq 是最轻量的 DNS 方案——安装简单、配置直观、自带 DHCP 集成。适合内网和开发环境。

# 安装
apt install -y dnsmasq

# /etc/dnsmasq.conf 完整配置
# 监听地址(内网网卡)
interface=eth0
bind-interfaces

# 上游 DNS(转发非本地域名)
server=8.8.8.8
server=1.1.1.1

# 本地域名解析
local=/local/
domain=local
expand-hosts

# 静态 DNS 记录
address=/dev-server.local/192.168.1.10
address=/gitlab.local/192.168.1.20
address=/jenkins.local/192.168.1.30
address=/wiki.local/192.168.1.30

# CNAME 记录
cname=app.local,dev-server.local

# 缓存配置
cache-size=1000
neg-ttl=60
min-cache-ttl=300

# DHCP 集成(可选)
dhcp-range=192.168.1.100,192.168.1.200,12h
dhcp-option=3,192.168.1.1      # 网关
dhcp-option=6,192.168.1.1       # DNS 服务器(自己)
dhcp-host=aa:bb:cc:dd:ee:ff,192.168.1.50  # MAC 绑定固定 IP
# 启动与检查
systemctl enable --now dnsmasq
# 输出: Created symlink /etc/systemd/system/multi-user.target.wants/dnsmasq.service → /lib/systemd/system/dnsmasq.service
systemctl status dnsmasq
# 输出: ● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
# 输出:    Active: active (running)

# 测试
dig @192.168.1.1 dev-server.local
# 输出: dev-server.local. 0 IN A 192.168.1.10
dig @192.168.1.1 example.com

# 验证缓存(第二次查询时间应 < 1ms)
dig @192.168.1.1 example.com +stats | grep "Query time"
# 输出: Query time: 0 msec
💡 防冲突 如果 systemd-resolved 占用了 53 端口,先关掉:systemctl stop systemd-resolved。或让 dnsmasq 绑定特定 IP:listen-address=192.168.1.1

2. BIND 主从配置

BIND(Berkley Internet Name Domain)是 DNS 的"重武器"——功能最全、生产最广、配置也最复杂。

# 安装
apt install -y bind9 bind9utils bind9-doc

# /etc/bind/named.conf(主配置文件)
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";
# /etc/bind/named.conf.options
options {
    directory "/var/cache/bind";
    recursion yes;
    allow-query { any; };
    allow-recursion { 192.168.0.0/16; 127.0.0.0/8; };
    forwarders {
        8.8.8.8;
        1.1.1.1;
    };
    forward only;
    dnssec-validation auto;
    listen-on { 192.168.1.1; 127.0.0.1; };
    listen-on-v6 { none; };
};
# /etc/bind/named.conf.local
# 正向区域
zone "example.local" {
    type master;
    file "/etc/bind/zones/db.example.local";
    allow-transfer { 192.168.1.2; };   # 仅允许 slave 传输
    also-notify { 192.168.1.2; };
};

# 反向区域
zone "1.168.192.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.192.168.1";
    allow-transfer { 192.168.1.2; };
};
# /etc/bind/zones/db.example.local
$TTL 604800
@   IN  SOA ns1.example.local. admin.example.local. (
    2026072901  ; Serial(YYYYMMDDNN — 每次修改必须递增)
    3600        ; Refresh(同步间隔)
    1800        ; Retry(重试间隔)
    604800      ; Expire(失效时间)
    86400       ; Minimum TTL
)

; Nameservers
@   IN  NS  ns1.example.local.
@   IN  NS  ns2.example.local.

; A Records
ns1 IN  A   192.168.1.1
ns2 IN  A   192.168.1.2
@   IN  A   192.168.1.10
www IN  A   192.168.1.10
api IN  A   192.168.1.20
db  IN  A   192.168.1.30

; CNAME
mail IN  CNAME   www

; MX(邮件交换器优先值越低越优先)
@   IN  MX  10  mail.example.local.

; TXT(SPF 记录)
@   IN  TXT "v=spf1 mx ~all"
# Slave 节点配置(/etc/bind/named.conf.local)
zone "example.local" {
    type slave;
    file "/var/cache/bind/db.example.local";
    masters { 192.168.1.1; };
};

# 检查 BIND 配置
named-checkconf
# 输出: (no output — syntax OK)
named-checkzone example.local /etc/bind/zones/db.example.local
# 输出: zone example.local/IN: loaded serial 2026072901
# 输出: OK

# 重载配置
rndc reload
# 输出: server reload successful
rndc status
# 输出: version: BIND 9.18.x ... server is up and running
rndc flush     # 清空缓存
rndc stats     # 查看统计

3. CoreDNS(K8s DNS)

CoreDNS 是 CNCF 孵化的、用 Go 编写的灵活 DNS 服务器。它是 Kubernetes 的默认 DNS 组件。

# Corefile — CoreDNS 配置文件
.:53 {
    # 插件:错误日志
    errors

    # 插件:健康检查端点
    health {
        lameduck 5s
    }

    # 插件:转发到上游
    forward . 8.8.8.8 1.1.1.1 {
        policy sequential
        health_check 5s
    }

    # 插件:缓存
    cache 30

    # 插件:从 /etc/hosts 加载
    hosts {
        192.168.1.10 dev-server.local
        192.168.1.20 gitlab.local
        fallthrough
    }

    # 插件:自动加载 zone 文件
    file zones/example.local

    # 插件:记录查询日志
    log . {
        class all
    }

    # 插件:压测
    whoami

    # 插件:指标
    prometheus :9153
}

# K8s 内部域名
cluster.local:53 {
    errors
    kubernetes cluster.local {
        pods verified
        fallthrough in-addr.arpa ip6.arpa
    }
    forward . /etc/resolv.conf
    cache 30
}
# 启动 CoreDNS
./coredns -conf Corefile

# 测试
dig @127.0.0.1 -p 53 dev-server.local
# 输出: dev-server.local. 0 IN A 192.168.1.10

# 查看指标
curl http://127.0.0.1:9153/metrics | grep coredns_dns_requests_total
# 输出: coredns_dns_requests_total{server="dns",zone="."} 42
💡 插件即一切 CoreDNS 的设计哲学是"每个功能都是一个插件"。forward 转发查询、file 读 zone 文件、hosts 读 /etc/hosts、kubernetes 跟 K8s API 交互查询 Service DNS。需要什么加什么,没有的就不编译。

4. BIND 分视域(Split-horizon)

生产环境中,同一域名在内网和外网需解析到不同 IP——BIND 的 view 语句根据客户端来源返回不同结果。

# BIND 分视域配置
view "internal" {
    match-clients { 192.168.0.0/16; 10.0.0.0/8; 127.0.0.0/8; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/db.example.com.internal";
    };
};

view "external" {
    match-clients { any; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/db.example.com.external";
    };
};
# db.example.com.internal — 内网用户看到 10.x.x.x
www IN A 10.0.1.10
api IN A 10.0.1.20

# db.example.com.external — 外网用户看到公网 IP
www IN A 203.0.113.10
api IN A 203.0.113.20
⚠️ dnsmasq 不支持 split-horizon dnsmasq 设计为简单场景。需要分视域时务必用 BIND 或 CoreDNS,否则会出现"内网外网解析混乱"的故障。

5. DNS 排错实战(dig / nslookup)

# dig 排错标准流程
# 1. 测试本地解析是否正常
dig www.example.com
# 输出: ;; ANSWER SECTION: www.example.com. 1234 IN A 93.184.216.34

# 2. 追踪完整解析路径
dig +trace www.example.com
# 输出: Tracing from 192.168.1.1... root → .com TLD → example.com → 93.184.216.34

# 3. 指定特定 DNS 服务器测试
dig @8.8.8.8 www.example.com
dig @ns1.example.com www.example.com

# 4. 查询特定记录类型
dig MX example.com
dig SOA example.com

# 5. 反向查询
dig -x 93.184.216.34
# 输出: ;; ANSWER SECTION: 34.216.184.93.in-addr.arpa. IN PTR www.example.com.

# 6. 查看完整响应+时间
dig www.example.com +all +stats

# 7. 批量查询(从文件读域名)
dig -f domains.txt +short

# nslookup(简单场景)
nslookup www.example.com
# 输出: Name: www.example.com
# 输出: Address: 93.184.216.34
nslookup www.example.com 8.8.8.8
💡 快速自测curl -v https://example.com 2>&1 | grep "Trying" 看 curl 解析到的 IP。用 getent hosts example.com 查看系统本地的解析结果。

常见错误

错误表现根因正确做法
域名解析为意外结果zone 文件域名末尾忘了 .绝对域名必须加尾点 .,不加 . 会自动拼接源域名
BIND 新增记录一直不生效修改 zone 文件后 Serial 未递增每次修改 zone 文件必须增大 Serial(如 2026072902),然后 rndc reload
BIND 主从不自动同步Serial 号未更新或网络不通确认 slave 能访问 master 的 53 端口,allow-transfer 包含 slave IP
dnsmasq 启动失败:port 53 already in usesystemd-resolved 占据了 53 端口systemctl stop systemd-resolved,或让 dnsmasq listen-address=特定IP
Temporary failure in name resolutionDNS 解析器未运行或网络不通systemctl status systemd-resolved / ss -tulpn | grep :53
内网域名解析到公网 IP未配置 split-horizon 或 search 域错误使用 BIND view 语句分视域;检查 resolvectl domain
CoreDNS 启动后查询无响应插件顺序错误或配置语法有误检查 Corefile 缩进(用空格而非 tab),验证 ./coredns -conf Corefile 启动日志

最佳实践

实践原理示例
内网小规模用 dnsmasq配置简单、资源极低、自带 DHCP开发环境、家庭实验室 5–50 台设备时首选 dnsmasq
生产环境用 BIND 主从高可用、区域传输、成熟稳定master + 2 slave 分布在不同的机房或云可用区
K8s 环境用 CoreDNS原生的 Kubernetes 服务发现、插件可扩展Corefile 中 kubernetes 插件直接查询 K8s API
统一管理 Serial 号规则避免主从同步失败YYYYMMDDNN 格式(如 2026072901),每次修改加 1
zone 文件修改后先检查语法防止错误配置导致服务不可用先执行 named-checkzone example.local /etc/bind/zones/db.example.localrndc reload
/etc/resolv.conf 配置 fallback DNS单一上游故障时自动切换配 2–3 个不同厂商的公共 DNS(如 8.8.8.8 和 1.1.1.1)
合理设置 TTL平衡缓存效率与变更传播速度稳定域名设 3600+,变更频繁的设 300,故障切换前提前降低 TTL

练习题

  1. (概念)什么是"递归查询"?什么是"迭代查询"?二者在 DNS 流程中分别由谁执行?
  2. (概念)CNAME 记录和 A 记录的区别是什么?什么场景下应该使用 CNAME?
  3. (实操)使用 dnsmasq 在局域网搭建 DNS 服务:配置 dev-server.local 解析到 192.168.1.10,用 dig 验证解析成功,再用 +stats 查看查询时间。
  4. (实操)在 BIND master 上创建一个正向区域 lab.local,包含 A 记录 web.lab.local → 10.0.0.10db.lab.local → 10.0.0.20,用 named-checkzone 验证语法。
  5. (🔍 挑战)在 BIND 主从架构中故意制造 Serial 不同步的故障:只在 master 修改记录但不递增 Serial,观察 slave 是否同步。然后递增 Serial 并修复。
点击查看答案
  1. 递归查询:客户端告诉解析器"帮我去查,我只要最终结果"——由本地解析器(如 systemd-resolved)执行。迭代查询:解析器逐级向 DNS 根→TLD→权威服务器发起查询,每级只告知下一级地址。
  2. A 记录将域名映射到 IPv4 地址,是最终的解析结果。CNAME 是别名,将域名指向另一个域名(最终仍须解析为 A/AAAA 记录)。场景:多个域名指向同一服务时用 CNAME(如 www.example.com CNAME example.com),改 IP 只需改一处。
  3. dnsmasq 配置:address=/dev-server.local/192.168.1.10dig dev-server.local @server_ip 返回 A 记录。dig +stats 显示 Query time 在 ms 级。
  4. named.conf 加 zone 定义,zone 文件写 SOA + NS + A 记录。named-checkzone lab.local /var/named/lab.local.zone 返回 OK。
  5. Serial 不递增时 slave 不触发 zone transfer。rndc reload 不报错但 slave 的 serial 不变。递增 serial 后 rndc reload,slave journal 显示 transfer 成功。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 DNS 解析流程和 DNS 记录类型尝试向他人讲解
命令操作能不查文档完成 BIND/Unbound DNS 服务器搭建和配置在终端实际执行
原理掌握能说出 DNS 缓存机制和 DNSSEC 验证原理画出流程图
故障排查能独立排查 DNS 解析失败或缓存不更新的问题模拟故障并修复
最佳实践能说明为什么生产环境需要配置 DNS 主从同步和日志对比不同方案

本章总结

DNS 是网络基础设施的"电话本"——正确配置和快速排错是运维核心技能。三种主流方案各有适用场景:

方案适用场景资源消耗配置复杂度
dnsmasq内网小规模、开发环境、DHCP 集成极低
BIND生产级权威 DNS、主从架构、split-horizon中等
CoreDNSKubernetes 集群、插件化扩展中等中等

速查表

命令/操作用途
dig +trace example.com追踪完整解析路径
dig @8.8.8.8 example.com指定 DNS 服务器查询
dig MX example.com查询 MX 记录
dig -x 93.184.216.34反向 IP 查询
named-checkconf检查 BIND 主配置语法
named-checkzone example.local db.example.local检查 BIND zone 文件语法
rndc reload重载 BIND 配置
rndc flush清空 BIND 缓存
resolvectl status查看 systemd-resolved 状态
systemctl stop systemd-resolved释放 53 端口(dnsmasq/CoreDNS 占用前)

学习路径建议

延伸阅读

常见问题

dnsmasq 和 BIND 分别适合什么场景?
dnsmasq 轻量级,配置极简,适合局域网 DNS 缓存和 DHCP 服务。BIND 是完整功能的 DNS 服务器,支持主从复制、DNSSEC、视图 view、TSIG 认证。小公司内网用 dnsmasq,大型 DNS 架构或权威 DNS 用 BIND。Cloud-Native 场景越来越多用 CoreDNS(Kubernetes 默认 DNS)。
DNS 记录类型 A、CNAME、MX、NS 各有什么用途?
A 记录将域名指向 IPv4 地址;AAAA 指向 IPv6。CNAME 将域名别名指向另一个域名(如 www.example.com → example.com)。MX 指定邮件交换服务器(优先级 + 主机名)。NS 指定域名的 DNS 服务器。TXT 存放任意文本(常用于验证域名所有权和 SPF/DKIM 邮件认证)。
dig 排错有哪些常用技巧?
dig +short example.com(简化输出,只显示 IP);dig +trace example.com(跟踪完整解析链路);dig @8.8.8.8 example.com(用指定 DNS 服务器查询);dig MX example.com(查邮件记录);dig ANY example.com(查所有类型记录)。注意:很多 DNS 服务器不响应 ANY 查询。query time 字段显示解析耗时。
↑ 回到顶部