Апгрейд NetBox 4.4 → 4.5 и кастомное поле, которое пришлось чинить в PostgreSQL руками
NetBox у меня — источник правды. Из него живёт документация по железу, из него же скрипт раз в полчаса заливает хосты в Icinga Director. Поэтому апгрейд такой штуки — это не «обновил и пошёл дальше», а «обновил и проверил всё, что к ней присосалось».
Плюс рядом лежала задача, которая на первый взгляд решается за две минуты: сменить у кастомного поля тип «выбор» на «множественный выбор». Не решается. Ни через веб-интерфейс, ни через API. Пришлось идти в базу и тащить за собой все уже записанные значения.
Обе истории — в одной статье, потому что вывод у них общий.
Что проверить до апгрейда
Мажорная версия не меняется (4.4 → 4.5), так что промежуточные остановки не нужны — с 4.4.6 идём сразу на 4.5.10. Но два системных требования подросли, и оба способны превратить получасовую задачу в вечер.
Python
В 4.5 поддерживаются 3.12, 3.13 и 3.14. Поддержка 3.10 и 3.11 прекращена.
python3 --version
Если там 3.10 или 3.11 — сначала ставим подходящий интерпретатор (на Ubuntu удобнее всего через deadsnakes PPA) и пересоздаём виртуальное окружение. Именно на этом месте чаще всего и выясняется, что апгрейд был запланирован слишком оптимистично.
PostgreSQL
Четырнадцатая версия объявлена устаревшей и будет удалена в NetBox 4.7. Рабочий минимум — 15. Сейчас на 14 всё заведётся, но обновление СУБД стоит вынести в отдельную задачу, а не пристёгивать к этой: две миграции одновременно — это две причины отката вместо одной.
Бэкап
Дамп базы и снимок директории:
sudo -u postgres pg_dump netbox | gzip > /var/backups/netbox-$(date +%F).sql.gz
sudo tar czf /var/backups/netbox-dir-$(date +%F).tar.gz /opt/netbox
Второе нужнее, чем кажется: upgrade.sh пересоздаёт venv, и если в нём жили пакеты, поставленные когда-то руками, восстанавливать их придётся по памяти.
Ломающиеся изменения, важные для автоматизации
Главная особенность этого апгрейда в том, что он ломает не интерфейс, а то, что ходит в API. Веб-морда после обновления выглядит идентично — а скрипты молча перестают работать.
| Что изменилось | Кого затрагивает |
|---|---|
Убрано получение токена в открытом виде через API, параметра ALLOW_TOKEN_RETRIEVAL больше нет | скрипты, которые вытаскивали токен программно |
Старый формат токенов объявлен устаревшим, новый — v2 с префиксом nbt_ | всё, что ходит в API |
| Изменён синтаксис GraphQL: нужны явные модификаторы lookup’ов | отчёты и дашборды на GraphQL |
Появилось отдельное разрешение render_config | учётки, дёргающие рендер конфигураций |
/api/dcim/cable-terminations/ стал доступен только на чтение | скрипты инвентаризации кабельной части |
Если ходишь в API из pynetbox — обнови его вместе с NetBox: поддержка токенов v2 появилась в 7.6.0.
У меня из всего списка выстрелили только токены. Выстрелили бы молча, если бы я не прочитал release notes заранее — синхронизация с Icinga просто перестала бы находить хосты, а узнал бы я об этом по отсутствию новых хостов в мониторинге. То есть очень нескоро.
Сам апгрейд
Проверяем, что установка из git:
ls -d /opt/netbox/.git
Если каталог на месте — дальше всё коротко:
cd /opt/netbox
sudo git fetch --tags
sudo git checkout v4.5.10
sudo ./upgrade.sh
Если системный python3 не 3.12 и новее, интерпретатор указывается явно:
PYTHON=/usr/bin/python3.12 sudo ./upgrade.sh
Скрипт пересоберёт venv, поставит зависимости, прогонит миграции и соберёт статику. Отдельно проверь local_requirements.txt: внешние пакеты вроде django-auth-ldap должны быть прописаны именно там, иначе после пересборки окружения они просто исчезнут, а LDAP-авторизация отвалится вместе с ними.
sudo systemctl restart netbox netbox-rq
sudo systemctl status netbox netbox-rq --no-pager
Что проверить сразу после
Порядок проверки — от внутреннего к внешнему:
# версия и миграции
cd /opt/netbox && source venv/bin/activate
python netbox/manage.py showmigrations | grep -c '\[ \]' # должен быть 0
# API отвечает и токен принимается
curl -s -H "Authorization: Token <TOKEN>" \
https://netbox.example.com/api/status/ | python3 -m json.tool
Дальше — то, что вокруг: скрипты синхронизации, задания в cron, вебхуки, экспорт в системы мониторинга. Их логи в первые сутки после апгрейда стоит просмотреть глазами, а не полагаться на отсутствие алертов. Отсутствие алертов на скрипт, который перестал запускаться, — это тоже тишина.
Вторая история: поле «выбор» → «множественный выбор»
Есть кастомное поле monitoring_template типа «выбор» — им размечаются виртуалки, чтобы автоматика понимала, какие проверки вешать. Понадобилось разрешить несколько значений сразу.
В веб-интерфейсе тип поля после создания не редактируется вообще. Логика разработчиков понятна: за типом стоят уже записанные данные.
Через API — тоже мимо:
curl -s -X PATCH \
-H "Authorization: Token <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"type": "multiselect"}' \
https://netbox.example.com/api/extras/custom-fields/<ID>/
Остаётся база. И здесь ключевой момент, из-за которого статья вообще написана: сменить тип мало.
Значения кастомных полей хранятся не в отдельных колонках, а в поле custom_field_data типа jsonb у каждого объекта. Для типа select там лежит строка "linux-servers", а multiselect ожидает массив ["linux-servers"]. Поменяешь только тип — получишь поле, которое NetBox не сможет ни отрисовать, ни провалидировать, а API начнёт отдавать ошибки на ровном месте.
То есть смена типа — это миграция данных. Просто её никто за тебя не написал.
Шаг 1. Найти поле
curl -s -H "Authorization: Token <TOKEN>" \
https://netbox.example.com/api/extras/custom-fields/?name=monitoring_template \
| python3 -m json.tool
Шаг 2. Сменить тип
sudo -u postgres psql netbox
SELECT id, name, type FROM extras_customfield WHERE name = 'monitoring_template';
UPDATE extras_customfield SET type = 'multiselect' WHERE name = 'monitoring_template';
Если есть сомнения в формате значения — посмотри, что вообще встречается в этой колонке:
SELECT DISTINCT type FROM extras_customfield;
Шаг 3. Найти все таблицы с данными
Поле может быть привязано к нескольким типам объектов, и у каждого своя таблица. Список даёт такой запрос:
SELECT cf.name, ct.app_label, ct.model
FROM extras_customfield cf
JOIN extras_customfield_object_types cfot ON cf.id = cfot.customfield_id
JOIN django_content_type ct ON cfot.contenttype_id = ct.id
WHERE cf.name = 'monitoring_template';
Читается прямолинейно: dcim + device → таблица dcim_device, virtualization + virtualmachine → virtualization_virtualmachine. Следующий шаг выполняется для каждой строки из этого вывода. Пропустишь одну — получишь половину объектов со строками вместо массивов, и вылезет это не сразу.
Шаг 4. Конвертировать строки в массивы
Сначала смотрим, что получится, ничего не меняя. Запрос намеренно повторяет условия будущего UPDATE — так видно ровно те строки, которые попадут под изменение:
SELECT id,
custom_field_data->>'monitoring_template' AS old_value,
to_jsonb(ARRAY[custom_field_data->>'monitoring_template']) AS new_value
FROM virtualization_virtualmachine
WHERE custom_field_data ? 'monitoring_template'
AND custom_field_data->>'monitoring_template' IS NOT NULL
AND jsonb_typeof(custom_field_data->'monitoring_template') != 'array'
LIMIT 5;
В new_value должно быть ["linux-servers"]. Убедился — применяем:
BEGIN;
UPDATE virtualization_virtualmachine
SET custom_field_data = jsonb_set(
custom_field_data,
'{monitoring_template}',
to_jsonb(ARRAY[custom_field_data->>'monitoring_template'])
)
WHERE custom_field_data ? 'monitoring_template'
AND custom_field_data->>'monitoring_template' IS NOT NULL
AND jsonb_typeof(custom_field_data->'monitoring_template') != 'array';
COMMIT;
Условие jsonb_typeof(...) != 'array' делает запрос идемпотентным: повторный прогон не завернёт массив в массив. Это важнее, чем кажется, когда таблиц пять и ты уже сбился со счёта, какие прошёл.
Проверка результата:
SELECT id, custom_field_data->'monitoring_template'
FROM virtualization_virtualmachine
WHERE custom_field_data ? 'monitoring_template'
LIMIT 5;
И перезапуск, чтобы NetBox перечитал определение поля:
sudo systemctl restart netbox netbox-rq
После этого поле в интерфейсе становится множественным, старые значения на месте, а автоматика, которая его читает, продолжает работать — при условии, что она умеет получать список, а не строку. Свои скрипты на этом месте тоже стоит перепроверить: у меня один из них ожидал строку и начал бы молча сравнивать её с массивом.
Чек-лист
Короткий вариант, чтобы не перечитывать всю статью:
-
python3 --version— 3.12 и новее - версия PostgreSQL — 15 и новее (или задача на обновление СУБД поставлена)
- дамп базы и архив
/opt/netboxсделаны - внешние пакеты прописаны в
local_requirements.txt - release notes прочитаны в части «что убрали», а не «что добавили»
- после апгрейда: миграции применены,
/api/status/отвечает, токен принимается - скрипты, cron и вебхуки проверены по логам, а не по отсутствию алертов
- при смене типа поля: тип изменён, все таблицы из
django_content_typeобработаны, данные сконвертированы, сервис перезапущен
Мораль
Первое. В NetBox тип кастомного поля — не метаданные, а контракт на формат уже записанных данных. Поэтому интерфейс и не даёт его менять: он не знает, что делать с тем, что вы уже нащёлкали. Меняя тип руками, вы берёте миграцию данных на себя целиком.
Второе. BEGIN перед UPDATE в чужой схеме — не паранойя, а норма. Предпросмотр тем же условием, что и будущий UPDATE, стоит тридцати секунд и снимает половину риска.
Третье. Обновление NetBox ломает не веб-интерфейс, а то, что ходит в API. Читать release notes надо начиная с раздела «что удалено» — там живут все неприятные сюрпризы, и все они проявляются молча.
#netbox #postgresql #python #автоматизация