NetBox: почему нельзя менять disk у VM с VirtualDisk
АйТи Фреш
Linux, Docker и DevOps

Почему в NetBox нельзя вручную поменять общий объём диска VM после добавления VirtualDisk

Автор: , директор ООО «АйТи-Фреш» · · ~14 мин чтения
Общий объём диска виртуальной машины в NetBox как сумма отдельных VirtualDisk, которую нельзя выставить вручную
Общий disk виртуальной машины — это сумма дисков, а не отдельное число, которое можно вписать вручную.

Если в NetBox у виртуальной машины появились отдельные объекты VirtualDisk, поле общего объёма диска (disk) перестаёт быть тем, чем было раньше — это не отдельное значение, а агрегат суммы всех дисков, и NetBox проверяет соответствие при каждом сохранении. Разбираю точный механизм по исходному коду модели и что с этим делать интеграции, которая пишет и то и другое.

Как было раньше и что изменилось с VirtualDisk

До версии 3.7 у модели VirtualMachine было одно числовое поле disk — «сколько всего гигабайт диска у этой машины», без разбивки по отдельным дискам. Учтите и единицы: до NetBox 4.1 поле disk у VM и size у VirtualDisk хранились в гигабайтах, а с 4.1 — в мегабайтах (при обновлении значения пересчитываются автоматически, а вот старые скрипты, которые шлют «40» вместо «40000», — нет). Для учёта виртуализации в DCIM/IPAM-системе NetBox этого какое-то время хватало, но у многодисковых машин (система на одном диске, данные на другом) такая модель теряла важную информацию: сколько дисков, какого они размера, как называются.

NetBox v3.7.0, вышедший 29 декабря 2023 года, добавил отдельную модель VirtualDisk именно для раздельного учёта — по мотивам issue #8356 в официальном репозитории. С этого момента у виртуальной машины может быть произвольное число объектов VirtualDisk, каждый со своим именем и размером, а старое поле disk у самой VM никуда не делось — но поменяло смысл своей работы, как только у машины появляется хотя бы один VirtualDisk.

Для меня как для человека, который внедряет NetBox клиентам без штатного DevOps-отдела, это ровно тот случай, когда «мелкое» изменение модели данных ломает работающие интеграции, хотя в release notes 3.7 оно описано как новая функция, а не в разделе Breaking Changes. Формально обратная совместимость сохранена — старое поле работает, если не заводить VirtualDisk. Но как только администратор один раз воспользуется новой функцией через интерфейс, поведение API для этой конкретной машины меняется, и об этом нужно знать заранее, а не выяснять на проде после первого сбойного PATCH-запроса.

Точные поля модели VirtualDisk

Я специально сверял поля по исходникам модели в основной ветке репозитория netbox-community/netbox, а не по пересказам — там VirtualDisk наследуется от абстрактной ComponentModel (общей для всех дочерних объектов виртуальной машины, включая интерфейсы) и добавляет своё поле размера: - nameCharField, максимум 64 символа, с натуральной сортировкой; должно быть уникальным в пределах назначенной виртуальной машины (в модели это отдельный UniqueConstraint на пару virtual_machine + name); - descriptionCharField, максимум 200 символов, необязательное; - virtual_machine — обязательная внешняя ссылка на VM, on_delete=CASCADE (диск удаляется вместе с машиной); - sizePositiveIntegerField, выделенный размер диска в мегабайтах (в 3.7–4.0 — в гигабайтах, единица сменилась в 4.1).

Через REST API объекты доступны по эндпоинту /api/virtualization/virtual-disks/ — это отдельный роутер VirtualDiskViewSet, зарегистрированный под именем virtual-disks в virtualization/api/urls.py. Такое же разделение на «объект и его компоненты со своим API» я закладываю и в общую модель документирования инфраструктуры на NetBox для небольшой компании — там же описано, с каких объектов вообще стоит начинать учёт, если DCIM в компании только внедряется. Никаких скрытых полей вроде типа диска (thin/thick) или интерфейса подключения (SCSI/IDE) в базовой модели VirtualDisk нет — если такая детализация нужна, для неё используют custom fields, а не ждут её из коробки.

Через ComponentModel модель VirtualDisk наследует NetBoxModel — то есть у каждого диска, как и у большинства объектов NetBox, есть поля created/last_updated, теги, custom fields и записи в журнале изменений (changelog) наравне с другими сущностями. А TrackingModelMixin, который тоже есть в объявлении класса, нужен для другого — он отслеживает изменённые поля и поддерживает счётчик virtual_disk_count у виртуальной машины. Это удобно, когда нужно понять, кто и когда добавил конкретный диск руками через интерфейс, а не через синхронизацию — обычно это и есть первый шаг диагностики конфликта, о котором я расскажу дальше.

Схема: поле disk виртуальной машины в NetBox автоматически суммирует размер всех привязанных объектов VirtualDisk
disk у VM — это витрина суммы дисков, а не отдельное значение для ручного ввода.

Что реально происходит с полем disk у VirtualMachine

Здесь — самое важное для тех, кто пишет интеграцию. Поле disk у VirtualMachine формально остаётся обычным PositiveIntegerField, допускающим blank=True, null=True — то есть технически его можно передать в запросе на изменение. Но при сохранении объекта в методе clean() модели выполняется отдельная проверка: если у машины есть привязанные VirtualDisk, NetBox считает их суммарный размер агрегатным запросом self.virtualdisks.aggregate(Sum('size', default=0)) и сравнивает с переданным значением disk.

Дальше срабатывает одно из двух: если поле disk в запросе не указано (None), NetBox сам подставляет туда посчитанную сумму — заполняет автоматически. А если вы передали конкретное число, которое не совпадает с суммой размеров дисков, сохранение падает с ошибкой валидации. Формулировка в исходниках дословная: "The specified disk size ({size}) must match the aggregate size of assigned virtual disks ({total_size})." — то есть указанный размер диска должен совпадать с агрегированным размером назначенных виртуальных дисков.

Поэтому «поменять вручную» в интерфейсе или через API не получится не потому, что поле физически заблокировано (в схеме это не read-only флаг), а потому что любое значение, отличное от суммы дисков, отклоняется валидацией при попытке сохранить объект. Единственный способ реально изменить итоговый disk у VM с VirtualDisk — поменять размер одного из дисков в самой модели VirtualDisk, либо добавить/удалить диск; агрегат в VirtualMachine.disk пересчитается сам — за это отвечает сигнал update_virtualmachine_disk в virtualization/signals.py, который срабатывает на post_save и post_delete каждого VirtualDisk и сразу пишет новую сумму в поле VM. Обратная сторона: если удалить у машины последний VirtualDisk, сумма по пустому набору даёт null, и поле disk у VM станет пустым — его придётся заполнить заново вручную.

В веб-интерфейсе это видно ещё нагляднее, чем через API: в форме редактирования VM поле disk физически блокируется, если у машины уже есть хотя бы один VirtualDisk — в исходниках формы это прямо прописано: if self.instance.virtualdisks.exists(): self.fields['disk'].widget.attrs['disabled'] = True, а рядом с полем появляется подсказка «Disk size is managed via the attachment of virtual disks» (размер диска управляется через привязку виртуальных дисков). Через API же блокировки на уровне HTML-виджета нет — есть только серверная валидация, поэтому там несовпадающее число просто вернёт ошибку в момент запроса, а не спрячет поле заранее.

Проверка агрегата срабатывает только для уже существующих объектов (`if not self._state.adding`) — то есть при создании новой VM это ограничение не действует, оно вступает в силу, когда вы редактируете уже сохранённую машину, у которой есть привязанные VirtualDisk.

Типичная ошибка интеграции: пишем оба поля одновременно

Самый частый источник тикетов «NetBox не даёт поменять размер диска» — это не ручное редактирование через веб-интерфейс, а автоматическая синхронизация из гипервизора (Proxmox, VMware) или системы мониторинга, которая по старой привычке обновляет disk у VM отдельным запросом, не зная, что у той же машины уже есть объекты VirtualDisk. Если такая интеграция продолжает писать общий объём напрямую, она рано или поздно передаст число, не совпадающее с суммой дисков — и получит 400 Bad Request с той самой ошибкой про несовпадение агрегата.

Правильный путь для интеграции, которая хочет пользоваться преимуществами VirtualDisk — прекратить писать disk у VirtualMachine напрямую и вместо этого создавать/обновлять записи через /api/virtualization/virtual-disks/, передавая virtual_machine, name и size для каждого физического или виртуального диска машины. Поле disk тогда трогать вообще не нужно — NetBox обновит его агрегатом сам — сразу после сохранения или удаления любого VirtualDisk, без отдельного запроса к VM.

Если же переписывать логику интеграции пока нет ресурсов и хочется временно оставить учёт «одной цифрой», как было до 3.7 — тоже можно: NetBox сохраняет обратную совместимость, и если у VM вообще нет привязанных VirtualDisk, поле disk редактируется свободно, как раньше, без всякой валидации агрегата. Проблема возникает именно в смешанном состоянии — когда часть машин учтена по-старому, а часть уже получила VirtualDisk вручную через интерфейс, и общий скрипт синхронизации не делает разницы между ними.

Сравнение двух режимов учёта дисков виртуальной машины в NetBox: одно поле disk или отдельные объекты VirtualDisk
Выбирайте один режим учёта дисков на машину — смешение почти всегда ломает синхронизацию.

Как проектировать синхронизацию с гипервизором правильно

Когда я подключаю к NetBox выгрузку дисков из Proxmox или vCenter, первым делом решаю: система будет вести учёт дисков либо целиком через VirtualDisk (по одной записи на каждый виртуальный диск гостя), либо целиком через старое поле disk одной суммой — но не вперемешку. Смешанный режим почти гарантированно даёт конфликты валидации на части машин, где кто-то из администраторов уже вручную добавил VirtualDisk через интерфейс, не зная, что синхронизация ожидает единое поле. Этот же принцип — не мешать в одной интеграции две разные модели данных — я применял, когда переносил виртуальные машины с VMware на Proxmox: учёт ресурсов проще выстроить с нуля по правилам новой платформы, чем пытаться скрестить старую схему выгрузки с новой моделью хранения.

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

Ещё один практический совет для тех, кто уже поймал ошибку валидации на проде: не пытайтесь чинить её ретраем с тем же значением disk — оно не изменится само. Сначала запросом к /api/virtualization/virtual-disks/?virtual_machine_id=<id> смотрите, какие диски уже привязаны к машине и какова их сумма, и только потом решаете, нужно ли синхронизировать сами диски или достаточно убрать поле disk из тела запроса синхронизации вовсе.

Кейс: театральная школа «Маска и рампа», расхождение после переезда на Proxmox

В театральной школе «Маска и рампа» (17 рабочих мест) NetBox использовался для учёта серверной инфраструктуры после перехода с VMware на Proxmox. Штатный скрипт синхронизации, написанный ещё под старую версию NetBox, при каждом запуске обновлял поле disk у виртуальных машин напрямую через PATCH-запрос к /api/virtualization/virtual-machines/{id}/, считая суммарный объём дисков на стороне Proxmox и передавая готовое число.

После того как администратор школы вручную завёл несколько объектов VirtualDisk для машины с базой данных 1С (чтобы разделить системный диск и диск с базой в отчётности), синхронизация на этой одной машине начала падать с ошибкой валидации — сумма дисков в VirtualDisk не совпадала с тем, что скрипт пытался записать в поле disk напрямую, потому что скрипт считал объём по-своему, не читая уже существующие VirtualDisk.

Решение заняло меньше дня: я переписал шаг синхронизации так, чтобы для машин с уже существующими VirtualDisk скрипт обновлял записи в /api/virtualization/virtual-disks/ по каждому диску отдельно (сопоставляя диски гипервизора с VirtualDisk по имени), а поле disk у самой VM вообще не трогал — NetBox сам пересчитывает агрегат сигналом при сохранении каждого диска. Заодно поправил единицы: скрипт писался под NetBox 3.x и слал размер в гигабайтах, а после обновления до 4.x размеры нужно передавать в мегабайтах. Для остальных шестнадцати машин без VirtualDisk синхронизация продолжила работать по-старому, через прямое поле disk — переделывать их не было смысла, они однодисковые.

Отдельно я добавил в скрипт проверку перед стартом синхронизации: если у машины уже есть хотя бы один VirtualDisk, а список дисков из Proxmox содержит диск с именем, которого нет среди существующих VirtualDisk, синхронизация не удаляет и не пересоздаёт объекты молча, а логирует расхождение и пропускает эту машину до ручной проверки. Это осознанный компромисс — я предпочитаю, чтобы одна машина осталась неактуальной на день, чем чтобы автоматика тихо удалила вручную заведённый администратором диск с историей изменений.

Цифры кейса театральной школы: конфликт синхронизации NetBox из-за VirtualDisk устранён за один день
Разделили логику синхронизации по режиму учёта — и конфликт исчез.

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

Можно ли вообще изменить поле disk у VM, если у неё есть VirtualDisk?

Напрямую произвольным числом — нет: NetBox сравнивает переданное значение с суммой размеров привязанных VirtualDisk и отклоняет несовпадающее. Менять нужно размер конкретного VirtualDisk, а не общее поле VM.

Что произойдёт, если оставить поле disk пустым при сохранении VM с VirtualDisk?

NetBox заполнит его сам — агрегатной суммой размеров всех привязанных VirtualDisk. Явно указывать это значение не нужно; при изменении или удалении любого диска сумма пересчитывается автоматически.

С какой версии NetBox появилась модель VirtualDisk?

С версии 3.7.0, выпущенной 29 декабря 2023 года, по мотивам issue #8356 в официальном репозитории netbox-community/netbox.

Как называется API-эндпоинт для управления виртуальными дисками?

/api/virtualization/virtual-disks/ — отдельный REST-роутер VirtualDiskViewSet; диски конкретной машины фильтруются параметром ?virtual_machine_id=<id>. Размер size передаётся в мегабайтах (с NetBox 4.1).

Обязательно ли использовать VirtualDisk для всех виртуальных машин?

Нет. Если у VM нет ни одного объекта VirtualDisk, поле disk редактируется свободно, как в версиях до 3.7 — обратная совместимость сохранена. Но после удаления последнего VirtualDisk поле disk станет пустым, и его нужно заполнить заново. Смешивать оба подхода на одной машине не стоит.

Может ли имя VirtualDisk повторяться у одной машины?

Нет, в модели есть ограничение уникальности на пару virtual_machine + name — два диска одной VM не могут называться одинаково, хотя одинаковое имя у дисков разных машин допустимо.

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

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

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

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

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

Источники

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