Skip to content

架构

本文说明系统如何把控制平面的数据库记录变成节点上的 Docker、WireGuard、BIRD、RPKI 和 DNS runtime,以及节点状态如何回流到控制面,覆盖"组件—边界—数据流—变更闭环"的全貌。各组件内部细节见 Control Server 内部Node Agent 内部共享包

组件职责

控制面是单一实现 apps/control-server(FastAPI),入口 https://api.natlan.io。同生态另有两个独立后端服务:auth-server(独占 auth.natlan.io)与 registry-server(仅容器网内可达)。

组件位置职责
Control Serverapps/control-serverFastAPI 控制面:Admin / UI BFF / Agent / 公开 API,materialize、健康推导、token、门铃。见 Control Server 内部
Node Agentapps/node-agent节点常驻守护进程:拉取 DesiredState、渲染配置、规划执行本机变更、上报结果
Web UI独立仓库 natlan-webSvelteKit 单页管理界面,Cloudflare Git 关联 Worker 静态资产托管(console.natlan.io),账号登录后经 CORS 调用 Admin/UI API
DatabasePostgreSQL(生产)/ SQLite(本地开发与 CI)节点、接口、BGP、DNS、token、generation 快照、节点状态、路由、flap、账号
dn42_schemaspackages/dn42_schemas跨组件传输的数据结构
dn42_templatespackages/dn42_templatesDesiredState 渲染为配置文件和脚本
dn42_runtimepackages/dn42_runtime渲染文件类型、写盘计划、router Dockerfile 渲染
dn42_commonpackages/dn42_common公共校验、命名、label、community、crypto 工具

系统边界

Control Server 内部包含 API 路由、token / enrollment、desired-state、node-status、routing、注册审批、审计等存储服务,以及 materialize() / provision 与门铃发布——这些不是外部服务。

数据流

一:管理端写入变更

业务表包括 nodespeeringswg_interfacesbgp_sessionsdns_groups / dns_group_zones / dns_recordsagent_tokensenrollment_tokensgenerations 保存已发布给 Agent 的完整状态快照。校验失败(schema 不过)返回 422,业务表写入随事务一起回滚。详见 Control Server 内部

二:Agent 接收新状态

WebSocket 只传事件门铃,不传业务数据——例外只有拨测与日志查看两个旁路作业(probe_request / log_request,它们携带作业 spec)。Agent 收到门铃后通过 HTTP 拉取完整 DesiredState。门铃由控制面进程内的 EventBus 承载。

三:Agent 本地部署

Docker backend 先准备镜像、再删需重建的旧容器,避免构建失败时先破坏现有部署。容器不用 docker-compose,由 Agent 直接通过 Docker Engine API 创建;容器名固定 <project>-<service>-1 后缀。详见 Node Agent 内部

四:状态回流与健康视图

健康为五态,由上报状态 + 漂移计数 + generation 差推导,再叠加时间阈值覆盖(stale_after / down_after)。判定逻辑见 Control Server 内部

Agent 另有一组旁路任务独立于 reconcile 闭环:路由表采集(直连 BIRD 控制 socket)、WireGuard 流量采集、endpoint 域名周期重解析、L3 自愈回路、自监控指标、flapfeed(进程内 BGP 监管会话,前缀级抖动检测)。清单与周期见 Node Agent 内部

上报侧的写路径瘦身与最小扰动同源:路由明细按 per-prefix 规范化哈希与 node_route_prefix_hashes 基准差分写,无变化的前缀零写入;prefix-flap 打分热态存 Valkey 单键 blob 而非 SQL 行(它是唯一副本,必须落在不淘汰、会落盘的实例上——数据层参考);大表摄入端点先回响应、DB 事务放后台,解耦 agent 读超时。

新节点接入生命周期

approve 只是放行名单;真正能工作取决于 provision 是否下发了 DesiredState。完整步骤见 节点接入

节点 runtime

role作用
router-netns提供共享 network namespace(其它服务 network_mode 共享它)
wg-gateway应用 WireGuard 配置、创建隧道接口
bird-router运行 BIRD 2,承载 BGP、OSPF、静态路由和过滤策略
rpki-cache为 BIRD 提供 DN42 RPKI/ROA 数据
dns运行 CoreDNS(订阅 DNS 组时注入)
debug-shell可选调试容器

internal_topology 一致性不变量:同一 AS 内多节点的 iBGP/OSPF 由 DesiredState.bird.internal_topology 合成(不是 bgp_sessions)。所有节点的 routers+hosts 必须是同一份完整集合,否则会隐蔽缺路由。设计与加节点 checklist 见 内部互联

最小扰动设计

系统刻意采用电平触发(level-based)+ 内容寻址而不是"控制面推送 delta"(边沿触发):事件丢失、乱序、agent 重启都不影响正确性,每轮 reconcile 都从最新全量状态推导出最小动作集。

机制效果
容器身份 = dn42.config_hash(服务 spec + underlay + 构建参数的哈希)generation 递增不重建任何容器;只有容器定义本身变化才重建
渲染产物不携带 generation,跨代逐字节稳定无实质变化时 file plan 全 noop
配置文件file plan(SHA-256 对比)算出精确差异agent 本地就知道"到底变了什么"
数据面定向收敛:birdc configure 热重载、按接口 WireGuard 同步/拆除加一个 peer 只拉起一条隧道,其余 BGP 会话零扰动
事件WS 门铃 + 防抖合并 + 兜底周期突发批量变更合并为一次 reconcile

控制面在事件里附带 reason 供日志排错,但 agent 的收敛判定完全基于本地观测对比——"该收敛什么"永远以事实为准。

并发与一致性

场景保障
并发 admin 写同一节点materialize 对节点行加 SELECT ... FOR UPDATE,generation 严格单调;UNIQUE(node_id, generation) 兜底
事件先发、事务后回滚materialize 不发事件;路由层在事务提交后才广播
WS 事件丢失 / 队列溢出agent 兜底周期 reconcile 拉平
突发批量变更agent 防抖窗口合并;每次 reconcile 拉取的都是最新全量状态,中间代次天然被跳过
agent 重复 reconcile幂等:file plan 全 noop、容器 plan 全 keep、收敛零动作
同节点多 agent 实例不支持,部署约定每节点单实例(systemd 单元天然如此)

生产为 PostgreSQL,行级锁真实生效。SQLite 仅用于本地开发与 CI:它忽略 FOR UPDATE 子句,靠单写者与唯一约束兜底。

变更闭环

这条闭环保证系统不是"远程执行命令",而是"发布期望状态、节点本地收敛、回报观察结果"。Control Server 不提供远程 shell 或任意命令执行接口(见 安全模型)。