Почему API NetBox отдаёт порты только одного коммутатора, хотя в интерфейсе виден весь стек Virtual Chassis
Открываете master-устройство стека Virtual Chassis в вебе NetBox — видите интерфейсы всех членов стека. Дёргаете `/api/dcim/interfaces/?device_id=` того же устройства — а там только его собственные порты. Это не баг API, а осознанное изменение фильтрации с версии 3.6. Разбираю механику и правильные фильтры для всего стека.
Разница между тем, что видно в UI, и тем, что отдаёт стандартный фильтр API
VirtualChassis в NetBox объединяет несколько физических Device с общей плоскостью управления — типичный пример — стек коммутаторов доступа, которые администрируются как одно логическое устройство. Каждому участнику назначается позиция в стеке и, опционально, приоритет; один из участников назначается master — именно на него обычно смотрят при внедрении NetBox как на «лицо» всего стека.
На странице интерфейсов master-устройства NetBox показывает интерфейсы всех участников стека — это подтверждено официальной документацией дословно: при определённом master-устройстве «интерфейсы всех участников VC отображаются при переходе на страницу его интерфейсов», за одним исключением — management-only интерфейсы, принадлежащие другим участникам, в этот список не включаются. Такое поведение задаётся отдельным внутренним механизмом рендеринга страницы устройства, а не тем же фильтром, что стоит за REST API.
Ровно здесь и возникает путаница у тех, кто автоматизирует работу с NetBox: страница master-устройства в UI агрегирует интерфейсы всей стойки, а вызов GET /api/dcim/interfaces/?device_id=<id_master> — начиная с версии 3.6 — отдаёт только собственные физические интерфейсы этого конкретного устройства, без соседей по стеку. UI и стандартный фильтр API просто ведут себя по-разному, и это разное поведение официально задокументировано, а не результат недоработки.
Психологически ловушка усиливается тем, что до версии 3.6 фильтр по master-устройству действительно мог возвращать более широкий набор данных — то есть у части администраторов есть личный опыт, когда именно так это и работало на более старой версии NetBox. Когда после апгрейда поведение меняется, а HTTP-код ответа остаётся 200 и структура JSON та же самая, ничего не сигнализирует о том, что изменился именно объём выборки, а не формат. Похожая по духу история — пакет виден в tcpdump, но Linux его всё равно отбрасывает из-за rp_filter: один инструмент показывает полную картину, другой — часть, и оба технически правы, просто смотрят на разные срезы.
- UI: страница интерфейсов master-устройства агрегирует интерфейсы всех участников VC (кроме management-only соседей).
- API: фильтр device/device_id с версии 3.6 отдаёт только интерфейсы указанного конкретного устройства.
- Расхождение — задокументированное поведение, не баг.
Как и почему изменилась фильтрация в NetBox 3.6
До версии 3.6 (релиз от 30 августа 2023 года) фильтры device и device_id для интерфейсов вели себя не так однозначно: в обсуждении #8679 на GitHub пользователь показывал, что фильтр по master-устройству стека возвращает интерфейсы сразу нескольких членов стека, а по рядовому члену — только его собственные. Участники обсуждения объяснили механику: на master-устройстве внутренний метод vc_interfaces возвращает все интерфейсы стека, а на обычном члене — только его порты. Позже в issue #11478 зафиксировали обратный перекос: API по рядовому члену тоже начал отдавать интерфейсы всего стека, хотя страница этого устройства в вебе показывала только локальные порты.
В релизе 3.6.0 это поведение сознательно сделали строгим и предсказуемым (issue #11478 — «restore default behavior for device filter»): с этой версии фильтры device и device_id для интерфейсов больше не включают интерфейсы соседей по Virtual Chassis — то есть ведут себя одинаково что для master, что для рядового члена стека, всегда возвращая только собственные интерфейсы указанного устройства. Формулировка из релиза прямая: «фильтр device и device_id для интерфейсов больше не будет включать интерфейсы соседей по virtual chassis» — это официально помеченное breaking change для тех, кто уже писал автоматизацию поверх старого поведения.
Взамен агрегирующего поведения в том же релизе 3.6.0 добавили два новых, явных фильтра: virtual_chassis_member и virtual_chassis_member_id. Они работают ровно так, как раньше интуитивно ожидали от device_id на master-устройстве: подбирают интерфейсы всех участников virtual chassis указанного устройства (кроме management-only интерфейсов соседей), независимо от того, master оно или рядовой член. Отдельно в 3.6.0 закэшировали количество участников каждого virtual chassis — эта оптимизация к фильтрации API отношения не имеет, но ускоряет саму работу со стеками в целом.
- До 3.6: поведение фильтров device/device_id для VC было неоднозначным (#8679, #11478).
- С 3.6.0 (30.08.2023): device/device_id — всегда только собственные интерфейсы указанного устройства, без соседей.
- С 3.6.0 добавлены virtual_chassis_member и virtual_chassis_member_id — специально для получения всего стека.
- Изменение официально помечено как breaking change в релизе.
Как правильно получить интерфейсы всего стека через API
Если нужно получить все интерфейсы стека независимо от того, какое устройство в нём указано, используется virtual_chassis_member_id с ID любого устройства этого стека — он вернёт интерфейсы этого устройства плюс интерфейсы остальных участников. Одна оговорка из исходников NetBox: management-only интерфейсы других участников этот фильтр, как и страница master в вебе, не включает. Второй нюанс — пагинация: по умолчанию API отдаёт 50 объектов на страницу, поэтому считать порты по длине results нельзя, смотрите на count или поднимайте limit:
curl -s -H "Authorization: Token $NETBOX_TOKEN" \
"https://netbox.example.com/api/dcim/interfaces/?virtual_chassis_member_id=142&limit=1000" | jq '.count'Параметр virtual_chassis_member принимает имя устройства вместо ID — поведение то же самое, просто другой способ адресации. Если ID самого объекта VirtualChassis уже известен, на том же эндпоинте /api/dcim/interfaces/ работает фильтр virtual_chassis_id (или virtual_chassis по имени стека): он отбирает интерфейсы всех устройств этого стека, включая management-only. А в актуальных версиях в фильтрсете интерфейсов есть и virtual_chassis_member_or_master_id — он повторяет логику страницы в вебе: весь стек, только если указанное устройство является master, иначе — лишь его собственные порты. Но для задачи «дай мне все порты стека, зная ID одного из коммутаторов» я беру именно virtual_chassis_member_id: не нужно отдельным запросом узнавать ID стека, и результат не зависит от того, кто сейчас master.
Второй рабочий вариант — вообще не фильтровать по устройству, а запросить интерфейсы напрямую у объекта VirtualChassis через /api/dcim/virtual-chassis/<id>/, но в актуальном REST API этот эндпоинт не отдаёт вложенный список интерфейсов напрямую в теле ответа — он используется только для получения атрибутов самого стека (master, домен, участники). Поэтому для интерфейсов правильный путь — именно фильтр virtual_chassis_member_id на эндпоинте /api/dcim/interfaces/, а не попытка получить их через объект стека.
Для скриптов, которым дальше нужно сгруппировать интерфейсы по физическому устройству внутри стека (например, чтобы свериться поинтерфейсно с конфигом каждого конкретного коммутатора, а не стека целиком), в каждом объекте интерфейса в ответе есть вложенное поле device с ID и именем конкретного устройства-участника — группировка через jq '.results | group_by(.device.id)' или аналогичный код на Python по ключу device.id даёт ровно то разбиение, которое раньше интуитивно ожидали получить прямо из структуры ответа по одному фильтру.
- `?virtual_chassis_member_id=<id_любого_устройства_стека>` — весь стек (кроме management-only интерфейсов соседей).
- `?virtual_chassis_member=<имя_устройства>` — то же самое, по имени вместо ID.
- `?virtual_chassis_id=<id_стека>` — все интерфейсы устройств стека, включая management-only.
- `?device_id=<id>` — только собственные интерфейсы этого конкретного устройства (с 3.6).
- Считать по полю `count`, а не по длине `results`: по умолчанию страница — 50 объектов.
Как я проверяю автоматизацию на стеках перед вводом в прод
Прежде чем встраивать выгрузку интерфейсов из NetBox в системы мониторинга или конфигурационные скрипты, я всегда завожу тестовый Virtual Chassis минимум из двух устройств и прогоняю оба запроса — по device_id и по virtual_chassis_member_id — сравнивая количество и состав результатов. Если для сценария нужен весь стек, а в коде стоит device_id, расхождение видно сразу же на тестовом стенде, а не постфактум на проде, когда мониторинг вдруг «не видит» половину портов только что введённого в эксплуатацию коммутаторного стека.
Отдельно проверяю поведение при указании ID именно master-устройства versus рядового члена: с версии 3.6 оба случая для device_id дают один и тот же тип результата — только собственные интерфейсы указанного устройства, поэтому неважно, чей именно ID передан в фильтр device_id, стек целиком он не вернёт ни в одном из случаев. Раньше (до 3.6) поведение зависело от того, master это или нет, и старые скрипты, написанные под ту версию, могли специально указывать именно master, рассчитывая на агрегацию — после апгрейда до 3.6+ такой скрипт молча начинает терять данные без единой ошибки в логах, просто возвращает меньше интерфейсов, чем раньше.
На тестовом стенде я обычно держу постоянный Virtual Chassis из трёх виртуальных устройств именно для таких регрессионных прогонов при апгрейдах NetBox — не только ради этой конкретной фильтрации, но и в целом ради того, чтобы любые изменения в моделях DCIM, которые задевают стеки и агрегацию, были видны на понятном, заранее известном наборе данных, а не искались постфактум по продовым отчётам. Такой тестовый стенд обычно появляется ещё на этапе внедрения NetBox для документации сети — и потом просто продолжает жить рядом с продом именно для подобных регрессионных проверок при апгрейдах.
Именно поэтому я советую при апгрейде NetBox через версию 3.6 отдельно проверять весь код, который читает /api/dcim/interfaces/ с фильтром по устройству в контексте Virtual Chassis — это тихий breaking change, он не бросает ошибку API, а просто меняет объём данных в ответе.
То же самое касается не только собственных скриптов, но и сторонних интеграций — плагинов синхронизации с системами мониторинга, экспортёров в Zabbix или LibreNMS, кастомных отчётов. Если такая интеграция писалась под версию NetBox до 3.6 и с тех пор не пересматривалась, я закладываю отдельный пункт в план апгрейда: прогнать её на тестовом стенде со стеками Virtual Chassis и сравнить количество строк в выгрузке до и после — благо это быстрая проверка, а последствия тихой потери данных обычно обнаруживаются далеко не сразу.
- Тестовый стек минимум из 2 устройств — сравнить device_id и virtual_chassis_member_id до прод-внедрения.
- С 3.6+ не важно, master или член стека — device_id всегда даёт только собственные интерфейсы.
- При апгрейде через версию 3.6 отдельно проверять код, читающий interfaces по device_id для VC-стеков — тихий breaking change без ошибок в логах.
Кейс: «HR-Компас», 9 рабочих мест — мониторинг видел только половину портов стека
У кадрового консалтинга «HR-Компас» (9 рабочих мест, один офис) стек из двух 24-портовых коммутаторов доступа был заведён в NetBox как Virtual Chassis, и на него завязан скрипт, который раз в сутки выгружал список интерфейсов через API для сверки с реальной конфигурацией сети (простейший compliance-чек: сколько портов занято, какие VLAN на них назначены). Скрипт запрашивал /api/dcim/interfaces/?device_id=<id>, передавая ID master-устройства стека — так его написали ещё под версию NetBox 3.4, когда это действительно возвращало более широкий набор данных.
После планового апгрейда NetBox до 4.x (переход через 3.6 в их случае прошёл в рамках цепочки минорных обновлений) отчёт скрипта резко «похудел»: вместо ожидаемых 48 интерфейсов (два коммутатора по 24 порта) стал показывать 24 — ровно интерфейсы одного master-устройства. Ошибок не было, HTTP 200, просто меньше данных. Заметили не сразу — через полторы недели, когда в отчёте не оказалось нового подключения, которое администратор точно настраивал на одном из member-коммутаторов.
Правка заняла одну строку: заменили device_id на virtual_chassis_member_id в запросе скрипта. Заодно поправили вторую, более старую ошибку: скрипт считал длину results в первой странице ответа, а API NetBox по умолчанию отдаёт 50 объектов на страницу — на стеке крупнее он бы снова «потерял» порты, теперь скрипт берёт поле count и проходит по ссылке next. После этого отчёт сразу стал показывать все 48 интерфейсов стека, ежедневная сверка снова видела полную картину. Заодно я предложил клиенту добавить в скрипт простую проверку-инвариант: сверять фактическое число полученных интерфейсов с ожидаемым количеством портов по паспорту устройств стека, и писать предупреждение, если числа расходятся — чтобы подобная тихая деградация данных не терялась в логах ещё раз. Ту же идею регулярной сверки фактического состояния с ожидаемым я закладываю и в бэкап настроек сетевого оборудования: без периодической проверки «а совпадает ли снятое с реальностью» расхождения обнаруживаются только по факту аварии.
Отдельно я прогнал через UI ручную проверку: открыл страницу master-устройства этого стека, пересчитал видимые интерфейсы глазами — 48, совпало с ожиданием. Это подтвердило, что данные в самом NetBox были в порядке всё это время, страдала только точка доступа к ним из скрипта. Ситуация типичная: когда отчёт «худеет», первая мысль — что-то потерялось в источнике данных, хотя на деле проблема почти всегда в том, как именно к этим данным обращаются.
- Стек из 2 коммутаторов по 24 порта — ожидалось 48 интерфейсов в выгрузке.
- После апгрейда через 3.6 скрипт с device_id стал возвращать только 24 (интерфейсы master).
- Проблему заметили через полторы недели — по отсутствию известного подключения в отчёте.
- Правка: device_id → virtual_chassis_member_id, одна строка кода.
- Добавлена проверка-инвариант: фактическое число интерфейсов против ожидаемого по паспорту.
Частые вопросы
С какой версии NetBox фильтр device/device_id перестал включать интерфейсы соседей по Virtual Chassis?
С версии 3.6.0, релиз от 30 августа 2023 года. Изменение официально помечено как breaking change в релиз-заметках.
Какой фильтр использовать, чтобы получить интерфейсы всего стека Virtual Chassis?
`virtual_chassis_member_id=<id устройства>` или `virtual_chassis_member=<имя устройства>` на эндпоинте `/api/dcim/interfaces/` — оба возвращают интерфейсы всех участников стека, кроме management-only интерфейсов соседей. Если нужны и они, фильтруйте по `virtual_chassis_id` самого стека.
Почему в вебе NetBox на странице master-устройства видно все порты стека, а через API — нет?
Страница устройства в UI использует отдельный механизм агрегации для отображения интерфейсов всех участников VC (кроме management-only соседей), а стандартный фильтр API device/device_id с версии 3.6 намеренно этой агрегации не делает.
Наш скрипт написан под NetBox 3.4 и использует device_id для master-устройства — что будет после апгрейда?
После апгрейда через версию 3.6 он начнёт молча возвращать меньше данных — только интерфейсы самого master, без ошибок в логах. Нужно заранее заменить фильтр на virtual_chassis_member_id.
Есть ли способ получить список интерфейсов стека прямо из объекта VirtualChassis в API?
Нет, эндпоинт /api/dcim/virtual-chassis/<id>/ отдаёт атрибуты самого стека (master, домен, участников), но не вложенный список интерфейсов — интерфейсы нужно запрашивать отдельно через /api/dcim/interfaces/ с фильтром virtual_chassis_member_id.
Источники
- NetBox Documentation — Virtual Chassis — Формулировка: интерфейсы всех участников VC отображаются на странице master-устройства, кроме management-only интерфейсов других участников: https://netboxlabs.com/docs/netbox/en/stable/models/dcim/virtualchassis/
- GitHub — netbox-community/netbox Discussion #8679 — Поведение фильтра device для master и рядового члена стека до 3.6 (вывод vc_interfaces на master), ссылка на issue #11478: https://github.com/netbox-community/netbox/discussions/8679
- NetBox Labs Blog — NetBox v3.6.0 Released — Точная формулировка breaking change (device/device_id больше не включают интерфейсы VC-соседей) и добавление фильтров virtual_chassis_member/virtual_chassis_member_id, дата релиза 30.08.2023: https://netboxlabs.com/blog/netbox-v360-released/
- NetBox v3.6 release notes и исходники dcim/filtersets.py — Breaking change 3.6.0 (2023-08-30) про device/device_id, #11478; реализация virtual_chassis_member(_id), virtual_chassis_member_or_master(_id) и Device.vc_interfaces (исключение mgmt_only соседей): https://github.com/netbox-community/netbox/blob/main/docs/release-notes/version-3.6.md



