外观
地址概念模型
本文把项目里所有类型的地址盘一遍:每个地址是什么、有什么用、真相源在哪、影响半径多大,并按对「单一真相源」最关键的维度给每个地址分类。
这是概念参考。fleet 的具体数值分配见 寻址与 renumber;internal_topology 的语义与一致性不变量见 内部互联;承载这些地址的 schema 见 DesiredState。
三种分类:源 / 派生 / 副本
每个地址都标两件事——影响半径与真相源分类。后者直接决定它是不是 renumber 的危险点:
| 分类 | 含义 | renumber 时 | 例子 |
|---|---|---|---|
| 源(source) | 被人手 authored 的权威值,改这里才算改 | 改这一处 | Node.loopback_ipv4、DnsGroup.bind_addresses、NodeSpec.link_local |
| 派生(derived) | 渲染 / normalize / materialize 时从源算出来,不落独立存储 | 不用动,自动跟随 | OWNIP、dns-anycast 接口地址、外部 eBGP 接口上的 link-local |
| 副本(copy) | 源的独立第二份,没有机制保证一致 | 必须手动同步,漏一处就坏 | internal_topology.hosts[*].ownip、bgp.source_address |
设计方向是把「副本」逐个变成「派生」。 代码里已有三个落地范例:
_normalize_dns_anycast(packages/dn42_schemas/dn42_schemas/desired_state.py):dns.bind_addresses是 DNS 服务地址的唯一真源,dns-anycastdummy 接口地址与它进 BGP 的任播前缀全部在 normalize 阶段派生。_interface_payload的 LLA 派生(apps/control-server/app/services/materializer.py):NodeSpec.link_local是节点 link-local 的唯一真源,materialize 时派生到所有外部 eBGP WG 接口的addresses。_inject_node_keys(同上):节点 WireGuard 私钥的唯一真源是node_wireguard_keys表,materialize 时无条件覆盖每个 WG 接口的private_key_ref。
以最贵的节点 loopback 为例,一处真值的下游全景(实线为自动派生,虚线为仍需手动同步的副本):
按影响半径分层
从影响半径最大(动一下牵动全 fleet 与注册表)到最小(节点内部、链路层)。
第 1 层:节点身份地址
影响半径:全 fleet + DN42 注册表 + DNS。 这层是最贵的地址——一处真值,但被复制或派生到很多地方。
| 地址 | 是什么 / 用途 | 真相源 | 分类 | 影响半径 |
|---|---|---|---|---|
router_id | BIRD router id;iBGP / OSPF 身份;large-community 的 origin_node_id 取它低 16 位 | Node.router_id | 源 | iBGP / OSPF 会话重建;community 编号变化 |
loopback_ipv4 / loopback_ipv6 | 节点身份 loopback。渲染成 OWNIP / OWNIPv6:内核导出源地址 krt_prefsrc、iBGP next-hop 与 source address、router id 兜底。必须属于对应的 *_prefixes | Node.loopback_ipv4/ipv6 | 源 | 全 fleet(见 1.1)+ DNS 正反向记录 + 注册表 |
ipv4_prefixes / ipv6_prefixes | 节点自有、对外宣告的聚合前缀。派生 static reject 路由、is_self_net ipset、iBGP 宣告前缀 | Node.ipv4_prefixes/ipv6_prefixes | 源 | 节点对 DN42 起源什么(注册表 route 对象) |
派生自本层(安全,自动跟随):OWNIP / OWNIPv6、router id OWNIP、krt_prefsrc、origin_node_id、ownnets*_ipset、iBGP 的 source address 与 next hop——全部在 packages/dn42_templates/ 里从 loopback 与 prefixes 算出。
1.1 副本:internal_topology.hosts[*].ownip / ownip6
bird.internal_topology.hosts[<node>].ownip/ownip6 是每个节点 loopback 的独立第二份,而且按 fleet 一致性不变量要复制进每一个节点的 base_template。
- 影响半径:全 fleet。renumber 一个节点的 loopback,要同步 N 个节点里的这份 hosts。
- 这是历史上「改不干净」最严重的点,
deploy/tools/unify_internal_topology.py里硬编码旧地址导致回退的坑也出在这里。 - 收敛方向:hosts 的
ownip/ownip6从各节点Node.loopback_*派生。改动牵动全 fleet iBGP、须逐节点灰度验证收敛,属于不可逆风险,尚未实施。
第 2 层:eBGP 会话地址
影响半径:单个对端。
| 地址 | 是什么 / 用途 | 真相源 | 分类 |
|---|---|---|---|
BgpSessionSpec.neighbor | 对端 BGP 地址;可带 %zone | 会话 spec | 源 |
BgpSessionSpec.source_address | 本端建会话的源地址 | 会话 spec | 副本 |
2.1 副本:source_address 与接口地址
source_address 和 InterfaceSpec.addresses(第 3 层)里的本端地址是同一个值、独立写两遍,没有任何机制强制相等。renumber 链路时改了接口漏了 source(或反之),BGP 就起不来。
收敛方向:source_address 改可选,从绑定接口按地址族派生并加强校验。这牵动寻址模型的一次决策(per-link /31 还是节点单 /32 + link-local),须先拍板。
第 3 层:接口与链路地址
影响半径:单条链路(两端)。 承载在 InterfaceSpec / WgInterface.spec。
| 地址 | 是什么 / 用途 | 真相源 | 分类 |
|---|---|---|---|
addresses | ip addr add 到接口的本端地址。外部 eBGP 接口的 fe80::X/64 由 materialize 派生注入 | 接口 spec(LLA 部分为派生) | 源(LLA 为派生) |
peer_routes | 经该接口可达的对端地址(peer_v4/32、peer_v6/128、对端 link-local) | 接口 spec | 副本(对端本地地址的镜像) |
wireguard_peer.endpoint | 拨向对端的 underlay host:port;可为域名,agent 周期重解析 | 接口 spec | 源 |
wireguard_peer.allowed_ips | WG 加密选路范围,通常 0.0.0.0/0 + ::/0 | 接口 spec | 源 |
3.1 已收敛:节点 LLA fe80::X
节点本端 link-local(一节点一个,所有外部 eBGP WG 接口复用)已从副本收敛为派生:
- 唯一真源
NodeSpec.link_local(裸fe80::/10地址,不带%zone)。 - materialize 的
_interface_payload对peering.is_internal=False的 WG 接口,把<link_local>/64注入接口addresses(已存在则去重)。渲染器照旧与各接口的 fe80peer_route配成ip addr add fe80::X/64 peer fe80::Y,输出不变。 - 存量接口里的副本由
scripts/tools/backfill_node_lla.py一次性剥除,之后由派生重新提供。 - 内部互联的 WG 接口用各自的 link-local,不复用节点级值;对端 link-local 是 per-peer,留在
peer_routes不动。
第 4 层:任播服务地址
影响半径:跨节点共享 + 注册表。
| 地址 | 是什么 / 用途 | 真相源 | 分类 |
|---|---|---|---|
dns.bind_addresses | DNS 任播服务地址。normalize 派生 dns-anycast dummy 接口(v4 /32、v6 /128)并登记 track_service,于是 BIRD 起源任播前缀进 BGP | DnsGroup.bind_addresses | 源 |
dns-anycast 接口地址与任播前缀 | 上者的派生产物 | —(派生) | 派生 |
| DNS A / AAAA / PTR 记录值 | 解析到 loopback 或任播地址 | DnsRecord.content | 副本(指向 loopback 的手工绑定) |
bind_addresses → dns-anycast 这条链是全项目最干净的单一真相源实现。见 DNS 与任播。
第 5 层:runtime / underlay
影响半径:节点内部。 仅在单节点容器编排内有意义,不进公网身份或任播分区。
| 地址 | 是什么 / 用途 | 真相源 | 分类 |
|---|---|---|---|
runtime.underlay.subnet / gateway / ipv6_subnet | router netns 的容器互联网。ipv6_subnet 纳入容器 config_hash,改动触发声明式重建 | UnderlayNetworkSpec | 源 |
runtime.rpki.listen_host | underlay 内 RPKI cache 地址(默认 10.254.42.3) | RpkiSpec.listen_host | 源 |
RuntimeServiceSpec.ipv4_address | 某服务在 underlay 的显式地址;须属于 underlay 子网且不等于网关 | service spec | 源 |
不随节点变的字面量
is_valid_network / is_valid_network_v6 里的 DN42 可接受前缀范围、anycast 172.2x.0.0/24 段等,是协议级常量,不随本节点 renumber 变化,不属于源 / 派生 / 副本三分类。
副本清单(renumber 危险点速查)
| # | 副本 | 应派生自 | 状态 |
|---|---|---|---|
| 1 | internal_topology.hosts[*].ownip/ownip6 | 各节点 Node.loopback_* | 仍是副本。影响半径全 fleet,历史事故最多 |
| 2 | bgp_sessions[*].source_address | 绑定接口的 addresses | 仍是副本。收敛前需先定寻址模型 |
| 3 | interfaces[*].peer_routes 的对端地址 | 对端 addresses(以 Peering 聚合根作单源) | 仍是副本 |
| 4 | DNS A / AAAA / PTR 记录 | 指向 loopback 的手工绑定 | 仍是副本。renumber 必须连带改 |
| 5 | 节点 LLA fe80::X | NodeSpec.link_local | 已派生(materialize 注入 + backfill 剥副本) |
| 6 | 内部对端 WG 公钥 | 对端 Node.wireguard_public_key | 已派生(materialize 注入) |
| 7 | 节点 WG 私钥 | node_wireguard_keys 表 | 已派生(materialize 无条件注入) |
| 8 | dns-anycast 接口地址与任播前缀 | DnsGroup.bind_addresses | 已派生 |
其它几处已知的同源双写(不属于地址范畴,一并记在这里备查):
| 重复的事实 | 现状 |
|---|---|
router_id 与 loopback_ipv4 常恒等却分存 | 二者仍各存一份。合并是破坏性 schema 变更,影响存量节点数据 |
NodeSpec.region 与 Bird2ConfigSpec.region | 后者是前者的可选覆盖,但字段名没有体现覆盖意图 |
BgpSessionSpec.neighbor 与 peer_routes 的镜像关系 | 无校验强制二者一致 |
applied_generation 与 observed_generation 术语 | 两个名字指同一件事的不同侧面,易混淆;改名会动 agent 上报协议 |
每消灭一个副本,就少一处「改不干净」。范例形态见 _normalize_dns_anycast 与 _interface_payload。