外观
Control Server —— 控制台 BFF API
源码:apps/control-server/app/api/v1/ui/。前缀 /control/v1/ui,下文表格的「路径」列省略该前缀。
这一层为**某个界面「一次取全 / 服务端派生」**而存在:只读聚合视图加少量写辅助,取代浏览器侧多次拉取、扒 last_snapshot、客户端算差分的做法。通用资源接口仍在 /admin 下。整个 /ui 前缀挂 require_admin,与管理面同一把凭据,未配置 admin token 时同样 fail-closed 403。
接口面向前端消费、字段较宽,本文每个端点给一行语义加必要的行为说明。错误 detail 文案是稳定接口:前端按字面展示或匹配,改动视为破坏性变更。
趋势类端点共享 range / compare 语义,见 通用约定。
观测
源码:apps/control-server/app/api/v1/ui/observability.py
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /fleet/overview | fleet 健康 + 每节点能力 / agent 活体 / geo + 物理 WG 邻接网格,总览页单次拉取 |
GET | /nodes/{node_id}/traffic | 单节点 WG 吞吐时间线(字节/秒);支持 range / compare |
GET | /fleet/traffic | 全 fleet 吞吐时间线(各节点按时间桶对齐求和);支持 range / compare |
GET | /fleet/traffic-breakdown | 按节点 / 按 WG 对端的当前吞吐排行(peers_limit 1..200 默认 25,breakdown_top 截断节点榜,缺省全量) |
GET | /fleet/peering-issues | 全机群有问题的 BGP 会话:非 Established 或抖动中,按严重度排序(抖动最前、down 次之、外部会话优先) |
GET | /fleet/bgp-flaps | 全机群会话 flap 榜(指数衰减打分降序;min_score 默认 0.05,limit 1..500 默认 100) |
GET | /fleet/prefix-flaps | fleet 前缀级 flap 榜,详见下文 |
GET | /fleet/prefix-flaps/localize | 全部抖动前缀的根因区定位,详见下文 |
GET | /fleet/flap-stats | fleet 级 flap 活动时序与瞬时速率仪表,详见下文 |
GET | /fleet/flap-peers | 「哪家对端最抖」榜:会话级 + 前缀级按对端 AS 收拢,severity(阈值归一,≥1 表示至少一层过阈)降序(limit 1..200 默认 50) |
GET | /fleet/flap-peers/{asn} | 单对端 flap 详情:榜行聚合 + 该方向前缀行(prefixes_limit 1..200 默认 50)+ 含已消警历史的告警事件(alerts_limit 1..100 默认 20);该 AS 完全无 flap 痕迹 404 |
GET | /fleet/flap-alerts | flap 告警事件流,新→旧、before_id 游标分页;active=1 只看在开(limit 1..200 默认 50) |
GET | /nodes/{node_id}/links | 单节点链路状态(服务端按握手新鲜度判 up / stale / down);无上报 404 |
GET | /nodes/{node_id}/bgp-sessions/status | 单节点内 iBGP 与外 eBGP 综合状态(内外按 DesiredState 配置归类),行带 flapping / flap_score;无上报 404 |
GET | /nodes/{node_id}/bgp-flaps | 单节点会话 flap 详情 + 最近状态转移历史(transitions_limit 1..200 默认 50);无上报 404 |
GET | /nodes/{node_id}/overview | 节点页一次取全:健康行 + 能力 + 自观测 + drift + 链路 / BGP + 完整 node 记录 + DNS 组引用 + 内嵌 trends 趋势三件套 |
吞吐时间线
三层降级:优先 agent 30s 轻量采样(Redis 热窗口 / 5min 存档),无采样回落快照差分(约 5min 粒度);带 range / compare 时改从 5min 存档蒸馏定长网格,对比序列放在 points_previous。
/nodes/{node_id}/overview 对存在但从未上报的节点返回 health = unknown 的可渲染骨架而非 404,内嵌 trends 同理为空 series;404 仅表示节点不存在。trends 段是自观测 sparkline + drift 数 + apply 结果序列(升序)。
前缀级 flap 榜
GET /fleet/prefix-flaps 按 (prefix, 归因对端 AS) 跨节点归组,按 max_score 降序(min_score 默认 1.0,limit 1..500 默认 100)。每行附:
| 字段 | 语义 |
|---|---|
peer_asn / peer_name | AS_PATH 第一跳(入向对端)及其 registry 名称 |
origin_asn / origin_name | AS_PATH 最后一跳(前缀起源)及其 registry 名称 |
rate_per_s | 当前变化速率。行级为跨节点、跨方向求和;节点展开项为单节点视角。无差分基准时 null |
flapping_since / flap_duration_s | 抖动持续时长,来自同键在开告警的上穿时刻。只对告警级强度(分数 ≥100)有值;flapping=true 但未达告警级、或刚过线而告警循环尚未开单时为 null |
响应另附各节点 flapfeed 喂送健康度。
?group=prefix 改返回前缀优先的层级形态:服务端按前缀折叠,顶层聚合 + 内嵌 peers[] + origins[] / moas 多源判定,limit 作用于前缀数;每行内嵌 localization 根因定位。这是可选形状,不影响默认扁平消费。
GET /fleet/prefix-flaps/localize 是根因定位的独立端点,返回 { localizations: [...] },每行含 zone ∈ near(入向对端)/ origin(源头 AS)/ path(中段邻接)/ moas(多源冲突),加 confidence(按其降序)与归因 AS / 邻接边。与 ?group=prefix 内嵌的 localization 同源同算法。
flap 时序与速率
GET /fleet/flap-stats 响应:{ range, window_s, bucket_s, generated_at, series[], gauge }。
series[] 是 [窗口起点, 最近一个完整桶] 的定长网格(只出完整桶,无部分桶),每点 { t, changes, listed_changes, rate_per_s, tracked_prefixes, flapping_prefixes, flapping_sessions, open_alerts }。前三者来自速率存档(rate_per_s = changes / bucket_s),后四者来自活跃度落点(粗桶取桶内峰值)。空桶各字段为 null——「无上报」不等于「0」,前端应画断线而非归零。
gauge = { rate_per_s, current_rate_per_s, percentile: 90, window_s: 900 },其中 rate_per_s 是 90 分位截尾均值,current_rate_per_s 是最近一个完整 60s 桶。gauge 不随 range 变化。
range 与桶宽 / 点数:
range | 桶宽 | 点数 |
|---|---|---|
1h(默认) | 60s | 60 |
6h | 5min | 72 |
24h | 10min | 144 |
7d | 1h | 168 |
30d | 4h | 180 |
数据分辨率下限 60s(受 agent 上报节奏限制)。「路由表规模随时间」不在此处,用 /ui/routing/fleet-overview 的 trend。
三层检测(会话级 P0、前缀级 P1、告警 P2)的数据源与算法见 flap 检测。
路由
源码:apps/control-server/app/api/v1/ui/routing.py。细粒度路由检索仍在 /admin 下(见 管理面),供对接其他系统使用。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /nodes/{node_id}/internal-topology | iBGP + OSPF 内部互联视图(拓扑配置 + 路由层 liveness);无已发布 DesiredState 404 |
GET | /routing/fleet-overview | 概览「路由全表」板块一次取全:summary + 每节点 + 规模 / churn 趋势(origins_sort ∈ count / v4 / v6,origins_top 1..200 默认 100) |
GET | /nodes/{node_id}/routing/dashboard | 路由页头部一次取全:summary + origins + timeline;支持 range / compare(compare 未带 range 时按 24h);origins[].name 由 registry 副本富化;无路由上报 404 |
节点目录、事件与机群指标
源码:apps/control-server/app/api/v1/ui/nodes.py
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /nodes | 节点列表一次取全:Node 标量字段(不含 base_template / inventory)+ 服务端 join 的 health / agent 活体 / geo + 行富化标量 |
GET | /nodes/metrics | 全 fleet 指标矩阵:共享桶网格上的 cpu / rss / 流量 / 对账失败序列(range ∈ 1h / 6h / 24h / 7d 默认 6h,5s TTL 缓存) |
GET | /nodes/{node_id}/status-events | 瘦身事件列表(新→旧,不带 payload):kind 同管理面端点,limit 1..500 默认 100,before_id 传上一页最后一条 id 向旧翻页 |
GET | /status-events/{event_id} | 单条完整事件(含 payload),展开详情时按需取;未知 404 |
/nodes 行富化标量:drift_count(最新 report 的漂移项数,未上报为 0)、peers_up / peers_total(最新 runtime snapshot 的 BGP 会话 Established 数与总数,无快照为 null)、rx_bytes_per_sec / tx_bytes_per_sec(当前吞吐,与 traffic-breakdown 的 nodes 同源同口径,无数据为 null)。
/nodes/metrics 响应:顶层 timestamps[] 是全体节点共享的桶起点网格(升序),每节点的 cpu_percent[] / rss_mb[] / rx_bytes_per_sec[] / tx_bytes_per_sec[] / reconcile_failures[] 与其按索引对齐、等长。空桶为 null(缺数据是缺口,不是 0),reconcile_failures 的 0 表示「有上报且无失败」。桶宽按 range 取 5min 存档的整数倍:1h→5min(12 点)、6h→10min(36)、24h→30min(48)、7d→3h(56)。cpu / rss / 失败数来自自观测 5min 存档(node_metrics_rollup,保留 60 天;失败数为累计计数的桶内差分),rx / tx 来自流量 5min 存档。归档不足窗口时前导桶自然为 null。全量矩阵返回,不做服务端 top-N——排名与站点过滤在前端。
调优与向导
源码:apps/control-server/app/api/v1/ui/tuning.py。写端点遵守与管理面写路径相同的事务纪律:业务变更与 materialize 同事务,提交后广播。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /nodes/{node_id}/route-tuning | 路由调优视图:节点级 cold_potato_med / route_local_pref + 全部会话的 link_latency |
PUT | /nodes/{node_id}/route-tuning | 字段级合并写入 base_template.bird 的两个调优 key(其余部分原样保留) |
PUT | /bgp-sessions/{session_id}/link-latency | 会话 spec 单字段合并:只动 link_latency(1..9,null 清除) |
GET | /nodes/{node_id}/peer-defaults | peer 向导默认值:节点 WG 私钥引用、link_local 建议值、已占用监听端口 |
PUT .../route-tuning 的两个字段均可选(缺省不动):cold_potato_med、route_local_pref[](逐条经 RouteLocalPrefSpec 校验,失败 422)。
peer-defaults 响应 { node_id, wireguard: { private_key_ref, link_local, used_listen_ports[] } },推不出的字段为 null。其中 link_local 读节点档案列 Node.link_local——外部 eBGP LLA 的单一真相源。
Registry 代理
源码:apps/control-server/app/api/v1/ui/registry.py。控制面不落 registry 库,这些端点是到独立副本服务 registry-server 的薄代理。未配置服务地址或 token、服务不可达时,显式查询返回 503,榜单的名称富化回落占位符 AS<asn>。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /registry/status | 同步状态:{ enabled, commit, asn_count, synced_at } |
POST | /registry/sync?force= | 手动触发一轮同步(force=1 跳过 sha 门控强制重拉);未配置 token 409,上游失败 502(既有数据保留),sha 未变返回 synced=false(非错误) |
GET | /registry/names?asns=… | 批量 ASN → as-name。asns 为逗号分隔 ASN 列表(去重保序,单次上限 200,超限截断),返回 { "names": { "<asn>": "<as-name>" } }(未收录的 ASN 省略);缺 asns 参数或含非法 token 422 |
GET | /registry/asn/{asn} | 单 ASN 摘要 { asn, as_name, mnt_by, updated_at };未知 404 |
GET | /registry/asn/{asn}/verification-options | 该 ASN 可用的归属验证方式(email / pgp / ssh,带来源 mntner);未知 404 |
GET | /registry/mntner/{name} | 单维护者记录;未知 404 |
副本服务本身的接口见 registry-server API,运维见 registry 副本。
仪表盘与会话
源码:apps/control-server/app/api/v1/ui/dashboard.py
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /dashboard | 仪表盘首屏一次取全:overview + traffic + traffic-breakdown + peering-issues + routing 五块合一,服务端短 TTL 缓存 |
GET | /session | 鉴权探测 + 服务端元信息(版本 / agent 目标版本 / 心跳周期 / liveness 阈值 / features 能力开关 / identity 当前登录者),不查库 |
/dashboard 各分块结构与对应细粒度端点逐字段一致。range / compare 同时作用于 traffic 与 routing 趋势,origins_sort / origins_top / breakdown_top 透传对应榜单参数。
?as_traffic=1 追加 AS 级流量扩展:响应恒含 generated_at 与 traffic_by_as / traffic_mix 两个键——未请求时二者为 null;请求时 traffic_by_as 为 Top5 对端 AS 的时间线序列加 Top50 份额表(名称回落 registry 副本),traffic_mix 为 internal / external 与 rx / tx 的流量构成。
/session 走到即表示凭据有效(401 / 403 语义与其余端点一致)。identity 为 {"username", "kind"}:账号会话令牌 kind=user(用户名为登录账号),静态 admin token kind=static-token(用户名固定 admin)。features 为前端灰度用的能力开关列表,当前下发:
account-login audit-cursor trend-range trend-compare as-traffic
ui-nodes-rich ui-nodes-metrics bgp-flaps prefix-flaps flap-peers
flap-alerts flap-mitigations flap-localize flap-stats另有 origins-name,仅在接入 registry 副本服务时条件下发。
性能与缓存行为
前端对接时需要知道的三点:
| # | 行为 | 前端可见影响 |
|---|---|---|
| 1 | /ui/dashboard 响应按**完整 URL(含全部 query)**缓存 3 秒 | 高频轮询可能拿到 ≤3s 前的同一份响应 |
| 2 | 流量端点的默认序列窗口固定为最近 10 小时 | 默认流量图历史深度有明确上限 |
| 3 | 服务端聚合与预计算 | 无(纯提速) |
响应缓存。判断数据实际生成时刻请读响应内的
generated_at——缓存命中时它是缓存体的生成时间,不是请求时间;需要展示「数据更新于 x 秒前」时以它为准。不同 query 组合是不同缓存键(?range=24h与默认视图互不影响)。轮询间隔 ≥3s 才有意义。缓存体不含任何用户特定字段(identity只在/ui/session)。默认流量窗口。涉及
GET /ui/fleet/traffic、GET /ui/nodes/{id}/traffic、/ui/dashboard的traffic块,在不带?range=时固定返回最近 120 桶 × 5min = 10 小时(升序,有数据的桶才出点),不随存档积累加深(流量存档本身保留 60 天)。需要更长历史一律用?range=6h|24h|7d|30d——定长网格 + 空桶null+ 可选compare=1,该路径不受默认窗口限制。/ui/fleet/traffic-breakdown与/ui/nodes只取最新点,无此约束。服务端聚合。dashboard 五个聚合块共享一次 node_status 与 desired-state 读取;fleet 状态列表查询不拖回
last_snapshot等大 JSON 列;desired-state 按 generation 键缓存(内容不可变)。peering-issues、BGP 会话状态、/ui/nodes行富化、traffic-breakdown 对端速率读的是快照摄入时预提取的小 JSON 列(node_status.bgp_summary/peer_rates,与解析完整快照同算法同形态),不再逐请求解析几百 KB 的完整快照;列未填充时自动回退完整快照解析路径。
审计
源码:apps/control-server/app/api/v1/ui/audit.py
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /audit | 审计日志一页(新→旧):limit 1..500 默认 100,before_id 游标向旧翻页,q 对 actor / method / path 做大小写不敏感子串匹配 |
行结构与 GET /admin/audit-log 一致,返回 { "entries": [...] }。
拨测
源码:apps/control-server/app/api/v1/ui/probes.py。实时链路:浏览器 SSE ← 控制面 ProbeHub fan-out ← agent 临时 WS 回传 ← debug-shell 容器执行。拨测输出不落库——作业与缓冲都在 ProbeHub 内存,完成后 TTL(120s)回收。
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /probes | 发起一次拨测,202 返回 { probe_id };节点无在线 agent 409,并发超上限(全局 256 / 单节点 3)429 |
GET | /probes/{probe_id}/stream | SSE 订阅拨测输出:先补发 ring buffer 已产生消息,再实时 fan-out,收到 done 收尾;作业不存在或已过期 404 |
POST /probes 请求 { node_id, spec },spec 为 ProbeSpec:
| 字段 | 取值 | 说明 |
|---|---|---|
tool | ping / mtr / traceroute / mtu | 拨测工具。mtu 是 DF ping 二分扫描,末行输出 PMTU_PAYLOAD <n>(0 表示最小尺寸都不通) |
target | IP 字面量 | 白名单收敛在 DN42、私网、link-local,非法 422 |
count | 1..30,默认 10 | ping 包数 / mtr report-cycles / traceroute 每跳 query 数 |
resolve | 默认 true | 是否对逐跳地址做反向 DNS。关掉只显示数字地址,免 DNS 查询更快 |
interface | ≤15 字符接口名,默认 null | link-local 目标的 scope(fe80::x%iface);非 link-local 目标忽略 |
max_size | 1280..9200,默认 null | 仅 mtu 工具:扫描上界(接口 MTU;payload 上界为它减去地址族开销) |
SSE 帧为 event: output 与 event: done,data: 是对应 JSON 消息体。
操作见 主动拨测。
Agent 日志查看
源码:apps/control-server/app/api/v1/ui/logs.py。与拨测同构的按需过境链路(复用同一 ProbeHub 单例作业机与门铃通道):日志只在查看时经控制面中转,不落库。
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /logs | 发起一次日志查看,202 返回 { log_id };节点无在线 agent 409,并发超上限(与拨测共享配额)429 |
GET | /logs/{log_id}/stream | SSE 订阅日志条目:event: log(entries 为条目批)与 event: done 收尾;作业不存在或已过期 404 |
POST /logs 请求 { node_id, spec },spec 为 LogSpec:
| 字段 | 取值 | 语义 |
|---|---|---|
mode | snapshot / tail | 一次性拉取 / 实时跟随(时长受 duration_seconds 10..600 约束) |
source | buffer / journal | agent 进程内 ring buffer(结构化,含级别与模块)/ journald 历史(覆盖 agent 重启前)。tail 仅支持 buffer |
lines | 10..2000 | 默认 200 |
min_level | DEBUG(默认) / INFO / WARNING / ERROR | 级别下限(含) |
contains | ≤200 字符 | 对消息与 logger 名的不区分大小写子串过滤;null 不过滤 |
duration_seconds | 10..600,默认 120 | 仅 tail 模式;snapshot 模式忽略 |
日志条目为 { ts, level, logger, message }。
agent 侧配套:运行时快照的 self_metrics 捎带 log_warning_count / log_error_count / last_error_at / last_error_message(ring buffer 最近 1h 口径),作为节点页进入日志查看的入口信号。