ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

银河麒麟V10下用Bind9搭建内网DNS:绕过systemd-resolved的坑

银河麒麟V10下用Bind9搭建内网DNS:绕过systemd-resolved的坑 先说结论如果你在银河麒麟V10上面打算跑一个内网DNS或者只是想让/etc/resolv.conf别再动不动被重置、让内网域名解析不再“灵异失效”那么拦在你面前的第一个敌人往往不是Bind9配置有多复杂而是systemd-resolved这个“顺手的坑”。我一开始接手公司内网一台麒麟V10服务器时以为装个bind9、写两个zone文件、改一下resolv.conf就收工了。结果一路踩过去53端口被systemd-resolved抢占、named服务起不来、chroot路径找不到、重启后resolv.conf被还原、客户端解析时好时坏……折腾了整整一下午才意识到问题不在Bind9本身而是系统和DNS服务之间那层“看似透明、实则霸道”的机制没理顺。这篇文章就围绕银河麒麟V10基于Debian系内核下用Bind9搭建内网DNS的全过程来写重点不是把bind教程再抄一遍而是把“systemd-resolved会在哪些环节坑你、怎么识别、怎么绕开、怎么让配置持久化”讲透。适合刚接触Linux DNS服务、被内网域名解析折磨过的运维和开发同学尤其是国产化系统环境下需要自建DNS的场景。1. 先搞清楚内网DNS到底解决什么问题为什么非得自建1.1 内网解析的三个典型痛点很多团队最初都用/etc/hosts硬编码内网IP几十台机器还好说一旦超过50台、或者服务出现扩缩容hosts文件就是一坨定时炸弹某个IP变了你要满世界改文件漏改一台就出现访问超时。内网DNS要解决的第一件事就是让“服务名到IP”的对应关系变成集中管理。第二个痛点是DNS转发。内网机器既要解析内部域名比如gitlab.internal、nas.office又要解析外网域名比如apt源、镜像站。如果每台机器直接指向公网DNS内网域名完全没法解析如果统一用一台内网DNS做“中间层”既能解析内部zone又能把外部请求转发给上游这才是标准做法。第三个痛点是缓存和故障切换。公网DNS偶尔抽风或者延迟高内网DNS做一层缓存后局域网内解析速度和稳定性都会有明显提升尤其在网络抖动时缓存命中能顶住不少压力。1.2 银河麒麟V10的“Debian底子”带来的特殊问题银河麒麟V10桌面版和服务器版大多基于Debian系改造systemd体系完整保留。这意味着你会有systemd-resolved这个服务默认在跑它会接管本机DNS解析流程默认监听127.0.0.53:53。问题就在这里Bind9默认监听53端口而systemd-resolved已经把它占了。你装好bind9后启动named大概率会看到“could not listen on UDP socket: address in use”这类报错。这不是Bind9的问题是端口冲突引发的“假故障”。另一个坑是/etc/resolv.conf。银河麒麟V10默认情况下这个文件是指向/run/systemd/resolve/stub-resolv.conf的软链内容写的是“nameserver 127.0.0.53”。你手动把resolv.conf改成“nameserver 192.168.1.10”表面上生效了但一旦重启网络服务、重启机器、或者NetworkManager刷新一下连接这个文件很可能被还原成systemd-resolved的stub配置。所以在动手搭Bind9之前必须先处理掉systemd-resolved的“管辖权”。这一步做不好后面全是返工。2. 动手前必须做的两件事关停systemd-resolved与释放53端口2.1 判断当前53端口和resolv.conf的真实状态不要凭感觉操作先上命令确认现状。用root权限执行ss -lntup | grep 53正常情况下你会看到一个叫systemd-resolve的进程监听127.0.0.53:53。再看resolv.confls -l /etc/resolv.conf readlink /etc/resolv.conf如果输出的是/run/systemd/resolve/stub-resolv.conf恭喜你这就是我前面说的坑位。这个stub配置不是不能用但它的存在意味着本机所有DNS请求会先走systemd-resolved这道中转如果你想测试Bind9是否正常解析而Bind9又没监听127.0.0.53那请求根本到不了你的named进程测试结果永远是“解析失败”但你可能半天都想不到是这条链路断了。2.2 关停systemd-resolved的两条路径一种方案是直接关闭systemd-resolved服务这也是我在生产服务器上推荐的做法。既然你自己搭了内网DNS就没必要让systemd-resolved在中间横插一脚把它停掉让所有解析请求直连Bind9systemctl stop systemd-resolved systemctl disable systemd-resolved systemctl mask systemd-resolvedstop是立即停disable是防止开机自启mask是彻底“封印”哪怕别的服务想拉起它也会失败。三层保险一起上基本杜绝了“凌晨三点它自己活过来抢53端口”的诡异情况。第二种方案是只改配置不停服务。比如编辑/etc/systemd/resolved.conf设置DNS127.0.0.1并设置Domains~. 让systemd-resolved把所有请求都转发给本机。这个方案的好处是保留systemd的解析链坏处是链路多了一层、排查更绕、重启后行为容易漂移。我个人实测下来对于已经决定用Bind9的服务器不如直接走“停止禁用mask”这条路干净利落。注意mask这个动作要慎用只有你确定这台机器不再需要systemd-resolved时才做。如果你的服务器上还有其他服务依赖它的D-Bus接口mask之后可能报错不过这种依赖极其少见一般内网DNS服务器不会涉及。2.3 释放53端口并验证停掉systemd-resolved后再次执行ss -lntup | grep 53确认没有任何进程占用53端口后再启动Bind9就不会出现端口冲突了。很多教程没说这层关系直接让读者改Bind9配置结果bind9怎么改都起不来看了日志才发现是systemd-resolved占着端口没放。这一步是“地基”地基没打好上面的楼盖多高都白搭。另外还要处理resolv.conf把软链替换成真实文件否则重启后还是会被还原rm -f /etc/resolv.conf echo nameserver 127.0.0.1 /etc/resolv.conf为什么指向127.0.0.1而不是127.0.0.53因为Bind9监听的是本机所有地址的53端口包括回环地址127.0.0.1会直接命中named进程。如果指向127.0.0.53而systemd-resolved又已经停了这个地址是没人监听的DNS请求就“石沉大海”了。这是一条非常容易踩的连环坑你以为改了DNS地址实际上指到了一个已经关闭的服务上。注意如果你使用的是静态IP配置请确保/etc/network/interfaces或netplan配置里不再写dns-nameservers 127.0.0.53而应写127.0.0.1。否则网卡重启后系统会用网卡配置里的DNS覆盖resolv.conf你手工改的文件又会被冲掉。这块下面第4节会再展开。3. 银河麒麟V10下Bind9的安装与核心配置3.1 安装Bind9包名和体系差异Debian系含麒麟V10下Bind9的包名叫bind9服务名是named。这里跟CentOS/RHEL体系有个明显区别CentOS里叫bind、named配置文件在/etc/named.conf而麒麟V10用apt安装的话装的是bind9包主配置文件在/etc/bind/named.conf服务名也叫named。安装命令apt update apt install -y bind9 bind9-utils bind9-dnsutilsbind9-utils里有named-checkconf和named-checkzone这两个工具后面排错离不开它们属于必备品。bind9-dnsutils提供dig、nslookup等客户端工具用来验证解析结果。装完后先检查一下版本named -v确认安装成功后再进行配置。如果是离线环境需要提前准备deb安装包麒麟V10一般自带apt源里有bind9问题不大。3.2 主配置named.conf.options的合理设置先看一下默认的/etc/bind/named.conf.options内容然后逐项修改。我这里给出一个适合内网DNS的配置模板注释写清楚每一项的用途options { directory /var/cache/bind; listen-on port 53 { any; }; listen-on-v6 { any; }; recursion yes; allow-query { any; }; allow-recursion { any; }; allow-transfer { none; }; forwarders { 223.5.5.5; 119.29.29.29; }; forward only; dnssec-validation no; auth-nxdomain no; version not disclosed; };逐项说下套路listen-on port 53 { any; };让named监听所有网卡的53端口。如果你只想让特定网段访问这里可以写{ 192.168.1.10; 127.0.0.1; }限制监听地址也是一种安全措施。recursion yes;允许递归查询这是作为内网DNS的基本能力否则只能做权威解析。allow-query { any; };允许所有客户端发查询请求。如果只允许内网段可以改成{ 192.168.1.0/24; 127.0.0.1; }。forwarders和forward only内网DNS的关键逻辑——内部zone由自己解析外部域名转发给上游DNS。这里用阿里DNS和腾讯DNS做双保险。如果你有更合适的上游比如公司出口路由器的DNS或运营商DNS也可以替换。dnssec-validation no;说实话DNSSEC验证在纯内网环境里往往利大于弊。开了之后如果上游某个域名DNSSEC配置有问题会导致解析失败排查起来非常麻烦。内网环境优先保证可用性和速度所以这里建议关掉。改完配置后用下面命令校验格式named-checkconf /etc/bind/named.conf.options没有任何输出就说明格式正确。有报错就按提示逐行排查最常见的错误是少分号、括号不匹配。Bind9配置对分号极其敏感漏一个分号整个配置文件就废了。3.3 自定义zone内网域名如何映射到IP现在进入正式的正向解析和反向解析配置。假设你的内网域名是corp.local有一台服务器叫gitlab.corp.localIP是192.168.50.10。编辑/etc/bind/named.conf.local添加zone声明zone corp.local { type master; file /etc/bind/db.corp.local; allow-update { none; }; }; zone 50.168.192.in-addr.arpa { type master; file /etc/bind/db.192.168.50; };正向zone文件db.corp.local内容$TTL 600 IN SOA ns1.corp.local. admin.corp.local. ( 2025061001 ; serial 7200 ; refresh 3600 ; retry 1209600 ; expire 300 ) ; negative cache TTL IN NS ns1.corp.local. ns1 IN A 192.168.50.10 gitlab IN A 192.168.50.10 nas IN A 192.168.50.20 router IN A 192.168.50.1反向zone文件db.192.168.50内容$TTL 600 IN SOA ns1.corp.local. admin.corp.local. ( 2025061001 7200 3600 1209600 300 ) IN NS ns1.corp.local. 10 IN PTR ns1.corp.local. 10 IN PTR gitlab.corp.local. 20 IN PTR nas.corp.local.serial号我习惯按日期加序号来写2025061001代表2025年6月10日第一次修改。每次改zone内容时serial必须递增否则从服务器不会刷新记录这是一个非常隐蔽的坑——你改了数据客户端却还拿到旧IP排查半天才发现serial没变。用named-checkzone校验两个zone文件named-checkzone corp.local /etc/bind/db.corp.local named-checkzone 50.168.192.in-addr.arpa /etc/bind/db.192.168.50看到“OK”字样后再重启named服务。我建议每次改配置都跑一遍check别图省事直接重启服务配置有错的话named不会启动你会被迫进入“改错→重启失败→看日志→再改”的循环。3.4 named服务启动与开机自启启动服务systemctl start named systemctl enable named查看状态systemctl status named如果启动失败看日志是最直接的排查手段journalctl -u named -n 50 --no-pager tail -f /var/log/syslog | grep named我遇到的启动失败大多数是这两类端口被占、zone文件语法错误。前者按第2节的流程释放端口后者用named-checkzone排查。还有一类情况是/etc/bind目录权限不对导致named无法读取zone文件这就要检查目录权限确保bind用户对zone文件有读权限。注意Bind9默认以bind用户运行zone文件属主如果是root并且权限是600named进程会提示“permission denied”而无法加载。配置完记得给/etc/bind下的zone文件设置644权限属主可以保持root不变但权限必须放开给其他用户读。4. 配置持久化与客户端接入如何彻底摆脱“重启后失效”4.1 让resolv.conf不再被覆盖的两种手段前面提到过银河麒麟V10的/etc/resolv.conf默认是指向systemd-resolved stub文件的软链。即使你删掉软链换成真实文件NetworkManager或dhclient在特定时机也可能重写它。第一种可靠的方案是修改网卡配置文件从源头把DNS指到127.0.0.1。如果你用的是Netplan麒麟V10常见编辑/etc/netplan下的yaml文件network: version: 2 ethernets: eth0: dhcp4: no addresses: - 192.168.50.10/24 gateway4: 192.168.50.1 nameservers: addresses: - 127.0.0.1然后应用配置netplan apply注意Netplan的缩进规则很严格yaml文件写错缩进会导致配置不生效或者应用失败。应用后检查cat /etc/resolv.conf确认nameserver确实是127.0.0.1。如果你用的是/etc/network/interfaces管理网络那就写成auto eth0 iface eth0 inet static address 192.168.50.10 netmask 255.255.255.0 gateway 192.168.50.1 dns-nameservers 127.0.0.1然后重启网络或执行ifdown/ifup让配置生效。第二种方案是加固resolv.conf文件本身用chattr加写保护chattr i /etc/resolv.conf这个命令会让文件变成“不可篡改”即使NetworkManager想重写也会失败。但这种方式有个副作用你自己后续想改DNS时得先执行chattr -i /etc/resolv.conf解除锁定否则改不了。所以我一般建议先改网卡配置再用chattr兜底双保险。注意如果DNS服务器本身就运行在这台机器上resolv.conf写127.0.0.1没问题但如果这是客户端机器resolv.conf应写DNS服务器的内网IP比如192.168.50.10而不是127.0.0.1。4.2 客户端机器如何接入新DNS客户端接入很简单两步把客户端的DNS指向内网DNS服务器IP。验证内网域名解析是否正常。Linux客户端临时修改echo nameserver 192.168.50.10 /etc/resolv.confWindows客户端则在“网络适配器选项→IPv4属性”里把首选DNS改成192.168.50.10。验证命令dig gitlab.corp.local如果应答里出现“status: NOERROR”和正确的A记录就说明解析通了。如果出现“status: SERVFAIL”或者“NXDOMAIN”就要从Bind9配置或者zone数据上找问题了。还有一点客户端如果是Windows解析内网域名时很依赖DNS后缀。建议在客户端“高级TCP/IP设置”里的DNS标签页添加“corp.local”作为附加后缀否则你ping gitlab时得写全域名gitlab.corp.local写成gitlab就解析不了。这是很多人在Windows客户端上遇到的“明明DNS配置了怎么还是不生效”的隐藏原因。4.3 防火墙和selinux/AppArmor对Bind9的影响麒麟V10默认开着ufw或者iptables的情况都有要确保53端口对客户端开放ufw allow 53/tcp ufw allow 53/udp如果不开防火墙放行DNS服务器本机解析没问题但局域网其他机器发来的DNS请求会被防火墙丢掉表现出来就是“本机能解析、别的机器解析不了”这种问题最迷惑人。AppArmor方面Debian系的bind9默认带有AppArmor配置限制named对文件系统的访问范围。如果你把zone文件放在非默认路径比如自定义的/opt/dns目录就可能触发AppArmor拦截。解决办法有两种把zone文件放到默认的/var/cache/bind或/etc/bind目录下省心或者编辑/etc/apparmor.d/usr.sbin.named添加你自定义路径的读写规则/opt/dns/** rw,然后执行apparmor_parser -r /etc/apparmor.d/usr.sbin.named我建议新手直接用默认目录避免跟AppArmor纠缠。等你对系统熟悉了再去自定义路径也不迟。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查/解决方式named启动失败日志显示“address in use”systemd-resolved仍占用53端口停用systemd-resolved并maskss -lntup确认端口释放本机解析正常其他机器解析超时防火墙未放行53端口或listen-on未包含服务器内网IPufw allow 53/tcp、53/udp确认listen-on配置修改了zone数据客户端仍返回旧IPserial号未递增或未重启named修改serial号并递增systemctl restart nameddig内网域名返回SERVFAILzone文件语法错误或权限不足named-checkzone校验检查zone文件644权限/etc/resolv.conf重启后被还原NetworkManager或systemd-resolved在重写修改网卡配置中DNS必要时chattr i外网域名解析失败但内网正常forwarders配置错误或上游DNS不可达ping 223.5.5.5测试连通性检查forwarders配置客户端解析内网域名要写全域名才能通未设置DNS后缀Windows客户端添加DNS附加后缀corp.local这张表是我实际排障过程中最常遇到的场景基本涵盖了内网DNS从搭建到上线的“翻车高发区”。如果你遇到表里没有的情况下一步操作永远是看日志多看日志能解决90%的问题。5.2 最容易误判的一个坑DNS指向127.0.0.53但服务已停我第二次踩这个坑的时候才猛然明白问题的严重性部署完Bind9后我手动改了resolv.conf为nameserver 127.0.0.1本机解析正常。但第三天同事反馈有一台机器解析不了我过去一看resolv.conf变成了nameserver 127.0.0.53而systemd-resolved早就被停了等于DNS请求发到了一个无人监听的回环地址。这事的根源是那台机器某种操作触发了NetworkManager重新生成resolv.conf又重新指向了stub。解决方式就是我前面讲的“改网卡配置 chattr锁定文件”双保险。单改文件是不够的要从网络配置源头动手。5.3 bind9服务“假活”问题有时systemctl status named显示active (running)但dig就是超时。我第一次遇到时还以为是网络问题后来用ss一看named监听的不是内网网卡而是只有127.0.0.1。问题出在listen-on配置listen-on port 53 { any; };如果你写成了{ 127.0.0.1; }就只有本机能用局域网其他机器全都连不上。这种“假活”比启动失败更坑因为服务看起来一切正常实际上完全没提供服务。遇到这种问题直接看监听地址ss -lntup | grep named如果只有127.0.0.1:53而缺了内网IP那一行那问题就定位了。5.4 缓存导致“看起来没生效”Bind9默认开了缓存内网域名改动后客户端可能继续拿到旧的缓存结果。这不是bug是DNS设计使然。如果你的zone文件改了serial号并重启named但客户端还是解析到旧IP可以在客户端用dig查权威应答dig gitlab.corp.local 192.168.50.10 noall answer如果这条命令返回的还是旧IP可能是zone数据没改对如果返回新IP问题出在客户端本地缓存Windows用ipconfig /flushdns刷新Linux用systemd-resolve --flush-caches如果没关resolved的话或重启网络服务。6. 扩展思路从单点DNS到主从架构搭好单机Bind9之后如果你所在的内网环境不止一台服务器或者对可用性有要求下一步就是做主从DNS。主DNSprimary放zone数据允许区域传输从DNSsecondary只做只读复制定期从主DNS拉取zone。客户端把两个DNS都写上主挂了从顶上。配置其实非常简单主服务器named.conf.local里允许传输zone corp.local { type master; file /etc/bind/db.corp.local; allow-transfer { 192.168.50.11; }; };从服务器named.conf.local里定义zone corp.local { type slave; file /var/cache/bind/db.corp.local; masters { 192.168.50.10; }; };从服务器上不需要手写zone数据它会自动从主服务器同步。同步的前提是主节点53端口可达且allow-transfer里写对了从节点IP。如果同步失败看两个节点的系统日志通常会有明确提示。主从架构相当于给内网DNS上了双保险。初始阶段先把单节点跑顺理解了zone传递的机制后再升级也不迟。最后说一个重要心得DNS这个服务最大的特点就是“看起来简单坑起来要命”。它不复杂但每一个细节都可能成为生产事故的源头。systemd-resolved、NetworkManager、dhclient、防火墙、AppArmor、serial号、缓存……任何一个环节没理顺都可能让一个“理应正常”的DNS变得诡异莫测。我建议任何人在部署内网DNS后都要做一次“重启全流程测试”重启机器后检查解析是否正常、查看resolv.conf是否被篡改、查询named进程是否自动拉起。这几分钟能帮你提前暴露绝大多数“重启后失效”的问题比事后手忙脚乱地排查要高效得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进