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-servicedns-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.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 默认不存在。项目启动时会自动探测这些路径(见第八节),跨发行版部署一般不用手工改代码。

六、安装 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 文件。

类型数据格式示例
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 有没有更新。

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 更合适。两个版本用起来没有任何区别,选哪个只取决于你更信任脚本还是更信任自己的手。

发表回复

后才能评论