外观
节点与网络
L1 配置事实源(分层模型见分层与存储角色):由人与流程写入、agent 读取执行,丢失后不可重建,必须纳入备份。全局约定(命名、引擎与连接池、类型、spec 双层结构)见全局约定。
本册十张表按职责分三组:
| 组 | 表 | 写入方 |
|---|---|---|
| 身份与供给 | nodes、node_wireguard_keys、port_pools | 管理面与 services/node_keys / PortAllocator |
| 接入凭据 | agent_tokens、enrollment_tokens、pending_registrations | 管理面签发,agent 注册时消费 |
| 网络配置 | peerings、wg_interfaces、bgp_sessions、generations | 管理面与自动对等,经 materialize 发布 |
这些表如何合成为 agent 拉取的 DesiredState,见写入路径。
nodes
节点聚合根。app/db/models/node.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
node_id | String(64) | PK | 节点稳定字符串 ID(如 edge1) |
site | String(32) | 可空 | 站点标识(IATA 城市码),公开地图按它折叠 |
asn | Integer | NOT NULL | 节点 ASN |
router_id | String(64) | NOT NULL | BGP router-id |
loopback_ipv4 | String(64) | 可空 | 身份 loopback v4 |
loopback_ipv6 | String(64) | 可空 | 身份 loopback v6 |
link_local | String(64) | 可空 | 节点级 IPv6 link-local(fe80::/10,不带 %zone)。外部 eBGP LLA 的单一真相源:materializer 把 <link_local>/64 派生注入所有外部 eBGP WG 接口的 addresses |
ipv4_prefixes | JSON | NOT NULL,默认 [] | 宣告的 v4 前缀列表 |
ipv6_prefixes | JSON | NOT NULL,默认 [] | 宣告的 v6 前缀列表 |
inventory | JSON | NOT NULL,默认 {} | agent 注册带来的可读元信息 |
labels | JSON | NOT NULL,默认 {} | 标签 |
base_template | JSON | NOT NULL,默认 {} | DesiredState 去掉 generation / interfaces / bgp_sessions / dns 后的部分(runtime、bird、templates、schema_version 等) |
current_generation | Integer | NOT NULL,默认 0 | 已发布最新世代号;0 表示尚未发布 |
lifecycle | String(16) | NOT NULL,默认 active | active / decommissioned。退役态 materialize 产出空 interfaces / bgp / dns |
dns_group_id | Integer | FK→dns_groups.id(SET NULL),可空,index | 订阅的共享 DNS 组;NULL 表示不部署 DNS |
wireguard_public_key | String(64) | 可空 | 节点 WG 公钥。只读投影——权威值在 node_wireguard_keys,写路径只有 services/node_keys |
wireguard_private_key_escrow | Text | 可空 | 节点 WG 私钥经离线恢复公钥 RSA-OAEP 封装的密文,控制面只存不解。仅在配置了 [server] recovery_public_key 时填充,生产当前未配置故恒为 NULL |
created_at / updated_at | DateTime(tz) | server_default now,updated_at 带 onupdate | 时间戳 |
关系:agent_tokens / peerings(foreign_keys=Peering.local_node_id)/ wg_interfaces / bgp_sessions / generations 均 cascade="all, delete-orphan";dns_group 为 joined 只读。
提示: 这张 10 行的表是全库读取次数最多的表(3 300 万次顺序扫描)。成因与是否需要处置的判断见性能与容量。
node_wireguard_keys
节点 WireGuard 密钥对,fleet 密钥收权后的单一事实源。app/db/models/provisioning.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
node_id | String(64) | PK,FK→nodes.node_id(CASCADE) | 所属节点 |
private_key | String(64) | NOT NULL | X25519 私钥,base64 明文。本表是全 fleet 隧道密钥的持有处,备份与访问控制按最高敏感级别对待 |
public_key | String(64) | NOT NULL | 由私钥派生的公钥。不接受独立写入 |
created_at / updated_at | DateTime(tz) | server_default now | 时间戳 |
行由控制面生成(节点无密钥时自动生成一对),或由迁移 backfill 收编既有的字面密钥。
三条纪律:
- 接口 spec 与 DesiredState 中一律存储密钥字面串,不引入引用语法;单一来源由 materializer 对每个 WG 接口无条件注入本表的私钥实现,spec 中的原有取值一律被节点密钥覆盖。
Node.wireguard_public_key是本表公钥的只读投影,密钥托管的一致性校验对照的即为此处派生的公钥。- 公钥恒由私钥派生,任何写路径都必须经
services/node_keys。
设计见 fleet 供给收权。
port_pools
全 fleet 统一的 WireGuard 端口段位。app/db/models/provisioning.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
scope | String(16) | PK | external(对外对等,含自动对等的监听段)/ internal(节点间内部隧道段) |
start / end | Integer | NOT NULL | 段位闭区间 |
updated_at | DateTime(tz) | server_default now,带 onupdate | 更新时刻 |
生产段位:external 51800–51830、internal 52800–52820(由迁移 seed 写入)。分配段位不再分散于各节点的 base_template:所有分配渠道经同一个 PortAllocator 取号,节点 runtime 的发布段由 materializer 从本表派生。
agent_tokens
长期 Bearer token,绑定具体节点。node.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
token | String(128) | PK | 非机密查找键 id(形如 agt_xxxx);明文 secret 永不落库 |
token_hash | String(128) | NOT NULL,unique,index | 完整 Bearer 的 sha256;校验只走它 |
node_id | String(64) | FK→nodes.node_id(CASCADE),NOT NULL,index | 绑定节点 |
agent_id | String(128) | NOT NULL | agent 标识 |
issued_at | DateTime(tz) | server_default now | 签发时刻 |
expires_at | DateTime(tz) | 可空 | 非空即会过期 |
revoked_at | DateTime(tz) | 可空 | 吊销时刻 |
鉴权解析结果有 60 秒的 L5 缓存(agtok:<sha256>,见 KV 与缓存键索引),吊销与轮换时主动删除该键。
enrollment_tokens
一次性注册 token,与 agent_tokens 同安全模型。node.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
token | String(128) | PK | 非机密查找键 id(ent_*) |
token_hash | String(128) | NOT NULL,unique,index | sha256;校验只走它 |
node_id | String(64) | FK→nodes.node_id(SET NULL),可空 | 空表示尚未指定节点(管理员预生成) |
description | String(256) | 可空 | 备注 |
expires_at | DateTime(tz) | 可空 | 过期时刻 |
used_at | DateTime(tz) | 可空 | 非空表示已消费(一次性语义) |
created_at | DateTime(tz) | server_default now | 创建时刻 |
pending_registrations
待审批的 agent 注册请求。node.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
requested_node_id | String(64) | NOT NULL,index | 请求的节点 ID |
hostname | String(255) | 可空 | 主机名 |
inventory | JSON | NOT NULL,默认 {} | 上报的硬件与系统信息 |
status | String(16) | NOT NULL,默认 pending,index | pending / approved / rejected |
note | String(256) | 可空 | 审批备注 |
created_at / updated_at | DateTime(tz) | server_default now,updated_at 带 onupdate | 时间戳 |
部分唯一索引 UNIQUE(requested_node_id) WHERE status='pending':同一节点同时只允许存在一条 pending 记录(重复注册刷新同一行),历史的 approved / rejected 行不受此约束。
generations
每节点的已发布 DesiredState 世代快照。app/db/models/generation.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
node_id | String(64) | FK→nodes.node_id(CASCADE),NOT NULL,index | 所属节点 |
generation | Integer | NOT NULL | 世代号,节点内严格单调递增 |
snapshot | JSON | NOT NULL | 控制面合成的完整 DesiredState JSON,agent 直接读 |
reason | String(256) | 可空 | 发布原因备注 |
published_at | DateTime(tz) | server_default now | 发布时刻 |
唯一约束 UNIQUE(node_id, generation)。当前世代由 Node.current_generation 指向。保留窗口默认为最近 100 代,超出部分在 materialize 的同一事务内裁剪。实测 752 行占用 4 MB,单行 snapshot 是全库体积最大的 JSON 值。
peerings
对等关系聚合根。app/db/models/peering.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
local_node_id | String(64) | FK→nodes.node_id(CASCADE),NOT NULL,index | 本端节点 |
remote_node_id | String(64) | FK→nodes.node_id(SET NULL),可空,index | 对端受管节点(外部对等留空);materializer 据此取对端 WG 公钥 |
name | String(64) | NOT NULL | peering 名 |
remote_asn | Integer | NOT NULL | 对端 ASN |
remote_label | String(128) | 可空 | 对端标签 |
is_internal | Boolean | NOT NULL,默认 False | 内部互联(iBGP / OSPF)标志;影响 LLA 注入 |
enabled | Boolean | NOT NULL,默认 True | 启用标志 |
notes | String(512) | 可空 | 备注 |
created_at / updated_at | DateTime(tz) | server_default now,updated_at 带 onupdate | 时间戳 |
唯一约束 UNIQUE(local_node_id, name)。关系:wg_interfaces / bgp_sessions 均 cascade="all, delete-orphan"(selectin 加载);local_node 与 remote_node 用各自的 foreign_keys 消歧。
wg_interfaces
节点接口资源(WireGuard / dummy 等)。peering.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
node_id | String(64) | FK→nodes.node_id(CASCADE),NOT NULL,index | 所属节点 |
peering_id | Integer | FK→peerings.id(SET NULL),可空,index | 空表示纯节点级接口(dummy lo、IGP 隧道等) |
name | String(64) | NOT NULL | 接口名(apply_spec 从 spec 派生) |
kind | String(32) | NOT NULL | 接口种类(从 spec.kind 派生) |
enabled | Boolean | NOT NULL,默认 True | 控制面列;disabled 接口不进 snapshot |
spec | JSON | NOT NULL | 完整 InterfaceSpec 的 Pydantic dump |
sort_order | Integer | NOT NULL,默认 0 | 排序键(materializer 按 sort_order, id 取) |
唯一约束 UNIQUE(node_id, name)。apply_spec(InterfaceSpec) 投影 name / kind / spec;enabled 由调用方单独维护。
bgp_sessions
节点上的一条 BGP 会话。peering.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
node_id | String(64) | FK→nodes.node_id(CASCADE),NOT NULL,index | 所属节点 |
peering_id | Integer | FK→peerings.id(SET NULL),可空,index | 关联 peering |
name | String(64) | NOT NULL | 会话名(从 spec 派生) |
remote_asn | Integer | NOT NULL | 对端 ASN(从 spec 派生) |
enabled | Boolean | NOT NULL,默认 True | 从 spec 派生;disabled 会话仍进 snapshot 但 enabled=False |
spec | JSON | NOT NULL | 完整 BgpSessionSpec 的 Pydantic dump |
sort_order | Integer | NOT NULL,默认 0 | 排序键 |
唯一约束 UNIQUE(node_id, name)。apply_spec(BgpSessionSpec) 投影 name / remote_asn / enabled / spec;materializer 读取时把列 enabled 投影回 spec。
提示: iBGP 与 OSPF 内部互联不写入本表,由 bird.internal_topology 合成,见 内部互联。