跳到主要内容

DNS 与流量工程

Cirrus CDN 集成内部权威 DNS,以动态反映控制平面状态。本章阐述 CNAME 服务如何构建区域数据、监控节点健康,并与从域名服务器协同,将客户端路由至健康的缓存节点。

CNAME 服务概览

src/cirrus/cname/service.py 定义的 CnameService 负责:

  • 通过 get_cname_settings()cname/settings.py)加载基础配置。
  • 启动隐藏主 DNS 服务器(cname/dns_server.py 中的 HiddenMasterServer)。
  • 订阅 Redis 发布订阅通道 cdn:cname:dirty
  • 在配置或健康事件变更时重建 DNS 区域。
  • 向下游从节点(如 NSD)发送 NOTIFY 消息。

该服务在 FastAPI 启动时初始化,并维护后台任务以响应 dirty 事件。

配置与设置

cname/settings.py 从环境变量读取并构建 CnameSettings

  • CNAME_BASE_DOMAIN:顶级域名(docker-compose.yml 中为 cdn.local.test)。
  • CNAME_REPLICAS_PER_SITE:每个站点分配的节点数量(默认 2)。
  • CNAME_DEFAULT_TTL:生成记录的 TTL(默认 60 秒)。
  • DNS_SOA_*:SOA 计时参数,用于阴性缓存与刷新策略。
  • DNS_NS1_ADNS_NS1_AAAA:权威 DNS 的 glue 记录。
  • DNS_MASTER_BIND_ADDRDNS_MASTER_PORT:隐藏主节点监听地址/端口(默认 0.0.0.0:10053)。
  • CNAME_DNS_SLAVES:以逗号分隔的从节点端点列表,支持 IPv4/IPv6。

辅助结构 NodeHealthSettings 控制健康检查行为,对应环境变量包括 NODE_HEALTH_PORTNODE_HEALTH_INTERVAL_SECSNODE_HEALTH_FAILS_TO_DOWNNODE_HEALTH_SUCCS_TO_UP 等。

区域构建

Rendezvous 哈希

cname/hashing.py 实现了 rendezvous 哈希(rendezvous_topk),用于生成稳定的域名节点映射:

  • 以访问 FQDN 作为哈希种子。
  • 为每个节点 ID 评分,选出前 replicas_per_site 个。
  • 当节点增减时仅最小化重排。

ZoneBuilder

cname/zone.pyZoneBuilder 负责:

  • 过滤出 node.can_serve() 的激活节点。
  • 为每个域名生成指向节点 IP 的 A/AAAA 记录,同时将域名转换为 IDNA 安全的访问 FQDN。
  • 为基础区域添加 SOA 与 NS 记录。
  • 依据记录内容生成确定性签名,仅在签名变化时递增序列号,保持符合 RFC。
  • 产出包含 recordsserialgenerated_at 时间戳的 ZoneSnapshot

访问 FQDN

compute_access_fqdn(domain, settings) 将业务域转换为服务端点:

<site-domain>.<base_fqdn>

例如 www.example.com 会映射为 www.example.com.cdn.local.test。函数会验证每个标签不超过 63 字符,整体 FQDN 不超过 255 字符。

隐藏主服务器

cname/dns_server.py 实现了同时支持 UDP 与 TCP 的轻量权威服务器:

  • 安全性——仅允许配置的从节点 IP 查询/AXFR(通过 ipaddress 归一化),未授权客户端返回 REFUSED
  • UDP 处理——响应标准查询,区分 NXDOMAIN 与 NOERROR,并在阴性应答中附加 SOA。
  • TCP AXFR——顺序回放记录完成全量区域传输,确保与 NSD 兼容。
  • NOTIFY——每次区域重建后向各个从节点发送 DNS NOTIFY,提示其执行 AXFR;重试采用指数退避,并在线程池中运行以避免阻塞事件循环。

这里未采用 OpenResty 式的 LRU 缓存;DNS 依靠内存快照加锁机制保证重建过程的一致性。

基于健康的路由

cname/health.py 通过 Celery 运行(见第 4 章):

  • 遍历 cdn:nodes 中注册的节点。
  • 对每个节点在配置端口(默认 9145,对应 OpenResty 指标服务器)执行 /healthz HTTP GET。
  • 调整 health_fails / health_succs 计数,在达到阈值时切换 active 标志。
  • 当激活状态变化时发布 cdn:cname:dirty,强制触发 DNS 更新。
  • 返回结构化结果,用于日志或调试。

此健康循环将控制平面操作与数据平面稳定性解耦,确保 DNS 仅公布健康端点。

与外部 DNS 集成

docker-compose.yml 中的 nsd 容器作为可公开访问的从节点:

  • 10054 端口接收 NOTIFY。
  • 向客户或其他递归解析器提供区域。
  • Cirrus 可通过扩展 CNAME_DNS_SLAVES 来横向增加从节点。

生产环境中,NSD 实例通常分布在不同区域并使用 anycast IP 以降低延迟。

故障场景与恢复

  • Redis 故障——缺少配置时 DNS 服务无法启动;API 会记录 cname_settings_invalid。运行期若因数据拉取异常导致无法重建区域,会记录日志并等待后续事件。
  • 节点抖动——健康检查依赖连续失败/成功阈值避免震荡;rendezvous 哈希减少记录变动。
  • 从节点断连——NOTIFY 失败会逐个记录,运维需排查网络连通性。
  • 配置错误——非法的基础域名或从节点配置会导致启动失败,API 在启动阶段即可暴露问题。

运维要点

  • 确保生产环境将 CNAME_BASE_DOMAIN 委派给 NSD(或等效)实例。
  • 关注 cname_hidden_master_listening 日志确认绑定地址,并留意 cname_notify_failed 以排查从节点通信。
  • 定期检查 Redis 键空间防止遗留条目;通过 API 删除域名即可自动清理,描述性键名也便于手工排查。

DNS 是将控制平面声明连接至终端体验的关键环节。下一章将探讨 OpenResty 在请求处理时如何解读同一份域名与节点状态。