本文是《从 IP 到 SRv6 计算编排》新手教材第 10/13 编。 总目录 · 上一篇:第九编:HEFT 全链联合编排 · 下一篇:第十一编:从读懂到会验证
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任务的完整旅程
下面把此前分散的概念按真实先后顺序连接起来。
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验证和本地安装框架相同,主要差别是规划:
- 入口不运行HEFT;
- DAG operator type直接变为逻辑Anycast SID;
- task SRH只包含稳定的逻辑服务顺序和最终交付;
- 每到一个逻辑SID,当前节点普通FIB选择provider;
- 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搬走。
概念过程为:
- 移动端根据局部链路条件选择新接入;
- 新接入节点上报endpoint positive fact(正向事实);
- 旧归属随lifetime过期或由协议detach;
- 控制面分发新的endpoint ownership;
- 各网络节点本地更新
fd42:4:<access>::/64等交付路由; - Exact任务若目的交付路径相关则重算/替换;
- 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 | 图片、较大日志包、报告、正式证据 | 对象存储适合二进制和大文件 |
同一张图片不应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”时,不应直接改超时。按边界逐步提问:
- 源端是否产生该
task/object/epoch/tile? - 入口是否对task发READY且安装Color策略成功?
- classifier是否识别task并打了正确mark?
- SR Policy是否包含预期segment?
- 普通FIB和邻居是否把每一active segment送到正确下一跳?
- AF_XDP是否因connectivity、queue或delay规则丢弃?
- 算子是否收到、KEEP、DROP、ERROR或等待窗口?
- Anycast provider在对象期间是否漂移?
- 最终End.DX6/DT6是否交付?
- 目的端收到但观察器是否漏采?
找到第一个“输入存在而预期输出不存在”的边界,再修改那个组件。这样修复的是机制,而不是 现象。
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. 第十编自测
- 为什么系统启动后没有业务也必须存在?
- READY精确证明到哪一个边界?
- HEFT结果由谁安装进内核?
- 10%损毁实验中的A和B分别是什么?
EAGAIN为什么不等于永久网络故障?- 一个观察器失败后,后端为什么必须继续?
- Run ID为什么不能用查询时间范围代替?
- 诊断少Tile时,第一个应核查的事实是什么?