跳过正文
  1. 博客文章/

生产 DNS 集群平滑升级复盘:dnsdist 与 Technitium 的滚动变更

·989 字·5 分钟·
DevOps SRE DNS Dnsdist Technitium Docker DevOps SRE
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录
生产变更复盘 - 这篇文章属于一个选集。
1: 本文
这次 DNS 集群升级真正值得沉淀的不是命令本身,而是变更顺序:入口先行、后端滚动、每一步都有 Review Gate 和回滚点。最终 dnsdist 升级到 1.9.14,两个 Technitium DNS Server 节点升级到 15.2.0,生产 DNS 查询保持可用。
本文记录的是一次真实生产变更复盘。文中的 IP、域名和镜像仓库地址均已替换为文档示例值,所有密码、API Key、Token、私钥均未写入本文。

0. 结论和使用边界
#

升级目标:

组件升级前升级后结果
dnsdist1.9.111.9.14正常
Technitium 从节点14.2.015.2.0正常
Technitium 主节点14.2.015.2.0正常

本文不是从零安装教程,而是一份生产变更复盘。它关注的是:已经有一套 DNS 集群在线服务时,如何用最小风险完成入口和后端升级,并在每个阶段验证功能完整性。

读完这篇,你应该能复用三件事:

  • 升级顺序:入口先行,后端从节点先于主节点
  • Review Gate:每一步变更前后要检查什么
  • 证据链:遇到 Drops、ACL、timeout 这类信号时怎么判断

不建议直接复用的部分:

  • 示例 IP、域名、镜像仓库地址都已经脱敏
  • Web 控制台 ACL 不能照抄,必须按自己的管理端来源收紧
  • 回滚数据目录前必须确认 Technitium 版本迁移是否可逆

最终验证:

dig @192.0.2.211 corp.example.internal A +short
# 192.0.2.20

dig @192.0.2.234 corp.example.internal A +short
# 192.0.2.20

dig @192.0.2.235 corp.example.internal A +short
# 192.0.2.20

公网递归解析也正常:

dig @192.0.2.211 example.com A +short
# 返回公开递归解析结果

dnsdist Web 控制台返回 401 Unauthorized,说明服务已起来,等待认证登录:

curl -I http://192.0.2.211:5380/
# HTTP/1.1 401 Unauthorized

验证矩阵:

验证项方法通过标准
入口 DNS 查询dig @入口 内部域名返回预期 A 记录
后端主节点查询dig @主节点 内部域名返回预期 A 记录
后端从节点查询dig @从节点 内部域名返回预期 A 记录
递归解析dig @入口 example.com能返回公网递归结果
Web 控制台curl -I :5380返回认证挑战,不是连接失败
配置合法性dnsdist --check-config配置检查通过

1. 升级前准备
#

这一步的目的不是“多做几个检查”,而是避免在变更窗口里临时判断风险。生产 DNS 变更至少要先准备好这些信息:

准备项为什么需要
当前拓扑知道入口、主节点、从节点分别承担什么职责
当前版本和目标版本判断是否跨大版本、是否涉及配置迁移
核心验证域名升级后用同一组域名做对比
备份目录确保配置和数据能快速恢复
回滚命令出问题时不用现场拼命令
观察窗口给健康检查、端口监听和缓存恢复留时间

建议先把参数写清楚,再执行命令:

ENTRY_DNS="192.0.2.211"
MASTER_DNS="192.0.2.234"
SLAVE_DNS="192.0.2.235"
CHECK_DOMAIN="corp.example.internal"
PUBLIC_DOMAIN="example.com"

用同一组变量做升级前检查:

dig @"${ENTRY_DNS}" "${CHECK_DOMAIN}" A +short
dig @"${MASTER_DNS}" "${CHECK_DOMAIN}" A +short
dig @"${SLAVE_DNS}" "${CHECK_DOMAIN}" A +short
dig @"${ENTRY_DNS}" "${PUBLIC_DOMAIN}" A +short

如果升级前这些检查不稳定,先排查现状,不要把升级当成修复手段。

2. 架构和职责
#

这套 DNS 集群由一个入口节点和两个后端 DNS 节点组成。

角色主机部署方式作用
DNS 入口192.0.2.211systemd 运行 dnsdist对外提供 :53,转发到后端
主 DNS192.0.2.234Docker Compose 运行 Technitium权威区、递归解析、主节点
从 DNS192.0.2.235Docker Compose 运行 Technitium权威区、递归解析、从节点

入口层的职责是接收查询、健康检查和后端调度;后端 Technitium 节点负责权威区、递归解析和集群同步。这个职责拆分决定了升级顺序:入口先完成安全更新和配置校验,后端再按从节点到主节点滚动。

flowchart LR
    client["业务客户端"] -->|UDP/TCP 53| lb["dnsdist
192.0.2.211"] lb -->|健康检查 / 转发| master["Technitium Master
192.0.2.234:53"] lb -->|健康检查 / 转发| slave["Technitium Slave
192.0.2.235:53"] master <-->|Technitium Cluster
HTTPS 53443| slave master --> upstream["上游 DNS"] slave --> upstream

后端 Technitium 使用 host 网络模式,容器内 dotnet 进程直接监听宿主机端口:

53/tcp
53/udp
5380/tcp
53443/tcp

3. 为什么先升级 dnsdist
#

入口层先动,不是因为它最容易,而是因为它暴露面最大、影响路径最短。变更前 dnsdist 日志持续提示安全更新:

PowerDNS DNSDist Security Update Mandatory: Upgrade now

当前仓库里可升级版本已经是 1.9.14

Installed: 1.9.11-1pdns.jammy
Candidate: 1.9.14-1pdns.jammy

同时,dnsdist --check-config 暴露了两个配置问题:

newServer: Unknown key 'udpTimeout' given - ignored
Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised
Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised

这一步的判断依据有三个:

  • dnsdist 是所有 DNS 查询的统一入口,安全更新优先级高
  • 配置检查已经提示无效参数和明文凭据风险
  • 后端两个 Technitium 节点仍保持可用,入口变更失败时回退面清晰

所以先升级入口,再滚动升级后端。

4. 变更策略
#

这次没有把所有组件一起升级,而是拆成三个阶段。

  1. 先升级入口节点 192.0.2.211 上的 dnsdist
  2. 再升级从节点 192.0.2.235
  3. 再升级主节点 192.0.2.234

每个阶段都按同一个模式执行:

Review -> Backup -> Change -> Verify -> Observe
flowchart LR
    review["Review
确认现状"] --> backup["Backup
保留回滚点"] backup --> change["Change
执行最小变更"] change --> verify["Verify
验证业务查询"] verify --> observe["Observe
观察日志和指标"]
不要在同一个窗口里同时升级入口和两个后端。DNS 是基础服务,出问题时最重要的是保留一个可用回退面。

每个阶段都要有明确的“继续/停止”判断:

阶段可以继续的条件应该停止的信号
入口升级后dnsdist active,配置检查通过,入口解析正常配置检查失败、入口解析失败、Web 控制台连接失败
从节点升级后从节点本机解析正常,入口解析仍正常从节点端口未就绪、容器反复重启
主节点升级后主从节点都 up,入口解析稳定后端被摘除、延迟异常、TCP 查询失败

5. 升级前 Review
#

Review Gate 的目标是确认“现在是好的”,否则后面的升级失败很难判断是新问题还是旧问题。

先确认入口和两个后端都能解析核心域名。

dig @192.0.2.211 corp.example.internal A +short
dig @192.0.2.234 corp.example.internal A +short
dig @192.0.2.235 corp.example.internal A +short

确认服务状态:

systemctl status dnsdist --no-pager
docker ps

确认 dnsdist 配置可被当前版本加载:

dnsdist --check-config

确认后端 Compose 只需要改镜像 tag:

grep -n "image:" /data/docker-compose/dns-server/docker-compose.yaml

Review Gate 清单:

检查项为什么要检查
入口和后端解析结果确认变更前业务路径正常
systemctl status dnsdist确认入口服务不是异常状态
docker ps确认后端容器运行状态
dnsdist --check-config提前发现升级后可能放大的配置问题
Compose 镜像 tag确认后端升级只需要改版本,不引入额外变量

6. 升级 dnsdist
#

入口节点先做备份。这里备份的重点不是“备份所有系统文件”,而是保留能快速恢复入口能力的最小集合:dnsdist 配置、当前版本信息和软件源候选版本。

stamp="$(date +%F-%H%M%S)"
backup_dir="/root/dns-upgrade-backup-${stamp}"

mkdir -p "${backup_dir}"
cp -a /etc/dnsdist/dnsdist.conf "${backup_dir}/dnsdist.conf"
cp -a /etc/dnsdist "${backup_dir}/etc-dnsdist"

dnsdist --version > "${backup_dir}/dnsdist.version.before" 2>&1
apt-cache policy dnsdist > "${backup_dir}/dnsdist.apt-policy.before" 2>&1

执行升级:

apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --only-upgrade dnsdist

升级完成后确认版本:

dnsdist --version
# dnsdist 1.9.14

升级后先不要继续动后端。入口必须完成三类验证:

  1. dnsdist --check-config 能通过
  2. systemctl is-active dnsdist 返回 active
  3. 入口 :53 能正常解析内部域名和公网域名

只有入口稳定,后端滚动才有意义。

7. 加固 dnsdist 配置
#

本次配置改动只做三件事:

  • 删除 newServer() 中无效的 udpTimeout
  • 将 Web 控制台 password 改为 hashPassword() 生成的 hash
  • 将 Web 控制台 apiKey 改为 hashPassword() 生成的 hash

验证配置:

dnsdist --check-config
# Configuration '/etc/dnsdist/dnsdist.conf' OK!

重启入口:

systemctl restart dnsdist
systemctl is-active dnsdist

验证 DNS 查询:

dig @192.0.2.211 corp.example.internal A +short
dig @192.0.2.211 example.com A +short
这次曾尝试把 dnsdist Web 控制台 ACL 从全开放收紧到管理网段,但实际访问路径不匹配,导致 http://192.0.2.211:5380/ 无法正常打开。随后将 Web ACL 回退到升级前策略,保留密码和 API Key hash 加固。

当前 DNS 查询 ACL 仍保持内网限制:

setACL({'192.0.2.0/24', '198.51.100.10/32', '127.0.0.0/8', '::1/128'})

当前 Web 控制台 ACL 暂时保持升级前策略:

setWebserverConfig({ acl = "0.0.0.0/0", "::/0" })

这是一个遗留项,需要后续确认真实管理端来源后再收紧。

8. 滚动升级 Technitium 从节点
#

先升级从节点,主节点保持不动。这样做的收益很直接:如果新版本启动失败、数据迁移异常或端口没有及时起来,主节点仍然能兜住解析能力。

从节点备份:

stamp="$(date +%F-%H%M%S)"
backup_dir="/root/dns-upgrade-backup-${stamp}"

mkdir -p "${backup_dir}"
cp -a /data/docker-compose/dns-server/docker-compose.yaml "${backup_dir}/docker-compose.yaml.before"
tar --warning=no-file-changed --ignore-failed-read \
  --exclude='./apps/Query Logs (Sqlite)/querylogs.db' \
  -czf "${backup_dir}/dns-server_config_without_live_querylogs.tgz" \
  -C /var/lib/docker/volumes/dns-server_config/_data .

querylogs.db 是实时写入文件,备份时排除它,避免 tar 因文件变化返回非 0。

只改镜像 tag:

- image: registry.example.internal/technitium/dns-server:14.2.0
+ image: registry.example.internal/technitium/dns-server:15.2.0

执行升级:

cd /data/docker-compose/dns-server
docker compose pull
docker compose up -d

刚启动时不要立刻判断失败。Technitium 容器启动后,53 端口需要几秒钟才完全就绪。这里的判断标准不是“容器 Up”,而是 DNS 查询能返回预期结果。

验证从节点:

docker ps --filter name=dns-server-slave
docker logs --tail 200 dns-server-slave
dig @127.0.0.1 corp.example.internal A +short
dig @127.0.0.1 example.com A +short

从节点升级后确认入口仍正常:

dig @192.0.2.211 corp.example.internal A +short

从节点通过后,观察入口是否还能稳定转发查询。如果入口查询正常,说明后端池里至少有一个升级后的节点已经可用。

9. 滚动升级 Technitium 主节点
#

从节点验证通过后,再升级主节点。流程和从节点一致。

只改镜像 tag:

- image: registry.example.internal/technitium/dns-server:14.2.0
+ image: registry.example.internal/technitium/dns-server:15.2.0

执行:

cd /data/docker-compose/dns-server
docker compose pull
docker compose up -d

验证:

docker ps --filter name=dns-server-master
docker logs --tail 200 dns-server-master
dig @127.0.0.1 corp.example.internal A +short
dig @127.0.0.1 example.com A +short

主节点升级完成后,再回到入口做多次验证:

for i in 1 2 3 4 5; do
  dig @192.0.2.211 corp.example.internal A +short
done

结果均返回:

192.0.2.20

主节点升级后要再看一次后端池状态。原因是有些问题不会直接表现为解析失败,而是表现为健康检查延迟、TCP 查询异常或某个后端被摘除。升级窗口里至少要确认:

  • 两个后端都处于 up
  • UDP 查询延迟没有异常抬高
  • TCP 查询没有明显失败
  • 入口 QPS 和后端分布符合预期

10. Drops 判断:不是攻击
#

升级过程中看到 dnsdist 页面里有 Drops 字段,于是单独做了一轮判断。

采集到的关键指标:

dns-server-master dropRate=0
dns-server-slave  dropRate=0

dnsdist API 和日志没有看到这些异常:

acl-drops
rule-drop
dyn-blocked
downstream-timeouts
downstream-send-errors
servfail-responses

抓 20 秒 53 端口流量,来源都是内网地址,最高来源约十几 QPS 量级,没有公网来源,也没有明显攻击特征。

判断是否攻击,不应该只盯页面上的一个列名。更稳的证据链是:

证据观察结果判断
后端 dropRate0不支持后端丢弃异常
ACL / rule drop未见有效非零证据不支持规则丢弃
dyn-block未见触发不支持动态封禁
timeout / send error未见明显异常不支持后端不可达
抓包来源内网来源,QPS 不高不支持公网攻击

结论:这次看到的 Drops 不支持“正在被攻击”的判断,更像是页面表格空列或误读。真正需要关注的是 dropRateacl-dropsrule-dropdyn-blocked、后端 timeout 以及来源 IP 的 QPS。

排查顺序建议固定下来:

  1. 先看后端 dropRate
  2. 再看 ACL、rule、dyn-block 是否命中
  3. 再看 timeout、send error、SERVFAIL
  4. 再抓包确认来源 IP 和 QPS

这样能避免看到一个 Drops 字段就直接归因到攻击。

11. 回滚方案
#

回滚方案要按组件拆开。入口和后端不要混在一起回滚,否则会把问题范围扩大。

dnsdist 回滚:

cp /root/dns-upgrade-backup-*/dnsdist.conf /etc/dnsdist/dnsdist.conf
dnsdist --check-config
systemctl restart dnsdist

Technitium 回滚:

cd /data/docker-compose/dns-server
# 将镜像 tag 改回 14.2.0
docker compose up -d

如果跨版本升级导致配置迁移不可逆,需要停止容器后恢复升级前备份的 volume 内容。这个动作会影响节点数据目录,必须单独确认后执行。

回滚后仍要执行同样的验证矩阵:入口、主节点、从节点、内部域名、公网递归和 Web 控制台状态。

12. 常见问题
#

问题:服务是 active,但 DNS 查询失败

可能原因:

  • dnsdist 配置加载成功,但后端健康检查失败
  • Technitium 容器已启动,但 53 端口还没就绪
  • ACL 允许了管理访问,但没有允许查询来源

处理方式:

dnsdist --check-config
systemctl is-active dnsdist
dig @192.0.2.211 corp.example.internal A +short
docker ps

问题:Web 控制台打不开

先确认是服务没起来,还是 ACL 不匹配:

curl -I http://192.0.2.211:5380/
systemctl status dnsdist --no-pager

如果是 ACL 收紧导致访问路径不匹配,优先恢复可用性,再重新设计管理网段策略。

问题:备份 querylogs.db 时 tar 报错

querylogs.db 是实时写入文件,备份过程中变化很正常。不要让它阻塞配置备份,可以排除这个文件,保留配置和核心数据。

13. 遗留项
#

这次升级完成后还有三个后续事项。

  1. Web 控制台 ACL

    dnsdist Web 控制台 ACL 当前保持升级前全开放策略。虽然它监听在内网 IP,且认证凭据已 hash,但仍建议确认真实管理端来源后再收紧。

  2. Packet Cache

    配置中定义了 newPacketCache(),但 API 统计里没有明显缓存命中。后续要确认是否需要显式绑定到 pool 或 server policy。

  3. 系统维护窗口

    APT 升级时提示有内核和系统服务重启需求。本次只处理 DNS 组件,没有重启宿主机。内核和系统服务重启应放到独立维护窗口。

14. 复盘
#

这类基础设施升级,不追求“一步到位”,而是追求每一步都有退路。命令可以复制,顺序不能随便改。

这次比较有效的做法是:

  • 入口和后端拆开升级
  • 后端先从节点,再主节点
  • 每个组件升级前做 Review
  • 每个组件升级后立即验证内部域名和公网递归
  • 不把实时写入的日志数据库作为阻塞备份对象
  • 发现 Web 控制台 ACL 不适配时,先恢复可用性,再记录遗留项

DNS 是很多系统的隐性依赖。升级时不要只看服务是否 active,要直接用 dig 验证业务域名,最好同时验证入口、主节点和从节点。

可以复用到其他基础设施升级的原则:

  • 先处理入口风险,再滚动处理后端
  • 先证明变更前是好的,再证明变更后仍然是好的
  • 每一步只引入一个变量
  • 回滚点要能在几分钟内执行
  • 安全加固失败时,先恢复可用性,再把加固项作为遗留风险跟踪
生产变更复盘 - 这篇文章属于一个选集。
1: 本文

相关文章

Docker 常用命令速查手册
·378 字·2 分钟
Docker DevOps Docker Container DevOps Cheatsheet
企业级 GitLab 平台部署与运维完整指南
·3964 字·19 分钟
Git CI/CD Docker DevOps GitLab +3
企业级 Nexus3 制品仓库平台部署与运维完整指南
·6475 字·31 分钟
Docker DevOps Nexus3 Docker DevOps +2