DNS Web Manager 教程:Flask + BIND9 的 Web 管理实战
内网 DNS 是基础设施里最容易被忽视、又最不能出问题的一环:域名解析一挂,内网所有服务都跟着找不到北。传统做法是 SSH 上服务器手改 BIND 的 zone 文件,改完 named-checkzone、rndc reload 一套流程走下来,改一个记录要敲十几条命令。DNS Web Manager(仓库 AI-Agent/dns-service)把这套流程搬进了浏览器:Flask + BIND9 的 Web 管理界面,域名、记录、配置、服务控制全在页面上点几下完成,还自带一键部署脚本,从干净系统一路装到能用。本文按官方 README 的完整流程讲透。
一、项目简介
DNS Web Manager 是基于 BIND 9.18+ 的 DNS 服务器 Web 管理界面,用 Flask 3.0 + SQLite 构建,前端是 Jinja2 模板加原生 CSS,没有任何前端框架依赖,WSGI 层用 Gunicorn。它和 DHCP Web Manager 是同一作者的同一套思路:不重复实现 DNS 协议本身,而是给系统里已经在跑的 BIND(named)做一个“遥控器”。你在界面上的操作,最终都会落到 /etc/bind/(Debian)或 /etc/named.conf(RHEL)这些标准位置,再由 named 对外提供解析服务。
项目最大的亮点是部署体验:自带 scripts/deploy.sh 一键部署脚本,支持 --dry-run 预演、幂等重跑、换端口、跳过 BIND 配置等选项;同时还做了 BIND 路径自动探测,Debian、RHEL、Rocky、AlmaLinux、FreeBSD 的默认布局都能自动识别,多数情况下不需要任何手工配置。
dns-service/
├── app.py # Flask 应用主文件(路由、模型、BIND 操作)
├── wsgi.py # Gunicorn 入口
├── init_db.py # 数据库初始化脚本
├── requirements.txt # Python 依赖
├── scripts/
│ ├── deploy.sh # 一键部署(装包 + venv + 建库 + 配 BIND + systemd)
│ └── setup-bind.sh # 单独初始化 BIND
├── templates/ # Jinja2 模板(仪表盘 / 域名 / 记录 / 查询 / 配置 / 日志)
├── static/style.css
└── instance/ # SQLite 数据库(运行时生成)
└── dns_web.db
二、功能特性一览
| 功能 | 说明 |
|---|---|
| 仪表盘 | 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 |
| 一键部署 | scripts/deploy.sh 从干净系统一路到运行中的服务,幂等、可预演 |
三、适用场景
- 内网域名解析:给内网服务起统一域名(比如
git.corp.lan、nas.corp.lan),告别靠 IP 和 hosts 文件人肉记忆。 - 测试/实验环境:频繁创建、删除测试域名和记录,Web 界面比手改 zone 文件快一个数量级。
- 多记录类型管理:除了常见的 A 记录,还要配 MX(邮件)、TXT(SPF/DKIM 验证)、SRV(服务发现)等,界面上的记录类型表单比手写 zone 语法更不容易出错。
- 团队协作:运维、开发都能登录操作,所有变更有审计日志,谁改了哪条记录一查就知道。
- 不适合的场景:对外提供权威 DNS 服务的高可用场景(需要多节点同步、DNSSEC 签名流程、Anycast 等),这个轻量工具只适合内网和中小规模使用。
四、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 默认不存在。项目启动时会自动探测这些路径(见第七节),所以跨发行版部署时一般不用手工改代码。
五、一键部署:deploy.sh(推荐)
如果你是干净系统、想最快跑起来,直接用一键部署。scripts/deploy.sh 会一路完成:安装系统包(bind9 / bind-utils / python3 / pip / venv / git,自动识别 apt/yum/dnf)→ 创建 Python 虚拟环境 venv/ 并安装依赖(用 venv 的 pip,避开 Debian 12+ 的 PEP 668 限制)→ 初始化 SQLite 数据库(默认 admin/admin)→ 调用 scripts/setup-bind.sh 配置 BIND(建 zone 目录、确保 named.conf.local 被 include、设权限)→ 安装并启用 systemd 服务 dns-web(gunicorn,端口 5300,2 workers)→ 启动服务并验证端口和 HTTP 响应。
git clone ssh://git@git.cnbugs.com:10022/AI-Agent/dns-service.git
cd dns-service
# 先预览会做什么(强烈推荐先看一遍)
sudo bash scripts/deploy.sh --dry-run
# 确认后真跑
sudo bash scripts/deploy.sh
# 如果 BIND 已经配好了,只想更新应用
sudo bash scripts/deploy.sh --skip-bind
# 换端口
sudo bash scripts/deploy.sh --port 5301
# 或用环境变量
sudo DNS_WEB_PORT=5301 DNS_WEB_WORKERS=4 bash scripts/deploy.sh
| 环境变量 | 默认 | 说明 |
|---|---|---|
| DNS_WEB_PORT | 5300 | Web 面板监听端口 |
| DNS_WEB_USER | root | 服务运行用户(需能操作 BIND 配置 + systemctl) |
| DNS_WEB_WORKERS | 2 | gunicorn worker 数 |
脚本是幂等的,可以重复执行;升级代码后重跑一次即可(venv 和数据库保留,只补缺失的部分)。关于 root 用户:Web 应用需要 root 权限来操作 /etc/bind/* 配置文件和执行 systemctl / rndc 命令。如果环境不允许 root 跑服务,用 DNS_WEB_USER=<user> 覆盖,并给该用户配好 sudo 规则。还有一个细节:systemd unit 要用 Type=exec——gunicorn 21.x 不带 sd_notify,用 Type=notify 会卡 90 秒超时然后失败。
六、手动部署:每一步都自己控制
如果你的机器已经有 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
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
6.3 安装 Web 应用并生产部署
git clone ssh://git@git.cnbugs.com:10022/AI-Agent/dns-service.git
cd dns-service
# 一键初始化 BIND(自动识别 Debian vs RHEL;默认是干跑,先看清再真跑)
sudo bash scripts/setup-bind.sh --dry-run
sudo bash scripts/setup-bind.sh
# 安装 Python 依赖,初始化数据库
pip install -r requirements.txt
mkdir -p instance
python3 init_db.py # 创建默认 admin/admin 账号
# 开发模式启动
python3 app.py
# 生产部署:gunicorn + systemd(注意 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-service
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
七、路径自动探测与环境变量覆盖
服务启动时按下面三级优先级确定每个 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 这个文件,RHEL 的 /etc/named.conf 默认不会 include,需要按第六节的命令追加。
# 环境变量覆盖示例(systemd)
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-service
Environment=BIND_ZONES_DIR=/opt/bind/zones
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 restart dns-web
八、使用指南:从建域名到查记录
浏览器打开 http://<服务器IP>:5300,默认账号 admin / admin,首次登录后请修改密码。登录后核心是四个页面:域名管理、DNS 查询、配置管理、仪表盘。
8.1 创建域名(Zone)
进入「域名管理」→「+ 添加域名」,填域名(如 example.com)、选类型(master/slave),NS 服务器和管理员邮箱可留空(留空自动生成),点「创建域名」。创建后 Zone 详情页能看到 SOA 信息、记录列表、添加记录表单,还能直接编辑原始 zone 文件。
8.2 添加 DNS 记录
| 类型 | 数据格式 | 示例 |
|---|---|---|
| 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 有没有更新。
8.3 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. 部署(已按第五节一键部署好的跳过这步)
sudo bash scripts/deploy.sh
# 浏览器打开 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,见第五节说明 |
十一、运维建议
- 必须以 root 运行:Web 应用要操作
/etc/bind/*配置文件、执行systemctl和rndc,普通用户权限不够。不允许 root 跑服务时,用DNS_WEB_USER覆盖并配 sudo 规则。 - 先改密码,再开端口:默认
admin/admin,首次登录立刻改掉;5300 端口只在内网开放,不要暴露到公网,必要时前面加反代和 HTTPS。 - 备份 zone 文件和数据库:
/etc/bind/zones/和instance/dns_web.db要进备份计划;配置文件编辑时自动生成的.bak只能算临时后悔药,不能替代定期备份。 - 升级流程:拉新代码后重跑一次
scripts/deploy.sh即可,脚本幂等,venv 和数据库都会保留。 - 定期看操作日志:审计日志在页面上有分页查看,删记录、改配置这些敏感操作都能追溯到人。
总结:DNS Web Manager 把 BIND 这套“改文件、查语法、重载服务”的手工流程,收敛成了一个带一键部署、带自动探测、带审计日志的 Web 面板。对内网 DNS 来说,它覆盖了从部署到日常记录管理的全流程;唯一要反复提醒的只有一件事——named.conf.local 必须被主配置 include,否则一切配置都是写给自己看的。按本文的流程部署一次,后面加域名、加记录就都是点几下鼠标的事了。






