DNS Web Manager 手动部署版教程:没有一键脚本的 BIND9 Web 管理搭建实战
在 AI-Agent 组织里有两个名字只差一个字母的 DNS 项目:dns-service 和 dns-serrvice(注意第二个名字里 service 被写成了 serrvice,多了一个 r)。很多人第一次看到会以为是手抖建重了,或者其中一个是另一个的分支。本文先把结论放在最前面:这两个项目是同一个 DNS Web Manager 应用,功能、界面、技术栈完全相同,唯一的区别在部署方式——dns-serrvice 没有一键部署脚本,只能手动一步步搭建。如果你已经分不清该用哪一个,看完第二章的对比表就清楚了。
一、项目简介:它到底是什么
DNS Web Manager(仓库 AI-Agent/dns-serrvice)是基于 BIND 9.18+ 的 DNS 服务器 Web 管理界面,用 Flask 3.0 + SQLite 构建,前端是 Jinja2 模板加原生 CSS,无前端框架依赖,WSGI 层用 Gunicorn。它给系统里已经在跑的 BIND(named)做一个 Web“遥控器”:域名、记录、配置、服务控制全在浏览器里完成,所有操作最终落到 /etc/bind/(Debian)或 /etc/named.conf(RHEL)这些标准位置。
dns-serrvice/
├── app.py # Flask 应用主文件(路由、模型、BIND 操作)
├── wsgi.py # Gunicorn 入口
├── init_db.py # 数据库初始化脚本
├── requirements.txt # Python 依赖
├── README.md
├── templates/ # Jinja2 模板(仪表盘 / 域名 / 记录 / 查询 / 配置 / 日志)
│ ├── base.html # 布局模板(导航栏 + 页脚)
│ ├── login.html # 登录页
│ ├── dashboard.html # 仪表盘
│ ├── zones.html # 域名列表
│ ├── zone_form.html # 创建域名表单
│ ├── zone_detail.html # Zone 详情 + 记录管理
│ ├── zone_raw.html # 原始 Zone 文件编辑
│ ├── query.html # DNS 查询测试
│ ├── config.html # BIND 配置管理
│ ├── logs.html # 操作日志
│ ├── change_password.html
│ └── error.html # 错误页
├── static/
│ └── style.css # 全部样式
└── instance/ # SQLite 数据库(运行时生成)
└── dns_web.db
注意上面这个目录结构里没有 scripts/ 目录——这就是它和 dns-service 最本质的区别,后面会详细展开。README 里还有两处沿用旧名字的痕迹:目录结构图的标题写的是 dns-service/,克隆命令里写的也是 dns-service.git,这些都是从 dns-service 复制 README 时没改过来的地方,实际使用时以组织页面上的仓库名 dns-serrvice 为准。
二、和 dns-service 的区别:只有部署方式不同
我把两个项目的 README 和文件清单逐行对比过,结论如下表。先说结论:应用本身没有区别,区别只在“怎么把它装起来”。
| 对比项 | dns-service | dns-serrvice(本篇) |
|---|---|---|
| 应用功能 | 域名、记录、查询、配置、日志、认证 | 完全相同 |
| 技术栈 | BIND 9.18+、Flask 3.0、SQLite、Gunicorn | 完全相同 |
| 核心代码 | app.py、wsgi.py、init_db.py、templates、static | 完全相同(文件清单一致) |
| 一键部署脚本 | 有 scripts/deploy.sh(--dry-run、--skip-bind、--port) | 没有 |
| BIND 初始化脚本 | 有 scripts/setup-bind.sh | 没有 |
| 部署方式 | 一键部署或手动部署(二选一) | 只能手动部署 |
| 部署环境变量 | DNS_WEB_PORT、DNS_WEB_USER、DNS_WEB_WORKERS | 没有(README 未提及) |
| README 克隆地址 | dns-service.git | 写的是 dns-service.git(疑似复制未改,以组织页面为准) |
换句话说,dns-serrvice 可以看作是 dns-service 的“手动部署版”或“无脚本版”:应用代码一样,少了两层自动化外衣。这意味着两件事:第一,用起来的体验完全一样,登录进去的界面、功能、操作流程没有任何区别;第二,装起来的工作量不一样,你需要自己装 BIND、自己建目录、自己写 systemd unit,整个过程大概多花 20–30 分钟,但每一步都在自己手里,出问题也更容易定位。
选哪个版本?一句话:想最快跑起来、或者要批量部署多台机器,选 dns-service;机器上已经有 BIND 在跑、想完全控制每一步、或者想顺便学一遍 BIND 配置,选 dns-serrvice。两个版本的数据(zone 文件、SQLite 库)格式一致,部署方式不影响后续使用。
三、功能特性一览
| 功能 | 说明 |
|---|---|
| 仪表盘 | BIND 服务状态、域名/记录统计、服务控制(启动/停止/重启/重载)、域名概览、最近操作日志 |
| 域名管理 | 创建/删除 Zone(支持 master/slave)、查看 Zone 详情、编辑原始 zone 文件 |
| 记录管理 | 添加/删除 DNS 记录:A、AAAA、CNAME、MX、TXT、NS、PTR、SRV、CAA |
| DNS 查询 | 在线 dig 测试,支持所有记录类型,可指定上游 DNS 服务器 |
| 配置管理 | 在线编辑 named.conf.options 和 named.conf.local,带语法校验 |
| 操作日志 | 全部操作的审计日志,支持分页 |
| 用户认证 | 登录/登出/修改密码,默认账号 admin/admin |
四、适用场景
- 已有 BIND 的机器:服务器上 BIND 已经配好了,只想加个 Web 界面来管 zone 和记录,手动部署正好,不需要一键脚本去动现有的 BIND 配置。
- 想学 BIND 配置:手动走一遍装包、写 options、建 zone 目录、配 include 的全过程,对理解 BIND 很有帮助;一键脚本把这些都藏起来了,反而不利于学习。
- 内网域名解析:给内网服务起统一域名(比如
git.corp.lan、nas.corp.lan),告别靠 IP 和 hosts 文件人肉记忆。 - 测试/实验环境:频繁创建、删除测试域名和记录,Web 界面比手改 zone 文件快得多。
- 不适合的场景:对外提供权威 DNS 的高可用场景(多节点同步、DNSSEC 签名流程、Anycast),以及需要批量部署多台机器的场合——后者请用
dns-service的一键部署脚本。
五、BIND 配置文件:先搞清四个文件的关系
动手之前先把 BIND 的配置文件分工搞清楚。最关键的一点是:Web UI 写入 zone 声明的文件(named.conf.local)必须被 named 主配置 include,否则 zone 文件存在、named 也不会加载——这是新手最常踩的坑,没有一键脚本帮你检查这一步,手动部署时更要自己确认。
| 文件 | 用途 | 默认路径(Debian/Ubuntu) |
|---|---|---|
| named.conf | 主配置,include 其他文件 | /etc/bind/named.conf |
| named.conf.options | 全局选项:监听端口、转发器、查询权限 | /etc/bind/named.conf.options |
| named.conf.local | Zone 声明,由 Web UI 自动管理 | /etc/bind/named.conf.local |
| zone files | DNS 记录文件 | /etc/bind/zones/db.* |
RHEL/CentOS/Rocky/AlmaLinux 的布局不同:主配置是 /etc/named.conf,zone 文件在 /var/named/,/etc/named.conf.local 默认不存在。项目启动时会自动探测这些路径(见第八节),跨发行版部署一般不用手工改代码。
六、安装 BIND 并手动配置
下面分 Debian 和 RHEL 两套写,全新机器按顺序做,已经有 BIND 在跑的机器只看“追加 include”那一步。
6.1 Debian / Ubuntu
apt-get update
apt-get install -y bind9 bind9utils dnsutils
# 创建 zone 文件目录(项目代码默认就写这里)
mkdir -p /etc/bind/zones
chown bind:bind /etc/bind/zones
# named.conf.options(监听 53 端口,转发器 8.8.8.8)
cat > /etc/bind/named.conf.options << 'EOF'
options {
directory "/var/cache/bind";
listen-on port 53 { any; };
listen-on-v6 { none; };
forwarders { 8.8.8.8; 8.8.4.4; };
allow-query { any; };
allow-recursion { 127.0.0.0/8; 10.168.1.0/24; };
dnssec-validation auto;
auth-nxdomain no;
};
EOF
# 确认主配置 include 了 named.conf.local(bind9 包默认就是这套,确认一下即可)
grep -q '/etc/bind/named.conf.local' /etc/bind/named.conf || \
echo 'include "/etc/bind/named.conf.local";' >> /etc/bind/named.conf
# 空的 named.conf.local(由 Web UI 自动填充)
touch /etc/bind/named.conf.local
chown bind:bind /etc/bind/named.conf.local
systemctl enable named
systemctl restart named
# 验证 named 正常
systemctl status named --no-pager
named-checkconf && echo "named config OK"
6.2 RHEL / CentOS 7 / Rocky / AlmaLinux
yum install -y bind bind-utils
mkdir -p /var/named
chown root:named /var/named
chmod 2770 /var/named
# 关键:RHEL 的 /etc/named.conf 默认不 include /etc/named.conf.local,
# 必须手动追加,否则 Web UI 写的 zone 声明不会生效
grep -q '/etc/named.conf.local' /etc/named.conf || \
echo 'include "/etc/named.conf.local";' >> /etc/named.conf
# 空白入口文件,让 named 重启时不报 include 文件不存在
touch /etc/named.conf.local
chown root:named /etc/named.conf.local
chmod 640 /etc/named.conf.local
systemctl enable named
systemctl restart named
# 验证
systemctl status named --no-pager
named-checkconf && echo "named config OK"
七、安装 Web 应用并生产部署
# 注意:本项目没有一键部署脚本,下面每一步都要手动做
git clone ssh://git@git.cnbugs.com:10022/AI-Agent/dns-serrvice.git
cd dns-serrvice
# 安装 Python 依赖
pip install -r requirements.txt
# 创建数据库目录并初始化(创建默认 admin/admin 账号)
mkdir -p instance
python3 init_db.py
# 开发模式启动验证
python3 app.py
# 浏览器打开 http://<服务器IP>:5300 能看到登录页即成功,Ctrl+C 停掉再做生产部署
生产部署用 gunicorn + systemd。注意下面 unit 里的 Type=exec:gunicorn 21.x 不带 sd_notify,如果写成 Type=notify 会卡 90 秒超时然后失败——README 的手动部署示例里写的是 Type=notify,这是 README 自身前后不一致的地方,按 Type=exec 写才稳妥。
cat > /etc/systemd/system/dns-web.service << 'EOF'
[Unit]
Description=DNS Web Manager
After=network.target named.service
[Service]
Type=exec
User=root
WorkingDirectory=/root/dns-serrvice
ExecStart=/usr/local/bin/gunicorn --workers 2 --bind 0.0.0.0:5300 wsgi:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable dns-web
systemctl start dns-web
systemctl status dns-web --no-pager
# 验证端口和 HTTP 响应
ss -tlnp | grep 5300
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:5300/
八、路径自动探测:不用改代码也能跨发行版
虽然没有部署脚本,但应用本身的路径自动探测功能是完整的。服务启动时按三级优先级确定每个 BIND 路径:环境变量(最高,可强制指定)→ 自动探测(按常见路径顺序找第一个存在的)→ 内置默认值(都没探测到时用,主要为 Debian/Ubuntu)。
| 变量 | 探测顺序 |
|---|---|
| BIND_CONF_DIR | /etc/bind → /etc → /etc/named |
| BIND_ZONES_DIR | /etc/bind/zones → /var/named → /var/named/data |
| BIND_CONF_LOCAL | /etc/named.conf.local → /etc/bind/named.conf.local |
| BIND_CONF_OPTIONS | /etc/named.conf → /etc/bind/named.conf.options |
| BIND_RNDC_KEY | /etc/bind/rndc.key → /etc/rndc.key → /var/named/key |
| BIND_SERVICE | 固定 named |
注意 BIND_CONF_LOCAL 的探测顺序是优先 RHEL 路径:为了避免“两个文件都存在但 named 只读其中一个”的歧义场景——典型踩坑就是 zone 文件写出来了,named 却不加载。多数情况下完全不需要配置:Debian 上自然走 /etc/bind/*,RHEL 上自然走 /var/named + /etc/named.conf*。只有 BIND 装在非标准路径(比如容器里 mount 到 /opt/bind)或同机多实例、测试场景才需要用环境变量强制覆盖,覆盖后务必让 named 主配置 include 这个文件。
# 环境变量覆盖示例(加到 systemd unit 里)
# Environment=BIND_ZONES_DIR=/opt/bind/zones
systemctl edit dns-web
# 在编辑器里加入:
# [Service]
# Environment=BIND_ZONES_DIR=/opt/bind/zones
systemctl daemon-reload
systemctl restart dns-web
九、使用指南:从建域名到查记录
浏览器打开 http://<服务器IP>:5300,默认账号 admin / admin,首次登录后请修改密码。登录后核心是四个页面:域名管理、DNS 查询、配置管理、仪表盘。
9.1 创建域名(Zone)与添加记录
进入「域名管理」→「+ 添加域名」,填域名(如 example.com)、选类型(master/slave),NS 服务器和管理员邮箱可留空(留空自动生成),点「创建域名」。创建后在 Zone 详情页能看到 SOA 信息、记录列表、添加记录表单,还能直接编辑原始 zone 文件。
| 类型 | 数据格式 | 示例 |
|---|---|---|
| A | IPv4 地址 | 10.168.1.100 |
| AAAA | IPv6 地址 | 2001:db8::1 |
| CNAME | 别名(FQDN,以 . 结尾) | www.example.com. |
| MX | 优先级 + 邮件服务器(FQDN) | 10 mail.example.com. |
| TXT | 文本内容 | "v=spf1 ~all" |
| NS | NS 服务器(FQDN) | ns1.example.com. |
| PTR | 反向解析目标(FQDN) | host.example.com. |
| SRV | 优先级 权重 端口 目标 | 10 5 5060 sip.example.com. |
注意 CNAME、MX、NS、PTR 这些指向域名的类型,数据要写完整 FQDN 并以点号结尾(www.example.com.),少了末尾的点,BIND 会把它当相对域名补上当前 zone 名,解析结果就错了。每次添加或删除记录时,SOA Serial 会自动递增(YYYYMMDDNN 格式),从服务器靠这个判断 zone 有没有更新。
9.2 DNS 查询测试、配置管理与服务控制
- DNS 查询:进「DNS 查询」页,输入域名和记录类型,DNS 服务器留空默认查本地 BIND(127.0.0.1:53),点查询看完整 dig 输出。改完记录先在这里验证,比在客户端上试要快得多。
- 配置管理:在线编辑
named.conf.options(监听端口、转发器等)和named.conf.local(Zone 声明),保存时自动执行named-checkconf校验,通过后自动rndc reload。编辑前自动创建.bak备份。 - 服务控制:仪表盘上可以直接启动、停止、重启 BIND,或
rndc reload热加载配置(不中断服务)。
十、完整示例:搭建内网域名解析
假设内网网段是 10.168.1.0/24,要建一个 corp.lan 域名,让 git.corp.lan 指向 10.168.1.10、nas.corp.lan 指向 10.168.1.20,并验证解析生效:
# 1. 按第六、七节手动部署完成,服务已起来
systemctl status named dns-web --no-pager
# 浏览器打开 http://<服务器IP>:5300,admin/admin 登录,改密码
# 2. 在 Web 界面操作
# 域名管理 → 添加域名 corp.lan(类型 master)
# 进入 corp.lan 详情 → 添加 A 记录 git → 10.168.1.10
# 再添加 A 记录 nas → 10.168.1.20
# 3. 在服务器上验证解析(走本地 BIND 的 53 端口)
dig @127.0.0.1 git.corp.lan A +short
# 预期输出:10.168.1.10
dig @127.0.0.1 nas.corp.lan A +short
# 预期输出:10.168.1.20
# 4. 让内网客户端用上:把客户端的 DNS 改成这台服务器的 IP,再测一次
# nslookup git.corp.lan
# 5. 出问题时先看三个地方
systemctl status named --no-pager
named-checkconf
cat /etc/bind/zones/db.corp.lan
如果第 3 步查不到记录,先确认两件事:一是 /etc/bind/named.conf 里有没有 include named.conf.local(见第五节);二是 zone 文件的属主是不是 bind:bind(Web UI 会自动设置,手动改过文件的要检查)。这两个都对,再看 journalctl -u named 的报错。
十一、常见问题
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| zone 文件写出来了,但 named 不加载 | 主配置没有 include named.conf.local | 按第五节追加 include,重启 named;优先检查 BIND_CONF_LOCAL 指向的文件 |
| 53 端口被占用,named 起不来 | dnsmasq 或 systemd-resolved 占了 53 | 停掉占用的服务,或改 named 的监听端口 |
| CNAME/MX 解析结果多了后缀 | 记录数据没写末尾的点,被当相对域名补了 zone 名 | 数据写完整 FQDN 并以 . 结尾 |
| 从服务器收不到 zone 更新 | SOA Serial 没递增,或 master 不允许传输 | 确认 Web UI 自动递增了 Serial,检查 allow-transfer 配置 |
| 保存配置时校验失败 | named.conf.options 语法错误 | 看页面提示的 named-checkconf 报错,修正后重试;旧配置仍在生效,.bak 备份可恢复 |
| systemd 服务启动后 90 秒超时失败 | unit 用了 Type=notify,但 gunicorn 21.x 不带 sd_notify | 改成 Type=exec,见第七节说明 |
| git clone 时提示仓库不存在 | README 里写的克隆地址是 dns-service.git | 以组织页面上的实际仓库名 dns-serrvice 为准,不要照抄 README 里的地址 |
十二、运维建议
- 必须以 root 运行:Web 应用要操作
/etc/bind/*配置文件、执行systemctl和rndc,普通用户权限不够。 - 先改密码,再开端口:默认
admin/admin,首次登录立刻改掉;5300 端口只在内网开放,不要暴露到公网,必要时前面加反代和 HTTPS。 - 备份 zone 文件和数据库:
/etc/bind/zones/和instance/dns_web.db要进备份计划;配置文件编辑时自动生成的.bak只能算临时后悔药,不能替代定期备份。 - 升级流程:因为没有一键部署脚本,升级就是拉新代码、重启
dns-web服务两步;改动了依赖的话先重装requirements.txt再重启。 - 定期看操作日志:审计日志在页面上有分页查看,删记录、改配置这些敏感操作都能追溯到人。
总结:dns-serrvice 和 dns-service 是同一个应用的两副面孔——前者把部署的每一步都摊开给你看,后者用脚本把这些步骤包了起来。如果你看重“每一步都在自己手里”,或者机器上已经有现成的 BIND 不想让脚本去动它,手动部署版就是正确的选择;如果追求速度和批量部署,隔壁的 dns-service 更合适。两个版本用起来没有任何区别,选哪个只取决于你更信任脚本还是更信任自己的手。






