IS-IS 运维实战

IS-IS Watcher 实战手册

每次只做一处受控变更,预测其结果,并从 Watcher 事件到 Topolograph 逐层核对同一事实。

开始实战

相关链接: IS-IS Watcher - 仓库与部署指南 · Topolograph

1. 本手册的使用方式

每个练习:做一处变更,预测 IS-IS 结果,观察 Watcher 事件,然后在 Topolograph 监控页面、SDK 中,以及在关联练习中在 agent 的回答里找到同一事实。下一个独立练习前先还原实验环境。

isis01 是单区域(49.0001,AS 65100)内的六路由器 FRR 域。router2-router3 运行 Level-1-2;router3 到 router6,以及 router3 通往 router4/router5 LAN 的一段,均为仅 Level-2。Watcher 通过 router1 上的一条 Level-2 邻接接入,并借助 IS-IS 动态主机名 TLV 将每个 System ID 解析为主机名,因此事件中显示 router2、router3、router6 而非 0100.1001.000x。

  1. Watcher CSV 行是确定性的源事件。
  2. 监控页面与 SDK 证明事件已被接收且可查询。
  3. agent 仅用于把多个事件关联成一次事件,绝不替代源行。

2. 六路由器实验环境的启动与验证

运行 IS-IS Watcher 仓库中基于 GRE 的公开 isis01 拓扑。prepare.sh 还会创建 isis-br-dr 网桥并加载 IS-IS TE 所需的 MPLS 内核模块。

先用 sudo clab inspect --all 检查是否已有实验环境在运行。若列出残留的 isis01,请在 containerlab/isis01/ 目录下用 sudo clab destroy --topo isis01.clab.yml --cleanup 拆除后再重新部署——指明拓扑文件,以免影响其他实验环境。

命令

cd containerlab/isis01
sudo clab inspect --all
sudo clab destroy --topo isis01.clab.yml --cleanup   # only if a stale isis01 is listed
sudo ./prepare.sh
sudo clab deploy --topo isis01.clab.yml
sudo docker logs clab-isis01-isis-watcher
sudo tail -f watcher/logs/watcher1.isis.log
需确认项 继续前需确认的事实
  • 六个路由器容器加上 watcher 均已运行:docker ps --filter name=clab-isis01 列出 clab-isis01-router1..6 和 clab-isis01-isis-watcher,State 全部为 Up。
  • watcher 已获得 LSDB 并在抓包:docker logs clab-isis01-isis-watcher 显示 ISIS LSDB has been received 与 Sniffing packets on interface: eth1。
  • 邻接为 Up:docker exec clab-isis01-router1 vtysh -c 'show isis neighbor' 列出处于 Up 状态的 router3;此时第一个 Topolograph 图包含六个路由器。

回滚: 完成本手册后,在 containerlab/isis01/ 目录下用 sudo clab destroy --topo isis01.clab.yml --cleanup 删除实验环境。

3. 网络事件记录的格式

本手册使用三类事件:host、metric 和 network。将 event_object 读作发生变化的对象,event_status 读作状态转变,event_detected_by 读作发布或探测到该事件的路由器。graph_time 是 watcher 为本次运行自定的标签,用于选定 Topolograph 图。

IS-IS 行带有一个 OSPF 行没有的 level 字段(1 或 2)——它是第三个字段,紧跟在 watcher_name 之后。把上面的 metric 行读成一句话:在 2026-09-07T06:55:14Z,watcher lab-isis01 看到 router3 重新发布其指向 router2 的 Level-1 链路,度量从 10 变为 -1(邻接丢失),接口地址为 192.168.23.2,位于区域 49.0001 / AS 65100。router2 背后的身份是其 NET / System ID 49.0001.0100.1001.0002.00;因为每台路由器都发布动态主机名 TLV,watcher 才打印主机名。

单独的 host 或 network up/down 行不带度量字段,因此 Fluent Bit 只转发配对的 changed 行。Topolograph 从 new_cost 为 -1 的 metric 行判断邻接中断,从 old_cost 为 -1 的行判断邻接恢复。

  1. 字段顺序:watcher_time、watcher_name、level、event_name、event_object、event_status、[度量字段]、event_detected_by、graph_time、area_num、asn、[local_ip, remote_ip | subnet_type, int_ext_subtype]、sesid、srcid。
  2. area_num 49.0001 与 asn 65100 标识路由域;sesid 是 watcher 会话,srcid 是源路由器的 System ID(router1,0100.1001.0001)。

Watcher 日志 (CSV)

host:    2026-09-07T06:55:14.754Z,lab-isis01,1,host,router2,down,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
metric:  2026-09-07T06:55:14.756Z,lab-isis01,1,metric,router2,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
network: 2026-09-07T06:55:14.761Z,lab-isis01,1,network,192.168.23.0/24,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001

4. 事件从何而来

一次 IS-IS 变更在每个 level 上产生一行 Watcher CSV。Fluent Bit 将其转发给 Topolograph,后者存储并通过监控页面、事件 API 和 SDK 对外提供。下面每个练习都会在你可用的每个层面上核对同一事实。

  1. isis01 Watcher 日志 -> Fluent Bit CSV 解析器 -> Topolograph 接收 -> 监控页面 + 事件 API -> Topolograph SDK
  2. 监控页面:OSPF/IS-IS Real-Time Monitoring。在 Choose the graph 中按时间戳选择图,按 UTC 设置 From/To 时间窗,打开 L1 与 L2 开关,点击 Find logs;New/Old Subnets、Up/Down Links 和 Changed metric 开关用于过滤所列内容。
  3. 图选择器列出 watcher 上报的每个拓扑快照——那就是 watcher 发送的拓扑。
图 07Sep2026_06h49m43s_6_hosts 的 Topolograph OSPF/IS-IS Real-Time Monitoring 控件:图选择器、From/To 时间窗、均已打开的 L1 与 L2 级别开关、New/Old Subnets、Up/Down Links 和 Changed metric 开关,以及 Find logs。Watchers Status 面板显示 "No watchers registered yet",因为 containerlab watcher 只发送拓扑而不发送心跳。

SDK 请求

from topolograph import Topolograph

topo = Topolograph(url="http://<your-topolograph>:8080",
                   username="<email>", password="<password>")
graph = topo.graphs.get(latest=True)
print(graph.graph_time, graph.protocol, graph.hosts)

SDK 输出

07Sep2026_06h49m43s_6_hosts isis {'count': 6}

5. 点到点链路上的度量变化

更改 router2 eth1 朝向 router3 的度量。IS-IS 度量是有向的,因此不能把 router3 到 router2 的反向度量说成同一个标量。router2 eth1 没有显式的 isis metric,因此从 wide-metric 默认值 10 起步。router2-router3 是 Level-1-2 电路,因此该变更在两个 level 上都被通告。

命令

sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'isis metric 222'

核对此次变更。 打开你环境中每一个可用的来源,并与下面的示例逐一核对。

Watcher 日志 变更及其回滚的 metric 与 network 行,L1 与 L2 L1 + L2

Watcher 日志 (CSV)

# router2: interface eth1 / isis metric 222
2026-09-07T06:52:15.035Z,lab-isis01,1,metric,router3,changed,old_cost:10,new_cost:222,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:15.037Z,lab-isis01,1,network,192.168.23.0/24,changed,old_cost:10,new_cost:222,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:15.045Z,lab-isis01,2,metric,router3,changed,old_cost:10,new_cost:222,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# rollback (isis metric 10):
2026-09-07T06:52:37.514Z,lab-isis01,1,metric,router3,changed,old_cost:222,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:37.526Z,lab-isis01,2,metric,router3,changed,old_cost:222,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
监控 事件流中的 10 -> 222 度量变更

在 OSPF/IS-IS Real-Time Monitoring 中,于 Choose the graph 选择 07Sep2026_06h49m43s_6_hosts,将 From/To 时间窗设在 06:52 UTC 前后,打开 L1 与 L2,点击 Find logs。开启 Changed metric 后,事件流显示 object router3、detected by router2、10 -> 222——仅 router2 -> router3 方向,L1 一次、L2 一次。反向不列出。

SDK 核对 get_adjacency_events 与 get_network_events 返回相同的对象与度量 2 个事件

SDK 请求

adj = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:52:14Z", end_time="2026-09-07T06:52:20Z")
net = graph.events.get_network_events(
    start_time="2026-09-07T06:52:14Z", end_time="2026-09-07T06:52:20Z")
for e in adj["adjacency_cost_change_events"]:
    print(e.event_object, e.event_detected_by, e.old_cost, "->", e.new_cost, "L" + str(e.level_number))
for e in net["network_cost_change_events"]:
    print(e.event_object, e.old_cost, "->", e.new_cost, "L" + str(e.level_number))

SDK 输出

router3 router2 10 -> 222 L1
router3 router2 10 -> 222 L2
3ffe::192:168:23:2/127 10 -> 222 L1
192.168.23.0/24 10 -> 222 L1
3ffe::192:168:23:2/127 10 -> 222 L2
192.168.23.0/24 10 -> 222 L2

metric 事件标明 router2 -> router3 方向(event_object router3,event_detected_by router2),10 -> 222,两个 level 都有;直连的 IPv4 与 IPv6 子网带有同样的变化。没有反向。

询问 agent 关于 router2 的通用故障提问

提问: What happened with router2 in the last 10 minutes?

合格的回答在提问未提及 cost 或 metric 的情况下,指出 router2 -> router3 方向和两个度量值(10 和 222),并且不声称反向 router3 -> router2 发生了变化。

回滚: 在 router2 eth1 上执行 isis metric 10(或 no isis metric),确认反向的 222 -> 10 metric 与 network 事件。

6. 检测内部与外部前缀事件

各前缀实验独立进行。与 OSPF 不同,IS-IS 按配置的掩码通告 loopback,并以接口度量作为其开销——/24 仍是 /24,开销 10,不会被收敛为 /32 主机路由。router6 仅为 Level-2,因此其前缀只出现在 L2。

  1. 6a. 在 router2 上,interface lo / ip address 192.168.123.1/24;观察 192.168.123.0/24 up 以及 L1 与 L2 上开销 -1 -> 10。
  2. 6b. 在 router6 上,interface lo / ip address 10.10.36.6/24;观察 10.10.36.0/24 up 以及 L2 上开销 -1 -> 10。
  3. 6c. 在 router6 上,no ip route 6.6.6.6/32 192.168.36.3;观察 6.6.6.6/32 down 以及 L2 上开销 11 -> -1。FRR 在重分发进 IS-IS 时不设置 external 位,因此 watcher 将其标记为 internal。

命令

sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface lo' -c 'ip address 192.168.123.1/24'

核对此次变更。 打开你环境中每一个可用的来源,并与下面的示例逐一核对。

Watcher 日志 每个子练习一行 up/down 和一行 changed 3 个前缀

Watcher 日志 (CSV)

# 6a router2: interface lo / ip address 192.168.123.1/24
2026-09-07T06:52:52.074Z,lab-isis01,1,network,192.168.123.0/24,up,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:52.075Z,lab-isis01,1,network,192.168.123.0/24,changed,old_cost:-1,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# 6b router6: interface lo / ip address 10.10.36.6/24
2026-09-07T06:53:39.099Z,lab-isis01,2,network,10.10.36.0/24,up,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:53:39.099Z,lab-isis01,2,network,10.10.36.0/24,changed,old_cost:-1,new_cost:10,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# 6c router6: no ip route 6.6.6.6/32 192.168.36.3
2026-09-07T06:54:14.533Z,lab-isis01,2,network,6.6.6.6/32,down,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:54:14.533Z,lab-isis01,2,network,6.6.6.6/32,changed,old_cost:11,new_cost:-1,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
监控 事件流中新增与撤销的前缀

在 OSPF/IS-IS Real-Time Monitoring 中选择本图,将时间窗设在 06:52-06:55 UTC 前后,打开 L1 与 L2,点击 Find logs。开启 New/Old Subnets 后,192.168.123.0/24 与 10.10.36.0/24 显示为新增,6.6.6.6/32 显示为撤销。

SDK 核对 get_network_events,每个子练习一个 changed 事件 3 个事件

SDK 请求

for start, end in [("2026-09-07T06:52:50Z", "2026-09-07T06:52:55Z"),
                   ("2026-09-07T06:53:37Z", "2026-09-07T06:53:42Z"),
                   ("2026-09-07T06:54:12Z", "2026-09-07T06:54:17Z")]:
    net = graph.events.get_network_events(start_time=start, end_time=end)
    for e in net["network_up_down_events"]:
        print(e.event_object, e.event_status, e.event_detected_by,
              f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number), e.subnet_type)

SDK 输出

192.168.123.0/24 changed router2 -1 -> 10 L1 internal
192.168.123.0/24 changed router2 -1 -> 10 L2 internal
10.10.36.0/24 changed router6 -1 -> 10 L2 internal
6.6.6.6/32 changed router6 11 -> -1 L2 internal

/24 保留其掩码——不收敛为 /32。可达性是 forwarder 丢弃的 up/down 行;开销变化才是 SDK 返回的 changed 事件。router2 的 loopback 在 L1 与 L2 都能看到,router6 的仅在 L2,重分发的 6.6.6.6/32 被标记为 internal。

回滚: 6a:在 router2 interface lo 上 no ip address 192.168.123.1/24。6b:在 router6 interface lo 上 no ip address 10.10.36.6/24。6c:在 router6 上 ip route 6.6.6.6/32 192.168.36.3。每一步之后确认反向事件。

7. 检测连通性丢失与恢复

关闭 router2 eth1,查看相关联的事件集合,然后用 no shutdown 恢复接口。由于 router2-router3 是 Level-1-2,每行都会 L1 一次、L2 一次地出现。

命令

# down
sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'shutdown'
# recovery
sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'no shutdown'

核对此次变更。 打开你环境中每一个可用的来源,并与下面的示例逐一核对。

Watcher 日志 router2-router3 链路的 L1 中断集合与 L1 恢复集合 down + up

Watcher 日志 (CSV)

# router2: interface eth1 / shutdown
2026-09-07T06:55:14.754Z,lab-isis01,1,host,router2,down,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:14.756Z,lab-isis01,1,metric,router2,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:14.760Z,lab-isis01,1,metric,router3,changed,old_cost:10,new_cost:-1,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:14.761Z,lab-isis01,1,network,192.168.23.0/24,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# router2: interface eth1 / no shutdown
2026-09-07T06:55:39.545Z,lab-isis01,1,metric,router3,changed,old_cost:-1,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:39.607Z,lab-isis01,1,metric,router2,changed,old_cost:-1,new_cost:10,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
监控 事件流中作为一个波次的中断

在 OSPF/IS-IS Real-Time Monitoring 中选择本图,将时间窗设在 06:55 UTC 前后,打开 L1 与 L2,点击 Find logs。开启 Up/Down Links 后,router2 与 router3 各自显示其一侧链路降到 -1 再恢复,192.168.23.0/24 变为不可达再恢复——两次,每个 level 一次。

SDK 核对 get_adjacency_events 返回配对的 down/up 变化 4 次变化

SDK 请求

down = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:55:14Z", end_time="2026-09-07T06:55:16Z")
for e in down["all_host_up_down_events"]:
    print(e.event_object, "detected_by", e.event_detected_by,
          f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number))

SDK 输出

router2 detected_by router3 10 -> -1 L1
router3 detected_by router2 10 -> -1 L1
router2 detected_by router3 10 -> -1 L2
router3 detected_by router2 10 -> -1 L2

host router2 down,由 router3 探测到,两条有向度量均为 -1,192.168.23.0/24 down——L1 与 L2 都有;随后是镜像恢复。恢复时 watcher 还记录了 router2 上一次 node attr:attached 抖动,Topolograph 目前尚不接收该事件。

询问 agent 关于该网络的通用故障提问

提问: What happened in the network in the last 30 minutes?

已记录的回答(Qwen;每次运行措辞会变)

In the last 30 minutes the IS-IS domain (area 49.0001, AS 65100) had 7 event waves detected by watcher lab-isis01, starting at 06:55:15 UTC. Every device that went down recovered within the window.

06:55:15 - router2 and router3 each lost the adjacency between them, at L1 and L2 (router2 detected by router3, router3 detected by router2).

06:55:40 - both routers restored that adjacency at L1 and L2, followed by a few repeated up notifications through 06:56:33.

06:57:23 - router6 lost its L2 adjacency to router3; 06:57:56 - router6 and router3 restored it.

In short: the router2-router3 link dropped at 06:55:15 and came back at 06:55:40; the router3-router6 link dropped at 06:57:23 and recovered at 06:57:56 - both fully converged.

回答指出中断的邻接(router2 - router3)、router3 探测到 router2 丢失且反之亦然、两条度量均为 -1,以及恢复——与上面 CSV 行相同的事实,而提问从未提到 "adjacency" 或 "failure"。router6 相关的行属于下面的中转练习,被同一个 30 分钟时间窗一并捕获。

回滚: 在 router2 eth1 上执行 no shutdown 恢复接口;等待两个 level 上的 host、network 与 metric 恢复事件。

8. 广播中转网段的事件呈现

router6 eth1 在一条广播(LAN)电路上对接 router3。router6 的 isis priority 100 高于 router3 的 64,因此 router6 是 DIS 并发起伪节点 LSP。该电路仅为 Level-2,因此每一行都是 L2。router6 eth1 没有显式的 isis metric,因此基线为默认值 10。

命令

# cost
sudo docker exec clab-isis01-router6 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'isis metric 66'
# then, separately: shutdown, then no shutdown
sudo docker exec clab-isis01-router6 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'shutdown'
sudo docker exec clab-isis01-router6 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'no shutdown'

核对此次变更。 打开你环境中每一个可用的来源,并与下面的示例逐一核对。

Watcher 日志 L2 开销变更及其对 network 的连带影响,外加 shutdown/恢复 这对事件 开销 + up/down

Watcher 日志 (CSV)

# router6: interface eth1 / isis metric 66   (rollback: isis metric 10)
2026-09-07T06:56:47.139Z,lab-isis01,2,network,192.168.36.0/24,changed,old_cost:10,new_cost:66,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:56:47.140Z,lab-isis01,2,metric,router3,changed,old_cost:10,new_cost:66,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:04.561Z,lab-isis01,2,metric,router3,changed,old_cost:66,new_cost:10,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# router6: interface eth1 / shutdown
2026-09-07T06:57:22.037Z,lab-isis01,2,network,192.168.36.0/24,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:22.055Z,lab-isis01,2,host,router6,down,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.36.3,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:22.055Z,lab-isis01,2,metric,router6,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.36.3,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# router6: interface eth1 / no shutdown
2026-09-07T06:57:55.962Z,lab-isis01,2,host,router6,up,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.36.3,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:55.973Z,lab-isis01,2,metric,router3,changed,old_cost:-1,new_cost:66,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
监控 中转开销变更与 router6 中断,仅 L2

在 OSPF/IS-IS Real-Time Monitoring 中选择本图,将时间窗设在 06:56-06:58 UTC 前后,打开 L2,点击 Find logs。metric 事件的 object 是 router3,event_detected_by 为 router6,且变更仅在 L2——该电路为 level-2-only。shutdown 时事件流显示 host router6 down 和 192.168.36.0/24 不可达,随后是恢复。

SDK 核对 针对中转开销与 shutdown/恢复 的 get_adjacency_events 仅 L2

SDK 请求

cost = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:56:46Z", end_time="2026-09-07T06:56:49Z")
for e in cost["adjacency_cost_change_events"]:
    print("cost", e.event_object, e.event_detected_by, f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number))
flap = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:57:21Z", end_time="2026-09-07T06:58:00Z")
for e in flap["all_host_up_down_events"]:
    print("updown", e.event_object, e.event_detected_by, f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number))

SDK 输出

cost router3 router6 10 -> 66 L2
updown router6 router3 10 -> -1 L2
updown router6 router3 -1 -> 10 L2
updown router3 router6 -1 -> 10 L2

在广播网段上,metric 事件仍然标明邻居(router3),由 router6 探测到,且仅在 L2;该 metric 变更还带动 192.168.36.0/24 和两个 IPv6 /127。shutdown 表现为 L2 上 host router6 down,随后恢复。

询问 agent 关于 router6 的通用故障提问

提问: What happened on router6 in the last 20 minutes?

合格的回答在提问未提及 cost、DIS 或 shutdown 的情况下,指出 router6 -> router3 的中转度量在 10 -> 66 间往返,随后 router6 的接入中断(L2 上度量 -1)并恢复。

回滚: 在 router6 eth1 上执行 isis metric 10(或 no isis metric)恢复基线 10;no shutdown 恢复邻接。确认反向的 metric 事件。

9. 加载 13 台路由器的 IS-IS 实验室

六路由器实验室适合查看 Watcher 事件和 TE 属性,但缺少足够的备用路径,无法进行有意义的 CSPF 练习。请使用 13-hosts-demo-isis 实验室:可以自行部署,也可以跳过部署,直接下载现成的 LSDB demo_isis_LSDB.txt。

将 LSDB 作为 FRR IS-IS 文件上传到 Topolograph:可以是从 r30 获取的,也可以是下载的 demo_isis_LSDB.txt。生成的图包含 13 个节点和 52 条有向边,其中 Level 1 有 12 条,Level 2 有 40 条。50 条边带有 TE 属性;r110-r111 的两个方向不带 TE 属性。

命令

cd containerlab/13-hosts-demo-isis
sudo clab deploy --topo 13-hosts-demo-isis.clab.yml
sudo docker exec clab-13-hosts-demo-isis-r30 vtysh -c 'show isis database detail'

SDK 请求

from topolograph import Topolograph

topo = Topolograph(url="http://<your-topolograph>:8080", username="<email>", password="<password>")
graph = topo.graphs.upload(open("demo_isis_LSDB.txt").read(), vendor="FRR", protocol="isis")
print(graph.graph_time, graph.hosts)
print(graph.edges_list(per_page=200)["pagination"]["total"])
print(graph.edges_list(is_te_link=True, per_page=200)["pagination"]["total"])
print(graph.edges_list(is_te_link=False, per_page=200)["pagination"]["total"])

SDK 输出

<new graph time> {'count': 13}
52
50
2
需确认项 继续前需确认的事实
  • 已上传图的 hosts 字段包含 {'count': 13},边总数为 52。
  • edges_list(is_te_link=True) 返回 50 条边;edges_list(is_te_link=False) 返回 r110-r111 的两个方向。

10. 计算路径前读取 TE 值

应用约束前先检查图中的 TE 属性。找出 gold 和 red 管理组、较高的 TE metric、容量较小的 priority 7 带宽池、带有 SRLG 的链路以及非 TE 链路。普通最短路径计算无法说明某条链路是否适合隧道。

在此图中,gold (0x00000002) 匹配 10 条有向边,red (0x00000004) 匹配 2 条,temetric__gt=30 匹配 4 条,unreserved_bw_7__lt=1000000 匹配 2 条,is_te_link=False 返回 r110-r111 的两个方向。

命令

# 在步骤 1 上传的图上通过 SDK 运行以下过滤器

SDK 请求

for query in (
    {'admin_group': '0x00000002'},
    {'admin_group': '0x00000004'},
    {'temetric__gt': 30},
    {'unreserved_bw_7__lt': 1_000_000},
    {'is_te_link': False},
):
    result = graph.edges_list(per_page=200, **query)
    print(query, result['pagination']['total'])

SDK 输出

{'admin_group': '0x00000002'} 10
{'admin_group': '0x00000004'} 2
{'temetric__gt': 30} 4
{'unreserved_bw_7__lt': 1000000} 2
{'is_te_link': False} 2
需确认项 继续前需确认的事实
  • r10-r100、r100-r110 和 r100-r111 上的 8 条边带有 SRLG 数据;两条并行 r10-r100 链路保留各自的风险组。
  • 字符串值 is_te_link='yes' 会被 HTTP 400 拒绝:Wrong type, expected 'boolean'。

11. 记录无约束路径

计算从 r10 到 r14 的无约束路径,并将其作为后续练习的基线。这样可以把每个新约束的结果与已知路径进行比较。

参考路径为 r10 r100 r110 r14,成本 30,使用 r10 与 r100 之间的并行链路 eth2。

命令

不修改路由器,执行参考 CSPF 请求。

SDK 请求

print(graph.cspf_path('r10', 'r14'))

SDK 输出

{'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
需确认项 继续前需确认的事实
  • 后续四步每次只增加一个约束,并观察路径变化。

12. 避开共享风险组

使用 srlg_exclude 绕开正在维护的链路或共用管道。参考路径 r10 r100 r110 r14 的成本为 30:首跳属于 SRLG 300,两条并行首跳都属于 SRLG 100。

排除 300 会保留相同的路由器,但改用 eth1,成本升至 35。排除 100 得到 r10 r11 r100 r110 r14,成本 40;排除 200 得到 r10 r100 r101 r111 r14,成本 45;同时排除 100 和 200 得到 r10 r11 r101 r111 r14,成本 50。

命令

只修改 `srlg_exclude` 列表。

SDK 请求

for groups in ([300], [100], [200], [100, 200]):
    print(groups, graph.cspf_path('r10', 'r14', srlg_exclude=groups))

SDK 输出

[300] {'cost': 35, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
[100] {'cost': 40, 'path': ['r10', 'r11', 'r100', 'r110', 'r14'], 'reason': ''}
[200] {'cost': 45, 'path': ['r10', 'r100', 'r101', 'r111', 'r14'], 'reason': ''}
[100, 200] {'cost': 50, 'path': ['r10', 'r11', 'r101', 'r111', 'r14'], 'reason': ''}
需确认项 继续前需确认的事实
  • 约束会移除链路,不会修改 IGP metric。

13. 按带宽和建立优先级选择路径

使用不同的建立优先级请求一条 8 Mbit 隧道。参考路径 r10 r100 r110 r14 的成本为 30,但 r100-r110 在 priority 7 带宽池中只通告 4 Mbit,在 priorities 0-3 中通告 10 Mbit。

使用 priority 7 时,CSPF 将隧道改到 r10 r100 r111 r14,成本 40;使用 priority 0 时仍选择 r10 r100 r110 r14,成本 30。20 Mbit 请求无法放入任何可用路径,并返回约束失败。

命令

修改 CSPF 请求中的 `bandwidth` 和 `setup_priority`。

SDK 请求

for priority in (7, 0):
    print(priority, graph.cspf_path('r10', 'r14', bandwidth='8M', setup_priority=priority))
print(graph.cspf_path('r10', 'r14', bandwidth='20M'))

SDK 输出

7 {'cost': 40, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
0 {'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
{'cost': None, 'path': [], 'reason': 'no path satisfies the requested constraints'}
需确认项 继续前需确认的事实
  • 建立优先级选择用于放置的 unreserved bandwidth 池,但不改变 IGP cost。

14. 使用 TE metric 计算

确认每条候选链路都有 TE metric 后,使用 metric_type='te' 重新计算。IGP 路径 r10 r100 r110 r14 的成本为 30,但 r100-r110 的 TE metric 为 100,而 r100-r111 的 TE metric 为 10。

CSPF 选择 r10 r100 r111 r14,TE 成本为 30。同一路径的 IGP 成本为 40,因此可以清楚看到所选 metric 的影响。

命令

使用 `metric_type='te'`,不要修改路由器。

SDK 请求

print(graph.cspf_path('r10', 'r14', metric_type='te'))

SDK 输出

{'cost': 30, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
需确认项 继续前需确认的事实
  • 链路缺少 TE metric 时,该链路会回退使用 IGP metric。

15. 包含或排除 admin-group 位

使用管理组位来要求或避开特定链路类别。位 1 表示 gold,位 2 表示 red。从 r13 到 r15 的无约束路径为 r13 r100 r110 r15,成本 30。

通过 admin_include_all=['1'] 要求 gold 后,路径变为 r13 r101 r111 r15,成本 45;从 r10 到 r14 时得到 r10 r101 r111 r14,成本 55。通过 admin_exclude_any=['2'] 排除 red 后,得到 r13 r100 r111 r15,成本 40。

命令

传递位编号,不要传十六进制掩码。

SDK 请求

print(graph.cspf_path('r13', 'r15', admin_include_all=['1']))
print(graph.cspf_path('r10', 'r14', admin_include_all=['1']))
print(graph.cspf_path('r13', 'r15', admin_exclude_any=['2']))

SDK 输出

{'cost': 45, 'path': ['r13', 'r101', 'r111', 'r15'], 'reason': ''}
{'cost': 55, 'path': ['r10', 'r101', 'r111', 'r14'], 'reason': ''}
{'cost': 40, 'path': ['r13', 'r100', 'r111', 'r15'], 'reason': ''}
需确认项 继续前需确认的事实
  • 位 1 是 0x00000002,位 2 是 0x00000004;位 0 是最低有效位。

16. 将 CSPF 限制在一个 IS-IS 层级

设置 level=1 或 level=2,在单个 IS-IS 拓扑内计算,并确认两个端点都属于该拓扑。从 r30 到 r31 的 Level 1 直连路径成本为 10,而 Level 2 路径 r30 r10 r11 r31 的成本为 40。

r130-r131 在 Level 1 的成本为 10,但在 Level 2 返回 src/dst not found。合并拓扑中存在成本为 50 的 r130-r15 路径,但它跨越层级边界,因此任何单一层级都不包含该路径。

命令

在 CSPF 请求中加入 `level=1` 或 `level=2`。

SDK 请求

for level in (None, 1, 2):
    print(level, graph.cspf_path('r30', 'r31', level=level))
print(graph.cspf_path('r130', 'r131', level=2))

SDK 输出

None {'cost': 10, 'path': ['r30', 'r31'], 'reason': ''}
1 {'cost': 10, 'path': ['r30', 'r31'], 'reason': ''}
2 {'cost': 40, 'path': ['r30', 'r10', 'r11', 'r31'], 'reason': ''}
{'cost': None, 'path': [], 'reason': 'src/dst not found'}
需确认项 继续前需确认的事实
  • 没有 per-level 数据的旧图在显式 level 请求时返回 HTTP 422 和 isis_level_calculation_unavailable,需要重新上传。

17. 区分约束无法满足和 endpoint 不存在

当 CSPF 返回空 path 时,请读取 reason。步骤 5 的 20 Mbit 请求具有有效端点,但没有任何路径具备足够带宽;r130 到 r15 的 Level 1 请求则没有包含两个端点的单层拓扑。

带宽请求返回 no path satisfies the requested constraints;层级不匹配的请求返回 src/dst not found。这两种失败需要不同的修正方式。

命令

重复第 5 步和第 8 步中失败的请求。

SDK 请求

print(graph.cspf_path('r10', 'r14', bandwidth='20M'))
print(graph.cspf_path('r130', 'r15', level=1))

SDK 输出

{'cost': None, 'path': [], 'reason': 'no path satisfies the requested constraints'}
{'cost': None, 'path': [], 'reason': 'src/dst not found'}
需确认项 继续前需确认的事实
  • 第一种结果要求放宽 bandwidth、affinity 或 SRLG;第二种表示 endpoint 不在所选拓扑中。

18. 编辑一条链路并重新计算

在不修改路由器的情况下模拟 r100-r110 维护。在一次性图副本上使用 update_edge 为 Level 2 边添加 SRLG 999,排除该组后重新计算,再清除 SRLG。

排除 999 会把成本 30 的 r10 r100 r110 r14 改为成本 40 的 r10 r100 r111 r14;清除 SRLG 后恢复参考路径。仅属于 Level 2 的边会拒绝 isis_level=1。由于 replace_edge 会删除分层数据,完整替换后显式指定层级的 CSPF 请求会返回 HTTP 422。

命令

# 请使用一次性图:编辑会持久化
# 先通过 edges_list(include=['edge_key']) 查找边 id

SDK 请求

edge = next(e for e in graph.edges_list(include=['edge_key'], per_page=200)['items']
             if e['src'] == 'r100' and e['dst'] == 'r110')
print(graph.update_edge(edge['id'], isis_level=2, srlg=[999]))
print(graph.cspf_path('r10', 'r14', srlg_exclude=[999]))
print(graph.update_edge(edge['id'], isis_level=2, srlg=[]))
print(graph.cspf_path('r10', 'r14'))

SDK 输出

... srlg: [999] ...
{'cost': 40, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
... srlg: [] ...
{'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
需确认项 继续前需确认的事实
  • update_edge 只修改提供的字段;replace_edge 会完整重写边。不要对需要保持不变的参考图使用这两个方法。

19. 放置 LSP 并检查剩余带宽

从 r10 到 r14 逐个添加四条 4 Mbit 隧道,并在每次放置后检查剩余带宽和所选路径。放置第一条隧道前,参考路径 r10 r100 r110 r14 上的 4 Mbit priority 7 带宽池可用。

T1 使用 r10 r100 r110 r14,成本 30;T2 使用 r10 r100 r111 r14,成本 40;T3 使用另一条 r10-r100 链路,成本 45;T4 使用 r10 r11 r101 r110 r14,成本 55。练习完成后删除所有测试 LSP。

命令

# 不修改路由器:通过 SDK 创建四条 4 Mbit RSVP-TE 隧道

SDK 请求

for name in ('T1', 'T2', 'T3', 'T4'):
    lsp = graph.add_lsp({
        'name': name, 'src': 'r10', 'dst': 'r14', 'bandwidth': '4M',
        'paths': {'primary': {'role': 'primary', 'bandwidth': '4M'}},
    })
    primary = lsp['paths']['primary']
    print(name, primary['path'], primary['cost'])
print(graph.edges_list(include=['lsp_left_bw', 'lsps'], per_page=200)['items'])
graph.delete_lsps()

SDK 输出

T1 ['r10', 'r100', 'r110', 'r14'] 30
T2 ['r10', 'r100', 'r111', 'r14'] 40
T3 ['r10', 'r100', 'r110', 'r14'] 45
T4 ['r10', 'r11', 'r101', 'r110', 'r14'] 55
... 每条边的 lsp_left_bw_7 和 lsps ...
{'deleted': 4}
需确认项 继续前需确认的事实
  • 每次放置后使用 edges_list(include=['lsp_left_bw', 'lsps']),查看哪个带宽池导致下一次路径变化。
  • 清理时务必调用 delete_lsps();LSP 放置会修改已存储的图。
Topolograph 2.72.1 📣 加入社区!