基于CoreDNS与etcd的二级域名分发V2.0重构实践
简介派小星DNS二级域名分发V2.0重构版面向域名服务商、IDC站长及需要批量开通二级域名给终端用户的平台运营者帮助完成域名资源统一接入、在线分发、会员套餐与合规审核等环节。本次重构重写了核心代码在接口层、业务层和管理端均有明显升级适合直接部署或二次开发。压缩包内共1371个文件以681个JavaScript、400个CSS、67个PHP及SQL数据库脚本为主覆盖前端交互、后台界面、业务逻辑与数据表结构整体约16.93MB。功能上集成腾讯云、阿里云、Cloudflare、华为云、西部数码等主流DNS厂商支持多级会员等级及专属折扣可自由组合基础版、企业版、定制版等域名套餐内置身份证、手机号、人脸三要素实名认证和违规关键词自动拦截帮助规避监管风险。后台UI全新改版支持域名批量操作、数据统计与模块化插件市场。已有191人学习下载。1. 派小星DNS二级域名分发V2.0重构版让每个用户都在几十秒内拿到独立子域名做SaaS平台、容器云、设备接入服务的团队迟早会遇到一个需求给每个注册用户分配一个独立二级域名比如 zhangsan.example.com、lisi.example.com用户拿到就能直接用后台改了配置域名要跟着变用户注销了域名还得能回收。这个需求听起来不复杂真正做起来却经常在 DNS 这一层翻车——改一条记录要等缓存过期、Zone 文件并发写入丢更新、日志里出现大量解析失败还定位不到原因。「派小星DNS二级域名分发V2.0重构版」这个项目就是在解决这个具体问题自建一套能动态分发二级域名的 DNS 解析服务把「分配域名」从手动改记录变成调用一次接口就能完成。这篇笔记会从模型讲起落到 CoreDNS 与控制面的具体实现再把上线前后的坑逐条拆开。2. 二级域名分发的核心模型权威应答、泛解析与分发策略怎么选2.1 在动手之前先分清楚权威DNS、递归DNS和防缓存穿透二级域名分发必须有一个自建的权威 DNS 服务器这是整个方案的基石。公共 DNS比如常见的 114.114.114.114 或 8.8.8.8只是递归服务器它们会替客户端去查询权威服务器然后把结果缓存在自己那里。我们要做的是把 example.com 这个域的权威解析权拿到自己手里让递归服务器在收到 someone.example.com 的查询时最终回到我们的服务器上来取答案。在 Linux 上自建 DNS 服务器最常见的做法是 BIND 9 或者 CoreDNS。V2.0 重构版我选的是 CoreDNS理由后面展开讲。但无论用哪个都要先理解一条关键链路递归服务器找权威服务器的顺序是从根域往下走的example.com 的 NS 记录指向哪里决定了解析权归谁。所以第一步不是写 Zone 文件而是去域名注册商那里把 example.com 的 NS 记录改成我们自建 DNS 服务器的地址。如果这一步没做后面所有配置都是白搭——自己服务器上解析得再对公网上永远拿不到结果。防缓存穿透是分发场景里必须提前设计的问题。普通网站解析量小缓存穿透最多慢一点但二级域名分发的场景下每个用户都可能有自己的解析请求如果每个二级域名都是第一次出现、没有缓存权威服务器会瞬间被打满。常见做法是给泛解析记录设置一个合理的 TTL让递归服务器缓存住大部分查询结果同时控制面在更新记录时主动压低 TTL 值等待缓存自然过期。2.2 泛解析与精确解析的优先级这是分发器最容易翻车的地方很多人一开始会想到用泛解析来偷懒在 Zone 里写一条*.example.com. IN A 1.2.3.4所有二级域名都解析到同一个 IP。这在早期确实能用但很快就会发现两个问题。第一所有用户都指向同一个入口业务层还得靠 Host 头来区分用户分发就变成了名字分发不是解析分发第二泛解析记录没法针对不同用户返回不同 IP也没法做下线回收用户注销后域名照样能解析。DNS 协议有一个必须记住的优先级规则精确匹配优先于泛匹配。也就是说如果 Zone 里同时存在zhangsan.example.com. IN A 5.6.7.8和*.example.com. IN A 1.2.3.4查询 zhangsan.example.com 时永远返回 5.6.7.8不会去匹配泛解析。这条规则本身是好事但它要求分发器在写入时小心处理“删除”逻辑——用户注销后如果只删了精确记录而忘了保留泛解析这个二级域名并不会变成不存在而是会“掉进”泛解析里继续可用。这是一个非常隐蔽的安全漏洞。所以在实际设计里我一般会区分两种模式。纯分发模式Zone 里不放泛解析每个二级域名都显式写入精确记录回收时直接删除。兜底模式Zone 里保留一条泛解析指向默认网关但控制面在删除精确记录时会把对应的用户标记为“已禁用”网关层再做一次校验。生产环境我更推荐后者因为纯分发模式下如果控制面写库失败会出现一个域名既没写在 Zone 里也没被兜底解析直接 NXDOMAIN用户侧看到的就是“域名不存在”。2.3 常见分发策略对比A记录轮询、CNAME到业务网关、按区域调度确定了精确加泛兜底的模型之后接下来要决定每条解析记录返回什么。这里有三条路线各有明确的适用边界。A 记录轮询是最直观的把一批后端 IP 放在 IP 池里分发器按负载或哈希选择一个 IP直接写入zhangsan.example.com. IN A 1.2.3.4。优点是解析链路短客户端拿到 IP 直连缺点是后端扩容时已经分出去的二级域名不会自动漂移除非做健康检查后强制更新 TTL。适合后端是一组无状态网关的场景。CNAME 到业务网关是第二种每个二级域名解析为zhangsan.example.com. IN CNAME edge.example.com业务网关根据域名做路由。这种做法的好处是后端 IP 变化时只需要改 edge.example.com 一条记录所有二级域名自动跟随坏处是 CNAME 链条增加了递归服务器的查询次数且有些场景要求根域不能是 CNAME但二级域名用 CNAME 没有这个限制。V2.0 重构版我最终采纳的就是这种模式后文会详细展开。按区域调度是第三种根据用户来源 IP 段返回不同区域的接入 IP。写着很美好但二级域名分发场景里用得不多因为大多数平台的用户量级根本到不了区域调度的规模。真需要的话CoreDNS 里有 geoip 插件可以做但我不建议一上来就上这个复杂度先把前两种跑通再说。策略运维成本后端扩容表现适合场景A记录轮询中需要管理IP池新域名走新IP老域名不漂移后端是固定网关池CNAME到网关低只改一条记录自动跟随网关记录SaaS多租户平台区域调度高需要region映射视策略而定全国性低延迟业务3. 自建DNS 控制面API用CoreDNS跑通二级域名分发的落地方案3.1 选型结论为什么V2.0用CoreDNS而不是BIND9BIND 9 是传统权威 DNS 的事实标准配置语法成熟出问题能找到大量文档。但它在二级域名分发场景里有一个致命短板动态更新要么通过 nsupdate 工具向服务器发 UPDATE 报文要么直接改 Zone 文件再 reload。nsupdate 协议写起来啰嗦Zone 文件 reload 在记录量超过几万条时会有明显延迟而且并发场景下两个进程同时写一个文件容易出现半个文件写坏的情况。CoreDNS 是 CNCF 出品的 Go 语言 DNS 服务器配置简洁插件化设计其中 etcd 插件可以直接把解析数据存在 etcd 里控制面只要写 etcd 的 keyDNS 数据面立即感知不需要 reload 任何文件。这对分发场景来说是本质性的改变分发器从「写文件等生效」变成了「写 KV 即生效」。当然CoreDNS 也不是没有坑。它默认不保存权威 Zone 的 SOA 记录细节需要自己配置它的泛解析插件写法也和 BIND 的 Zone 文件语法不同。但这些都可以接受。选型结论就是如果团队已经决定要上一套控制面 API直接选 CoreDNS etcd如果只想手工维护几百条记录、不想写任何代码那老老实实用 BIND 9 加 Zone 文件反而更稳。3.2 最小可运行的Corefile配置与启动命令安装 CoreDNS 很简单下载二进制解压就能跑。核心在 Corefile这是整个服务器的唯一配置入口。下面这个配置是 V2.0 分发服务最小可运行版本example.com:53 { etcd { path /skydns endpoint http://127.0.0.1:2379 ttl 60 } errors log health :8091 prometheus :9153 }这段配置的每一行都有讲究。example.com:53表示监听 53 端口作为 example.com 域的权威服务器。etcd插件指定解析数据从 etcd 读取path 是前缀/skydnsendpoint 是本机 etcd 服务地址ttl 是当 etcd 里的记录没有显式 TTL 时使用的默认值这里设 60 秒意味着递归服务器最多缓存一分钟。errors 和 log 开启错误和访问日志health 开启健康检查端口供负载均衡器探活prometheus 暴露指标。把这段保存在/etc/coredns/Corefile后启动命令是coturn不对这里写错了CoreDNS 的启动命令非常简单./coredns -conf /etc/coredns/Corefile启动后终端会打印监听的端口和加载的插件。如果出现Failed to start server: listen tcp :53: bind: permission denied是因为 53 端口需要 root 权限用sudo运行或者给二进制加cap_net_bind_service权限。这里提一下在 Linux 修改 DNS 配置时常见的系统层问题如果你是在本机用 CoreDNS 做测试务必确认/etc/resolv.conf里指向的是 127.0.0.1 而不是外部 DNS。Ubuntu 22.04 这类系统里NetworkManager 和 systemd-resolved 会自动改写这个文件你改了之后重启网络就被还原。后面避坑章会专门讲这个。3.3 把分发逻辑做成带状态的控制面etcd键设计与API代码片段CoreDNS 的 etcd 插件有一套自己的键路径规则。对于zhangsan.example.com.这条解析记录它会去 etcd 里查找键/skydns/com/example/www/zhangsan。注意顺序键根路径从顶级域开始写域名是反着拼的而且 www 这个中间段是example.com拆出来的。这套规则刚接触很容易搞反写错键名的话解析结果永远为空排查起来十分痛苦。我给出的通用做法是不手写键路径而是在控制面里写一个域名转键函数。下面是 Go 语言片段完成从完整域名到 etcd key 的转换func domainToKey(domain string) (string, error) { domain strings.TrimSuffix(domain, .) parts : strings.Split(domain, .) // 反转域名段: zhangsan.example.com - [com example zhangsan] for i, j : 0, len(parts)-1; i j; i, j i1, j-1 { parts[i], parts[j] parts[j], parts[i] } // 只保留 example.com 二级域名下的内容, 去掉前两段 if len(parts) 3 { return , fmt.Errorf(非法二级域名: %s, domain) } sub : parts[2:] return /skydns/ strings.Join(sub, /), nil }这个函数的核心逻辑是反转域名段并拼接成 CoreDNS etcd 插件期望的格式。参数说明传入的 domain 必须是完整二级域名比如zhangsan.example.com函数先把末尾的点去掉再按点分割、反转顺序最后去掉前两段顶级域和主域只保留二级域名字段。返回值就是 etcd key。写入解析数据时控制面需要同时设置 value 和 TTL。以 CNAME 分发为例向 etcd 写入一条记录curl -X PUT http://127.0.0.1:2379/v2/keys/skydns/com/example/www/zhangsan \ -d value{host:edge.example.com,ttl:60} \ -d ttl300这里 value 是一个 JSON 结构体host字段表示 CNAME 的目标ttl字段覆盖默认 TTL。curl 命令里那个ttl300是 etcd 键本身的租约时间到时间后键会自动删除相当于给了分发服务一个“自动回收”能力——如果控制面崩了旧记录最多存活 300 秒就消失不会一直错误解析。4. V2.0重构版做了什么从zone文件手动维护到动态状态分发4.1 V1版的三个让运维头疼的问题改记录靠sleep、状态黑匣子、并发下丢更新先说说 V1 版为什么需要重构这能帮你判断自己是否也在走同样的弯路。V1 版的做法是 BIND 9 加 Zone 文件控制面生成一份完整 Zone 文件替换到 BIND 的配置目录然后执行rndc reload。看起来简单跑起来全是问题。第一个问题是改记录靠 sleep。rndc reload是异步的命令返回不代表新 Zone 已经生效。为了确认记录真的可解析控制面必须在 reload 之后主动查询一次确认通过再返回成功。很多团队图省事直接 sleep 两秒就算完事结果记录量大的时候两秒根本不够用户端拿到的是旧解析。第二个问题是状态黑匣子。Zone 文件是文本控制面每次全量生成一旦某个用户的数据在内存里被误删生成的 Zone 文件里就少了一条目录但你没有 diff 工具的话完全看不出来。V1 时代我们经常遇到用户投诉“我的域名解析不了”查半天发现是某个后台任务把整段 Zone 记录覆盖了。第三个问题是并发丢更新。两个控制面实例同时生成 Zone 文件后写的会覆盖先写的一个用户的更新操作直接弄丢另一个用户的。加锁能缓解但 BIND reload 本身不是事务性的写到一半断电文件就坏了服务直接不可用。4.2 V2.0重构版的分发流程控制面写状态、数据面只应答V2.0 重构版的核心理念是“控制面和数据面彻底分离”。数据面保持无状态只是 CoreDNS 进程在读取 etcd 中的记录并应答查询控制面负责把接收到的业务请求翻译成对 etcd 的操作。这个架构最直接的好处是没有文件、没有锁、没有 reload所有状态都在 etcd 里天然具备事务性和一致性。分发流程可以概括为以下几步。第一步业务方调用控制面 API携带用户名、期望的二级域名前缀、目标 CNAME 或 IP第二步控制面做格式校验和权限校验包括检查前缀是否被占用、域名是否包含非法字符第三步控制面把解析记录写入 etcd路径用前面说的 domainToKey 拼接同时写入一份独立的业务元数据键记录这个二级域名属于哪个用户、什么时间分配的第四步客户端发起 DNS 查询CoreDNS 从 etcd 读出记录返回应答。这套流程下一个用户的完整生命周期变得可控创建域名就是一个 PUT变更域名就是一个 PUT 覆盖回收域名就是一个 DELETE。没有中间状态也没有文件残留。4.3 重构版关键参数TTL、更新间隔、健康检查与灰度切换重构后官方文档里看起来参数变少了但有几个参数直接决定线上表现我需要单独说明。TTL 是第一个要调优的参数。V2.0 默认把解析记录 TTL 设置为 60 秒这是刻意选择的。太短比如 5 秒会让递归服务器频频回源权威服务器的请求量暴涨太长比如 3600 秒会导致用户修改配置后要等一小时才能生效这在 SaaS 分发场景是不能接受的。60 秒是一个让失败恢复时间和回源压力都能接受的中间值。如果你对故障恢复速度有更高要求可以降到 30 秒但一定要给 CoreDNS 加一层视图缓存做对冲。etcd 的压缩间隔也要关注。分发场景下记录变更频繁etcd 的历史版本如果不压缩会不断膨胀。我一般在控制面加一个后台任务每隔一小时调用一次 etcd 的 compaction API保留最近几十分钟的变更历史即可。灰度切换是一个容易忽略的点。当你要调整分发策略时不要直接改 Corefile 里的插件配置然后重启 CoreDNS——重启期间所有解析都会超时。正确做法是启动一个新版本的 CoreDNS 实例监听 5353 端口先用 dig 手工验证新实例的解析结果确认无误后通过 DNS 层把部分查询流量切过去最后再平滑替换线上实例。5. 避坑二级域名分发上线前后的5个真实坑点5.1 改完域名不生效上游递归缓存、本地DNS缓存和TTL三层的联合作案现象在控制面更新了解析记录dig 到自己的权威服务器查询显示新 IP但在客户机上nslookup得到的还是旧 IP。原因这不是权威服务器的问题而是缓存链路的三层叠加。第一层是客户机本机的 DNS 缓存第二层是运营商递归服务器比如 114.114.114.114的缓存第三层才是权威服务器。你 dig 权威服务器绕过了前两层所以看到新记录但普通用户走的是完整链路旧记录还在缓存里。解决更新记录后的 60 到 120 秒内控制面返回值只代表权威侧已生效不代表全平台生效。如果业务要求“立即生效”可以在控制面实现一个主动通知调用公共递归服务器的缓存刷新接口但这类接口不是所有运营商都提供。更务实的做法是把 TTL 默认值设短并如实告知业务方“最长 60 秒生效”。排查时一定要会用 dig 指定不同服务器对比dig 127.0.0.1 zhangsan.example.com A noall answer dig 114.114.114.114 zhangsan.example.com A noall answer第一条命令查本地权威服务器第二条查公共递归服务器。如果前者新、后者旧基本可以断定是递归缓存等待 TTL 过期即可不要去重启权威服务。5.2 Ubuntu/Debian系统里/etc/resolv.conf总被重置NetworkManager与resolvconf的冲突现象在 Ubuntu 22.04 服务器上手工修改/etc/resolv.conf把 nameserver 指到本地 CoreDNS过一段时间或者重启网络后发现文件被还原成 127.0.0.53 或者运营商地址自建 DNS 服务变得无人问津。原因这是 Ubuntu 等发行版系统再正常不过的行为。/etc/resolv.conf是软链接指向 systemd-resolved 或 NetworkManager 生成的动态文件。系统每次网络事件触发都会重新生成这个文件手工修改必然被覆盖。解决如果 CoreDNS 跑在本机且要承接回环查询正确做法不是改 resolv.conf而是把 systemd-resolved 的 DNS 配置指向本机让 resolved 作为转发层。修改/etc/systemd/resolved.conf把 DNS 设置为 127.0.0.1然后执行systemctl restart systemd-resolved。如果不想用 systemd-resolved也可以直接 mask 掉它并让 NetworkManager 管理 DNS但那个配置更绕我的习惯是承认 resolved 的存在让它成为 CoreDNS 的上游。5.3 泛解析和下划线下划线与特殊字符导致的子域名分发失败现象控制面给用户分配了一个叫my_domain_1.example.com的二级域名写入 etcd 后查询一切正常但用户在自己系统里配置这个域名时提示非法浏览器访问直接报错。原因DNS 协议本身允许下划线等字符出现在域名标签里但公网证书签发机构、主流 Web 服务器和浏览器对域名有更严格的限制。RFC 1034 规定域名标签只能包含字母、数字和连字符且连字符不能出现在开头或结尾下划线只在某些特殊服务记录里合法普通主机名用下划线是给自己找麻烦。解决控制面在分配前做一次校验用正则过滤非[a-zA-Z0-9-]字符并限定段长不超过 63 个字符整个域名不超过 253 个字符。同时检查连续点、开头点、结尾点这些边界情况。校验不通过直接拒绝而不是写进 DNS 之后再让用户发现不能用。5.4 日志泄漏与错误记录导致的DNS查询风暴现象一个不存在的二级域名被客户端反复查询访问日志里每分钟刷出几百条 NXDAMIN。进一步查发现是某个客户端应用写死了这个域名地址在等待超时后无限重试。原因这是一次典型的 DNS 查询风暴。分发服务面对的查询者不只有浏览器还有各种 SDK、爬虫和写死的客户端。它们对同一个不存在的域名高频重试qps 瞬间升高打满 CoreDNS 的查询处理线程。解决两个防线。第一在 CoreDNS 前面加一层缓存让 NXDOMAIN 响应也被缓存。第二在 CoreDNS 中配置限流插件对同一源 IP 设置每秒最大查询次数超限直接丢弃。同时必须处理日志问题CoreDNS 的 log 插件在 qps 高的时候本身就是磁盘杀手生产环境建议把 log 关掉只保留 errors或者接采集器异步落盘。刚上线时我喜欢开着 log 看解析是否成功后来发现高流量时每条日志都是纯损耗就学乖了。5.5 恶意域名与黑产团伙的绕过黑名单过滤、日志溯源与告警联动现象有人批量注册了一堆与平台内某个品牌相似的三级域名通过分发服务指向恶意页面并且每天换一个新域名来躲避安全团队的封禁。原因DNS 分发服务天然会被黑产盯上。如果一个平台开放二级域名自助分配攻击者可以自动注册大量前缀把它们指向钓鱼页面或恶意下载站然后用这类域名去做推广。解决这已经超出 DNS 本身的范围需要和其他系统联动。我在控制面里维护一份黑名单前缀表包含常见恶意词根、竞品品牌词根以及高危后缀组合分配前做一次过滤。同时在数据面把解析日志全量接入日志系统用脚本定期扫描量异常的短期域名发现可疑的自动下线。最后把高德图这种思路放到一边追踪恶意记录只是第一步真正需要的是控制面加上“最近一小时内新分配域名解析量超过阈值就自动冻结”的规则这套告警联动机制比手动拉黑高效得多。6. 验证与进阶玩法用dig和dnspython完成分发正确性巡检整套服务上线后不能只靠人工 dig 验证必须有一套自动化巡检脚本。dig 适合单次排查批量巡检我推荐用 dnspython 写一个小脚本覆盖四类断言域名能正确解析、解析结果是预期目标、解析 TTL 在合理范围、已回收的域名返回 NXDOMAIN。一个简单的巡检脚本骨架如下import dns.resolver resolver dns.resolver.Resolver() resolver.nameservers [127.0.0.1] def check_record(domain, expected_cname): try: ans resolver.resolve(domain, CNAME) if str(ans[0].target).rstrip(.) ! expected_cname: print(fFAIL: {domain} CNAME 期望 {expected_cname}, 实际 {ans[0].target}) else: print(fOK: {domain}) except dns.resolver.NXDOMAIN: print(fFAIL: {domain} 解析为 NXDOMAIN) check_record(zhangsan.example.com, edge.example.com)这段脚本的逻辑是构造一个指向本地权威服务器的 resolver然后对每个已分配域名执行 CNAME 查询把返回目标和期望值做比对。这里的参数说明check_record的第二个参数是期望的 CNAME 目标查询失败时捕获 NXDOMAIN 异常并标记 FAIL。脚本本身很薄但接入到 CI/CD 里每次控制面发布后自动跑一遍能有效拦截“写错键路径”“覆盖了别家记录”这类低级错误。最后说一个我的个人习惯每次调整 TTL 或更新 CoreDNS 配置前我都会先用dig trace完整走一遍链路看从根域到权威域的每一跳。这样做不是多此一举而是能立刻暴露出是不是有递归服务器在中间劫持、NS 记录有没有被误改。只要你和我一样经历过缓存风暴和配置回滚的折磨就会明白这套动作比任何监控面板都来得直接。希望帮到你。本文还有配套的精品资源点击获取