动态命名服务器
摘要
作者描述了自托管中动态IP分配带来的挑战,并介绍了'treens',这是一个使用ed25519密钥对在本地网络中自动注册主机名的动态DNS工具。
<p><a href="https://lobste.rs/s/swzhtb/dynamically_naming_servers">评论</a></p>
查看缓存全文
缓存时间: 2026/08/24 15:32
# 动态命名服务器
来源:https://arch.dog/bark/dynamically-naming-servers
我遇到了一个问题——一个关于自托管的问题。
每当部署新容器或虚拟机来运行应用时,我都会让 Ubiquiti 路由器的 DHCP 服务器为其分配 IP。由于已经需要为 PostgreSQL 集群或 Kubernetes 节点等设备管理静态 IP 分配,因此允许那些*不需要*静态 IP 的机器自动获取地址确实减轻了我的管理负担。而且这些设备通常会长期运行(很少关机),路由器分配的 IP 预约信息能保持足够长时间,长到我逐渐默认这*几乎*是静态的,最终开始依赖 IP 地址始终不变。这当然是错误的做法,但我还是这么做了。
当把家庭子网从 192.168.0.0/16 网段迁移到 10.0.0.0/8 网段时,这个问题让我吃尽了苦头。虽然预料到所有设备都会获得新地址,也做好了准备,但逐一更新所有地址仍然非常繁琐。当 DHCP 预约*确实*失效导致设备 IP 变化时同样令人头疼(而我往往直到需要使用时才会发现)。我只能一边懊恼一边想:“DNS 本来不是解决这类问题的吗?”,然后继续手头的工作。
“但是 Arch!”你对着电脑屏幕说,“为什么不让所有设备都接入你的 Tailscale Tailnet 网络呢?!”
简而言之,即使 Tailscale 在设备上部署非常简便,我仍然觉得它有些麻烦——特别是对于变动频繁的家庭实验室环境。其次,我不希望在使用局域网服务时必须依赖 Tailscale 的可用性(尽管使用期间它从未出现过影响我的故障,但我想避免这种绑定)。此外,我的家人不会让电脑和手机时刻保持 Tailscale 连接,却仍希望能无需手动切换就能访问这些服务。最后,在无需考虑 Tailscale 路由或透明解决其潜在问题的情况下管理家庭网络要简单得多。
因此,尽管让所有虚拟机和 LXC 容器接入 Tailscale 会*很有用*,但我认为其带来的麻烦远大于收益,于是选择在 Tailnet 上设置少量“入口点”来访问局域网服务(例如我的 Proxmox NAS 运行着 Caddy,作为反向代理指向局域网内的各个服务,并且接入了 Tailscale)。
被原公司裁员后,我终于有充足的时间来解决这个问题。我曾尝试使用 Ubiquiti 路由器为 DHCP 客户端生成的 DNS 条目,但发现其行为并不理想:如果设备手动设置了静态 IP,路由器中就不会生成对应条目;而 DHCP 客户端的条目获取也时有时无。总之,我有更好的方案——Fortreens (https://git.gmem.ca/arch/treens)。
动态 DNS 服务早已存在。它能自动将域名指向非固定的家庭 IP 地址,并在 IP 变化时更新相关记录。虽然有许多使用“正规”域名服务器的方案,但我没找到能完全*在本地*运行且仅对局域网可见(这才是真正需要使用的场景)的选项。于是我开始规划方案:首先需要让设备向服务认证身份并告知需要绑定的 IP 地址。认证方式有无数种,但对于家庭实验室,关键是要*自动化*。我不希望生成 API 密钥再部署到服务器上,也不愿进行复杂的认证流程。我需要设备能基本自主地获取主机名,最终选择了基于 ed25519 密钥对的签名认证方案。客户端生成私钥并向服务器注册公钥,以此申领主机名。后续的更新消息会附带签名头,服务器通过签名验证消息真实性后,再更新主机名对应的 IP 记录。
原本还想引入 Merkle 树来验证记录,但暂时搁置了这个想法。Merkle 树确实是验证哈希链的高效方案,但在初步实现中显得冗余。我计划将其用于审计日志,但尚未实施。这也是“treens”(树状名称服务器)这个名字的由来。
实际上这个项目相当简单——前提是你没有选择 Rust 语言。我总是因为 Rust 顺手而选用它开发项目,但不得不花时间适应或处理直接操作 UDP/TCP 消息的细节。Hickory DNS 项目提供了处理 DNS 请求载荷和类型的库,这减轻了不少工作量,但我仍需思考诸如“如何避免耗尽服务器所有连接”或“为什么 TCP 的特殊头部如此复杂,UDP 就简单多了”这类问题。
基于简单性考虑,我选择了 SQLite 作为存储后端。虽然也想尝试 Sled 或 redb 等键值数据库,但 SQLite 凭借我的熟悉度胜出。此外我还考虑在未来支持集群部署和条目同步传播,使实例能在大型环境中灵活调度或扩展——不过如果真在大型环境中使用,在 SQLite 需要调整之前,恐怕早已遇到其他需要解决的边界情况了。
最终成果是一个具备 TCP/UDP 处理器的 DNS 服务器,能响应已注册主机的记录查询,并提供基础 HTTP 端点用于处理新主机注册和 IP 更新。只有主机通过“审核”后才会提供记录解析。由于我希望保持较高自主性,允许根据客户端请求 IP(而非声明的 IP,因为两者可能不同)所在的子网自动审核通过。
我已将家庭实验室的 Unbound 实例通过 `stub-zone` 配置指向该服务器,让它在后台持续运行,同时我逐步更新配置以指向新的本地子域名(在我的案例中是 `.lan.gmem.ca`)。目前运行良好,但随着利用空闲时间持续迭代开发,肯定会遇到一些问题——我一定会在 Mastodon (https://floofy.tech/@arch) 上分享。
这也是极少数我可能真正推荐自己项目的场合。虽然大多数项目都针对特定问题/场景开发,但这个项目足够通用,值得我投入精力完善“自行部署”部分的体验。无论如何,我们拭目以待吧。
相似文章
如何在客厅中通过静态IP自托管服务器
一份使用L2TP隧道和静态IP在家中自托管服务器的指南,绕过CGNAT等ISP限制。
DynIP – 支持RFC 2136、IPv6、DNSSEC和自带域名(BYOD)的动态DNS
DynIP是一个动态DNS解决方案,支持RFC 2136、IPv6、DNSSEC以及自带域名(BYOD)。
从不稳定拨号IP提供Web服务
文章介绍了一种方法:利用cron任务和SSH更新反向代理配置,以便从IP地址不稳定的拨号连接中托管Web服务器。
内部服务的TLS证书正确实践
介绍了如何使用分视域DNS、带DNS解析器的VPN以及ACME客户端(如acme.sh配合Let's Encrypt)来为内部服务设置TLS证书,为自签名证书提供了实用的替代方案。
一种让你的DHCP服务器耗尽动态IP的不寻常方式
克里斯·西本曼解释了他的反爬虫措施,由于针对LLM训练的高频爬虫激增,他阻止旧版浏览器,这给Feed阅读器和存档服务带来了混乱。