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)——根据客户端来源网段返回不同解析结果
知识关联
- 前置知识:熟悉 4.5:网络故障排查 网络故障排查(IP、端口、TCP/UDP)、2.11:SSH 深入 SSH 远程管理
- 后续影响:DNS 是 3.3:实战:搭建个人网站 网站搭建、3.6:MySQL 数据库/3.7:PostgreSQL 数据库 数据库、6.2:Kubernetes 入门 Kubernetes 服务发现的基础
- 配套技术:
systemd-resolved(系统 DNS 解析器)、dig/nslookup(命令行排错工具)
原理讲解
为什么 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,背后经历了一条完整的解析链:
- 本地缓存检查——浏览器和操作系统先检查本地 DNS 缓存
- /etc/hosts 优先级最高——系统在发起网络 DNS 查询前先读此文件,可覆盖 DNS
- 递归查询到本地解析器——如 systemd-resolved 或 dnsmasq 代你完成全部查询
- 迭代查询遍历 DNS 树——本地解析器从根 DNS 开始:根 → .com TLD → example.com 权威服务器
- 返回并逐级缓存——权威服务器返回 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 # 重试次数
/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)
. 表示"绝对域名"。不加 . 会自动补上 @域名。这是 DNS 配置中最常见的错误之一。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
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
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
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 use | systemd-resolved 占据了 53 端口 | systemctl stop systemd-resolved,或让 dnsmasq listen-address=特定IP |
Temporary failure in name resolution | DNS 解析器未运行或网络不通 | 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.local 再 rndc reload |
| /etc/resolv.conf 配置 fallback DNS | 单一上游故障时自动切换 | 配 2–3 个不同厂商的公共 DNS(如 8.8.8.8 和 1.1.1.1) |
| 合理设置 TTL | 平衡缓存效率与变更传播速度 | 稳定域名设 3600+,变更频繁的设 300,故障切换前提前降低 TTL |
练习题
- (概念)什么是"递归查询"?什么是"迭代查询"?二者在 DNS 流程中分别由谁执行?
- (概念)CNAME 记录和 A 记录的区别是什么?什么场景下应该使用 CNAME?
- (实操)使用 dnsmasq 在局域网搭建 DNS 服务:配置
dev-server.local解析到192.168.1.10,用dig验证解析成功,再用+stats查看查询时间。 - (实操)在 BIND master 上创建一个正向区域
lab.local,包含 A 记录web.lab.local → 10.0.0.10和db.lab.local → 10.0.0.20,用named-checkzone验证语法。 - (🔍 挑战)在 BIND 主从架构中故意制造 Serial 不同步的故障:只在 master 修改记录但不递增 Serial,观察 slave 是否同步。然后递增 Serial 并修复。
点击查看答案
- 递归查询:客户端告诉解析器"帮我去查,我只要最终结果"——由本地解析器(如 systemd-resolved)执行。迭代查询:解析器逐级向 DNS 根→TLD→权威服务器发起查询,每级只告知下一级地址。
- A 记录将域名映射到 IPv4 地址,是最终的解析结果。CNAME 是别名,将域名指向另一个域名(最终仍须解析为 A/AAAA 记录)。场景:多个域名指向同一服务时用 CNAME(如
www.example.com CNAME example.com),改 IP 只需改一处。 - dnsmasq 配置:
address=/dev-server.local/192.168.1.10。dig dev-server.local @server_ip返回 A 记录。dig +stats显示 Query time 在 ms 级。 - named.conf 加 zone 定义,zone 文件写 SOA + NS + A 记录。
named-checkzone lab.local /var/named/lab.local.zone返回 OK。 - 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 | 中等 | 高 |
| CoreDNS | Kubernetes 集群、插件化扩展 | 中等 | 中等 |
速查表
| 命令/操作 | 用途 |
|---|---|
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 占用前) |
学习路径建议
- 学完本章后,建议继续学习 3.3:实战:搭建个人网站 搭建个人网站(Nginx + DNS 域名绑定)
- 进阶阅读 6.2:Kubernetes 入门 Kubernetes 入门(CoreDNS 在 K8s 中的关键作用)
- 网络故障排查可读 4.5:网络故障排查「Linux 网络故障排查方法论与实战」
延伸阅读
- ISC BIND 官方文档
- CoreDNS 官方手册
- dnsmasq 官方文档
- BIND 9 管理员参考手册
- 推荐书籍:《DNS 与 BIND》(第 5 版,Cricket Liu 著,O'Reilly)
- 社区资源:ServerFault 系统管理员问答社区