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.localZone 声明,由 Web UI 自动管理/etc/bind/named.conf.local
zone filesDNS 记录文件/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_PORT5300Web 面板监听端口
DNS_WEB_USERroot服务运行用户(需能操作 BIND 配置 + systemctl)
DNS_WEB_WORKERS2gunicorn 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 记录

类型数据格式示例
AIPv4 地址10.168.1.100
AAAAIPv6 地址2001:db8::1
CNAME别名(FQDN,以 . 结尾)www.example.com.
MX优先级 + 邮件服务器(FQDN)10 mail.example.com.
TXT文本内容"v=spf1 ~all"
NSNS 服务器(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,否则一切配置都是写给自己看的。按本文的流程部署一次,后面加域名、加记录就都是点几下鼠标的事了。

发表回复

后才能评论