网络是有记忆的
重建网络在 14:02 时所相信的东西,以及「发生了什么变化」和「你学到了什么」之间的区别
Michel Wijnberg
到目前为止,这个系列里的每一样工具回答的都是关于此刻的问题。而对工程师最需要帮助的 那两个时刻来说,那个时态是错的。
第一个时刻是故障进行中:网络不正常,而您需要的不是它当前的状态,而是它二十分钟前的状态, 在有人开始动手修之前。第二个时刻是故障之后:网络又好了,人人都有一套说法,而没有人拿得出 它坏掉时的那份拓扑。
Osprey 的答案是一个时钟。
那不是在回放一段录像。那张画布上的每一个元素,都是为某个时间戳重建出来的:设备、链路、它
们的开销和状态、区域归属。下面的拖动条写着 Jul 29, 14:02 和 (30/36):所选 24 小时窗口
里三十六个不同拓扑状态中的第三十个。
那条轨道上的每一个点,都是网络真正不一样的一个时刻。
每一个监控工具都保存历史,所以这句话需要一个更锋利的边。它们保存的是测量值:CPU 曾经是 83%,接口跑了 4 Gbit,那次轮询成功了。这些是对着网络取的读数,在 14:02 它们告诉您某台设备很 忙。它们说不出它当时相信什么。Osprey 保存的是状态:哪些邻接存在,哪些路由被安装、度量是 多少,一台路由器属于哪些区域,哪些 BGP 路径被选中、为什么被选中。差别在您问出一个真问题的那 一刻就显出来了。一份测量历史能告诉您某条链路在 14:02 被打满了。一份状态历史能告诉您它被打满, 是因为四分钟前一台 ABR 撤回了一条 summary,半个区域忽然改走远路。前者是一个带时间戳的症状。 后者是网络自己的推理,被保存了下来。
只有发生了事情才存在的快照
天真的实现是按定时器写快照。每五分钟把拓扑导出一次。它很简单,产出的却是一个大部分被完全 相同的副本塞满的数据库,外加一个您看不穿的分辨率下限。
Osprey 只在拓扑发生变化时写快照,其他时候一概不写。
来自采集器的每一个拓扑事件都会先被哈希:
// computeTopologyHash computes an FNV-64a hash over the topology snapshot data.
// Uses addition accumulation of per-element hashes for order-independence.
设备贡献自己的 router ID 和 ABR/ASBR 位,链路贡献一个方向归一化的路由器对加上两侧开销及其 状态,stub 网络和接口各自贡献自己的部分。累加是加法式的,好让哈希不依赖元素到达的顺序:两 个采集器以不同次序上报同一个区域时,必须产出相同的哈希,否则每一次轮询看起来都会像一次变 更。
这个哈希会和该区域上一次的哈希在内存里比较。相同:什么都不写。不同:插入一行快照。
结果是一条在构造上就信息密集的时间线。过去 30 天里,我的实验室产生了51 个区域上的 478 份快照:整个环境每天约 16 份,每一份都是一次真实的差异。拖动条上的那些点不是采样点, 它们是事件。
这也意味着一个稳定的区域可能好几个小时都没有一行记录,而那是正确的,不是缺口:什么都没发 生,所以没有什么可记的。在您所选时间戳当刻或之前最近的那份快照,就是那个时间戳上的状 态。
Time travel 不是一个功能
时钟是看得见的那一部分。真正让它有用的,是大约二十个 API 端点都接受同一个 at= 参数,并按
那一刻的状态作答:拓扑图、LSDB 浏览器、路径计算、SPF 树、每台路由器的 RIB、跨区域路由和外
部路由、ASBR 条目、接口、stub 网络、BGP 对等体、BGP 最优路径、每个对等体收到的路径。
BGP 得到的待遇比快照更强,因为 BGP 变化得比拓扑频繁得多。它是以双时态变更日志的形式存储
的:每一条最优路径、每一条对等会话,以及(可选地)每一条收到的 RIB 条目,都是一个带
valid_from 和 valid_to 的区间,valid_to 为空表示”仍然成立”。于是,问”T 时刻是什么样”
就变成了一个谓词,而不是一次重建:valid_from <= T AND (valid_to IS NULL OR valid_to > T)。
这里面有两个细节花了最长时间才做对,而且两个都是关于不去记录某些东西:
- 区间的抖动被一个属性哈希抑制住了,而这个哈希刻意排除了 IGP 度量值和解析出来的下一跳设 备。这两样每次 IGP 重新收敛都会变,如果它们会开启一个新区间,那么每当某条毫不相干的链路 开销变动一次,网络里的每一个 BGP 前缀看起来都会震荡一次。
- 一个对等体掉线被记录为一次监控中断,而不是一次大规模撤销。当一条 BMP 会话掉线时,路由 器并没有撤销十万条前缀。只是没人再把它们告诉我了。把那写成撤销,等于每次监控会话打个嗝, 就制造出这张网络史上最大的一次路由事件。
这两处都是”简单实现会凭空造出一个事件”的情形。一段会捏造事件的历史,比没有历史更糟,因为您 真的会去调查它们。
在过去做仿真
这两种模式是可以叠加的,而这是我在这个产品里最喜欢的东西。
您可以先进入 time travel,把时钟定好位,然后再进入仿真。画布上会带一个
SIMULATION @ <timestamp> 水印,而这次假设分析跑的是那一刻实际存在的那份拓扑。
也就是说,您能问的问题是:
以 14:02 时网络实际的样子来看,这次故障是能扛过去的吗?
不是拿今天的拓扑来问,那份拓扑此后已经被修过、重新赋过度量值、扩过。而是拿当时真正在那儿的 那一份。对故障复盘工作来说,这就是”一个站得住脚的答案”和”一个听起来说得通的答案”之间的差别。
它也遵守自己的边界。在过去某个时刻做 BGP 对等体故障分析,要求那个作用域当时记录了完整的 RIB 历史;在没有记录的地方,结果不是一次悄无声息的近似,而是一次明确声明的跳过:
BGP effects (peer failure, hot-potato exit shifts, BGP traffic) are not evaluated at this time: no full-RIB history (history_mode=‘full’) is recorded for the scope. Enable full history mode on a BMP target to time-travel BGP.
那条消息点名了缺失的输入,以及能提供它的那个设置项。这是一条您可以据以行动的消息。
明说出来:哪些东西不在过去里
有几样东西确实没有历史,而触及它们的端点会照实说出来,而不是悄悄把实时数据打扮成历史数据端 上来。
向 LSDB 浏览器请求一个过去的时刻,响应里会带着:
Topology and route LSAs reflect the selected time; LSA header metadata (age/seq/ checksum) is not historized and shows live values.
请求一个过去时刻的跨域路径时,只有在答案确实依赖了实时证据的地方,解释才会多出额外的步 骤:
Time travel: L2 detail is live: L2/port annotations on this path reflect current LLDP/CDP wiring, not the selected time. L2 adjacency is not historized.
Time travel: entry resolved via current router-id: identity attributes (router-id, local address) are not historized and were borrowed from the live session record.
这种条件性很重要。一次完全靠历史证明出来的拼接,两条注记一条都不会带,所以当注记真的出现时, 它们是有意义的。在每一个历史视图上都挂一句”部分数据可能是实时的”的通用免责声明,技术上成立、 会被永久无视,而且毫无用处。
还有一个限制我要直说,因为否则您会在困惑中自己发现它:**time travel 期间,链路利用率着色是关 掉的。**流量热力图会变成空白,而不是把实时负载画在一份历史拓扑上。那是安全的行为,也是错误的 行为。空白读起来像”没有流量”,而它本该读作”此时无法提供”。能修好它的那个读取器已经排上日程, 还没做。
“发生了什么变化”和”你学到了什么”之间的区别
Time travel 有一份配套报告:比较两个时刻,列出差异。
AS 200 在过去 24 小时里:没有设备被添加、移除或修改。没有链路被添加或移除。而新增了 56 个
stub 网络,每一个都是 IPv6 的 /128 环回地址。
那看起来像是一次网络变更。它不是。
我查了这 56 个前缀是什么时候第一次进入数据库的,结果每一个都是今天早上 10:00:49 到
10:01:14 之间到的:一个25 秒的窗口。没有哪张网络会在 25 秒里跨 64 台路由器重新配置 56
个环回地址。真正发生的是一次发现过程:那些前缀一直都在 OSPFv3 数据库里,而这只是 Osprey 开始
记录它们的那一刻。
这个区分值得有个名字,因为把两者混为一谈,会让人去追一个从未发生过的变更:
一份拓扑差异告诉您的是模型学到了什么。那和网络做了什么不是一回事。
有时候两者重合:一条链路断了,模型就记下一条链路断了。有时候不重合,而破绽通常在时间戳的形状 里:真实的网络变更带着协议收敛的节奏到达,而模型的变更带着轮询周期的节奏到达。25 秒里五十六个 一模一样的前缀,就是一个穿着”变更”外衣的轮询周期。
一个没法告诉您正在看的是这两者中哪一个的可观测性产品,迟早会让某个人赔上一个下午。Osprey 不会 自动给它打标签。差异报告展示的是模型记录了什么,而上面那段推理是我的,不是工具的。但每一行都 带着您自己下判断所需的时间戳,而那是我认为一件诚实的工具欠您的最低限度。
这个系列在哪里结束
三个系列,其实是一个论点,从三个方向抵达。
第 1 系列讲的是模型是否为真:把网络按它实际的样子建模、在不成为它一部分的前提下观测 它、逐跳复现转发,以及拒绝猜测。
第 2 系列讲的是证据是否可排序:同时持有多个真相、给互相矛盾的来源分级、在客户之前发现 自己的错误。
第 3 系列讲的是这些换来了什么:一个正确到足以用来推理的模型。弄坏点什么,看看会发生什 么。问一问什么依赖什么。估计你测不到的东西,并且说清楚你是在估计。问一问网络在坏掉前一小时相 信的是什么。
这些在一个你不信任的模型上全都行不通。第 1 系列里每一次诚实的拒绝、第 2 系列里每一块被分了级的 证据,之所以存在,是为了让第 3 系列里的答案有意义:一个建立在 9% 是虚构的拓扑之上的假设分析, 不是规划工具,是一个配了漂亮画布的随机数生成器。
第八篇文章把这个论点表述得最短:一个拓扑引擎,是网络状 态的编译器:杂乱、互相矛盾的输入,确定性的模型输出,而路由器自己就是判定基准。编译器是按符合性 而不是按”看起来说得通”来评判的,而这十二篇文章里的每一次拒绝,都是在答案拿不到的那些情形下,符 合性所要付的代价。
把模型做对。说清楚你是怎么知道的。然后,也只有到那时,再开始向它提问。
完整系列:信任:三种 IGP,一张图 · 零足迹 · 逐跳的真相 · Osprey 拒绝猜测的东西。 把真相构建出来:同一台路由器,三个不同的真相 · 当网络自己跟自己不一致 · 我的拓扑骗了我的那一天 · 一名网络工程师的单元测试。 从看见到推理:如果我把这个弄坏会怎样? · 影响半径 · 估计你测不到的东西 · 以及本篇。