Skip to content

节点与网络

L1 配置事实源(分层模型见分层与存储角色):由人与流程写入、agent 读取执行,丢失后不可重建,必须纳入备份。全局约定(命名、引擎与连接池、类型、spec 双层结构)见全局约定

本册十张表按职责分三组:

写入方
身份与供给nodesnode_wireguard_keysport_pools管理面与 services/node_keys / PortAllocator
接入凭据agent_tokensenrollment_tokenspending_registrations管理面签发,agent 注册时消费
网络配置peeringswg_interfacesbgp_sessionsgenerations管理面与自动对等,经 materialize 发布

这些表如何合成为 agent 拉取的 DesiredState,见写入路径


nodes

节点聚合根。app/db/models/node.py

类型约束 / 默认说明
node_idString(64)PK节点稳定字符串 ID(如 edge1
siteString(32)可空站点标识(IATA 城市码),公开地图按它折叠
asnIntegerNOT NULL节点 ASN
router_idString(64)NOT NULLBGP router-id
loopback_ipv4String(64)可空身份 loopback v4
loopback_ipv6String(64)可空身份 loopback v6
link_localString(64)可空节点级 IPv6 link-local(fe80::/10,不带 %zone)。外部 eBGP LLA 的单一真相源:materializer 把 <link_local>/64 派生注入所有外部 eBGP WG 接口的 addresses
ipv4_prefixesJSONNOT NULL,默认 []宣告的 v4 前缀列表
ipv6_prefixesJSONNOT NULL,默认 []宣告的 v6 前缀列表
inventoryJSONNOT NULL,默认 {}agent 注册带来的可读元信息
labelsJSONNOT NULL,默认 {}标签
base_templateJSONNOT NULL,默认 {}DesiredState 去掉 generation / interfaces / bgp_sessions / dns 后的部分(runtime、bird、templates、schema_version 等)
current_generationIntegerNOT NULL,默认 0已发布最新世代号;0 表示尚未发布
lifecycleString(16)NOT NULL,默认 activeactive / decommissioned。退役态 materialize 产出空 interfaces / bgp / dns
dns_group_idIntegerFK→dns_groups.id(SET NULL),可空,index订阅的共享 DNS 组;NULL 表示不部署 DNS
wireguard_public_keyString(64)可空节点 WG 公钥。只读投影——权威值在 node_wireguard_keys,写路径只有 services/node_keys
wireguard_private_key_escrowText可空节点 WG 私钥经离线恢复公钥 RSA-OAEP 封装的密文,控制面只存不解。仅在配置了 [server] recovery_public_key 时填充,生产当前未配置故恒为 NULL
created_at / updated_atDateTime(tz)server_default now,updated_at 带 onupdate时间戳

关系:agent_tokens / peeringsforeign_keys=Peering.local_node_id)/ wg_interfaces / bgp_sessions / generationscascade="all, delete-orphan"dns_group 为 joined 只读。

提示: 这张 10 行的表是全库读取次数最多的表(3 300 万次顺序扫描)。成因与是否需要处置的判断见性能与容量


node_wireguard_keys

节点 WireGuard 密钥对,fleet 密钥收权后的单一事实源app/db/models/provisioning.py

类型约束 / 默认说明
node_idString(64)PK,FK→nodes.node_id(CASCADE)所属节点
private_keyString(64)NOT NULLX25519 私钥,base64 明文。本表是全 fleet 隧道密钥的持有处,备份与访问控制按最高敏感级别对待
public_keyString(64)NOT NULL由私钥派生的公钥。不接受独立写入
created_at / updated_atDateTime(tz)server_default now时间戳

行由控制面生成(节点无密钥时自动生成一对),或由迁移 backfill 收编既有的字面密钥。

三条纪律:

  1. 接口 spec 与 DesiredState 中一律存储密钥字面串,不引入引用语法;单一来源由 materializer 对每个 WG 接口无条件注入本表的私钥实现,spec 中的原有取值一律被节点密钥覆盖。
  2. Node.wireguard_public_key 是本表公钥的只读投影,密钥托管的一致性校验对照的即为此处派生的公钥。
  3. 公钥恒由私钥派生,任何写路径都必须经 services/node_keys

设计见 fleet 供给收权


port_pools

全 fleet 统一的 WireGuard 端口段位。app/db/models/provisioning.py

类型约束 / 默认说明
scopeString(16)PKexternal(对外对等,含自动对等的监听段)/ internal(节点间内部隧道段)
start / endIntegerNOT NULL段位闭区间
updated_atDateTime(tz)server_default now,带 onupdate更新时刻

生产段位:external 51800–51830、internal 52800–52820(由迁移 seed 写入)。分配段位不再分散于各节点的 base_template:所有分配渠道经同一个 PortAllocator 取号,节点 runtime 的发布段由 materializer 从本表派生。


agent_tokens

长期 Bearer token,绑定具体节点。node.py

类型约束 / 默认说明
tokenString(128)PK非机密查找键 id(形如 agt_xxxx);明文 secret 永不落库
token_hashString(128)NOT NULL,unique,index完整 Bearer 的 sha256;校验只走它
node_idString(64)FK→nodes.node_id(CASCADE),NOT NULL,index绑定节点
agent_idString(128)NOT NULLagent 标识
issued_atDateTime(tz)server_default now签发时刻
expires_atDateTime(tz)可空非空即会过期
revoked_atDateTime(tz)可空吊销时刻

鉴权解析结果有 60 秒的 L5 缓存(agtok:<sha256>,见 KV 与缓存键索引),吊销与轮换时主动删除该键。


enrollment_tokens

一次性注册 token,与 agent_tokens 同安全模型。node.py

类型约束 / 默认说明
tokenString(128)PK非机密查找键 id(ent_*
token_hashString(128)NOT NULL,unique,indexsha256;校验只走它
node_idString(64)FK→nodes.node_id(SET NULL),可空空表示尚未指定节点(管理员预生成)
descriptionString(256)可空备注
expires_atDateTime(tz)可空过期时刻
used_atDateTime(tz)可空非空表示已消费(一次性语义)
created_atDateTime(tz)server_default now创建时刻

pending_registrations

待审批的 agent 注册请求。node.py

类型约束 / 默认说明
idIntegerPK,autoincrement主键
requested_node_idString(64)NOT NULL,index请求的节点 ID
hostnameString(255)可空主机名
inventoryJSONNOT NULL,默认 {}上报的硬件与系统信息
statusString(16)NOT NULL,默认 pending,indexpending / approved / rejected
noteString(256)可空审批备注
created_at / updated_atDateTime(tz)server_default now,updated_at 带 onupdate时间戳

部分唯一索引 UNIQUE(requested_node_id) WHERE status='pending':同一节点同时只允许存在一条 pending 记录(重复注册刷新同一行),历史的 approved / rejected 行不受此约束。


generations

每节点的已发布 DesiredState 世代快照。app/db/models/generation.py

类型约束 / 默认说明
idIntegerPK,autoincrement主键
node_idString(64)FK→nodes.node_id(CASCADE),NOT NULL,index所属节点
generationIntegerNOT NULL世代号,节点内严格单调递增
snapshotJSONNOT NULL控制面合成的完整 DesiredState JSON,agent 直接读
reasonString(256)可空发布原因备注
published_atDateTime(tz)server_default now发布时刻

唯一约束 UNIQUE(node_id, generation)。当前世代由 Node.current_generation 指向。保留窗口默认为最近 100 代,超出部分在 materialize 的同一事务内裁剪。实测 752 行占用 4 MB,单行 snapshot 是全库体积最大的 JSON 值。


peerings

对等关系聚合根。app/db/models/peering.py

类型约束 / 默认说明
idIntegerPK,autoincrement主键
local_node_idString(64)FK→nodes.node_id(CASCADE),NOT NULL,index本端节点
remote_node_idString(64)FK→nodes.node_id(SET NULL),可空,index对端受管节点(外部对等留空);materializer 据此取对端 WG 公钥
nameString(64)NOT NULLpeering 名
remote_asnIntegerNOT NULL对端 ASN
remote_labelString(128)可空对端标签
is_internalBooleanNOT NULL,默认 False内部互联(iBGP / OSPF)标志;影响 LLA 注入
enabledBooleanNOT NULL,默认 True启用标志
notesString(512)可空备注
created_at / updated_atDateTime(tz)server_default now,updated_at 带 onupdate时间戳

唯一约束 UNIQUE(local_node_id, name)。关系:wg_interfaces / bgp_sessionscascade="all, delete-orphan"(selectin 加载);local_noderemote_node 用各自的 foreign_keys 消歧。


wg_interfaces

节点接口资源(WireGuard / dummy 等)。peering.py

类型约束 / 默认说明
idIntegerPK,autoincrement主键
node_idString(64)FK→nodes.node_id(CASCADE),NOT NULL,index所属节点
peering_idIntegerFK→peerings.id(SET NULL),可空,index空表示纯节点级接口(dummy lo、IGP 隧道等)
nameString(64)NOT NULL接口名(apply_spec 从 spec 派生)
kindString(32)NOT NULL接口种类(从 spec.kind 派生)
enabledBooleanNOT NULL,默认 True控制面列;disabled 接口不进 snapshot
specJSONNOT NULL完整 InterfaceSpec 的 Pydantic dump
sort_orderIntegerNOT NULL,默认 0排序键(materializer 按 sort_order, id 取)

唯一约束 UNIQUE(node_id, name)apply_spec(InterfaceSpec) 投影 name / kind / specenabled 由调用方单独维护。


bgp_sessions

节点上的一条 BGP 会话。peering.py

类型约束 / 默认说明
idIntegerPK,autoincrement主键
node_idString(64)FK→nodes.node_id(CASCADE),NOT NULL,index所属节点
peering_idIntegerFK→peerings.id(SET NULL),可空,index关联 peering
nameString(64)NOT NULL会话名(从 spec 派生)
remote_asnIntegerNOT NULL对端 ASN(从 spec 派生)
enabledBooleanNOT NULL,默认 True从 spec 派生;disabled 会话仍进 snapshot 但 enabled=False
specJSONNOT NULL完整 BgpSessionSpec 的 Pydantic dump
sort_orderIntegerNOT NULL,默认 0排序键

唯一约束 UNIQUE(node_id, name)apply_spec(BgpSessionSpec) 投影 name / remote_asn / enabled / spec;materializer 读取时把列 enabled 投影回 spec。

提示: iBGP 与 OSPF 内部互联不写入本表,由 bird.internal_topology 合成,见 内部互联