从 IP 到 SRv6 计算编排 · 第十编

第十编|完整业务、故障与证据

2026-08-20 · COMPIN Lab · 阅读约 9 分钟

系统仿真可观测性故障实验设计

77. 系统启动不等于业务启动

start 首先建立一个持续存在的网络系统。即使没有业务,它也应保有:

  • 节点容器和 PID 1;
  • network namespace 与 veth;
  • XDP 程序、AF_XDP 端口和物理 fabric;
  • IPv6/SRv6 配置;
  • HELLO 邻接与控制会话;
  • 资源和算子实例上报;
  • 本地 FIB 与逻辑 Anycast 路由;
  • 日志与观察进程。

业务发生器是端侧或网络节点内部的自治进程。控制器、前端、测试脚本都不通过外部docker exec 在某个“正确时刻”注入业务。

因此生命周期是两层:

系统生命周期:start ─────────────────────────── stop
业务生命周期:      request → ready → packets → report

一条业务结束不会使网络退出;某个节点失败也不应让其他无关路径退出。

78. 一条 Exact 任务的完整旅程

下面把此前分散的概念按真实先后顺序连接起来。

图 1 一条 Exact 任务的十五步旅程

图 1 一条 Exact 任务的十五步旅程

78.1 第一步:端侧自然产生 REQUEST

源端业务发生器根据业务规则产生 DAG 和目的端,生成非零 task_id,通过 ETS3 向当前接入网络节点发送 REQUEST。REQUEST 携带需求,不携带 HEFT 答案或 SRH。

78.2 第二步:入口验证契约

入口检查:

  • ETS3 magic、版本、kind 和长度;
  • task_id 是否有效;
  • placement 是否为 Exact;
  • DAG profile 是否支持;
  • source/destination 与当前接入事实是否一致;
  • 相同 task 的重复请求是否与原请求相容。

无效请求被明确 REJECTED,不能进入半安装状态。

78.3 第三步:冻结一份事实快照

入口从自己已经接收并应用的拓扑、资源、算子和 endpoint 事实构造不可变 planning snapshot。控制器当前“知道”的新事实若尚未被该节点应用,不能偷偷越过协议直接参与规划。

78.4 第四步:运行 Primary HEFT

入口过滤候选,计算成本、rank、EST/EFT,得到具体算子实例与预计总时间。无解则记录原因并REJECTED;不会安装一半链。

78.5 第五步:运行 Backup HEFT

若任务要求备份,入口排除 primary 首边和具体算子 SID 后重算。backup 无解按契约处理并留下证据。

78.6 第六步:构造 segment list

策略构造器把物理 uSID、具体算子 SID 和最终 endpoint delivery SID 按执行顺序组成 SR Policy。代码必须区分“概念执行顺序”和 SRH 在 wire 上的反向存储顺序。

78.7 第七步:分配 Color

入口在本节点选择未使用的非零 16-bit Color,建立:

task_id → Color
Color → routing table 10000+Color

这个映射只属于入口。

78.8 第八步:本地安装

单一 forwarding worker 按序安装 nexthop(如需要)、route、rule 和 eBPF task-color map。操作具有幂等性:重复应用同一个期望状态得到同样结果,而不是无限追加重复规则。

78.9 第九步:发 READY

只有内核实际安装成功,入口才回 ETS3 READY。READY 表示该入口能按当前策略接收任务包,不是“全网所有节点同时进入业务阶段”。

78.10 第十步:业务包被分类

入口 eBPF classifier 从 SPP/业务头提取 task_id,查 map 得到 Color,把 skb->mark 设为 Color。Linux ip rule 据此选择 task 专用 routing table。

78.11 第十一步:SRv6 封装

task table 的 /128 路由执行 encap seg6,在原 IPv6 包外加入外层 IPv6 和 SRH。外层 DA 设为当前active segment。

78.12 第十二步:逐跳物理传输

节点 Linux 查普通 IPv6 FIB,邻居表给出下一跳 MAC,veth XDP 按 MACredirect 给宿主 AF_XDP。fabric 执行当前有向链路的 connected、rate、queue、burst 和 delay 规则,再送入目标节点 veth。

78.13 第十三步:执行算子

active segment 到达具体算子 SID 时,End.X 把包送入算子 namespace。worker 解析 SPP、计算、更新 payload 后送回。SRH 继续推进到下一 segment。

78.14 第十四步:交付目的端

最终 delivery SID 执行 End.DX6 或 End.DT6,把内层 IPv6 包交付目的 endpoint service address。目的端按 task/object/tile 记录接收,不依据前端是否在线决定成功。

78.15 第十五步:观察与报告

节点、算子、控制器和 fabric 分别记录本边界事件。异步归集器把同一 Run ID 的事件送到ClickHouse,图片和报告送到 MinIO。前端稍后只读投影这些事实。

79. 一条 Resilient 任务的不同旅程

REQUEST 验证和本地安装框架相同,主要差别是规划:

  1. 入口不运行 HEFT;
  2. DAG operator type 直接变为逻辑 Anycast SID;
  3. task SRH 只包含稳定的逻辑服务顺序和最终交付;
  4. 每到一个逻辑 SID,当前节点普通 FIB 选择 provider;
  5. provider 变化时更新 FIB,不替换 task SRH。

这带来局部快速适应,也带来有状态 SPP 窗口可能漂移的损失。两者都是规则自然产生的效果,不能只保留优势而删除代价。

80. 10% 节点损毁实验应该回答什么

实验不是要求“某 10% 固定节点一定被业务绕开”。正确问题是:

系统100%节点正常时,业务成功率为A
按场景规则损毁固定的10%节点后,业务成功率为B
观察B以及A→B的下降

例如 B 是否大于目标阈值 80%,应由真实任务分母和成功 Tile 统计得出。不能为了让 B 好看而:

  • 预先选择不在业务路径上的损毁节点;
  • 在故障前人为完成所有业务;
  • 延迟故障直到某个测试信号;
  • 把无解任务从分母删除;
  • 把部分成功改成全成功。

损毁 generation 由场景自然发布,相关节点和路径失效;不相关节点继续存在。Exact 任务可能重算、Resilient 路由可能漂移、部分任务可能无解、SPP 可能丢 Tile,这些共同形成 B。

81. Handover 怎样发生

Handover(切换)是飞机或船从一个接入网络节点移动到另一个接入节点。它不是容器重启, 也不是控制器把 endpoint 搬走。

概念过程为:

  1. 移动端根据局部链路条件选择新接入;
  2. 新接入节点上报 endpoint positive fact(正向事实);
  3. 旧归属随 lifetime 过期或由协议 detach;
  4. 控制面分发新的 endpoint ownership;
  5. 各网络节点本地更新 fd42:4:<access>::/64 等交付路由;
  6. Exact 任务若目的交付路径相关则重算/替换;
  7. Resilient 任务保持逻辑服务链,最终交付随 endpoint 路由更新。

临时只收到部分 endpoint 事实时,节点保留仍然有效的其他归属,不把一个局部更新误当成“所有未出现 endpoint 都离线”。

82. 控制面 Backpressure 与 EAGAIN

Backpressure(背压)表示下游消费速度低于上游生产速度时,系统用队列、拒绝或减速把 压力反馈回来。

非阻塞 socket 暂时无法发送时,Linux 可能返回 EAGAIN,意为“现在再试会阻塞,请稍后再试”。它不等于连接永久损坏。

对于拓扑和 endpoint snapshot,旧代通常已被新代完全取代。正确队列语义是 latest-only:

待发 generation 100
新的 generation 101 到达
→ 用101替换尚未发送的100

不能在 EAGAIN 后永久重发每个历史快照,否则节点永远追赶过去,控制面延迟不断增长。资源事实则依据 instance、lifetime 和 sequence 合并,不能生硬套用同一种丢弃规则。

83. 观察面为什么不能反向控制系统

观察器可能因为 ClickHouse 暂不可达、MinIO 上传失败或前端 token 失效而失败。此时应:

  • 记录 observer failure;
  • 保留本地 JSONL 和 Run 目录;
  • 继续其余领域分析;
  • 不停止节点、业务或控制器;
  • 不把“前端没显示”改写成“业务没发生”。

shell 分析脚本不能让一个可选观察器的非零退出码在 set -e 下短路所有后续分析。每个领域分析应分别执行、分别记录 status,最后由总报告汇总。

84. Run ID 如何连接所有证据

Run ID(运行标识)是一轮仿真的全局非空身份。以下对象必须使用同一个 Run ID:

  • 场景与拓扑 generation;
  • 节点、控制器、AF_XDP 和算子事件;
  • task、object、Tile 与业务报告;
  • 原图、中间图和目的端图片;
  • 分析结果与 MinIO 证据索引;
  • 前端查询参数和页面状态。

Run ID 不能用时间范围猜测替代。两轮运行时间可能相邻或重叠;只有明确身份才能防止混读。

85. ClickHouse、MinIO、Gitea 各保存什么

系统保存内容原因
Gitea源码、配置、Markdown、Issue、提交小型可版本化事实
ClickHouse结构化离散事件和可查询指标适合按 Run/time/task 聚合
MinIO图片、较大日志包、报告、正式证据对象存储适合二进制和大文件

图 2 Run ID 串起三类证据存储

图 2 Run ID 串起三类证据存储

同一张图片不应 base64 塞进 Git 或 ClickHouse;一条 task 状态事件也不应只藏在无法查询的图片文件名里。

86. 前端怎样做到“全部是真实数据”

前端是只读投影,不生成演示节点。每个显示节点都必须对应 .2 本轮实际 Docker 节点,并由同一 Run 的事实驱动:

  • 200 颗卫星、50 架飞机、50 艘船来自场景和节点事件;
  • 动态位置与有向链路来自真实 generation;
  • SRv6 路径来自已安装/已观察策略和逐跳事件;
  • HEFT 重算来自 decision 与 install 事件;
  • Anycast 漂移来自 provider/FIB 变化;
  • BNN KEEP/DROP 来自算子事件;
  • Alarm 闭环来自同一 alarm 身份的时间戳链;
  • RGB/Gray/Edge 来自 SPP format 变化;
  • 图片从 MinIO 读取,事件与报告从数据接口读取。

前端 GET 使用独立只读身份,例如受限文件挂载的 GITEA_DATA_READ_TOKEN。写入身份SIM_CI_TOKEN 只供仓库绑定数据写入,不能发到浏览器。任何 token 都不能进入源码、镜像、URL、日志或命令输出。

87. 常用可靠性分母

“可靠性”没有分母就没有意义。报告必须说明是哪一种:

87.1 Tile 可靠性

成功到达目的端的预期Tile数 / 源端计划发送的预期Tile总数

87.2 Task 至少部分成功率

至少有一个有效输出的task数 / 被接受并进入观察窗的task数

87.3 Task 完整率

所有预期Tile都完成的task数 / 被接受并进入观察窗的task数

87.4 可行任务完成率

如果只统计规划时有完整候选链的 task,必须把“可行”条件和无解数量另外报告,不能用它替代总业务可靠性。

网络 ENETUNREACH 表示 Network Unreachable(网络不可达)。允许业务存在自然失败率时,分析器可以要求异常率受控,却不能同时要求业务路径上的 ENETUNREACH 绝对为零;宿主基础设施和业务结果应分开计数。

88. 用最小组件边界诊断问题

看到“目的端少了一个 Tile”时,不应直接改超时。按边界逐步提问:

  1. 源端是否产生该 task/object/epoch/tile?
  2. 入口是否对 task 发 READY 且安装 Color 策略成功?
  3. classifier 是否识别 task 并打了正确 mark?
  4. SR Policy 是否包含预期 segment?
  5. 普通 FIB 和邻居是否把每一 active segment 送到正确下一跳?
  6. AF_XDP 是否因 connectivity、queue 或 delay 规则丢弃?
  7. 算子是否收到、KEEP、DROP、ERROR 或等待窗口?
  8. Anycast provider 在对象期间是否漂移?
  9. 最终 End.DX6/DT6 是否交付?
  10. 目的端收到但观察器是否漏采?

找到第一个“输入存在而预期输出不存在”的边界,再修改那个组件。这样修复的是机制,而不是现象。

89. 一条包的字段变化总览

以 Exact Gray 任务为例:

源端产生
  inner IPv6 dst = fd42:10:<endpoint>::2
  SPP task_id    = 70001
  SPP format     = RGB24

入口分类
  skb mark       = Color 4466
  routing table  = 14466

SRv6封装
  outer IPv6 DA  = first active segment
  SRH segments   = physical path + concrete Gray + delivery

Gray算子后
  SPP task_id    = 70001(不变)
  object/tile    = 不变
  SPP format     = GRAY8
  payload_length = 256

最终交付
  outer IPv6/SRH被endpoint behavior消费
  inner IPv6 dst = fd42:10:<endpoint>::2
  目的端按task/object/tile记录

task_id 是端到端业务身份;Color 只在入口内核用于选择策略;active segment 随 SRH 推进;SPP format 由算子合法降级。这四种变化属于不同层。

90. 第十编自测

  1. 为什么系统启动后没有业务也必须存在?
  2. READY 精确证明到哪一个边界?
  3. HEFT 结果由谁安装进内核?
  4. 10% 损毁实验中的 A 和 B 分别是什么?
  5. EAGAIN 为什么不等于永久网络故障?
  6. 一个观察器失败后,后端为什么必须继续?
  7. Run ID 为什么不能用查询时间范围代替?
  8. 诊断少 Tile 时,第一个应核查的事实是什么?

< 返回技术专栏