NetBox: Config Template не видит custom field площадки
АйТи Фреш
Linux, Docker и DevOps

Почему Config Template в NetBox не видит кастомное поле площадки и как вывести его в конфигурацию

Автор: , директор ООО «АйТи-Фреш» · · ~14 мин чтения
Иллюстрация: значение custom field в NetBox заполнено в базе, но не доходит до конфигурации из-за неверного пути обращения в шаблоне
Поле заполнено в базе — пустым его делает не отсутствие данных, а неверный путь к ним в шаблоне

Если Config Template в NetBox рендерит конфигурацию устройства, а место кастомного поля площадки в ней пустое — дело почти всегда в синтаксисе Jinja2, а не в самих данных: значение реально хранится в базе, но обращение к нему через `custom_fields` возвращает не то, что вы думаете. Разбираю, как на самом деле устроен контекст рендеринга, чем `cf` отличается от `custom_fields` и `custom_field_data`, и как проверить конфигурацию одним запросом к API, не трогая продовую автоматизацию.

Симптом: конфигурация рендерится, а поле площадки в ней пустое

Типичная ситуация после того, как в компании внедрили NetBox как систему записи для сети и начали генерировать конфигурации через Config Templates: шаблон подключён к роли устройства, рендер запускается без ошибок, но в готовом конфиге на месте идентификатора площадки — либо пустая строка, либо буквально None. При этом в карточке площадки в веб-интерфейсе значение custom field видно прекрасно, оно заполнено, опечаток в названии нет. Инженер лезет в шаблон, видит там что-то вроде {{ device.site.custom_fields.site_id }}, всё выглядит логично — обратились к устройству, через него к площадке, через неё к кастомным полям, взяли нужное. И это как раз тот момент, где логика подводит.

Дело в том, что custom_fields на объекте NetBox — это не словарь значений, а атрибут, через который NetBox отдаёт связанные определения полей (то, что вы видите как список полей в разделе «Custom Fields» админки) для модели этого объекта. Значений там нет — есть структура. Обращение к .site_id на этой структуре либо падает с ошибкой в момент рендера (если шаблон строгий), либо тихо возвращает пусто, если Jinja2 в NetBox настроен прощать отсутствующие атрибуты — а он настроен именно так, поэтому симптом выглядит как «пропавшие данные», а не как ошибка синтаксиса.

Как устроен контекст рендеринга: device, virtualmachine и три способа добраться до custom field

Официальная документация по рендерингу конфигурации прямо описывает, что объект, для которого рендерится конфигурация, попадает в контекст шаблона под именем device — для физических устройств, и virtualmachine — для виртуальных машин. Дальше из этого объекта в Jinja2 доступны все связанные объекты как есть: device.site, device.device_type, device.platform, device.tenant и так далее — обычная навигация по связям, как в Python.

Для custom fields у любого объекта, который их поддерживает, NetBox даёт три разных пути, и только один из них — про значения в удобной форме. Первый — custom_field_data: это словарь сырых значений, как они физически хранятся в базе (для полей типа select там будет сохранённое значение, а не отображаемая метка). Второй — cf — обёртка над тем же самым словарём, которая отдаёт уже десериализованные значения в удобном виде: даты как объекты даты, множественный выбор как список и так далее. Третий путь — тот самый custom_fields, с которым чаще всего путают cf, но это отдельная сущность: связь с определениями полей модели, а не с их значениями на конкретном объекте.

Официальная документация по кастомным полям прямо рекомендует использовать именно cf: «для удобства объекты, которые поддерживают присвоение custom field, предоставляют доступ к данным custom field через свойство cf» — и это чище, чем работать напрямую с custom_field_data. Пример из документации предельно конкретный: кастомное поле с именем foo123 на модели Site доступно на экземпляре как {{ site.cf.foo123 }}. Обратите внимание — это slug поля (техническое имя без пробелов), а не отображаемое название («Label»), которое видно в интерфейсе; если в форме создания поля вы вводили удобочитаемое название, а slug сгенерировался автоматически с подчёркиваниями, обращаться в шаблоне нужно именно по slug.

Схема трёх способов обращения к custom field в NetBox: custom_fields, custom_field_data и рекомендованный cf
Только `cf` отдаёт значения в удобном для Jinja2 виде — два других пути ведут либо к определениям поля, либо к сырому словарю

Правильный путь до значения: обсуждение сообщества и официальный пример сходятся

Этот же вывод независимо подтверждается на практике. В обсуждении #14378 в репозитории NetBox на GitHub разбирается ровно эта ситуация — попытка достать custom field площадки через {{ device.site.custom_fields.site_id }} не даёт результата, а рабочим путём оказывается {{ device.site.cf.site_id }}. То есть цепочка навигации через связи (device → site) остаётся такой же, как и задумывалась, меняется только последнее звено — с custom_fields на cf.

На практике это значит, что при переносе custom field площадки в конфигурацию устройства правильная запись выглядит так: {{ device.site.cf.site_id }}. Если поле определено не на площадке, а прямо на устройстве — короче: {{ device.cf.site_id }}. Если название slug содержит символы, которые Jinja2 не примет как часть атрибута (например, поле было создано со slug через API и содержит нестандартные символы), можно использовать индексный синтаксис {{ device.site.cf['site_id'] }} — он равнозначен точечному обращению, но работает даже с именами, которые нельзя написать через точку.

Отдельно стоит проверить, что кастомное поле вообще назначено на нужный тип объекта. Custom field в NetBox создаётся с привязкой к одной или нескольким моделям (в разделе «Object types» при создании поля) — если поле «site_id» создано только для площадок, а шаблон пытается достать его же имя с устройства, cf на устройстве этого поля просто не будет содержать, и это снова будет выглядеть как «поле не видно», хотя на самом деле опечатки нет — не то место в объектной модели.

Как проверить рендер за один запрос, не трогая продовую автоматизацию

Самая частая ошибка при отладке — сразу лезть в связанную систему автоматизации (Ansible, скрипт разворачивания), которая дёргает конфигурацию из NetBox и применяет её на устройстве. Это удлиняет цикл проверки: правка шаблона — прогон плейбука — ожидание — снова правка. Быстрее проверить рендер напрямую через REST API NetBox, ничего не применяя.

У каждого устройства есть эндпоинт рендеринга конфигурации, и он вызывается отдельным запросом:

curl -X POST \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: text/plain" \
  https://netbox.example.com/api/dcim/devices/123/render-config/

Здесь 123 — ID устройства. Заголовок Authorization: Bearer — формат для токенов v2 (вида nbt_<ключ>.<токен>), которые появились в NetBox 4.5; старые токены v1 с заголовком Authorization: Token в 4.6–4.7 ещё принимаются, но объявлены устаревшими и уйдут в 5.0, так что новые скрипты я сразу пишу под Bearer. Для вызова нужно право render_config на устройства — его ввели в 4.5, и без него запрос вернёт отказ, даже если у токена есть право на чтение устройств (суперпользователя это ограничение не касается). Заголовок Accept управляет форматом ответа: text/plain вернёт сам текст конфигурации как есть, application/json — обёртку с полем content, удобную для встраивания в другой инструмент. По умолчанию используется Config Template, назначенный устройству (через сам device, либо унаследованный от роли/платформы — подробнее в следующем разделе); без дополнительных параметров эндпоинт рендерит именно эту, штатную цепочку разрешения шаблона. Если нужно прогнать против контекста устройства другой шаблон — например, черновик исправленной версии, — в актуальных релизах в тело запроса можно добавить config_template_id с ID нужного Config Template: назначения на устройстве и роли при этом не меняются, а для такой подмены пользователю дополнительно нужно право view на Config Templates.

В тело POST-запроса можно передать и произвольные дополнительные ключи — они попадают в контекст шаблона как переменные вместе с обычными данными объекта и его config context. Это удобно для отладки: если подозреваете, что дело не в самом custom field, а в чём-то ещё, можно временно передать заведомо корректное значение через тело запроса и посмотреть, появляется ли оно в выводе — так отделяется проблема доступа к custom field от проблемы в остальной части шаблона.

Сравнение до и после: замена custom_fields на cf возвращает значение поля площадки в конфигурацию устройства
Исправление — это одно слово в пути обращения, а не пересборка шаблона

Порядок наследования Config Template: от роли и платформы к самому устройству

Ещё один источник путаницы — какой именно шаблон реально рендерится, если их несколько. Config Template для устройства назначается на трёх уровнях: на самом устройстве, на его роли и на платформе (у типа устройства такого поля нет — частое заблуждение). Порядок разрешения документирован жёстко: сначала шаблон самого устройства, затем шаблон роли, затем шаблон платформы — берётся первый найденный. Если шаблон не назначен ни на одном из трёх объектов, запрос на рендер завершится ошибкой, а не пустым конфигом.

Отсюда практическая ошибка: инженер правит и тестирует один шаблон, а устройство на самом деле рендерит другой — потому что на конкретном устройстве десять месяцев назад кто-то один раз явно назначил Config Template вручную, и с тех пор изменения общего шаблона роли до этого устройства не доходят. Прежде чем разбираться, почему «шаблон не видит custom field», стоит открыть карточку конкретного устройства и явно посмотреть, какой шаблон у него назначен — через API это то же самое поле config_template, которое видно в ответе на GET /api/dcim/devices/{id}/.

Кейс «РемонтГрад»: пустой ID площадки во всех конфигурациях после переноса на новый шаблон

У клиента — сеть ремонтных мастерских «РемонтГрад», 27 рабочих мест, три площадки со своим сетевым оборудованием и общей учётной системой NetBox для DCIM/IPAM. Полгода назад мы переносили конфигурации коммутаторов доступа с ручных шаблонов в текстовом редакторе на Config Templates внутри NetBox — идея была в том, чтобы при вводе нового коммутатора конфигурация генерировалась автоматически, а не копипастилась из прошлого с правкой руками, где легко забыть один параметр.

После переноса рендер стал давать пустое значение там, где должен был подставляться внутренний код площадки — кастомное поле site_code, которое используется в конфигурации как часть hostname коммутатора и SNMP location. Само поле было заполнено на каждой из трёх площадок правильно, никаких опечаток. В шаблоне, унаследованном ещё от черновика, который писали в спешке, обращение выглядело как {{ device.site.custom_fields.site_code }} и встречалось трижды — в hostname, в SNMP location и в баннере входа. Работающий на вид синтаксис на деле обращался не к тому атрибуту.

Правка заняла пару минут — заменили путь на {{ device.site.cf.site_code }} и перепроверили рендер через render-config эндпоинт по всем трём площадкам до того, как выкатывать шаблон на реальные коммутаторы. Заодно проверили конфигурации остальных устройств на предмет того же паттерна .custom_fields. — нашли ещё два места в другом шаблоне (для точек доступа), где та же ошибка тихо оставляла пустым код площадки в имени SSID-профиля и в комментарии конфигурации. Итог: три площадки, пять исправленных мест в двух шаблонах (три плюс два), полный цикл проверки через API — около сорока минут вместо того, чтобы находить проблему по одной на живом коммутаторе методом проб и ошибок.

Чек-лист: если Config Template не видит custom field

Порядок, который я прохожу при разборе такой жалобы. Первое — открыть сам шаблон и найти точное обращение к полю: если там custom_fields.<name> — это почти наверняка причина, меняем на cf.<slug>. Второе — свериться со slug поля в разделе «Custom Fields» админки, а не с его отображаемым названием: Jinja2 обращается именно по техническому имени. Третье — проверить, на какой тип объекта (модель) назначено поле: если оно создано для площадок, а шаблон читает его с устройства (или наоборот), cf на неправильном объекте будет просто пустым, без ошибки.

Четвёртое — убедиться, какой именно Config Template реально назначен конкретному устройству: явное назначение на устройстве перебивает унаследованное от роли и платформы, и правка «общего» шаблона может не долетать до части парка. Пятое — проверять итог не на реальном оборудовании, а запросом к POST /api/dcim/devices/{id}/render-config/ с заголовком Accept: text/plain — это тот же самый рендер, который использует автоматизация, но без риска применить недоделанный конфиг. Такую же дисциплину — сначала проверка в изолированном контуре, потом выкладка — я держу и для регламента обновления конфигураций 1С: разница только в инструменте, а не в логике «сначала сухой прогон».

И последнее — если после исправления синтаксиса поле всё ещё пустое, значит дело не в Jinja2, а в данных: проверьте через API (GET /api/dcim/sites/{id}/ или .../devices/{id}/) поле custom_fields в самом JSON-ответе объекта — там, в отличие от шаблона, это как раз словарь текущих значений, и если там пусто — заполнение не сохранилось на самом объекте, а это уже вопрос не к шаблону, а к тому, где и как поле заполнялось. Одна оговорка для NetBox 4.7: значения полей типа selection и multiple selection в REST API теперь приходят объектом вида {"value": ..., "label": ...}, а не голой строкой. Внешние скрипты, которые читали такие поля из API, после обновления стоит перепроверить — а вот cf внутри Jinja2-шаблона работает с Python-объектами и этой смены формата ответа API не касается.

`custom_fields` на объекте NetBox — это определения полей, а не их значения. За значениями в Jinja2-шаблоне идите через `cf` (например, `device.site.cf.site_id`) или через сырой словарь `custom_field_data`; путать `cf` с `custom_fields` — самая частая причина «пропавших» кастомных полей в Config Template.

Частые вопросы

Почему `{{ device.site.custom_fields.site_id }}` не выводит значение поля?

Потому что `custom_fields` на объекте NetBox — это связь с определениями полей (структура: тип, обязательность, варианты выбора), а не со значениями. Для значений в шаблоне нужно свойство `cf`, документация NetBox описывает его именно для этого: `{{ site.cf.foo123 }}` для поля с именем `foo123`.

В чём разница между `cf` и `custom_field_data`?

`custom_field_data` — сырой словарь, как значения физически хранятся в базе. `cf` — обёртка над тем же словарём, которая отдаёт значения уже десериализованными (например, даты как объекты даты). NetBox docs называют `cf` более удобным вариантом именно для шаблонов и рекомендуют его.

Как проверить, что рендерит Config Template, не применяя конфигурацию на устройстве?

Запросом `POST /api/dcim/devices/{id}/render-config/` к REST API NetBox с заголовком `Accept: text/plain` (или `application/json`); токену нужно право `render_config`, а для проверки другого шаблона в тело можно передать `config_template_id`. Это тот же самый рендер, который использует автоматизация, но результат просто возвращается в ответе, без применения на оборудовании.

Что делать, если поле создано, заполнено, но с правильным `cf` всё равно пусто?

Проверить, на какой тип объекта назначено custom field при создании. Если поле привязано только к моделям Site, а шаблон читает его с Device (или наоборот), `cf` на неподходящем объекте не будет содержать это поле — это не баг, а вопрос того, где именно поле определено.

Какой Config Template реально используется, если он назначен и на устройстве, и на его роли?

Шаблон, явно назначенный на самом устройстве, имеет приоритет; дальше по порядку идут роль и платформа. Если шаблона нет ни на одном из трёх уровней, рендер вернёт ошибку. Если правка общего шаблона роли не долетает до конкретного устройства — сначала проверьте поле `config_template` в карточке или ответе API именно этого устройства.

Можно ли обратиться к полю по индексу, а не через точку — `cf['site_id']`?

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

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи