你有没有遇到过网络又卡得要命,打开个网页都要等半天,想要解决网络问题却找不到解决的办法!其实很多时候网络慢不是带宽的问题,而是DNS解析太慢了。今天就跟大家聊聊DNS这个东西,顺便教大家怎么搭建一个自己的DNS服务器。

说起DNS,可能很多人觉得这玩意儿很神秘,其实它就像是互联网的电话簿。你想打电话给张三,但是你只记得他的名字,不记得电话号码,这时候就需要查电话簿找到对应的号码。DNS就是干这个活的,把我们容易记住的域名(比如baidu.com)转换成计算机能理解的IP地址(比如182.61.201.211)。

image-20250803214553171

DNS的前世今生

回想起刚接触IT那会儿,我对DNS的理解还停留在"改个DNS就能上网"的层面。后来才知道这套系统的历史还挺有意思的。

早期的互联网规模很小,那时候大家都用一个叫HOSTS.TXT的文件来做域名解析。想象一下,全世界就一个文件,每次有新的主机加入网络,就要手动更新这个文件,然后分发给所有人。这简直就是噩梦,特别是到了80年代初,互联网开始快速发展的时候。

1983年,保罗·莫卡派乔斯(Paul Mockapetris)提出了DNS的概念,并在1984年发布了RFC 1034和RFC 1035两个标准。这套系统采用了分层的树状结构,彻底解决了集中式管理的问题。说实话,这个设计到现在都还在用,可见当时设计得有多巧妙。

DNS的工作原理其实很简单,就是一个分布式的数据库系统。根域名服务器在最顶层,然后是顶级域名服务器(比如.com、.cn),再下面是二级域名服务器,以此类推。当你访问一个网站时,DNS查询就像问路一样,一级一级地往下找,直到找到对应的IP地址。

为什么要自建DNS服务器

可能有人会问,用公共DNS不是挺好的吗,比如8.8.8.8或者114.114.114.114,为什么还要自己搭建呢?

我之前在一家小公司工作的时候,就遇到过这样的问题。公司有个内部的OA系统,域名是oa.company.local,这种内网域名公共DNS肯定是解析不了的。每次新员工入职,都要手动修改hosts文件,麻烦得要死。后来搭建了内部DNS服务器,这个问题就彻底解决了。

还有一个原因就是速度。虽然公共DNS服务器性能很好,但是毕竟距离远,而且查询量大。自建DNS服务器可以针对常用的域名做缓存,响应速度会快很多。特别是对于一些经常访问的网站,第二次查询基本上是秒级响应。

另外,自建DNS还能实现一些特殊功能,比如广告屏蔽、恶意网站过滤等等。我见过有些公司用DNS来屏蔽员工访问某些网站,虽然这种做法有点争议,但确实是一个应用场景。

DNS服务器的功能

  • 正向解析:根据注册的域名查找其对应的IP地址
  • 反向解析:根据IP地址查找对应的注册域名,不常用

DNS分布式结构

image-20250803215658345

根域名:
一级域名:
.cn
.us
.tw
hkjp.
.kr
二级域名:
.com.cn .org.cn .net.cn
三级域名:
haha.com.cn xixi.com.cn .nb.com.cn
FQDN (完全合格的主机名) :站点名+注册的域名

DNS域名管理机构

  • IANA,互联网数字分配机构

    • -Internet Assigned Numbers Authority
    • 整个域名系统的最高权威机构
    • 主管DNS根、.int、.arpa等国际化域名资源
  • CNNIC,中国互联网络信息中心

    • China Internet Network nformation Center
    • 主管国家顶级域名.cn

开始搭建DNS服务器

好了,废话不多说,开始动手搭建。我这里用的是CentOS 7系统,DNS软件选择BIND9,这是目前最流行的DNS服务器软件。

安装BIND9

首先更新系统包:

yum update -y

安装BIND及相关工具:

yum install -y bind bind-utils

安装完成后,BIND的主配置文件在/etc/named.conf,区域文件默认放在/var/named/目录下。

配置主配置文件

编辑/etc/named.conf文件:

vim /etc/named.conf

这个文件的配置看起来挺复杂,但其实理解了结构就很简单。主要分为几个部分:options(全局选项)、logging(日志配置)、zone(区域配置)。

options {
    listen-on port 53 { 127.0.0.1; 192.168.1.100; };
    listen-on-v6 port 53 { ::1; };
    directory "/var/named";
    dump-file "/var/named/data/cache_dump.db";
    statistics-file "/var/named/data/named_stats.txt";
    memstatistics-file "/var/named/data/named_mem_stats.txt";
    recursing-file "/var/named/data/named.recursing";
    secroots-file "/var/named/data/named.secroots";
    allow-query { localhost; 192.168.1.0/24; };
    
    recursion yes;
    
    dnssec-enable yes;
    dnssec-validation yes;
    
    bindkeys-file "/etc/named.root.key";
    
    managed-keys-directory "/var/named/dynamic";
    
    pid-file "/run/named/named.pid";
    session-keyfile "/run/named/session.key";
};

这里有几个重要的配置项需要注意:

  • listen-on:指定DNS服务器监听的IP地址和端口
  • allow-query:允许哪些客户端进行DNS查询
  • recursion:是否允许递归查询

配置区域文件

接下来配置正向解析区域。假设我们要为example.local这个域名配置DNS解析。

/etc/named.conf文件末尾添加区域配置:

zone "example.local" IN {
    type master;
    file "example.local.zone";
    allow-update { none; };
};

然后创建区域文件:

vim /var/named/example.local.zone

区域文件的内容:

$TTL 86400
@   IN  SOA     ns1.example.local. admin.example.local. (
        2023111501  ; Serial
        3600        ; Refresh
        1800        ; Retry
        604800      ; Expire
        86400       ; Minimum TTL
)

; Name servers
@   IN  NS      ns1.example.local.

; A records
ns1     IN  A   192.168.1.100
web     IN  A   192.168.1.101
mail    IN  A   192.168.1.102
ftp     IN  A   192.168.1.103

; CNAME records
www     IN  CNAME   web.example.local.

这个文件看起来有点复杂,我来解释一下各个部分:

  • SOA记录:Start of Authority,定义了这个区域的权威信息
  • NS记录:Name Server,指定这个域名的名称服务器
  • A记录:Address,将域名指向IPv4地址
  • CNAME记录:Canonical Name,域名别名

配置反向解析

除了正向解析,我们通常还需要配置反向解析,也就是通过IP地址查询域名。

/etc/named.conf中添加反向解析区域:

zone "1.168.192.in-addr.arpa" IN {
    type master;
    file "192.168.1.rev";
    allow-update { none; };
};

创建反向解析文件:

vim /var/named/192.168.1.rev

文件内容:

$TTL 86400
@   IN  SOA     ns1.example.local. admin.example.local. (
        2023111501  ; Serial
        3600        ; Refresh
        1800        ; Retry
        604800      ; Expire
        86400       ; Minimum TTL
)

; Name servers
@   IN  NS      ns1.example.local.

; PTR records
100 IN  PTR     ns1.example.local.
101 IN  PTR     web.example.local.
102 IN  PTR     mail.example.local.
103 IN  PTR     ftp.example.local.

设置文件权限

BIND对文件权限要求比较严格,需要设置正确的所有者和权限:

chown named:named /var/named/example.local.zone
chown named:named /var/named/192.168.1.rev
chmod 640 /var/named/example.local.zone
chmod 640 /var/named/192.168.1.rev

启动服务

检查配置文件语法:

named-checkconf /etc/named.conf
named-checkzone example.local /var/named/example.local.zone
named-checkzone 1.168.192.in-addr.arpa /var/named/192.168.1.rev

如果没有报错,就可以启动服务了:

systemctl start named
systemctl enable named

配置防火墙:

firewall-cmd --permanent --add-service=dns
firewall-cmd --reload

测试DNS服务器

服务启动后,我们需要测试一下是否工作正常。

使用nslookup测试

nslookup web.example.local 192.168.1.100

如果配置正确,应该会返回对应的IP地址。

使用dig测试

dig命令提供了更详细的查询信息:

dig @192.168.1.100 web.example.local

测试反向解析:

dig @192.168.1.100 -x 192.168.1.101

测试递归查询

测试DNS服务器是否能正确处理外部域名查询:

dig @192.168.1.100 www.baidu.com

如果能正常返回结果,说明递归查询功能工作正常。

常见问题排查

搭建过程中可能会遇到一些问题,我把常见的几个列出来:

服务启动失败
通常是配置文件语法错误导致的,可以用journalctl -u named查看详细错误信息。我之前就因为区域文件中少了一个分号,折腾了半天。

查询超时
检查防火墙设置,确保53端口已经开放。还有就是检查allow-query配置,确保客户端IP在允许范围内。

解析结果不正确
可能是缓存问题,可以用rndc flush清空DNS缓存,或者重启named服务。

权限问题
BIND对文件权限要求很严格,确保区域文件的所有者是named用户,权限设置为640。

高级配置和优化

基本功能搞定后,我们还可以做一些优化和高级配置。

DNS负载均衡配置

DNS负载均衡是个很实用的功能,特别是当你有多台服务器提供相同服务的时候。我之前在一个电商项目中就用过这个功能,效果还不错。

在区域文件中,你可以为同一个域名配置多个A记录:

web     IN  A   192.168.1.101
web     IN  A   192.168.1.102  
web     IN  A   192.168.1.103

这样当客户端查询web.example.local时,DNS服务器会轮询返回这三个IP地址,实现简单的负载均衡。不过这种方式有个问题,就是无法检测服务器的健康状态,如果其中一台服务器挂了,DNS还是会把流量分发过去。

更高级一点的配置可以使用权重:

web     IN  A   192.168.1.101  ; 权重高的服务器
web     IN  A   192.168.1.101  ; 重复配置增加权重
web     IN  A   192.168.1.102

虽然看起来有点笨拙,但在简单场景下还是很管用的。

泛域名解析配置

泛域名解析就是用通配符来匹配所有子域名,这个功能在很多场景下都很有用。比如你想让所有的*.example.local都指向同一个服务器。

在区域文件中添加:

*       IN  A   192.168.1.200

这样配置后,任何以.example.local结尾的域名(如test.example.local、api.example.local、随便什么.example.local)都会解析到192.168.1.200这个IP。

我记得之前做一个多租户系统的时候,每个客户都有自己的子域名,用泛域名解析就省事多了,不用每次新增客户都去改DNS配置。

不过要注意,泛域名的优先级比较低,如果你同时配置了具体的子域名,具体的会优先匹配:

*       IN  A   192.168.1.200
api     IN  A   192.168.1.201

这种情况下,api.example.local会解析到192.168.1.201,其他子域名才会解析到192.168.1.200。

有规律解析配置

有时候我们需要根据某种规律来解析域名,比如按照数字递增的方式。BIND本身不直接支持这种动态规律解析,但可以通过一些技巧来实现。

一种方法是预先配置好所有可能的解析:

server1     IN  A   192.168.1.11
server2     IN  A   192.168.1.12
server3     IN  A   192.168.1.13
server4     IN  A   192.168.1.14
server5     IN  A   192.168.1.15
; ... 继续到server100

虽然看起来很笨,但对于有限范围的规律解析还是可行的。

另一种更灵活的方法是使用脚本动态生成区域文件:

#!/bin/bash
ZONE_FILE="/var/named/example.local.zone"

# 生成server1-100的解析记录
for i in {1..100}; do
    echo "server$i     IN  A   192.168.1.$((10+i))" >> $ZONE_FILE
done

# 重新加载区域文件
rndc reload example.local

这种方式比较适合初始化配置,如果需要动态更新,可能需要结合其他工具。

别名解析配置详解

除了基本的CNAME记录,还有一些高级的别名配置技巧。

多级别名

www     IN  CNAME   web.example.local.
blog    IN  CNAME   www.example.local.

这样blog.example.local会先解析到www.example.local,然后再解析到web.example.local对应的IP。不过要注意,CNAME链不要搞得太长,会影响解析性能。

条件别名
虽然BIND不直接支持条件别名,但可以通过视图(view)功能实现:

view "internal" {
    match-clients { 192.168.1.0/24; };
    zone "example.local" {
        type master;
        file "example.local.internal";
    };
};

view "external" {
    match-clients { any; };
    zone "example.local" {
        type master;
        file "example.local.external";
    };
};

然后在不同的区域文件中配置不同的别名:

内网区域文件(example.local.internal):

api     IN  CNAME   internal-api.example.local.

外网区域文件(example.local.external):

api     IN  CNAME   external-api.example.local.

这样内网用户访问api.example.local会指向内网服务器,外网用户会指向外网服务器。

动态别名更新
如果需要经常更新别名配置,可以启用动态更新功能:

zone "example.local" IN {
    type master;
    file "example.local.zone";
    allow-update { key "update-key"; };
};

然后用nsupdate命令动态添加或删除记录:

nsupdate -k /etc/named/update.key << EOF
server 192.168.1.100
zone example.local
update add temp.example.local 300 CNAME web.example.local
send
EOF

这种方式特别适合需要程序化管理DNS记录的场景,比如容器化环境中的服务发现。

配置转发器

如果不想让DNS服务器直接进行递归查询,可以配置转发器,将查询转发给上游DNS服务器:

options {
    forwarders {
        8.8.8.8;
        8.8.4.4;
    };
    forward only;
};

配置访问控制列表

为了安全考虑,可以配置ACL来限制访问:

acl "trusted" {
    192.168.1.0/24;
    10.0.0.0/8;
};

options {
    allow-query { trusted; };
    allow-recursion { trusted; };
};

启用查询日志

为了方便排查问题,可以启用查询日志:

logging {
    channel query_log {
        file "/var/log/named/query.log";
        severity info;
        print-time yes;
        print-category yes;
    };
    category queries { query_log; };
};

记得创建日志目录并设置权限:

mkdir /var/log/named
chown named:named /var/log/named

监控和维护

DNS服务器搭建完成后,日常的监控和维护也很重要。

我通常会写个简单的脚本来监控DNS服务的状态:

#!/bin/bash
DNS_SERVER="192.168.1.100"
TEST_DOMAIN="web.example.local"

if nslookup $TEST_DOMAIN $DNS_SERVER > /dev/null 2>&1; then
    echo "DNS server is working fine"
else
    echo "DNS server is not responding"
    systemctl restart named
fi

把这个脚本加到crontab里,每5分钟执行一次,基本上就能保证DNS服务的稳定性了。

另外,定期检查日志文件也很重要,特别是/var/log/messages和named的专用日志,能及时发现一些潜在问题。

区域文件的备份也不能忘记,虽然DNS的配置相对简单,但是一旦出问题重新配置还是很麻烦的。我一般会把整个/var/named目录定期打包备份。

总结

搭建DNS服务器其实并不复杂,关键是要理解DNS的工作原理和BIND的配置逻辑。从最初的HOSTS.TXT文件到现在的分布式DNS系统,这套架构经过了几十年的发展和完善,已经非常成熟稳定了。

对于企业内网来说,自建DNS服务器确实能带来很多好处:解决内网域名解析问题、提高查询速度、实现访问控制等等。当然,如果只是个人使用或者小规模应用,直接用公共DNS服务器可能更简单一些。

整个搭建过程中,配置文件的语法检查很重要,BIND对语法要求比较严格,一个小错误就可能导致服务启动失败。还有就是文件权限问题,这个经常被忽略,但确实很关键。

DNS作为互联网的基础设施,虽然平时不太显眼,但是一旦出问题影响面很大。所以在生产环境中,建议做好冗余和监控,确保服务的高可用性。下次来讲讲如何搭建DNS主从架构

希望这篇文章能帮助大家更好地理解和使用DNS服务器。如果在搭建过程中遇到问题,欢迎留言讨论。网络技术这东西,实践出真知,多动手试试总是没错的。


如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记

标签: none