RTP DesportoNuno Tavares abre triunfo da Lazio com golaço frente ao MonzaESPN DeportesJags, sin puntos en última jugada de la primera parte y Philly mantiene la ventajaESPN😈 Oklahoma's troll of Texas leads top jabs of CFB Week 6PunchUkrainians will decide their next leader, Zelensky tells TrumpThe Jerusalem PostShin Bet to establish Islamic studies program to address Oct. 7 intelligence failuresInquirerCaloocan LGU offers free mental health services to constituentsBollywood HungamaArjun Rampal begins his new journey with Billionaire on the auspicious occasion of NavratriZDF heuteAktuelle Pressemitteilungen des ZDFCBS NewsDemocrat Chris Pappas says "we're taking nothing for granted" in N.H. raceScreen RantBox Office: Blumhouse Triumphs Over 2nd Straight Oscarbait DisappointmentVilaWebZelenski desmenteix la treva de Trump: “Si ha negociat amb Putin, que ens digui quan comença”VanguardINEC displays final list of 15 Abia gov candidates
The Daily Newsstand · Free, Always
Sunday, October 11, 2026

Фабрика EVPN-VXLAN, которая выглядит исправной и не передаёт ни одного пакета

Translate

Почти все статьи про EVPN-VXLAN заканчиваются одинаково: автор показывает вывод show evpn database, в нём видны MAC-адреса соседнего листа, и дальше идёт фраза вроде «фабрика работает». Я собрал такую фабрику на свободных образах Junos, получил ровно этот вывод — и не смог передать между хостами ни одного пакета.

Разбор занял вечер и оказался полезнее, чем сама сборка. Ниже — как поднять фабрику дома на обычном десктопе под Windows, почему show evpn database ничего не доказывает, и как мерить отказы так, чтобы числам можно было верить. Всё воспроизводится: конфиги и скрипты в репозитории, ссылка в конце.

Что понадобится и чего не понадобится

Juniper раздаёт виртуальные образы Junos бесплатно — без лицензии и без ограничения по времени, условие одно: непродакшен и низкий трафик. Образов несколько, и выбрать правильный оказалось первым нетривиальным шагом.

Очевидный кандидат — vJunos-switch, виртуальный EX. Он не подошёл, и причина не в ресурсах. В требованиях прямым текстом:

vJunos-switch is not supported on EVE-NG or any other deployments that launch vJunos from within a VM due to the constraints of deeply nested virtualization.

Архитектура у него унаследована от vMX: внутри виртуалки уже вложены VCP и VFP. Третьего слоя вложенности он не тянет. А у меня хост — Windows, то есть containerlab живёт в WSL2, а WSL2 — это виртуальная машина. Мимо. Там же, в требованиях, указан только Intel VT-x, а у меня Ryzen.

Подошёл cJunosEvolved — контейнерный Junos OS Evolved, эмулирующий транспортные PTX: PTX10001-36MR на чипсете Express 4 (флавор BT) или PTX10002-36QDD на Express 5 (BX). В его требованиях написано ровно то, чего не хватало:

any x86 processor (Intel or AMD) with VT-x capability

и отдельно — что containerlab является поддерживаемым способом развёртывания. Цена вопроса: 8 ГБ памяти и 4 ядра на узел против 5 ГБ у vJunos-switch, плюс 20 ГБ диска на контейнер в рантайме с ростом до 40.

Отдельная мелочь, которая экономит полчаса: через поиск по сайту эти продукты не находятся. Juniper сам об этом предупреждает и даёт прямые ссылки. Для cJunosEvolved это support.juniper.net/support/downloads/?p=cjunos-evolved. Файл cJunosEvolved-26.2R1.7-EVO.tar.gz весит 1822 МБ и после docker load разворачивается в образ на 4.55 ГБ.

Железо у меня такое: Ryzen 5 5600X (6 ядер / 12 потоков), 32 ГБ, Windows 11.

Хост под Windows: что проверить до начала

cJunosEvolved — это KVM-виртуалка внутри контейнера, значит внутри WSL2 обязан быть /dev/kvm. Проверяется до всего остального:

ls -l /dev/kvm
kvm-ok
# INFO: /dev/kvm exists
# KVM acceleration can be used

У меня он оказался на месте, и флаг svm прокинут в гостя — то есть вложенная виртуализация на AMD в WSL2 работает. Этого факта я не нашёл ни в одной инструкции: все они написаны под bare-metal Linux на Intel.

Дистрибутив я поставил отдельный и сразу на другой диск, чтобы не забивать системный:

wsl --install -d Ubuntu-24.04 --no-launch --location F:\WSL\clab --name clab

В .wslconfig подняты лимиты — по умолчанию WSL2 берёт половину памяти хоста, на два узла по 8 ГБ этого не хватит:

[wsl2]
memory=26GB
processors=12
nestedVirtualization=true
vmIdleTimeout=-1

Про vmIdleTimeout=-1 скажу отдельно, потому что без него я потерял первый собранный стенд. WSL гасит дистрибутив, когда в нём не остаётся ни одного процесса, и уносит с собой докер вместе со всеми контейнерами. Выглядит это так: containerlab отчитался об успешном развёртывании, вы выходите из шелла, возвращаетесь — а docker ps пуст, и ничего в логах. Лечится этой строкой плюс любым живым процессом внутри.

Докер нужен не ниже 28.0.0, и это не придирка. В документации Juniper отдельное предупреждение: на более старых версиях интерфейсы подключаются к контейнеру не в том порядке, и трафик между узлами просто не идёт. Отказ, разумеется, немой. Я ставил из официального репозитория, приехал 29.9.0.

Сборка фабрики

Топология стенда

Топология стенда

Топология простейшая: два листа спина к спине, по хосту за каждым. Андерлей — eBGP по прямому линку, оверлей — eBGP между loopback’ами с family evpn signaling, поверх MAC-VRF с VNI 10010.

name: evpn2
topology:
  kinds:
    juniper_cjunosevolved:
      image: cjunosevolved:26.2R1.7-EVO
  nodes:
    leaf1:
      kind: juniper_cjunosevolved
      startup-config: leaf1.conf
    leaf2:
      kind: juniper_cjunosevolved
      startup-config: leaf2.conf
    h1: { kind: linux, image: alpine:3.20 }
    h2: { kind: linux, image: alpine:3.20 }
  links:
    - endpoints: ["leaf1:eth4", "leaf2:eth4"]
    - endpoints: ["leaf1:eth5", "h1:eth1"]
    - endpoints: ["leaf2:eth5", "h2:eth1"]

Здесь первая ловушка для тех, кто привык к Junos. В топологии имена линуксовые, в конфиге — джуниперовские. eth0 — менеджмент, eth1–eth3 зарезервированы и трогать их нельзя, а eth4 и дальше соответствуют et-0/0/0 и дальше. То есть линк описывается как leaf1:eth4, а адрес на него вешается на et-0/0/0.

Конфигурация листа, сокращённо:

interfaces {
    et-0/0/0 { unit 0 { family inet { address 10.255.0.0/31; } } }
    et-0/0/1 {
        flexible-vlan-tagging;
        encapsulation extended-vlan-bridge;
        unit 10 { vlan-id 10; }
    }
    lo0 { unit 0 { family inet { address 10.0.0.1/32; } } }
}
routing-instances {
    MACVRF1 {
        instance-type mac-vrf;
        service-type vlan-based;
        protocols { evpn { encapsulation vxlan; } }
        vtep-source-interface lo0.0;
        route-distinguisher 10.0.0.1:10;
        vrf-target target:65000:10;
        vlans {
            v10 {
                vlan-id 10;
                interface et-0/0/1.10;
                vxlan { vni 10010; }
            }
        }
    }
}

Две мелочи, на которых я споткнулся при вводе конфигурации. Первая: интерфейс привязывается не к инстансу, а к VLAN внутри него, иначе получите EVPN: Interface et-0/0/1.10 must be added in a bridge-domain/vlan. Вторая: как только трогаешь стансу system, commit требует root-authentication, которого в образе нет. Предупреждение про отсутствующую лицензию BGP можно игнорировать, Juniper об этом пишет прямо.

Сколько это грузится

Отдельный пункт, потому что ожидания тут ломаются об реальность:

Событие

Время

containerlab deploy вернул управление

6 секунд

Загрузка ВМ внутри контейнера (из лога)

220 секунд

CLI реально ответил

814 секунд

containerlab через шесть секунд рисует красивую таблицу со статусом running — и это правда, контейнер запущен. Но до момента, когда с устройством можно разговаривать, проходит почти четырнадцать минут. В документации containerlab написано «около 5 минут»; у меня втрое больше, скорее всего из-за переподписки CPU. Любой скрипт, который после deploy сразу лезет настраивать, развалится.

И ещё: docker exec <node> cli -c "show version" не работает — отдаёт приглашение и молчит. Потому что внутри контейнера крутится виртуалка, а cli в контейнере лишь обёртка над её консолью. Работает обычный SSH на адрес, который выдал containerlab, с учёткой admin / admin@123. Многострочные конфиги прекрасно заходят туда heredoc’ом.

Фабрика собралась. Трафика нет

Теперь то, ради чего всё это писалось.

Контрол-плейн: обе сессии Establ, удалённый MAC через vtep, база EVPN заполнена

Контрол-плейн: обе сессии Establ, удалённый MAC через vtep, база EVPN заполнена

Всё, что принято показывать как доказательство работоспособности фабрики.

BGP поднялся с обеих сторон:

Peer                AS      InPkt   OutPkt  Last Up/Dwn  State
10.0.0.2         65002          7        6         1:28  Establ
  bgp.evpn.0: 1/1/1/0
10.255.0.1       65002          7        5         1:39  Establ
  inet.0: 1/1/1/0

Маршруты EVPN разошлись в обе стороны — и type-2 с MAC-адресами хостов, и type-3 для BUM-трафика. В базе EVPN на втором листе честно лежит MAC хоста из-за первого, с правильным IP:

Instance: MACVRF1
VLAN  DomainId  MAC address        Active source   IP address
     10010      aa:c1:ab:7e:89:07  10.0.0.1        10.10.10.1

MAC-таблица знает удалённый адрес и указывает на туннель:

   Vlan    MAC                 MAC flags   Logical interface    Active source
   v10     aa:c1:ab:27:09:e8   DR          vtep-54.32770        10.0.0.2
   v10     aa:c1:ab:7e:89:07   D           et-0/0/1.10

Туннельные эндпоинты на месте, VNI 10010 присутствует, loopback’и пингуются за 2 миллисекунды. По всем признакам, которые обычно приводят в статьях как доказательство, фабрика работает.

А ping между хостами даёт сто процентов потерь.

Причём таблица соседей на хосте при этом выглядит здоровой — MAC второго хоста разрешён, запись в состоянии REACHABLE. То есть обманывает не только вывод с коммутаторов: и на самом хосте всё в порядке, просто трафик не ходит. (В другом прогоне того же стенда ARP не разрешался и запись висела в INCOMPLETE — от чего зависит, я не выяснил, и выдавать догадку за объяснение не буду.)

Сто процентов потерь при живом ARP

Сто процентов потерь при живом ARP

И при этом между хостами не проходит ничего.

Разбор

Отказ немой: ни ошибки, ни записи в логе, ни единицы в счётчиках drops. Поэтому единственный способ — идти по пути пакета с tcpdump и смотреть, где он исчезает.

Три точки захвата

Три точки захвата

Шаг первый, отправитель. Слушаем андерлейный интерфейс со стороны первого листа:

13:40:40.119661 IP 10.0.0.1.56839 > 10.0.0.2.4789: VXLAN, flags [I] (0x08), vni 10010
IP 10.10.10.1 > 10.10.10.2: ICMP echo request, id 184, seq 1, length 64
13:40:40.626062 IP 10.0.0.1.56839 > 10.0.0.2.4789: VXLAN, flags [I] (0x08), vni 10010
IP 10.10.10.1 > 10.10.10.2: ICMP echo request, id 184, seq 2, length 64
4 packets captured

Инкапсуляция работает. Эхо-запрос завёрнут в VXLAN с правильным VNI и отправлен правильному соседу.

Шаг второй, получатель. Тот же линк со стороны второго листа:

13:40:40.119663 IP 10.0.0.1.56839 > 10.0.0.2.4789: VXLAN, flags [I] (0x08), vni 10010
IP 10.10.10.1 > 10.10.10.2: ICMP echo request, id 184, seq 1, length 64
4 packets captured

Те же пакеты, с точностью до микросекунд. Сеть между узлами ни при чём.

Шаг третий, хост за получателем. Ноль.

listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
0 packets captured
0 packets received by filter
0 packets dropped by kernel
Три захвата подряд: уходит, приходит, не доходит

Три захвата подряд: уходит, приходит, не доходит

То есть пакет приходит на оконечную точку туннеля и умирает в ней. Симметрично в обе стороны. Конфигурация при этом валидная, контрол-плейн идеальный, счётчики чистые.

Разгадка нашлась в списке ограничений cJunosEvolved — одной фразой среди прочих:

For cJunosEvolved to function correctly as a VXLAN Tunnel End Point (VTEP), you must configure tunnel termination using the set forwarding-options tunnel termination command. Otherwise, the traffic in the tunnel drops on the egress VTEP.

Лечение — одна строка на каждый узел, применяется на живую:

set forwarding-options tunnel-termination

Сразу после commit пинг идёт: 0% потерь, 1.1 мс.

Починка в одну строку на каждом узле и сразу живой пинг

Починка в одну строку на каждом узле и сразу живой пинг

Да, это особенность конкретного виртуального образа, а не Junos вообще. Но поучительна здесь не сама строка, а форма отказа. Весь контрол-плейн рапортовал успех. Все команды, которые принято приводить как доказательство работоспособности фабрики, показывали именно то, что должны показывать. Если бы я верил выводу show evpn database, я бы ещё долго искал проблему в BGP и в политиках импорта.

Как мерить отказы

Теперь, когда фабрика передаёт трафик, можно заняться тем, ради чего виртуальные лабы вообще поднимают, — ломать её и смотреть на часы.

Первый вопрос — чем мерить. Ответ «пингом» не годится: ping шлёт пакет раз в секунду, то есть провал короче секунды он не увидит в принципе, а более длинный округлит до целых секунд. Для сетевых отказов это всё равно что мерить спринт настенным календарём.

Я написал простую мерилку: UDP-поток с порядковым номером в каждом пакете, на приёмнике считается разрыв в последовательности и, главное, разрыв во времени между соседними пришедшими пакетами. При 1000 pps это даёт разрешение около миллисекунды.

Тут надо сразу оговорить потолок. В ограничениях cJunosEvolved написано: максимум 2000 pps и 3–5 Мбит/с суммарно по всем интерфейсам. Значит 1000 pps — это половина паспортной способности симулятора, выше лезть бессмысленно: будешь мерить эмулятор, а не сеть.

И мерилку тоже пришлось чинить дважды, о чём честно скажу, потому что обе ошибки типовые.

Первая: отправитель врал о своей скорости. Наивная пауза между пакетами через time.sleep() при интервале в миллисекунду даёт не 1000 пакетов в секунду, а около 740 — накладные расходы самого системного вызова съедают заметную часть бюджета. Лечится так: спим, только пока до следующей отправки есть запас, а последние полторы миллисекунды дожидаемся в цикле. И обязательно печатаем достигнутую скорость: инструмент, который врёт о собственной скорости, хуже отсутствия инструмента.

Вторая: приёмник умирал во время измеряемого простоя. Он завершался по таймауту тишины — и честно завершался ровно тогда, когда начинался отказ, который и надо было измерить. Правильно — работать фиксированное окно по часам, заведомо длиннее планируемого простоя.

Первые числа

Базовая линия. Фабрика в покое, 1000 pps, 10 секунд:

sent 10001 packets in 10.00s = 1000 pps (requested 1000)
received 10001, lost 0, loss 0.0%

Потерь нет. Худший разрыв между соседними пакетами, если отбросить артефакт на самом конце потока, — около 7 миллисекунд. Это и есть разрешающая способность стенда: провал короче десятка миллисекунд достоверно не различается.

Отказ линка, попытка первая. Гасим андерлейный интерфейс штатно, через deactivate и commit:

простой 26132.9 мс, потеряно 25911 пакетов

Число мусорное, и вот почему. Команда на выключение ушла на 8-й секунде потока. Трафик встал на 10.7-й. commit вернул управление на 15.2-й. commit на этой платформе занимает около семи секунд — и что из этого считать моментом отказа, непонятно.

Отсюда методический вывод, который я считаю главным в этой части: инструмент управления нельзя использовать как источник времени события. Если замер привязан к commit, вы измеряете латентность commit, а не сходимость сети.

Отказ линка, попытка вторая. Гасим линк мгновенно, со стороны Linux, не трогая Junos вообще:

линк погашен на 10.171 с, поднят на 20.324 с
последний пакет до разрыва  seq 10004  = 10.004 с
первый пакет после разрыва  seq 20158  = 20.158 с
простой 10109 мс, потеряно 10153 пакета

Простой совпал с тем временем, на которое линк был погашен: 10.11 секунды против 10.15 административного простоя. То есть трафик возобновился сразу, как только линк вернулся, — в пределах погрешности стенда.

Что из этого следует

Результат выглядит скучно ровно до того момента, пока не задашь вопрос: а почему восстановление мгновенное?

Потому что восстанавливать было нечего. За десять секунд отсутствия линка контрол-плейн не заметил ничего. Дефолтный hold-time BGP — девяносто секунд, сессия спокойно пережила обрыв, маршруты никто не отзывал, VTEP никто не перепрограммировал. Линк вернулся — пакеты пошли.

А теперь обратная сторона той же медали, и это то, ради чего стоит поднимать такие стенды. Будь отказ не временным, чёрная дыра длилась бы не десять секунд, а до истечения hold-time. И всё это время show bgp summary показывал бы честный Establ, а трафик уходил бы в никуда.

Девяносто секунд по умолчанию — это не «настройка по умолчанию». Это мина, и обезвреживают её не тюнингом таймеров ради тюнинга, а пониманием, какой именно вид отказа вы собираетесь переживать: пропадание линка, падение соседа или тихую смерть форвардинга при живой сессии. Это три разные истории с разным временем реакции, и дефолт закрывает из них ровно одну.

Честные границы этого стенда

Виртуальный образ — не железо, и выдавать его миллисекунды за характеристики PTX было бы враньём. В ограничениях cJunosEvolved прямо сказано, что не поддерживаются «QoS и протоколы с агрессивными (sub-second) таймерами, включая BFD». Для BT-флавора туда же — MC-LAG, VRRP и IPv6 в андерлее и оверлее.

Поэтому всё, что здесь измерено, — это механика и порядок величин: что отказ бывает разных видов, что разница между ними принципиальная, и откуда берётся девяностосекундная дыра. Абсолютные числа на вашем железе будут другими. Но вопрос, который стоит задать своей сети, от этого не меняется.

Что дальше

Главный замер — отказ не линка, а узла — на топологии из двух листьев сделать нельзя: убивая второй лист, убиваешь и получателя. Нужен резервный путь: либо второй линк и ECMP в андерлее, либо хост, подключённый к обоим листьям через ESI-LAG. Это следующая часть, и там же — сравнение видов отказа в одной таблице.

Топологию, конфиги листьев, скрипты сборки и мерилку выложу отдельным репозиторием — ссылку оставлю в комментариях к статье.

Если соберётесь повторять — закладывайте по 8 ГБ памяти на узел, около получаса на первую загрузку и то самое tunnel-termination сразу, чтобы не повторять мой вечер.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.