1. Принцип работы с практикумом
В каждом упражнении внесите одно изменение, заранее определите ожидаемое поведение IS-IS и проверьте событие в журнале Watcher. Затем найдите то же изменение на странице Monitoring в Topolograph, через SDK и, в упражнениях на сопоставление событий, в ответе агента. Перед следующим независимым упражнением верните лабораторию в исходное состояние.
isis01 - это домен FRR из шести маршрутизаторов в одной зоне (49.0001, AS 65100). Канал router2-router3 работает на уровнях 1 и 2; канал router3-router6 и подключение router3 к локальной сети router4/router5 - только на уровне 2. Watcher получает LSDB через соседство уровня 2 на router1 и сопоставляет System ID с именами узлов из TLV Dynamic Hostname. Поэтому в событиях указаны router2, router3 и router6, а не идентификаторы вида 0100.1001.000x.
- Строка CSV Watcher - первичная запись сетевого события.
- Monitoring и SDK доказывают, что событие принято и доступно для запроса.
- Агент используется только для сведения нескольких событий в один инцидент, но не как замена исходной строки.
2. Запуск и проверка лаборатории из шести маршрутизаторов
Запустите общедоступную топологию isis01 на базе GRE из репозитория IS-IS Watcher. Скрипт prepare.sh также создаёт мост isis-br-dr и загружает модули ядра MPLS, необходимые для IS-IS TE.
Сначала выполните 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 # только если в списке остался прежний isis01
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'показываетrouter3в состоянииUp; первый граф Topolograph тогда содержит шесть маршрутизаторов.
Откат: Когда закончите практикум, удалите лабораторию командой sudo clab destroy --topo isis01.clab.yml --cleanup из containerlab/isis01/.
3. Формат записи сетевого события
В практикуме используются три семейства событий: host, metric и network. Поле event_object указывает изменившийся объект, event_status - тип изменения, а event_detected_by - маршрутизатор, который объявил или обнаружил изменение. Поле graph_time содержит метку запуска Watcher, по которой выбирается граф в Topolograph.
В строках IS-IS есть поле level (1 или 2), которого нет в строках OSPF. Оно стоит третьим, сразу после watcher_name. Строку metric выше можно прочитать так: в 2026-09-07T06:55:14Z Watcher lab-isis01 обнаружил, что router3 объявил канал уровня 1 к router2 с новой метрикой -1 вместо 10, то есть соседство было потеряно. Изменение относится к интерфейсу 192.168.23.2 в зоне 49.0001, AS 65100. Узлу router2 соответствует NET/System ID 49.0001.0100.1001.0002.00; имя маршрутизатора получено из TLV Dynamic Hostname.
Строки host и network со статусами up или down не содержат прежнюю и новую стоимость, поэтому Fluent Bit пересылает связанную с ними строку changed. Topolograph определяет потерю соседства по событию metric с new_cost: -1, а восстановление - по событию с old_cost: -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. area_num49.0001иasn65100определяют домен маршрутизации;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 создаёт отдельную строку CSV Watcher для каждого уровня. Fluent Bit пересылает её в Topolograph, где событие сохраняется и становится доступно на странице Monitoring, через API событий и SDK. В каждом упражнении один и тот же факт проверяется во всех доступных источниках.
- Журнал Watcher isis01 -> парсер CSV Fluent Bit -> приём в Topolograph -> страница Monitoring + API событий -> SDK Topolograph
- Страница Monitoring: OSPF/IS-IS Real-Time Monitoring. Выберите граф по метке времени в
Choose the graph, задайте окноFrom/Toв UTC, включите переключателиL1иL2, нажмитеFind logs; переключателиNew/Old Subnets,Up/Down LinksиChanged metricфильтруют список. - В списке графов показаны все снимки топологии, отправленные 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. Изменение стоимости канала точка-точка
Измените метрику интерфейса eth1 на router2 в направлении router3. Метрики IS-IS задаются отдельно для каждого направления, поэтому обратная метрика router3 -> router2 от этого не меняется. На eth1 маршрутизатора router2 команда isis metric не задана, и используется широкая метрика по умолчанию - 10. Канал router2-router3 работает на уровнях 1 и 2, поэтому изменение объявляется на обоих уровнях.
Команда
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 выберите 07Sep2026_06h49m43s_6_hosts в Choose the graph, задайте окно 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, на обоих уровнях; подключённые подсети IPv4 и IPv6 несут то же изменение. Обратного направления нет.
Спросить агента Общий вопрос об инциденте на router2
Запрос: What happened with router2 in the last 10 minutes?
Правильный ответ называет направление router2 -> router3 и оба значения метрики (10 и 222), при том что вопрос не упоминает стоимость или метрику, и не утверждает, что обратное направление router3 -> router2 изменилось.
Откат: Выполните isis metric 10 (или no isis metric) на router2 eth1 и подтвердите обратные события 222 -> 10 metric и network.
6. События внутренних и внешних префиксов
Выполняйте каждый эксперимент с префиксом независимо. В отличие от OSPF, IS-IS объявляет адрес loopback с настроенной маской и назначает ему метрику интерфейса: префикс /24 остаётся /24 со стоимостью 10, а не преобразуется в маршрут /32. router6 работает только на уровне 2, поэтому его префиксы появляются только в L2.
- 6a. На
router2выполнитеinterface lo/ip address 192.168.123.1/24. Убедитесь, что для192.168.123.0/24появился статусup, а стоимость изменилась с-1на10в L1 и L2. - 6b. На
router6выполнитеinterface lo/ip address 10.10.36.6/24. Убедитесь, что для10.10.36.0/24появился статусup, а стоимость изменилась с-1на10в L2. - 6c. На
router6выполнитеno ip route 6.6.6.6/32 192.168.36.3. Убедитесь, что для6.6.6.6/32появился статусdown, а стоимость изменилась с11на-1в L2. 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. Изменение доступности записано в строке со статусом up или down, которую модуль пересылки отбрасывает; SDK возвращает связанную строку changed с изменением стоимости. Loopback router2 виден в L1 и L2, loopback router6 - только в L2, а перераспределённый маршрут 6.6.6.6/32 помечен как internal.
Откат: 6a: no ip address 192.168.123.1/24 на router2 interface lo. 6b: no ip address 10.10.36.6/24 на router6 interface lo. 6c: ip route 6.6.6.6/32 192.168.36.3 на router6. После каждого подтвердите обратное событие.
7. Обнаружение потери связи и её восстановления
Отключите интерфейс eth1 на router2, изучите связанный набор событий, затем восстановите интерфейс командой no shutdown. Канал router2-router3 работает на уровнях 1 и 2, поэтому каждое событие появляется по одному разу для L1 и L2.
Команда
# отключение
sudo docker exec clab-isis01-router2 vtysh \
-c 'conf t' -c 'interface eth1' -c 'shutdown'
# восстановление
sudo docker exec clab-isis01-router2 vtysh \
-c 'conf t' -c 'interface eth1' -c 'no shutdown'
Проверьте изменение. Откройте каждый доступный в вашей среде источник и сверьтесь с примерами ниже.
Журнал Watcher События потери и восстановления канала router2-router3 на уровне 1 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 становится недостижимой и возвращается - дважды, по разу на уровень.
Проверка через SDK get_adjacency_events возвращает парные события потери и восстановления 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
router3 обнаруживает событие host со статусом down для router2; метрики обоих направлений становятся равны -1, а 192.168.23.0/24 становится недоступной в L1 и L2. Затем следуют обратные события восстановления. Watcher также записывает кратковременное изменение атрибута node attr:attached на router2, которое Topolograph пока не принимает.
Спросить агента Общий вопрос об инциденте в сети
Запрос: 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-минутное окно.
Откат: no shutdown на router2 eth1 восстанавливает интерфейс; дождитесь событий восстановления host, network и metric на обоих уровнях.
8. Отчёт по широковещательному транзитному сегменту
Интерфейс eth1 на router6 соединён с router3 через широковещательный сегмент. Приоритет IS-IS на router6 равен 100, а на router3 - 64, поэтому router6 выбирается DIS и формирует LSP псевдоузла. Канал работает только на уровне 2, поэтому все события относятся к L2. На eth1 маршрутизатора router6 команда isis metric не задана, и используется значение по умолчанию - 10.
Команда
# изменение стоимости
sudo docker exec clab-isis01-router6 vtysh \
-c 'conf t' -c 'interface eth1' -c 'isis metric 66'
# затем отдельно отключите и восстановите интерфейс
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, потеря связи и восстановление стоимость + доступность
Журнал 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, поскольку канал работает лишь на уровне 2. После команды shutdown в ленте появляются событие host со статусом down для router6 и потеря доступности 192.168.36.0/24, затем события восстановления.
Проверка через SDK 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 в поле event_object, маршрутизатор router6 в поле event_detected_by и уровень L2. Изменение метрики также относится к 192.168.36.0/24 и двум префиксам IPv6 /127. Команда shutdown создаёт для router6 событие host со статусом down в L2, после которого следует восстановление.
Спросить агента Общий вопрос об инциденте на router6
Запрос: What happened on router6 in the last 20 minutes?
Правильный ответ указывает изменение метрики в направлении router6 -> router3 с 10 на 66 и обратно, затем потерю связи с router6 (метрика -1 в L2) и восстановление, хотя в вопросе не упомянуты метрика, DIS или команда shutdown.
Откат: isis metric 10 (или no isis metric) на router6 eth1 восстанавливает базовое значение 10; no shutdown восстанавливает соседство. Подтвердите обратные события metric.
9. Чтение TE-атрибутов и фильтрация рёбер по уровню IS-IS
Для каждого разобранного ребра сохраняются метрика, атрибуты TE из sub-TLV RFC 5305 (административная группа, доступная пропускная способность и TE-метрика) и поле isis_level. Значение 1 или 2 означает, что канал объявлен только на одном уровне; 3 (L1L2) - на обоих. Канал router3-router6 работает только на уровне 2, поэтому только его рёбра имеют isis_level: 2; у всех остальных рёбер этого графа значение равно 3.
Граф 18Sep2026_08h50m15s_6_hosts получен при полной обработке LSDB: в POST /graphs передан вывод show isis database detail с router1. Графы, полученные от Watcher, также хранят отдельные снимки атрибутов TE для каждого уровня, поэтому фильтр по уровню использует исходные данные IS-IS, а не объединённое представление.
Команда
curl -u "<email>:<password>" "http://<your-topolograph>:8080/api/graph/18Sep2026_08h50m15s_6_hosts/edges?isis_level=2"
curl -u "<email>:<password>" "http://<your-topolograph>:8080/api/graph/18Sep2026_08h50m15s_6_hosts/edges?admin_group=0x647a0001"
curl -u "<email>:<password>" "http://<your-topolograph>:8080/api/graph/18Sep2026_08h50m15s_6_hosts/edges?temetric__gt=25"
Запрос SDK
edges = graph.edges_list(isis_level=2)
for e in edges["items"]:
print(e["src"], "->", e["dst"], e["isis_level"], e["cost"])
Вывод SDK
10.10.10.3 -> 10.10.10.6 2 10
10.10.10.6 -> 10.10.10.3 2 10
Что проверить Факты для подтверждения перед продолжением
isis_level=2возвращает по одному ребру в каждом направлении канала, работающего только на уровне 2:10.10.10.3 -> 10.10.10.6и10.10.10.6 -> 10.10.10.3. У обоихisis_level: 2; других таких рёбер в графе из шести маршрутизаторов нет.admin_group=0x647a0001возвращает два ребра:10.10.10.1 -> 10.10.10.3со стоимостью 10 и10.10.10.2 -> 10.10.10.3со стоимостью 50. Это направления, в которыхrouter1иrouter2объявляют данную административную группу в сторонуrouter3; у каждого направления собственные значенияadmin_group,temetricиunreserved_bw_0..unreserved_bw_7.temetric__gt=25возвращает два ребра:10.10.10.1 -> 10.10.10.3сtemetric: 111111и10.10.10.3 -> 10.10.10.2сtemetric: 32. Фильтр использует то же поле TE-метрики, которое отображается в карточке канала.
10. Расчёт CSPF для одного уровня IS-IS
Запрос GET /graph/{graph_time}/cspf-path/{node_a}/{node_b}?level=1|2 рассчитывает путь только по метрикам и атрибутам TE выбранного уровня. Обычный расчёт по объединённому графу этого различия не показывает. Канал router3-router6 работает только на уровне 2, поэтому router6 не входит в топологию уровня 1. Запрос пути уровня 1 между router1 и router6 возвращает не отказ по ограничениям, а src/dst not found.
Если граф был сохранён до появления раздельных данных по уровням, запрос с параметром level завершается с HTTP 422 и кодом isis_level_calculation_unavailable. Topolograph не подменяет отсутствующие данные ответом из объединённого графа.
Команда
curl -u "<email>:<password>" "http://<your-topolograph>:8080/api/graph/18Sep2026_08h50m15s_6_hosts/cspf-path/10.10.10.1/10.10.10.6"
curl -u "<email>:<password>" "http://<your-topolograph>:8080/api/graph/18Sep2026_08h50m15s_6_hosts/cspf-path/10.10.10.1/10.10.10.6?level=1"
curl -u "<email>:<password>" "http://<your-topolograph>:8080/api/graph/18Sep2026_08h50m15s_6_hosts/cspf-path/10.10.10.1/10.10.10.6?level=2"
Запрос SDK
for level in (None, 1, 2):
r = graph.cspf_path("10.10.10.1", "10.10.10.6", level=level)
print(level, r)
Вывод SDK
None {'cost': 20, 'path': ['10.10.10.1', '10.10.10.3', '10.10.10.6'], 'reason': ''}
1 {'cost': None, 'path': [], 'reason': 'src/dst not found'}
2 {'cost': 20, 'path': ['10.10.10.1', '10.10.10.3', '10.10.10.6'], 'reason': ''}
Что проверить Факты для подтверждения перед продолжением
- Без параметра
levelиспользуется объединённый граф:"path": ["10.10.10.1", "10.10.10.3", "10.10.10.6"],"cost": 20. - При
level=1возвращаются"path": []и"reason": "src/dst not found", поскольку единственный каналrouter6работает только на уровне 2 и узел отсутствует в топологии уровня 1. - При
level=2результат совпадает с объединённым графом: стоимость равна 20. Каналrouter1-router3работает на обоих уровнях, поэтому он также доступен для пути уровня 2. - Такой же запрос с
?level=1к старому графу12Sep2026_19h04m47s_6_hostsвозвращает HTTP 422,"code": "isis_level_calculation_unavailable"и"action": "reupload_graph". Чтобы получить корректный ответ для отдельного уровня, снова загрузите LSDB в текущую версию Topolograph.
11. Загрузка лаборатории IS-IS из 13 маршрутизаторов
Лаборатория из шести маршрутизаторов подходит для изучения событий Watcher и атрибутов TE, но в ней недостаточно альтернативных путей для содержательных упражнений с CSPF. Используйте лабораторию 13-hosts-demo-isis: разверните её или пропустите развёртывание и скачайте готовую LSDB demo_isis_LSDB.txt.
Загрузите LSDB в Topolograph как файл FRR IS-IS: снятую с r30 или скачанную demo_isis_LSDB.txt. Граф должен содержать 13 узлов и 52 направленных ребра: 12 на уровне 1 и 40 на уровне 2. Для 50 рёбер доступны атрибуты TE; исключение составляют два направления канала r110-r111.
Команда
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.
12. Проверка атрибутов TE перед расчётом пути
Перед выбором ограничения проверьте атрибуты TE. Найдите рёбра с административными группами gold и red, высокой TE-метрикой, малым резервом пропускной способности для приоритета 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.
Команда
# Выполните через SDK для графа, загруженного на шаге 1
Запрос 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
Что проверить Факты для подтверждения перед продолжением
- Данные SRLG есть у восьми рёбер на каналах
r10-r100,r100-r110иr100-r111; два параллельных каналаr10-r100сохраняют собственные группы риска. - Строковое значение
is_te_link='yes'отклоняется с HTTP 400:Wrong type, expected 'boolean'.
13. Зафиксировать путь без ограничений
Рассчитайте путь от r10 до r14 без ограничений и сохраните его как исходный для следующих упражнений. Так каждое новое ограничение можно будет сравнить с известным маршрутом.
Исходный путь проходит через r10 r100 r110 r14, имеет стоимость 30 и использует параллельный канал eth2 между r10 и r100.
Команда
# Конфигурация маршрутизаторов не меняется: рассчитайте исходный путь CSPF
Запрос SDK
print(graph.cspf_path('r10', 'r14'))
Вывод SDK
{'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
Что проверить Факты для подтверждения перед продолжением
- Исходный расчёт использует метрики IGP. В следующих четырёх упражнениях путь будет меняться под действием одного ограничения за раз.
14. Избежать общих групп риска
Используйте srlg_exclude, чтобы проложить путь в обход обслуживаемого канала или общей кабельной трассы. Исходный путь r10 r100 r110 r14 имеет стоимость 30: его первый канал входит в SRLG 300, а оба параллельных канала между r10 и r100 - в SRLG 100.
При исключении SRLG 300 набор маршрутизаторов не меняется, но выбирается eth1 и стоимость возрастает до 35. Исключение SRLG 100 даёт путь r10 r11 r100 r110 r14 со стоимостью 40; SRLG 200 - r10 r100 r101 r111 r14 со стоимостью 45; одновременно SRLG 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 при этом остаются прежними.
15. Выбор пути по пропускной способности и приоритету установки
Запросите туннель 8 Мбит/с с разными приоритетами установки. Исходный путь r10 r100 r110 r14 имеет стоимость 30, но канал r100-r110 сообщает только 4 Мбит/с свободной пропускной способности для приоритета 7 и 10 Мбит/с для приоритетов 0-3.
С приоритетом 7 CSPF выбирает путь r10 r100 r111 r14 стоимостью 40; с приоритетом 0 сохраняется r10 r100 r110 r14 стоимостью 30. Запрос 20 Мбит/с не помещается ни на одном доступном пути и завершается отказом по ограничениям.
Команда
# Конфигурация маршрутизаторов не меняется: меняйте 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'}
Что проверить Факты для подтверждения перед продолжением
- Приоритет установки выбирает соответствующий резерв незанятой пропускной способности, но не изменяет стоимость пути по метрикам IGP.
16. Рассчитать путь по TE-метрике
Перед расчётом с metric_type='te' убедитесь, что для каждого рассматриваемого канала известна TE-метрика. Путь r10 r100 r110 r14 имеет стоимость IGP 30, но TE-метрика канала r100-r110 равна 100, а канала r100-r111 - 10.
По TE-метрике CSPF выбирает путь r10 r100 r111 r14 со стоимостью 30. Стоимость того же пути по метрикам IGP равна 40, поэтому влияние выбранной метрики хорошо видно.
Команда
# Конфигурация маршрутизаторов не меняется: выберите TE-метрику вместо метрики IGP
Запрос SDK
print(graph.cspf_path('r10', 'r14', metric_type='te'))
Вывод SDK
{'cost': 30, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
Что проверить Факты для подтверждения перед продолжением
- Если у канала нет TE-метрики, CSPF использует для него метрику IGP. Поэтому сначала проверьте атрибуты рёбер.
17. Включение и исключение административных групп
Используйте биты административных групп, чтобы потребовать или исключить определённый класс каналов. Бит 1 обозначает gold, бит 2 - red. Путь от r13 до r15 без ограничений проходит через r13 r100 r110 r15 и имеет стоимость 30.
Требование gold через admin_include_all=['1'] переводит этот путь на r13 r101 r111 r15 со стоимостью 45; для пары r10-r14 оно даёт r10 r101 r111 r14 со стоимостью 55. Исключение red через admin_exclude_any=['2'] даёт путь 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 является младшим.
18. Ограничить CSPF одним уровнем IS-IS
Задайте level=1 или level=2, чтобы выполнить расчёт внутри одной топологии IS-IS, и убедитесь, что оба конечных узла входят в неё. Для пары r30-r31 прямой путь уровня 1 имеет стоимость 10, а путь уровня 2 проходит через r30 r10 r11 r31 и имеет стоимость 40.
Путь r130-r131 имеет стоимость 10 на уровне 1, а на уровне 2 возвращает src/dst not found. В объединённой топологии путь r130-r15 имеет стоимость 50, но ни на одном отдельном уровне он не существует, потому что пересекает границу уровней.
Команда
# Конфигурация маршрутизаторов не меняется: ограничьте запрос уровнем 1 или 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'}
Что проверить Факты для подтверждения перед продолжением
- Для графа, сохранённого до появления раздельных данных по уровням, явный параметр
levelвозвращает HTTP 422 и кодisis_level_calculation_unavailable. Такой граф нужно загрузить заново.
19. Разбор причин отказа CSPF
Проверяйте поле reason, когда CSPF возвращает пустой path. В запросе на 20 Мбит/с оба конечных узла известны, но ни один путь не располагает достаточной пропускной способностью. Для запроса уровня 1 между r130 и r15 нет топологии выбранного уровня, содержащей оба узла.
При невыполнимом ограничении пропускной способности возвращается 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'}
Что проверить Факты для подтверждения перед продолжением
- В первом случае нужно ослабить ограничение по пропускной способности, административной группе или SRLG. Второй ответ означает, что выбранная топология не содержит обоих конечных узлов.
20. Моделирование изменения одного ребра
Смоделируйте работы на канале r100-r110, не меняя конфигурацию маршрутизаторов. На отдельной копии графа добавьте SRLG 999 к ребру уровня 2 с помощью update_edge, пересчитайте путь с исключением этой группы, а затем очистите SRLG.
Исключение SRLG 999 заменяет путь r10 r100 r110 r14 стоимостью 30 на r10 r100 r111 r14 стоимостью 40; очистка SRLG восстанавливает исходный путь. Ребро, существующее только на уровне 2, отклоняет isis_level=1. После полной замены через replace_edge раздельные данные по уровням теряются, поэтому CSPF с явным параметром level возвращает HTTP 422.
Команда
# Выполняйте упражнение на отдельном графе: изменение сохраняется
# Сначала найдите идентификатор ребра через edges_list(include=['edge_key'])
Запрос 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полностью перезаписывает ребро. Не применяйте эти методы к эталонному графу, который должен остаться неизменным.
21. Размещение LSP с учётом остаточной пропускной способности
Последовательно создайте четыре туннеля по 4 Мбит/с между r10 и r14 и после каждого размещения проверяйте остаточную пропускную способность и выбранный путь. До размещения первого LSP на исходном пути r10 r100 r110 r14 доступен резерв 4 Мбит/с для приоритета 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 четыре туннеля RSVP-TE по 4 Мбит/с
Запрос 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 изменяет сохранённый граф.