netbox-docker: почему плагин не грузится в worker
АйТи Фреш
Linux, Docker и DevOps

Как обновлять netbox-docker с плагинами, чтобы они загрузились и в веб-приложении, и в worker

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

Если после обновления netbox-docker плагин виден в интерфейсе, а фоновые задачи в очереди падают с ModuleNotFoundError — вы почти наверняка забыли прописать образ с плагином отдельно для сервиса netbox-worker. Ниже — как собирается такой образ, почему override.yml обманывает интуицию, и как я поднимаю обновление, чтобы оба контейнера были на одном образе.

Симптом: плагин работает в UI, но фоновые задачи падают

Классика после обновления netbox-docker: заходишь в веб-интерфейс — плагин на месте, пункт меню есть, модель видна в API. Радуешься рано. Стоит запустить что-то, что уходит в очередь через RQ — синхронизацию, вебхук, отложенный скрипт из плагина, — и job падает. В логах netbox-worker вылезает ModuleNotFoundError с именем пакета плагина, которого там как будто и не ставили. При этом в логах контейнера netbox то же самое имя модуля прекрасно импортируется.

Первая реакция — пересобрать образ ещё раз, «на всякий случай». Помогает редко: если проблема в конфигурации compose-файлов, пересборка образа ничего не изменит, потому что сам образ собирается нормально. Дело не в сборке, а в том, какой образ фактически подставляется контейнеру netbox-worker при старте — а это вопрос не Dockerfile, а docker-compose.override.yml. Эта путаница — типичный пункт из чек-листа, который я прохожу при внедрении NetBox с плагинами клиенту: интерфейс обманчиво зелёный, а фон не работает.

Я трижды видел эту историю у разных клиентов с самостоятельно поддерживаемым netbox-docker, и один раз — свежий, у книжного магазина «Читальный зал» (24 рабочих места), про который расскажу ниже. Во всех случаях причина одна и та же: override-файл переопределяет образ только для сервиса netbox, а netbox-worker остаётся на старом, community-образе без плагина.

Как netbox-docker вообще собирает образ с плагином

Официальный способ добавить плагин в netbox-docker — собственный Docker-образ поверх community-образа, а не установка пакета внутрь работающего контейнера. Это прямо прописано в wiki проекта: плагины — это Python-пакеты, и раз их нет в официальном образе netboxcommunity/netbox, их нужно установить на этапе сборки, чтобы изменения переживали пересоздание контейнера.

Схема из wiki простая. Заводите файл plugin_requirements.txt со списком PyPI-пакетов плагинов, по одному на строку (например, netbox-secrets). Рядом — Dockerfile-Plugins:

FROM netboxcommunity/netbox:latest

COPY ./plugin_requirements.txt /opt/netbox/
RUN /usr/local/bin/uv pip install -r /opt/netbox/plugin_requirements.txt

COPY configuration/configuration.py /etc/netbox/config/configuration.py
COPY configuration/plugins.py /etc/netbox/config/plugins.py
RUN DEBUG="true" SECRET_KEY="dummydummydummydummydummydummydummydummydummydummy" \
    /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input

Обратите внимание на пакетный менеджер: актуальные образы netbox-docker ставят зависимости через uv, а не через голый pip — если в вашем Dockerfile ещё pip install, это не ошибка, но лишний слой без ускорения, которое даёт uv.

Отдельный источник путаницы — имя пакета в plugin_requirements.txt почти всегда не совпадает с именем модуля, которое нужно прописать в PLUGINS внутри configuration/plugins.py. PyPI-пакет netbox-secrets (с дефисом) импортируется как netbox_secrets (с подчёркиванием):

PLUGINS = ["netbox_secrets"]

Перечитайте README конкретного плагина: если название модуля не совпадёт, NetBox не найдёт плагин при старте — и это отдельная, более простая проблема, которую легко спутать с той, что разбираю в этой статье.

Почему обновление молча ломает именно worker

Чтобы понять механику, нужно заглянуть в официальный docker-compose.yml проекта netbox-docker. Там сервис netbox-worker не описывает свой образ явно — он наследует его у сервиса netbox через YAML-якорь:

services:
  netbox: &netbox
    image: docker.io/netboxcommunity/netbox:${VERSION-v4.7-5.1.1}
    depends_on: [ postgres, redis, redis-cache ]

  netbox-worker:
    <<: *netbox
    depends_on:
      netbox: { condition: service_healthy }
    command:
      - /opt/netbox/venv/bin/python
      - /opt/netbox/netbox/manage.py
      - rqworker

Внутри одного YAML-файла это удобно: поменяли image: у netbox — worker автоматически получил тот же образ, потому что <<: *netbox разворачивается ещё на этапе разбора файла.

Проблема в том, что якоря YAML не переживают границу между файлами. docker-compose.override.yml — отдельный документ, и он ничего не знает про якорь *netbox, объявленный в базовом docker-compose.yml. Docker Compose сначала независимо разбирает каждый файл (внутри базового файла якорь уже разворачивается в конкретную строку с community-образом), и только потом склеивает получившиеся секции services по ключам. Если ваш override.yml переопределяет image: только у сервиса netbox, для netbox-worker в итоговой конфигурации так и останется значение, которое уже было подставлено якорем в базовом файле — то есть старый образ без плагина.

Отсюда и симптом: netbox стартует с образом из override.yml (с плагином), а netbox-worker — с образом из докер-компоуз-релиза (без плагина). Веб-приложение и очередь задач работают на двух разных образах NetBox, хотя оба контейнера подняты одной и той же командой docker compose up -d из одной и той же папки. Это не баг Compose — это ожидаемое поведение слияния YAML-файлов, которое просто не совпадает с интуитивным «оно же одно приложение».

Схема слияния docker-compose.yml и override.yml, показывающая почему netbox-worker остаётся на старом образе без плагина
Правило простое: что не прописано явно в override.yml для каждого сервиса — не гарантировано вообще.

Как писать override.yml, чтобы плагин был в обоих контейнерах

Официальная рекомендация wiki netbox-docker — явно прописать один и тот же образ для обоих сервисов в override-файле, а не полагаться на наследование из базового файла:

services:
  netbox:
    image: netbox:latest-plugins
    pull_policy: never
    ports:
      - "8000:8080"
    build:
      context: .
      dockerfile: Dockerfile-Plugins
  netbox-worker:
    image: netbox:latest-plugins
    pull_policy: never

Ключевых деталей здесь две. Во-первых, у обоих сервисов должно стоять ровно одно и то же значение image: — то, которое получает локально собранный образ. Во-вторых, pull_policy: never — без него Compose при следующем docker compose pull попытается утянуть образ с таким же именем из реестра, ничего не найдёт (или найдёт чужой публичный образ с совпадающим тегом) и либо упадёт с ошибкой, либо подставит не то, что вы собирали.

Секция build: нужна только у одного сервиса — обычно у netbox, потому что Compose использует её, чтобы понять, из какого контекста и Dockerfile собирать образ по команде docker compose build. Второй сервис (netbox-worker) просто ссылается на уже собранный тег по имени image:, без своей секции build: — дублировать её незачем, инструкции сборки от этого не изменятся, а рассинхронизация Dockerfile между сервисами исключена по определению одного тега.

Если у вас есть третий сервис на этом же образе, правило то же самое. В старых релизах netbox-docker был отдельный netbox-housekeeping для периодической чистки; в текущем docker-compose.yml ветки release (тег v4.7-5.1.1) его уже нет — только netbox и netbox-worker, но в унаследованных override-файлах он встречается до сих пор, и даже wiki по плагинам всё ещё упоминает его в пояснении. Любой сервис, которому нужен плагин, должен явно получить image: netbox:latest-plugins и pull_policy: never в override.yml. Не полагайтесь на то, что «он и так на одном образе с netbox» — если это не прописано явно в файле, где вы вносите изменение, оно не гарантировано.

Сравнение неправильного и правильного docker-compose.override.yml для netbox и netbox-worker с плагинами
Явно продублированная строка image: в override.yml — самая дешёвая страховка от расхождения контейнеров.

Порядок действий при обновлении

Обновление версии NetBox в такой схеме — это не просто docker compose pull. Community-образ тянуть некуда, вы собираете свой. Порядок такой: сначала поднимаете версию базового образа прямо в строке FROM файла Dockerfile-Plugins (например, FROM netboxcommunity/netbox:v4.7-5.1.1 вместо latest из wiki), затем пересобираете локальный образ, и только потом перезапускаете сервисы. Переменная VERSION из .env здесь не поможет: она подставляется только в image: базового docker-compose.yml, а в FROM вашего Dockerfile её никто не передаёт, если вы сами не завели ARG:

docker compose build --no-cache
docker compose up -d

Флаг --no-cache не обязателен на каждое обновление, но я ставлю его всегда. Если строка FROM и plugin_requirements.txt не менялись, Docker возьмёт слой uv pip install из кеша и новая версия плагина с PyPI просто не приедет; при FROM netboxcommunity/netbox:latest без --pull можно к тому же собрать образ на старом локальном базовом слое.

После docker compose up -d конфигурация обновится в базе автоматически — netbox-docker запускает manage.py migrate при старте контейнера netbox. Но это не отменяет проверку: зайдите в оба контейнера и сверьте версии.

docker compose exec netbox uv pip show netbox-secrets
docker compose exec netbox-worker uv pip show netbox-secrets
docker compose exec netbox-worker /opt/netbox/venv/bin/python -c "import netbox_secrets"

Обычного pip в venv образа нет — зависимости ставятся через uv, поэтому pip show внутри контейнера ответит «command not found», а не версией. Если uv pip show показал одинаковую версию в обоих контейнерах и импорт модуля прошёл без ModuleNotFoundError — образ действительно один и тот же на обоих сервисах. Дополнительно сверьте docker compose images — там видно, какой image ID фактически запущен у каждого сервиса; совпадающий ID для netbox и netbox-worker — это и есть подтверждение, которое я всегда смотрю первым при разборе подобных жалоб.

Отдельно стоит сказать про обсуждение на GitHub (discussion #1077 в netbox-docker, сентябрь 2023 года): пользователь поставил NetBox с плагинами по wiki, попытался обновиться через docker compose build --no-cache и up -d, получил ошибку сборки, откатился из бэкапа и попросил дописать в wiki раздел про обновление. На момент подготовки статьи в треде нет ни одного ответа, а wiki так и описывает только первичную установку: это не единичная путаница, а реальный пробел в документации проекта. Держите в голове, что стандартный docker compose pull && docker compose up -d из инструкций «для чистого NetBox» на схему с собственным образом не переносится буквально — тянуть у вас нечего, нужно собирать. Та же логика «своего образа вместо голого pull» касается и порядка сервисов на одном Docker-хосте в целом: чем больше у вас кастомных сборок, тем важнее явно фиксировать версии и образы в compose-файлах, а не полагаться на умолчания.

Кейс: книжный магазин «Читальный зал», 24 рабочих места

У клиента NetBox используется для учёта сетевого оборудования пяти торговых точек и склада — свитчи, точки доступа, принтеры чеков, кассовые терминалы с их портами и патч-панелями. Инфраструктуру заводили в NetBox по той же логике, что я обычно описываю в материале про документацию инфраструктуры вместо десятка Excel-файлов — с самого начала с прицелом на плагины. Из плагинов — netbox-secrets, туда вынесены пароли доступа к свитчам и учётки Wi-Fi точек, чтобы не держать их в отдельной таблице в облаке. После планового обновления NetBox с 4.5 на 4.6 инженер клиента заметил, что записи в секретах видны в интерфейсе, но скрипт синхронизации, который раз в сутки выгружает список активных секретов во внутренний бэкап-скрипт через плагинный API-хук на стороне worker, перестал отрабатывать — задача в очереди RQ падала.

Разбор занял около часа: сначала проверили, что пакет плагина в принципе установлен — uv pip show netbox-secrets внутри контейнера netbox отработал нормально, версия свежая. Тот же вызов внутри netbox-worker выдал «пакет не найден». Посмотрели override.yml клиента — там был прописан кастомный образ только для сервиса netbox, netbox-worker был скопирован из старого шаблона без секции image. Судя по всему, файл собирали по инструкции из старой статьи, где netbox-worker ещё не выделяли отдельно (плагин был установлен один раз вручную и пересборки образа с тех пор не делали).

Исправление — добавить netbox-worker в override.yml с тем же image: netbox:latest-plugins и pull_policy: never, пересобрать образ и поднять сервисы заново. Задача синхронизации отработала в первую же ночь. С тех пор в чек-лист обновления клиента добавлен один пункт: после docker compose up -d обязательно сверять docker compose images | grep netbox — оба сервиса netbox и netbox-worker должны показывать одинаковый Image ID, иначе обновление считается неполным вне зависимости от того, что показал веб-интерфейс.

Чек-лист из пяти шагов для безопасного обновления netbox-docker с собственным образом и плагинами
Обновление считается завершённым не тогда, когда открылся интерфейс, а когда Image ID совпали на всех сервисах.

Что я проверяю перед каждым обновлением netbox-docker с плагинами

Помимо синхронизации образов, я всегда смотрю на связку версий: у netbox-docker собственная схема тегов вида vX.Y-A.B.C, где первая часть — версия самого NetBox, а вторая — версия обвязки netbox-docker. Плагин, который вы тестировали на NetBox 4.5, не обязан быть совместим с 4.6 или 4.7 без изменений — большинство плагинов в README прямо указывают диапазон поддерживаемых версий NetBox. Перед обновлением базового образа в Dockerfile-Plugins стоит свериться с этим диапазоном, а не полагаться на то, что «плагин же не менялся».

Второй пункт чек-листа — резервная копия перед пересборкой. Раз обновление требует пересборки локального образа и обычно совпадает с обновлением самого NetBox (со своими миграциями базы), это тот же по рискам процесс, что и обычный апгрейд — с той разницей, что у вас есть ещё и локальный Dockerfile, который тоже может не собраться на новой версии образа (например, если в новом релизе поменялся путь до manage.py или изменился менеджер пакетов). Снимок тома PostgreSQL и файла .env перед началом работ занимает пару минут и снимает половину рисков, если пересборка пойдёт не по плану — та же проблема том остаётся, а база создаётся заново регулярно всплывает при небрежной пересборке образов на голом Docker без снятого снапшота.

Отдельно я всегда напоминаю клиентам, которые заводят NetBox с нуля: без порядка в самих сервисах Docker-хоста плагины — не единственный источник путаницы между контейнерами. Если у вас на том же хосте крутится ещё десяток внутренних сервисов, навести порядок в них до того, как множится число собственных образов с индивидуальными override-файлами, — отдельная и не менее важная задача, чем сама схема плагинов NetBox.

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

Плагин пропал из интерфейса после `docker compose pull` — почему?

Скорее всего без `pull_policy: never` Compose попытался взять одноимённый тег из реестра вместо локально собранного, либо override.yml не подхватился и сервисы поднялись на community-образе. При схеме с собственным Dockerfile нужен `pull_policy: never` в override.yml на оба сервиса — netbox и netbox-worker — и обновление через `docker compose build`, а не через `pull`.

Достаточно ли прописать плагин в PLUGINS, если он уже в образе?

Нет, это независимые шаги: пакет должен быть установлен на этапе сборки образа (plugin_requirements.txt) и отдельно включён в конфигурации (PLUGINS в plugins.py). Без первого шага второй ни к чему не приведёт — NetBox не найдёт модуль.

Почему имя в plugin_requirements.txt и в PLUGINS разное?

Первое — имя пакета PyPI (там может быть дефис), второе — имя импортируемого Python-модуля (обычно с подчёркиванием вместо дефиса). Уточняйте точное имя модуля в README конкретного плагина — оно не всегда совпадает механической заменой дефиса на подчёркивание.

Нужна ли секция build: у сервиса netbox-worker?

Нет. Достаточно одной секции build у сервиса netbox — именно она используется командой docker compose build. Сервису netbox-worker достаточно того же image:, он получит уже собранный тег.

Как быстро проверить, что оба сервиса реально на одном образе?

Команда docker compose images покажет Image ID для каждого сервиса — у netbox и netbox-worker (и у любого другого сервиса на этом образе) они должны совпадать. Разные ID — верный признак незавершённого обновления.

Обязательно ли собирать образ самому, нельзя ли поставить плагин через pip внутрь контейнера?

Можно, но изменения пропадут при следующем docker compose up -d или pull — сборка образа как раз и нужна, чтобы плагин переживал пересоздание контейнера. Официальная wiki рекомендует именно путь с собственным Dockerfile.

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

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

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

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

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

Источники

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