Tout ce qui se trouve dans ce billet a été exécuté contre un NetBox 4.7.2 posé sur mon propre bureau, avec un vrai parc chargé dedans : un site, une baie, quatorze équipements, câblés, alimentés et adressés chez trois clients. Chaque capture est cette instance, et chaque message d’erreur est un message que j’ai réellement obtenu.

L’ordre est celui dans lequel vous rencontreriez les choses. Ce qu’est la bête, pourquoi vous en voudriez une, le fork dont vous entendrez parler en une semaine de recherches, comment en monter une, l’ordre dans lequel il faut la remplir, puis ce que vous en ressortez. Le travail de personnalisation et d’extension arrive à la fin, parce que rien de tout cela n’a de sens avant d’avoir vu la forme de ce que vous étendez.

Ce qu’est NetBox

NetBox est une base de données qui a un avis très précis sur ce dont un réseau est fait, et une application web posée dessus. C’est une application Django sur PostgreSQL, elle est open source sous Apache 2.0 depuis que DigitalOcean l’a publiée en juin 2016, et le projet est aujourd’hui pris en charge par NetBox Labs aux côtés d’une équipe de mainteneurs bénévoles.12

En dessous, ce sont 149 modèles répartis sur dix applications, atteints par 146 endpoints REST et un endpoint GraphQL. Compté sur l’instance que j’ai construite pour l’occasion, et non relevé sur une page marketing :

ApplicationModèlesApplicationModèles
dcim56virtualization7
extras23tenancy6
ipam18wireless3
circuits11users7
vpn10core8

Cinquante-six d’entre eux sont du DCIM, la couche physique : sites, locations, baies, types d’équipement, équipements, et chaque sorte de port, de baie interne et de terminaison de câble qu’un équipement peut porter. Dix-huit sont de l’IPAM. Le reste couvre les liens opérateur, les tunnels et les politiques IKE, les machines virtuelles et les clusters, les liaisons sans fil, la gestion multi-client, et la machinerie qui rend l’ensemble extensible.

Le nombre n’est pas le sujet. Les jointures le sont, et le plus rapide pour le voir est l’écran pour lequel NetBox est le plus connu.

Commencez par la baie, parce que c’est l’écran qui vend la chose.

Page de détail de la baie MCR1-A07 dans NetBox, montrant à gauche la région North West, le site Manchester DC1, la location Hall 2, le statut Active et 28,6\u00a0% d’utilisation de l’espace, et à droite les élévations avant et arrière listant des panneaux de brassage en U41 et U42, deux routeurs en U38 et U39, deux commutateurs en U35 et U36 et six hyperviseurs entre U15 et U20, chacun coloré selon son rôle

Cette élévation est dessinée à partir des données, elle n’est pas téléversée. Chaque équipement y est parce que quelque chose dit qu’il occupe ces unités, orienté de cette façon, et les couleurs viennent du rôle que vous lui avez donné. L’utilisation de l’espace affiche 28,6 % parce que NetBox l’a calculée. Personne n’entretient ce chiffre.

Deux choses en découlent, et elles valent davantage que l’image.

Vous pouvez demander quelles unités sont libres et obtenir une réponse exploitable. Vous pouvez aussi réserver des unités avant que quoi que ce soit n’y soit installé, et c’est la différence entre vendre de l’espace que vous avez et vendre de l’espace que vous croyez avoir.

Ce qu’il refuse délibérément de faire

Un produit qui sait ce qu’il n’est pas est plus rare qu’un produit qui fait tout mal, et la documentation de NetBox est franche là-dessus. Il ne fournit ni supervision réseau, ni service DNS, ni RADIUS, ni gestion de configuration, ni gestion des installations techniques.1

Plus important encore, il contient l’état souhaité de votre réseau et non son état opérationnel, et la documentation dit que l’import automatisé de l’état réel du réseau est « strongly discouraged », parce que chaque enregistrement devrait d’abord être validé par un humain.1

C’est la décision sur laquelle tout le reste repose, et c’est celle que les gens contestent. L’argument est : une source de vérité devrait bien être la vérité, donc découvrez le réseau et chargez-le. La réponse est qu’un réseau découvert vous dit ce qui est là, et ce qui est là inclut toutes les erreurs que quiconque a jamais commises. Un port de commutateur laissé dans le mauvais VLAN en 2021 est un fait. Ce n’est pas une intention.

NetBox contient l’intention. Votre supervision contient la réalité. Le chiffre intéressant est l’écart entre les deux, et on ne calcule pas un écart à partir d’une seule entrée.

L’autre principe est énoncé tout aussi clairement : entre une solution relativement simple à quatre-vingts pour cent et une solution complète bien plus complexe, prenez la simple.1 Vous le sentirez la première fois que vous voudrez modéliser quelque chose qu’il ne modélise pas, et il y a vers la fin toute une série de sections sur ce qu’il faut faire à ce moment-là.

NetBox faitNetBox ne fait pas
Consigner ce qui devrait être làInterroger ce qui est là
Contenir le VLAN prévu pour un portVous dire que le port est tombé
Dire à quel client appartient un préfixeLe lui facturer
Rendre la configuration d’un équipement depuis un gabaritLa pousser sur l’équipement
Suivre le lien, l’opérateur et l’engagementSuperviser le lien
Dire dans quelle baie est un équipement, et à quelle UOuvrir l’armoire

Lisez la colonne de droite comme la liste des outils dont vous avez encore besoin. Lisez-la de travers et vous essaierez de faire de NetBox tous ces outils, et c’est ainsi qu’une source de vérité devient un système de plus auquel personne ne fait confiance.

Pourquoi il vous en faut une

Demandez à un prestataire de services managés où se trouve l’enregistrement de référence du réseau d’un client, et vous obtiendrez une réponse. Demandez séparément à deux de ses ingénieurs et vous en obtiendrez deux.

L’un montrera un tableur. L’autre montrera un schéma enregistré pour la dernière fois par quelqu’un parti en 2023. Un troisième dira que la configuration du pare-feu est la documentation, ce qui est au moins honnête, car une configuration décrit bien ce que fait une machine. Elle ne décrit simplement pas pourquoi, ni qui l’a demandé, ni lequel des quatre clients derrière cette machine paie pour la règle.

L’échec n’est jamais le jour où vous remarquez que l’enregistrement est faux. C’est le jour où quelqu’un en a besoin.

Le momentCe que vous devez produireCe que ça coûte quand vous ne pouvez pas
Une discussion de renouvellementUn détail poste par poste de ce que paie l’abonnement mensuelLe devis du concurrent est détaillé, parce qu’il est allé compter
Un ingénieur démissionneTout ce qu’il savait, écritSix mois à le redécouvrir, un ticket à la fois
Un client partUne description de son propre parcTrois semaines pour l’assembler, et une référence qu’il donnera honnêtement
Un auditeur pose une question de périmètreQuels systèmes détiennent des données personnelles, et où ils sont physiquementUn régulateur à qui on a dit quelque chose qui s’avère faux ensuite
Une migration doit être chiffréeUn décompte de ce qui est réellement làVous répondez sur une estimation et vous absorbez la différence

Rien de tout cela n’est exotique. C’est un mardi.

Ce que vous en retirez n’est pas de la documentation. La documentation est une chose qu’on écrit puis qu’on arrête d’entretenir. Ce que vous obtenez est une base qui refuse de contenir une contradiction, et qui répond à des questions que personne n’avait pensé à lui poser. Il y a plus loin dans ce billet une baie qui se révèle remplie à 28,6 pour cent de matériel et à 90,7 pour cent d’électricité. Personne n’a cherché à trouver ça. C’est tombé d’une puissance saisie une fois sur un type d’équipement.

La réserve honnête vient avec, et la dernière section en parle. Un enregistrement ne vaut que ce qu’il vous coûte de le tenir juste. Mais l’alternative est une entreprise incapable de se décrire, et le premier à s’en apercevoir est généralement un client.

L’autre : Nautobot

Vous tomberez dessus en à peu près une semaine de recherches, donc autant savoir ce qui s’est passé.

En 2021, Network to Code a forké NetBox et a appelé le résultat Nautobot. Pas un fork léger ni une distribution : un fork dur qui divergent depuis cinq ans. Ses propres notes de version v1.0 le décrivent comme « a divergent fork of NetBox 2.10 », le dépôt a été créé le 19 février 2021 et la v1.0.0 est sortie le 26 avril 2021.3

Les raisons annoncées sont sur leur propre blog et méritent d’être lues dans leurs mots plutôt que dans les miens. Trois choses l’ont motivé. Ils voulaient vendre du support entreprise : « We need to offer high-touch support models with Service Level Agreements (SLAs) we can guarantee. We need flexibility to offer Long-term Support (LTS) for customers who can’t upgrade at the pace of a fast moving open source project. » Ils voulaient que la source de vérité siège au centre d’une plateforme d’automatisation plutôt qu’elle serve la documentation. Et « there became a growing divergence in our vision about what a Source of Truth for networking should look like and how to get there ».4

À cette première raison, il faut attacher sa date, car elle a cessé d’être vraie. En février 2021, il n’y avait aucune société derrière NetBox pour vous vendre quoi que ce soit. NetBox Labs n’a été fondée qu’en 2023, en spin-out de NS1 après son acquisition par IBM, cofondée par le mainteneur principal de NetBox lui-même.5

Et ce n’est pas un tiers qui aurait bâti une activité sur le projet d’un autre. NetBox Labs est le dépositaire de NetBox : la documentation du projet dit elle-même « the open source project is stewarded by NetBox Labs and a team of volunteer maintainers ».1 Ils vendent NetBox Enterprise pour les installations autogérées, l’hébergent pour vous sous le nom de NetBox Cloud, et proposent un support 24/7.6

Donc « you cannot buy support for NetBox » était une chose juste à dire quand Network to Code a forké, et ce n’est plus une chose juste à dire aujourd’hui. Les deux projets ont derrière eux une société commerciale qui signera quelque chose, et dans le cas de NetBox cette société est celle qui prend en charge le projet.

La phrase que la plupart des gens manquent est la suivante, et c’est la raison pour laquelle ce n’est pas une histoire sordide : « the NetBox project team suggested that we should consider forking. »

Un fork ne devient guère plus civilisé. Deux groupes voulaient des choses différentes, l’ont dit sur une longue période, et se sont séparés plutôt que de se battre autour d’une seule base de code. Les deux moitiés sont toujours en Apache 2.0. Personne n’a pris quoi que ce soit qu’il n’avait pas le droit de prendre.

À quoi le fork servait vraiment

Les notes de version de Nautobot 1.0 listent ce qu’il a ajouté par rapport à NetBox 2.10, et la liste raconte le débat mieux que n’importe quel billet de blog :3

Ce que Nautobot a ajouté en 2021Où en est NetBox aujourd’hui
Prise en charge de GraphQLNetBox l’a
Intégration Git comme source de donnéesNetBox l’a, sous forme de sources de données synchronisées
Authentification uniqueNetBox l’a
SecretsNetBox l’a via un plugin
Scripts et rapports regroupés en JobsNetBox sort les scripts vers un plugin en 4.7
Custom fields sur tous les modèlesNetBox a une prise en charge large des custom fields
API de plugin de validation de donnéesNetBox a des custom validation rules
Statuts personnalisables, comme objets en baseLes statuts de NetBox restent un choice set Python
Relations définies par l’utilisateur entre modèlesNetBox n’a pas d’équivalent
Clés primaires UUIDNetBox utilise des clés entières
Améliorations de l’API de pluginLe framework de plugins de NetBox a beaucoup grandi depuis

La moitié supérieure a largement convergé. Plusieurs choses livrées par Nautobot en 2021 sont arrivées dans NetBox ensuite, et c’est généralement ce qui se produit quand deux projets résolvent les mêmes problèmes en public.

Les trois du bas n’ont pas convergé, et elles sont architecturales plutôt que cosmétiques. J’ai vérifié les deux bases de code aujourd’hui plutôt que de me fier aux notes de 2021.

Le Status de Nautobot est un modèle en base, décrit dans son propre code source comme un « Model for database-backend enum choice objects », donc un statut est une ligne que quelqu’un peut ajouter dans l’interface. Les statuts de NetBox viennent d’un choice set Python, ce qui explique pourquoi la section plus bas en ajoute un en modifiant configuration.py et en redémarrant. Nautobot a des modèles Relationship et RelationshipAssociation, vous pouvez donc définir une relation entre deux types d’objets existants sans écrire de code. La réponse de NetBox à ce problème est un plugin, et c’est la dernière série de sections de ce billet. Et les clés primaires de Nautobot sont des UUID, là où celles de NetBox sont des entiers.

Ils ont aussi fait ce pour quoi ils avaient forké. Aujourd’hui Nautobot publie 3.2.x et 2.4.x le même jour, ce qui est une véritable ligne de maintenance au long cours parallèle à la version courante, et c’était l’une des trois raisons annoncées.

Lequel des deux

Les chiffres honnêtes d’abord. NetBox a 21 625 étoiles et 3 133 forks ; Nautobot en a 1 617 et 422.7 Les deux ont reçu un push dans les deux derniers jours, les deux sont en Apache 2.0, et les deux ont désormais derrière eux une société commerciale qui vend du support et de l’hébergement.

Cet écart n’est pas un jugement sur la qualité. Il reflète cinq ans d’avance et le fait que la plupart des gens qui ont besoin d’une source de vérité trouvent NetBox en premier. Mais il décide de ce qui compte généralement plus que les fonctionnalités : combien de plugins, d’intégrations, de modules Ansible, de réponses de forum et de collègues vous trouverez pour celui que vous choisissez.

Donc : si vous voulez un inventaire et une source de vérité que d’autres systèmes lisent, et que vous voulez le plus grand écosystème et le recrutement le plus facile, la réponse est NetBox, et c’est de cela que parle le reste de ce billet. Si votre raison de vouloir une source de vérité est précisément d’en piloter de l’automatisation, ou si vous voulez que les statuts et les relations soient définis par votre équipe plutôt que par un fichier de configuration et un redémarrage, allez regarder Nautobot sérieusement avant de décider.

Ne laissez personne vous vendre l’un ou l’autre sur le seul support. Les deux camps couvrent ça désormais, et les différences qui seront encore là dans cinq ans sont celles du tableau ci-dessus.

Ce qu’il ne faut pas faire, c’est en choisir un parce que quelqu’un vous a dit que l’autre était mort. Aucun ne l’est, et les deux ont encore livré des versions cette semaine.

En mettre un en route

Deux chemins, et je les ai suivis tous les deux pour l’occasion. Les conteneurs si vous voulez que ça tourne en vingt minutes, les paquets sur un hôte si ça doit devenir porteur.

La pile de conteneurs

La communauté maintient netbox-docker, et c’est la réponse rapide :8

git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
tee docker-compose.override.yml <<'EOF'
services:
  netbox:
    ports:
      - 8000:8080
EOF
docker compose pull
docker compose up

Cela vous donne l’application, PostgreSQL, Redis et un worker d’arrière-plan, câblés ensemble. J’ai construit la même chose à la main sous podman pour voir les pièces, et les pièces méritent d’être connues parce que deux d’entre elles piègent les gens :

ConteneurCe qu’il faitSi vous l’omettez
netboxL’application Django derrière gunicornrien ne marche
postgresLa base, 15 ou plus récentrien ne marche
redis / valkeyDeux bases : une pour les tâches, une pour le cacherien ne marche
netbox-workerrqworker, qui vide la file de tâchesles webhooks ne partent jamais, les tâches de fond ne tournent jamais, et rien ne vous avertit
netbox-housekeepingLe rangement périodiqueles entrées du journal des changements n’expirent jamais

Cette quatrième ligne est la bonne. Sans worker, tout a l’air en bonne santé. Les event rules s’empilent et restent là.

Deux choses m’ont mordu au premier lancement, et aucune ne figure dans un message d’erreur que vous chercheriez.

La première migration prend longtemps. Pas une minute. Sur cette machine il en a fallu plusieurs, parce que NetBox 4.7 remplace django-mptt par ltree de PostgreSQL et reconstruit au passage chaque table hiérarchique. Le conteneur reste simplement là à appliquer des migrations. Laissez-le tranquille.

Sans API_TOKEN_PEPPERS vous ne pouvez pas créer de jeton d’API v2, et le seul signe est un avertissement dans le journal :

UserWarning: API_TOKEN_PEPPERS is not defined. v2 API tokens cannot be used.

Définissez-en au moins un, d’au moins cinquante caractères, avant d’aller chercher pourquoi l’API vous rejette.

Sur un hôte, depuis les paquets

Le chemin documenté est testé sur Ubuntu 24.04. Je l’ai mené de bout en bout sur une installation propre, et voilà où ça a atterri :

ComposantCe que 24.04 m’a donnéCe dont NetBox 4.7 a besoin
PostgreSQL16.1515 ou plus récent
Redis7.0.156.0 ou plus récent
Python3.12.33.12, 3.13 ou 3.14
Django6.1.1fourni avec NetBox
NetBoxv4.7.2

Ces minimums sont ceux de NetBox, et la 4.7 a relevé le plancher PostgreSQL comme le plancher Redis.9

Le travail entier tient en cinq étapes.

# 1. the services
sudo apt install -y postgresql redis-server
sudo -u postgres psql -c "CREATE DATABASE netbox;"
sudo -u postgres psql -c "CREATE USER netbox WITH PASSWORD 'something-you-generated';"
sudo -u postgres psql -c "ALTER DATABASE netbox OWNER TO netbox;"
sudo -u postgres psql -d netbox -c "GRANT CREATE ON SCHEMA public TO netbox;"

# 2. the build dependencies
sudo apt install -y python3 python3-pip python3-venv python3-dev build-essential   libxml2-dev libxslt1-dev libffi-dev libpq-dev libssl-dev zlib1g-dev git

# 3. the application, at a release tag rather than at main
sudo mkdir -p /opt/netbox && cd /opt/netbox
sudo git clone https://github.com/netbox-community/netbox.git .
sudo git checkout v4.7.2
sudo adduser --system --group netbox
sudo chown -R netbox /opt/netbox/netbox/media/ /opt/netbox/netbox/scripts/ /opt/netbox/netbox/reports/

# 4. the configuration: five values, no more
cd /opt/netbox/netbox/netbox/
sudo cp configuration_example.py configuration.py
python3 /opt/netbox/netbox/generate_secret_key.py   # run it twice
sudo $EDITOR configuration.py

# 5. let the upgrade script do the rest
sudo /opt/netbox/upgrade.sh

Ces cinq valeurs sont ALLOWED_HOSTS, DATABASES, REDIS, SECRET_KEY et API_TOKEN_PEPPERS. Générez les deux dernières séparément et n’utilisez pas l’une pour l’autre. Lancez donc le générateur de clé deux fois et collez les résultats sur des lignes différentes.

upgrade.sh construit l’environnement virtuel, installe chaque dépendance Python, exécute les migrations, construit la documentation pour l’usage hors ligne et collecte les fichiers statiques. Quand il termine sur une machine neuve, il affiche un avertissement qui a l’air alarmant et qui ne l’est pas :

WARNING: No existing virtual environment was detected. A new one has
been created. Update your systemd service files to reflect the new
Python and gunicorn executables. (If this is a new installation,
this warning can be ignored.)

Ensuite un superutilisateur, et ça se lance ainsi :

source /opt/netbox/venv/bin/activate
cd /opt/netbox/netbox && python3 manage.py createsuperuser

Pour quoi que ce soit de réel, mettez gunicorn devant plutôt que runserver. La configuration et les fichiers d’unité sont déjà dans le dépôt, et c’est le détail à connaître parce que les gens écrivent les leurs :

sudo cp /opt/netbox/contrib/gunicorn.py /opt/netbox/gunicorn.py
sudo cp -v /opt/netbox/contrib/*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now netbox netbox-rq

netbox.service fait tourner gunicorn sur 127.0.0.1:8001, netbox-rq.service fait tourner le worker, et nginx ou Apache se place devant pour terminer le TLS et servir /static. Deux services, et le second est le même worker dont la pile de conteneurs a besoin.

L’ordre dans lequel il faut le remplir

Un NetBox neuf est une base vide qui a des avis, et la première heure avec lui passe généralement à découvrir lesquels. Vous allez ajouter un équipement, et il ne vous laisse pas faire.

Formulaire Add a new device de NetBox, montrant Name, Device role marqué d’un astérisque rouge, Description et Tags sous un titre Device, puis Device type également marqué d’un astérisque rouge sous Hardware, suivi de Serial number, Asset tag, Cooling method et Airflow

Ces astérisques rouges sont toute la leçon. NetBox n’enregistre rien avant que les choses auxquelles il s’accroche existent, et il ne fait pas la mauvaise tête : un équipement sans type est une ligne incapable de répondre à aucune des questions pour lesquelles un équipement existe.

Plutôt que de deviner, j’ai donc demandé au modèle quelles clés étrangères sont réellement obligatoires, puis j’ai essayé de casser chaque règle par l’API pour voir ce qui revient.

POST /api/dcim/device-types/   no manufacturer
  {"manufacturer": ["This field is required."]}
POST /api/dcim/devices/        no device type
  {"device_type": ["This field is required."]}
POST /api/dcim/interfaces/     no device
  {"device": ["This field is required."]}
POST /api/ipam/aggregates/     no RIR
  {"rir": ["This field is required."]}
POST /api/circuits/circuits/   no provider, no type
  {"provider": ["This field is required."], "type": ["This field is required."]}
POST /api/virtualization/virtual-machines/   nothing at all
  {"__all__": ["A virtual machine must be assigned to a site, cluster, or device."]}

Chacun a refusé, en disant exactement ce qui manquait. Voici la même information sous forme de liste de prérequis :

Pour créerRequis d’abordOptionnel, mais vous le voulez d’abord
Tenantrienun groupe de tenants
Region, Site grouprienun parent de même nature, ils s’imbriquent
Siterienune Region, un Site group, un Tenant
Locationun Siteune Location parente, un Tenant
Rack typeun Manufacturer
Rackun Siteune Location, Rack group, Rack role, Rack type, Tenant
Device typeun Manufacturer
Device rolerienune Device role parente, elles s’imbriquent
Deviceune Device role, un Device type, un Siteune baie et une position, une Platform, un Tenant
Interface, et tout autre composantun Device
Cabledeux choses sur lesquelles terminerun Tenant
Aggregateun RIRun Tenant
VLANrienun VLAN group, une Role, un Tenant
Prefixrienun Site, un VLAN, une Role, un VRF, un Tenant
IP addressrienune Interface à laquelle l’affecter, un Tenant
Circuitun Provider et un Circuit typeun Tenant
Circuit terminationun Circuitun Site où atterrir
Clusterun Cluster typeun Cluster group, un Site, un Tenant
Virtual machineun Site, un Cluster ou un Deviceune Platform, un Tenant
VM interfaceune Virtual machine

La deuxième colonne est celle qui vous coûte. Préfixes, adresses IP, VLAN et tenants n’exigent rien du tout, donc rien ne vous empêche de les créer au premier jour dans l’ordre qui vous plaît. Savoir s’ils servent à quelque chose est une autre question, car une adresse sans interface derrière elle est une ligne dans une liste, et un équipement sans tenant est un équipement que vous rééditerez plus tard.

L’ordre dans lequel travailler vraiment

Cela donne une séquence. Descendez-la et rien ne vous refuse jamais rien.

D’abord, les choses auxquelles tout le reste s’accroche. Rien là-dedans n’est excitant et tout y est peu coûteux à rater.

  1. Les groupes de tenants, puis les tenants. Faites-les avant tout le reste. Vingt-huit modèles acceptent un tenant, dont Site, Location et Rack, donc si les clients n’existent pas encore vous ne pouvez pas les tamponner au passage et vous ferez de l’édition en masse plus tard.
  2. Les regions et les site groups. Les deux optionnels, les deux s’imbriquent en eux-mêmes, et ils sont indépendants l’un de l’autre. Les regions servent à la géographie, les site groups à la fonction, et vous pouvez utiliser l’un, les deux ou aucun.
  3. Les sites. La racine de presque tout. Un site n’exige rien, ce qui en fait la première chose que vous pouvez réellement créer.
  4. Les locations. Elles ont besoin d’un site, et elles s’imbriquent, donc une salle contenant des rangées contenant des pods est un seul modèle sur trois niveaux.
  5. Les rack roles et les rack groups. Les deux optionnels. Les rack groups sont plats et se placent à côté des locations comme second axe, ce qui est pratique pour les rangées et les pods.
  6. Les manufacturers, puis les rack types. Un rack type a besoin d’un manufacturer. Sautez les rack types si vous ne modélisez pas les armoires elles-mêmes.
  7. Les baies. Elles ont besoin d’un site. Si vous leur donnez aussi une location, cette location doit appartenir à ce site, et NetBox vérifie.

Puis le catalogue matériel, pas le matériel. Avant de pouvoir ajouter un seul équipement il vous faut des manufacturers, des device types, des device roles et, en pratique, des platforms.

  1. Les manufacturers. Vous en avez peut-être déjà depuis l’étape 6, car les rack types en ont besoin aussi. Les module types également.
  2. Les device types, chacun ayant besoin d’un manufacturer.
  3. Les device roles, qui s’imbriquent, et les platforms.

C’est l’étape que les gens sautent, et c’est l’étape qui décide de la quantité de saisie que coûtera le reste du travail. Un device type porte ses propres interfaces, ports et baies internes sous forme de gabarits, donc chaque équipement que vous créez à partir de lui arrive avec les bons composants déjà dessus. Faites le type correctement une fois et en racker quarante, c’est quarante noms.

Platform est l’intrus de cette liste, parce qu’un équipement n’en exige pas strictement une. Faites-le maintenant quand même. C’est elle qui portera plus tard le gabarit de configuration et le pilote NAPALM, et y revenir pour la poser sur un parc entier, c’est la même soirée que celle que vous auriez passée sur les tenants.

Puis le matériel lui-même.

  1. Les équipements. Rôle, type et site sont tous obligatoires. Baie et position sont optionnelles, et si vous les donnez elles doivent être cohérentes avec le site.
  2. Les composants, si le device type ne les a pas déjà fournis.
  3. Les câbles, entre les composants.

Puis l’adressage, car une adresse veut une interface sur laquelle vivre et l’interface n’existe qu’après l’étape 12.

  1. Les RIR, puis les aggregates.
  2. Les roles de préfixe et de VLAN, les VRF, les VLAN groups puis les VLAN.
  3. Les préfixes, puis les adresses IP individuelles.

Puis la couche commerciale.

  1. Les providers, les provider accounts et les circuit types.
  2. Les circuits, puis terminez-les sur des sites.

Et le parc virtuel, qui reflète le parc physique.

  1. Les cluster types et les cluster groups, puis les clusters.
  2. Les machines virtuelles, qui ont besoin d’un site, d’un cluster ou d’un équipement, puis leurs interfaces.

Les étapes 1 à 10 sont un après-midi et elles ont l’air d’être de l’administratif. Il n’y a là rien de glorieux. C’est aussi l’après-midi qui décide si l’étape 11 prend une matinée ou quinze jours.

Les règles qui mordent plus tard

Les champs obligatoires sont la moitié facile, car ils échouent immédiatement et vous disent pourquoi. Ce qui piège les gens, ce sont les règles de cohérence, qui ne se déclenchent qu’une fois que vous avez assez de données pour vous contredire :

device at Leeds, put in a Manchester rack
  {"rack": ["Rack MCR1-A07 (A07) does not belong to site Leeds Edge."]}
device at Leeds, in a Manchester location
  {"location": ["Location Hall 2 does not belong to site Leeds Edge."]}
rack at Leeds, in a Manchester location
  {"__all__": ["Assigned location must belong to parent site (Leeds Edge)."]}
a second device in a unit that is already taken
  {"position": ["U39.0 is already occupied or does not have sufficient
                 space to accommodate this device type: MX204 (1.0U)"]}

La dernière est celle que je montrerais si quelqu’un demandait pourquoi s’embêter avec tout ça. NetBox sait que l’équipement fait 1U, sait ce qu’il y a dans l’armoire, et ne vous laissera pas enregistrer deux choses au même endroit. Votre tableur vous laissera faire ça tout l’après-midi sans dire un mot, et vous le découvrirez quand quelqu’un sera debout dans la salle avec un carton dans les mains.

Rien de tout cela n’est configurable et rien ne devrait l’être. C’est la différence entre un enregistrement et un souhait.

À l’usage : ce que chaque écran vous donne

Le parc chargé, voici ce que vous en ressortez réellement. Tout ce qui suit est cette même instance : une baie, quatorze équipements, câblés et adressés chez trois clients.

Un équipement, ce sont ses composants

Entrez dans l’un de ces hyperviseurs et l’onglet intéressant n’est pas le résumé, ce sont les interfaces.

Onglet interfaces de NetBox pour l’équipement mcr1-hv-01 montrant trois interfaces\u00a0: eno1, SFP28 25GE, décrite to leaf-01, avec l’adresse IP 10.20.20.11/24, câble MCR1-DAC-0001A vers mcr1-leaf-01 Ethernet1\u00a0; eno2 SFP28 25GE to leaf-02 avec le câble MCR1-DAC-0001B vers mcr1-leaf-02 Ethernet1\u00a0; et ipmi 1000BASE-T décrite BMC avec 10.20.10.101/24

Lisez une ligne en travers. L’interface, sa vitesse, ce que vous avez écrit à son sujet, l’adresse dessus, l’étiquette du câble, et le port à l’autre bout. C’est une seule requête, et c’est la réponse à la question que tout le monde pose vraiment, à savoir « qu’est-ce qui est branché là ».

Remarquez où se trouve l’adresse. Elle est sur eno1, pas sur le serveur. Cela ressemble à du pédantisme jusqu’au jour exact où vous avez une machine avec une interface de management, deux interfaces de données et une loopback, et où quelqu’un demande quelle adresse répond pour elle. Un modèle qui accroche les adresses aux équipements ne peut pas vous le dire. Celui-ci peut, et il peut aussi contenir le cas parfaitement ordinaire de quatre adresses sur une seule interface.

Suivre le câble

Les câbles se terminent sur des composants, pas sur des équipements. Une interface à un bout, une interface à l’autre, ou un front port, un rear port, une prise électrique, une terminaison de lien. C’est le détail sur lequel repose toute la fonctionnalité, et c’est pourquoi la liste d’interfaces ci-dessus pouvait afficher l’autre bout dans sa propre colonne sans qu’on le lui dise.

Modélisez un câble d’équipement à équipement et vous avez dessiné une image. Terminez-le sur les ports et NetBox peut le parcourir.

Tracé de câble NetBox pour l’interface eno1, dessiné en diagramme vertical\u00a0: mcr1-hv-01, un Supermicro SYS-1029U-TN10RT à Manchester DC1 / Hall 2 / MCR1-A07 (A07) / Front / U20.0, son interface eno1, le câble MCR1-DAC-0001A marqué Connected, puis Ethernet1 sur mcr1-leaf-01, un Arista DCS-7050SX3-48YC8 en Front / U36.0. Trace Completed, total 1 segment

Un saut ici, parce que c’est un câble à attache directe. Mettez des panneaux de brassage au milieu et il les parcourt, panneau par panneau, et vous dit ce qu’il y a au bout d’un cheminement à travers trois armoires. C’est le travail qui demande sinon une lampe torche et quelqu’un qui tient l’autre bout d’une sonde.

Le tracé affiche aussi l’emplacement complet de chaque extrémité. Site, salle, armoire, face, unité. Si vous avez déjà été au téléphone à essayer d’expliquer à un technicien sur place quelle machine regarder, cette ligne est toute la valeur.

Les adresses, en arbre plutôt qu’en onglet

L’IPAM est la moitié pour laquelle les gens viennent.

Liste de préfixes NetBox montrant 10.20.0.0/16 comme Container avec 5 enfants à 1,6\u00a0% d’utilisation, décrit Manchester DC1, et en dessous, indentés d’un niveau, cinq préfixes enfants actifs\u00a0: 10.20.10.0/24 sur le VLAN mgmt (100) avec le rôle Management, 10.20.20.0/24 rattaché à Ravenscroft Legal sur le VLAN ravenscroft-prod (200), 10.20.21.0/24 à Padgate Foods, 10.20.22.0/24 à Hartley Components, et 10.20.30.0/29 sur le VLAN transit (110) à 16,7\u00a0% d’utilisation

L’indentation est calculée depuis les adresses elles-mêmes. Vous ne dites pas à NetBox que 10.20.20.0/24 est à l’intérieur de 10.20.0.0/16, il le déduit, et il continuera de le déduire quand quelqu’un ajoutera un /26 au milieu l’année prochaine.

Chaque ligne porte les choses sur lesquelles vous filtrez réellement : le VLAN vers lequel elle pointe, le rôle, et le client à qui elle appartient. L’utilisation est calculée aussi.

Ouvrez-en un et vous obtenez les adresses qu’il contient, et les trous.

Onglet IP Addresses de NetBox pour le préfixe 10.20.20.0/24, montrant une ligne verte «\u00a010 IPs available\u00a0», puis 10.20.20.11/24 et 10.20.20.12/24 toutes deux actives et rattachées à Ravenscroft Legal, puis une ligne verte «\u00a0242 IPs available\u00a0»

Ces lignes vertes sont l’espace libre, affiché dans le fil de l’espace utilisé. Il existe un appel d’API qui vous rend la prochaine adresse libre d’un préfixe, et c’est le morceau d’automatisation IPAM qui se rentabilise immédiatement, parce que c’est ce que les gens font sinon en plissant les yeux sur un tableur et en espérant.

Une interface prend autant d’adresses que vous voulez, des deux familles, et vous en désignez une de chaque comme primaire de l’équipement.

Liste d’adresses IP NetBox filtrée sur l’interface eno1 de mcr1-hv-01, montrant quatre adresses\u00a0: 10.20.20.11/24 marquée primaire, 10.20.20.201/24 une adresse de service, 2001:db8:20:20::11/64 primaire v6, et 2001:db8:20:20::201/64 une adresse de service, toutes rattachées à Ravenscroft Legal

Deux IPv4 et deux IPv6 sur un seul port, ce qui est un mardi ordinaire et quelque chose qu’un modèle centré sur l’équipement ne peut pas exprimer du tout.

Saisissez les adresses avec le masque du réseau sur lequel elles se trouvent, pas en /32. NetBox acceptera 10.20.20.11/32 et le classera sous le bon préfixe, car l’appartenance est déduite de l’adresse hôte. Ce qu’il ne fera pas, c’est vous corriger après coup : le masque est stocké exactement comme tapé et rendu tel quel à tout ce qui le lit, donc un /32 sur une adresse de LAN rend un /32 dans la configuration de l’équipement. L’endroit où ça mord, c’est trois mois plus tard dans un gabarit de configuration, pas aujourd’hui dans le formulaire.

Une installation, trois clients

La plupart des objets de NetBox peuvent être affectés à un tenant. Une entreprise s’en sert pour ses divisions. Si vous vendez des services managés, vous en créez un par client.

Page tenant de NetBox pour Ravenscroft Legal dans le groupe Customers, avec un panneau Related Objects listant Circuits 2, Devices 2, IP Addresses 2, Prefixes 1 et VLANs 1, et des onglets en haut pour Custom Objects, Contacts, Journal et Changelog

Ce panneau de droite est la réponse à « qu’est-ce que ce client possède », et il s’est assemblé tout seul. Pas de rapport, pas de tableur, pas besoin de demander à l’ingénieur qui l’a construit.

Il vaut la peine d’être précis sur ce que signifie la notion de tenant, car se tromper au premier jour, c’est une année à démêler plus tard. Un tenant signifie que l’objet est dédié à ce client. Un routeur qui ne sert que lui reçoit son tenant. Un pare-feu qui en sert quatre n’appartient à aucun d’eux, donc il n’en reçoit aucun, et la relation va ailleurs. Davantage là-dessus plus bas, car c’est le point où la plupart des gens découvrent qu’ils doivent ajouter quelque chose à eux.

Mettez les chiffres sur le type d’équipement

C’est l’étape qui sépare un inventaire de quelque chose qui répond aux questions, et elle coûte environ dix minutes par type d’équipement.

Un device type peut porter son poids et, via ses gabarits de power port, sa consommation électrique. Mettez-les une fois sur le type et chaque équipement que vous créerez jamais à partir de lui en hérite. Omettez-les et NetBox vous dira volontiers qu’une armoire est remplie à 28,6 % et rien d’autre.

Page device type de NetBox pour le Supermicro SYS-1029U-TN10RT, montrant une hauteur de 1U, full depth coché, un poids de 19,10 kg et la cooling method Air, avec un onglet Power Ports portant le compte 2 et un panneau Related Objects montrant 6 devices

J’ai mis un poids sur les cinq types présents, donné à chacun deux gabarits de power port avec une consommation maximale et une consommation allouée, donné au type PDU une entrée et douze prises, puis créé un power panel et deux arrivées vers l’armoire et câblé l’ensemble : chaque PSU1 d’équipement vers la PDU A, chaque PSU2 vers la PDU B, et l’entrée de chaque PDU vers son arrivée.

Power feed NetBox MCR1-A07-A, type Primary, statut Active, connecté à mcr1-pdu-a INPUT, montrant une utilisation allouée de 5340VA sur 5888VA sous forme de barre rouge à 90,7 pour cent, avec des caractéristiques électriques d’alimentation AC, 230 volts, 32 ampères, monophasé et 80 pour cent d’utilisation maximale

Alors la page de la baie change complètement.

Page de la baie MCR1-A07 dans NetBox montrant désormais une cooling capability Hybrid, une cooling capacity de 15,00 kW, une utilisation de l’espace de 28,6 pour cent en vert et une utilisation électrique de 90,7 pour cent en rouge, à côté des élévations avant et arrière

Utilisation de l’espace 28,6 %. Utilisation électrique 90,7 %.

Cette armoire est remplie à un tiers de matériel et presque à bout d’électricité, et c’est un fait sur votre parc qu’aucun tableur ne vous proposera jamais de lui-même. C’est aussi le fait qui décide si la prochaine commande sera rackée là ou ailleurs, et il est tombé de données que vous avez saisies une fois, sur les types.

Deux choses qui le laisseront à zéro

J’ai eu 0,0 % au début, deux fois, et les deux causes méritent d’être connues parce qu’aucune ne produit d’erreur.

Les prises doivent référencer l’entrée. Une prise électrique sur une PDU a un champ power_port qui pointe vers le port amont du même équipement. Laissez-le vide et la chaîne est rompue : NetBox n’a aucun moyen de savoir que ces douze prises sont alimentées par cette entrée, donc rien ne s’agrège.

Laissez vides les champs de consommation de l’entrée. C’est le point contre-intuitif. NetBox ne calcule la consommation d’un power port à partir de ce qui y est branché que si ses deux propres champs de consommation sont vides :

if self.allocated_draw is None and self.maximum_draw is None:
    ...aggregate the downstream power ports...
# otherwise
return {'allocated': self.allocated_draw or 0, ...}

J’avais obligeamment mis maximum_draw: 7400 sur l’entrée de la PDU, parce que c’est ce pour quoi la PDU est dimensionnée. NetBox m’a donc cru, a pris allocated_draw comme non défini, et a rapporté zéro. Effacez les deux et il le calcule :

mcr1-pdu-a INPUT -> allocated 5340 VA, maximum 8480 VA, across 12 outlets
mcr1-pdu-b INPUT -> allocated 5340 VA, maximum 8480 VA, across 12 outlets
feed MCR1-A07-A: available 5888 VA   (230 V x 32 A x 80% max utilisation)
RACK power utilisation: 90.7 %
RACK weight: 166.6 kg of 900 kg

La règle est donc : mettez de vrais chiffres sur les feuilles, et laissez les ports intermédiaires vides pour que NetBox puisse les additionner. Une valeur définie administrativement gagne toujours contre la valeur calculée, ce qui est un comportement correct et exactement la mauvaise chose à faire sur une PDU.

Le refroidissement, qui est nouveau

La version 4.7 a ajouté le refroidissement au DCIM, et il est arrivé avec la moitié qui devient coûteuse. Une baie porte une cooling capability air, hybride ou liquide et une capacité en kilowatts ; un device type porte une cooling method. Au-dessus siègent des cooling sources pour les groupes froids et les armoires de climatisation, des cooling feeds représentant une boucle vers une baie, et des composants d’admission et de sortie sur les équipements eux-mêmes pour les plaques froides et les collecteurs.

L’armoire ci-dessus affiche Hybrid, 15,00 kW, parce que je l’ai dit à la baie. Si vous prenez livraison de matériel refroidi au liquide cette année, voilà un modèle pour la chose que vous gardez actuellement dans un tableur.

Une installation, beaucoup de clients

La notion de tenant est la raison pour laquelle un prestataire peut faire tourner un seul NetBox plutôt qu’un par client, et il vaut la peine de comprendre ce qu’elle fait et, plus important, ce qu’elle ne fait pas.

Vingt-huit des modèles de NetBox portent un champ tenant. Sites, locations, baies et réservations de baies. Équipements, câbles et virtual device contexts. Préfixes, adresses IP, plages, aggregates, VLAN, VLAN groups, VRF, route targets, ASN. Liens opérateur et circuit groups. Clusters et machines virtuelles. Tunnels, L2VPN, réseaux et liaisons sans fil. Power feeds et cooling feeds.

C’est tout nom facturable. Renseignez-le de façon cohérente et « qu’est-ce que ce client possède » cesse d’être une enquête.

Mais un tenant est une étiquette, pas un verrou. Il dit que l’objet est dédié à ce client. Il n’empêche personne qui peut se connecter de lire l’ensemble, ce qui va très bien au sein d’une seule entreprise et ne sert plus à rien dès qu’un client a un compte.

Deux choses en découlent, et la seconde est celle que les gens ratent.

Un tenant signifie dédié. Un routeur qui ne sert qu’un client reçoit son tenant. Un pare-feu qui en sert quatre n’appartient à aucun, donc il ne reçoit rien, et vous consignez la relation sur la chose que vous avez réellement vendue. Forcer un tenant sur du matériel partagé rend discrètement faux chaque rapport bâti sur les tenants.

Et l’accès est un mécanisme entièrement distinct.

C’est dans les permissions que vit l’isolation

Les object permissions de NetBox prennent une contrainte JSON, et la contrainte est un filtre de l’ORM Django. Elle restreint le queryset avant que quoi que ce soit n’en soit construit, donc chaque vue, chaque export, chaque appel d’API et chaque recherche est restreint avec elle.

Page de détail d’une permission NetBox pour Ravenscroft read-only, montrant Enabled coché, Actions avec View coché et Add, Change, Delete, Render configuration et Synchronize data tous barrés, Object Types listant Circuits circuit, DCIM device et IPAM prefix, un utilisateur affecté ravenscroft-ro, et un panneau Constraints contenant le JSON tenant__slug fixé à ravenscroft-legal

Trois champs font le travail. Les types d’objets auxquels elle s’applique, les actions qu’elle accorde, et cette contrainte en bas. Tout le reste est de la comptabilité.

Voici la liste d’équipements en tant qu’administrateur.

Liste d’équipements NetBox en tant qu’utilisateur admin, Results 14, montrant mcr1-core-01 et 02, mcr1-hv-01 à 06, mcr1-leaf-01 et 02, mcr1-pdu-a et b, et mcr1-pp-01 et 02, avec des colonnes pour le statut, le tenant, le site, la location, la baie, le rôle, le constructeur, le type et l’adresse IP

Et voici la même URL, la même installation, connecté en tant que client.

La même liste d’équipements NetBox connecté en tant que ravenscroft-ro, Results 2, montrant seulement mcr1-hv-01 et mcr1-hv-02, tous deux rattachés à Ravenscroft Legal, avec la navigation de gauche réduite à Devices, IPAM, Circuits, Plugins et Admin

Deux lignes au lieu de quatorze, et regardez le menu de gauche. Il s’est réduit aux quatre choses que ce compte a le droit de toucher. Personne n’a configuré ça. La navigation est construite depuis les mêmes permissions, donc un client ne voit jamais un lien vers quelque chose qui le refuserait.

Les deux façons de dire non

C’est le détail à connaître, car les deux refus signifient des choses différentes et les deux sont délibérés.

Le jeton du client demandeIl obtient
/api/dcim/devices/200, une ligne sur deux
/api/dcim/devices/1/, le sien200
/api/dcim/devices/2/, celui d’un autre404
/api/tenancy/tenants/403
/api/dcim/sites/, jamais accordé403
aucun jeton du tout403

404 signifie que le type est à vous mais que cette ligne ne l’est pas. La contrainte l’a retirée du queryset, donc du point de vue de la requête elle n’existe pas. Un 403 à cet endroit confirmerait qu’elle existe, et permettrait à un client curieux de compter votre parc en parcourant les identifiants.

403 signifie que le type ne fut jamais à vous. Les tenants et les sites n’ont jamais été accordés, donc ils refusent d’emblée, et le client ne peut pas énumérer qui d’autre est sur la plateforme.

Puis laissez-les écrire

La lecture seule est le cas facile. La vraie question est de savoir si on peut confier des droits d’édition à un client, j’ai donc accordé change sous la même contrainte et je suis allé chercher la sortie.

TentativeRésultat
Éditer son propre équipement200, enregistré
Éditer l’équipement d’un autre client404
Éditer son propre équipement en le déplaçant vers le tenant de l’autre client403
Supprimer son propre équipement403, delete n’a jamais été accordé

La troisième ligne est celle qui compte. Réaffecter son propre équipement au tenant de quelqu’un d’autre est l’échappatoire évidente, car l’objet est à l’intérieur de votre contrainte à l’arrivée de la requête et à l’extérieur ensuite. NetBox évalue la contrainte contre l’état dans lequel l’objet serait laissé, donc il refuse. J’ai relu l’équipement plutôt que de me fier au code de statut, et le tenant n’avait pas bougé.

C’est le trou que laisse ouvert la plupart des systèmes multi-clients faits maison, et il est normalement trouvé par un client plutôt que par un test.

Les variables qui doivent s’hériter

Voici la distinction que les gens ratent, et la réussir épargne beaucoup d’édition.

Un custom field est une valeur sur un objet. Vous la fixez objet par objet, et elle y reste. Bien pour un fait sur la chose elle-même : un numéro d’inventaire, un numéro de contrat de support, une date de mise en service.

Un config context est une valeur attachée à une caractéristique, dont tout ce qui correspond à cette caractéristique hérite. Bien pour une variable qui doit ruisseler : vos serveurs NTP, vos cibles syslog, votre communauté SNMP, votre domaine DNS, votre VLAN de management, votre fenêtre de sauvegarde.

Si vous vous surprenez à fixer le même custom field à la même valeur sur quarante équipements, c’est un config context que vous vouliez.

Liste des config contexts de NetBox montrant North West base au poids 1000 affecté à la région North West, et Manchester DC1 syslog au poids 2000 affecté au site Manchester DC1, les deux actifs

Un context est du JSON arbitraire, et il peut être attaché à une region, un site group, un site, une location, un device type, un rôle, une platform, un cluster, un cluster type, un cluster group, un tenant group, un tenant ou un tag. Tenant figure dans cette liste, ce qui, pour un prestataire, signifie qu’un fait vrai d’un client partout le suit sur chaque équipement que vous ajouterez jamais pour lui.

La fusion se fait par clé, et le poids décide de qui gagne chacune.

Onglet Config Context de NetBox pour mcr1-core-01 montrant à gauche Rendered Context contenant domain, ntp_servers, snmp_community et syslog_servers, et à droite Source Contexts listant North West base au poids 1000 avec les quatre clés et Manchester DC1 syslog au poids 2000 ne contenant que syslog_servers

Lisez le panneau de droite contre celui de gauche. La region fournit quatre clés au poids 1000. Le site fournit une clé au poids 2000. Le context rendu garde intacts le domaine, les serveurs NTP et la communauté de la region, et prend le serveur syslog du site, parce que c’est la seule clé que quelque chose a disputée.

Vous écrivez l’exception, pas une copie neuve de tout avec l’exception dedans. C’est toute la valeur, et c’est pourquoi cela passe à l’échelle là où un fichier de variables par équipement ne le fait pas.

Le context local gagne contre tout ce qui est au-dessus

La pile d’héritage a un sommet, et c’est l’objet lui-même. Les local context data sur un équipement gagnent contre chaque source context qui s’applique à lui, quels que soient les poids.

J’ai mis ceci sur mcr1-core-01 :

{"syslog_servers": ["10.20.10.99"], "note": "this box logs somewhere else"}

et son context rendu est devenu :

{
  "note": "this box logs somewhere else",
  "domain": "mcr1.example.net",
  "ntp_servers": ["172.16.10.22", "172.16.10.33"],
  "snmp_community": "n0rthwest",
  "syslog_servers": ["10.20.10.99"]
}

Onglet Config Context de NetBox pour mcr1-core-01 avec Local Context désormais rempli avec syslog_servers 10.20.10.99 et une note, le panneau indiquant que le config context local écrase tous les source contexts, et le Rendered Context montrant le serveur syslog local aux côtés du domaine, des serveurs NTP et de la communauté hérités

Le serveur syslog local a battu à la fois la surcharge du site au poids 2000 et la region au poids 1000. Tout ce dont il n’a rien dit a quand même été hérité, donc le domaine, les serveurs NTP et la communauté sont passés intacts, et la nouvelle clé a simplement été ajoutée.

C’est la trappe de secours pour la seule machine qui est vraiment différente, et le panneau vous dit clairement qu’elle écrase tous les source contexts. Si vous vous surprenez à l’utiliser sur beaucoup d’équipements plutôt que sur un, vous avez trouvé une caractéristique que ces équipements partagent et c’est un context cadré dessus que vous vouliez.

Trois pièges

Un context sans portée est global. Créez-en un et oubliez de l’affecter à quoi que ce soit, et il s’applique à tous les équipements et à toutes les machines virtuelles que vous avez. C’est un comportement documenté et parfois ce que vous voulez. C’est aussi silencieux.

Les clés ne peuvent pas avoir de tirets si vous voulez les atteindre simplement. Les données de context sont du JSON, donc ntp-servers est parfaitement légal, mais les noms de variables Jinja ne sont pas des clés JSON. {{ ntp-servers }} est analysé comme une soustraction et lève UndefinedError: 'ntp' is undefined. Écrivez {{ ntp_servers }} contre des données à tirets et vous obtenez une chaîne vide, un HTTP 200, et aucun avertissement nulle part. Utilisez des tirets bas dans les clés et le gabarit évident fonctionne.

Un profil peut arrêter les fautes de frappe. Un profil de config context regroupe des contexts apparentés et impose un schéma JSON sur leurs données au moment de l’enregistrement. J’en ai doté un d’un schéma exigeant syslog_servers comme tableau de chaînes IPv4, puis j’ai fait les deux erreurs que les gens font vraiment :

{"syslog-server": ["10.1.1.1"]}          singular, by accident
  400  Data does not conform to profile schema:
       'syslog-servers' is a required property

{"syslog_servers": [...], "syslog_port": 99999}
  400  Data does not conform to profile schema:
       99999 is greater than the maximum of 65535

Rejetées là où quelqu’un les a faites, plutôt que quatre cents équipements plus tard.

Rendre la configuration

Des données de context plus un gabarit Jinja vous donnent un fichier de configuration. L’équipement est dans la portée sous device, son context fusionné est dans la portée sous forme de variables ordinaires, et vous pouvez parcourir ses composants.

Onglet Render Config de NetBox pour mcr1-core-01, montrant le config template Junos base et la configuration Junos rendue\u00a0: host-name mcr1-core-01, domain-name mcr1.example.net, une ligne location indiquant Manchester DC1 / MCR1-A07 / U39, deux serveurs NTP, l’unique hôte syslog 10.20.10.99, la communauté SNMP n0rthwest, et chaque interface avec sa description et sa famille d’adresses

Tout sur cette page vient d’un endroit différent, et c’est tout le sujet :

LigneD’où elle vient
host-name mcr1-core-01de l’équipement
domain-name mcr1.example.netdu config context régional
location "Manchester DC1 / MCR1-A07 / U39"du site, de la baie et de la position dedans
server 172.16.10.22du context régional, poids 1000
host 10.20.10.99 any noticedu propre context local de l’équipement, battant le site et la region
family inet address 10.20.10.11/24de l’adresse sur cette interface, avec son masque tel que tapé

Le gabarit est résolu par l’équipement, puis le rôle, puis la platform, et la requête échoue si aucun des trois n’en a. Vous affectez donc un gabarit à une platform une fois, et chaque équipement faisant tourner ce logiciel se rend depuis lui, sauf si son rôle ou l’équipement lui-même en dit autrement. C’est à cela que sert une platform : le système d’exploitation ou la famille logicielle du constructeur, pas le matériel.

NetBox rend. Il ne pousse pas. Amener la sortie sur la machine est le travail de votre automatisation, et le jour où un bug de gabarit aurait sinon reconfiguré quatre cents équipements, vous serez content que ce soient deux systèmes différents.

Les plugins

Un plugin est une application Django installée aux côtés de NetBox. Il peut ajouter des modèles, ajouter des pages, étendre les deux API, injecter du contenu dans des gabarits existants, ajouter de la navigation, ajouter des files de tâches de fond et charger d’autres applications Django. Il y a très peu de choses qu’il ne peut pas faire, car en dessous ce n’est que du Django.

En installer un, c’est quatre commandes et un redémarrage :

source /opt/netbox/venv/bin/activate
pip install netbox-topology-views netbox-qrcode
# add the package names to PLUGINS in configuration.py, then
python3 manage.py migrate
python3 manage.py collectstatic --no-input
sudo systemctl restart netbox netbox-rq

Sur la pile de conteneurs, les mêmes paquets vont dans plugin_requirements.txt et vous reconstruisez l’image. Dans les deux cas, épinglez les versions et vérifiez d’abord la matrice de compatibilité, car un plugin qui n’a pas suivi une version de NetBox refusera de démarrer toute l’application plutôt que de se désactiver lui-même.

Page installed plugins de NetBox listant Custom Objects version 0.7.0 par NetBox Labs, Topology views version 4.7.0 par Mattijs Vanhaverbeke, et qrcode version 1.0.0 par Nikolay Yuzefovich

Le catalogue publié en recense 31. Voici ceux qui méritent d’être connus, avec la licence sous laquelle chacun est réellement livré :

PluginCe qu’il faitLicence
DNSZones, enregistrements et serveurs de noms comme source de véritéMIT
BGPSessions, communautés et politiques de routageApache 2.0
Topology ViewsCartes de topologie graphiques construites depuis vos câblesApache 2.0
FloorplanPlans graphiques de sites et de locationsLGPL 3.0
QR CodeDes codes sur les baies, les équipements et les câbles, pour les étiquettes d’inventaireApache 2.0
ACLsListes d’accès et règlesApache 2.0
Prometheus SDSert à Prometheus sa liste d’hôtes directement depuis NetBoxMIT
DocumentsDes documents rattachés aux liens opérateur et aux équipementsApache 2.0
LifecycleFin de vie du matériel, licences et contratsApache 2.0
ContractContrats et facturesMIT
Reorder RackGlisser-déposer des unités de baieApache 2.0
BranchingBranches isolées et fusionnables de vos donnéesNetBox Limited Use
Custom ObjectsDe nouveaux types d’objets, définis dans l’interfaceNetBox Limited Use

Prometheus SD est la forme honnête de toute l’idée. NetBox sait ce qui existe, donc laissez NetBox le dire au système de supervision, et arrêtez d’entretenir une seconde liste d’hôtes qui dérive.

Une chose à savoir avant de bâtir sur les deux dernières lignes. NetBox lui-même est en Apache 2.0 et l’est depuis que DigitalOcean l’a publié en 2016, et le projet est aujourd’hui pris en charge par NetBox Labs aux côtés d’une équipe de mainteneurs bénévoles.21 NetBox Branching et NetBox Custom Objects ne le sont pas : ils sont livrés sous la NetBox Limited Use License 1.0, qui accorde l’usage « only as part of a NetBox installation obtained from NetBox Labs or a NetBox distributor authorized by NetBox Labs, and only for your own internal use », et qui n’accorde pas le droit d’utiliser le logiciel « to provide a managed service or software products that includes, integrates with, or extends NetBox in a way that competes with any product or service of NetBox Labs ». Si vous avez installé NetBox Community depuis GitHub et que vous l’exploitez pour le compte de clients, lisez les termes vous-même avant de mettre un schéma derrière eux.10 Le guide d’installation de NetBox recommande les deux plugins sans le mentionner.11

Les custom fields

Un custom field ajoute un attribut à un modèle existant. Les valeurs sont stockées en JSON à côté de chaque objet, donc pas de migration et pas de redémarrage, et il existe treize types dont des références objet et multi-objets vers d’autres enregistrements NetBox.

Page équipement de NetBox pour mcr1-core-01 montrant un panneau Custom Fields avec un groupe Asset contenant Warranty expires 2029-06-30 et Support contract JNPR-448120, à côté du panneau Device Type montrant Juniper MX204 et d’un panneau Dimensions montrant un poids total de 9,5 kilogrammes

Deux champs là, regroupés sous un titre « Asset » que j’ai choisi. Remarquez le panneau Dimensions à leur droite : 9,5 kg, que personne n’a tapés sur cet équipement. Ils viennent du type d’équipement.

Les custom fields sont validés, et il vaut la peine de savoir qu’ils le sont, car c’est une vraie différence avec la voie ci-dessous. J’ai donné au champ de contrat une expression régulière et l’API l’a imposée :

PATCH {"custom_fields": {"support_contract": "nonsense"}}
  {"__all__": ["Invalid value for custom field 'support_contract':
                Value must match regex '^[A-Z]{2,4}-[0-9]{6}$'"]}

Deux choses ont changé en 4.7 qui comptent à grande échelle. Créer un champ avec une valeur par défaut, ou supprimer un champ, doit réécrire les données stockées de chaque objet auquel il s’applique, donc sur une grosse table ce travail est confié à une tâche de fond et le champ rapporte « provisioning » ou « deleting » pendant qu’elle tourne. Un champ n’est vivant que tant qu’il est actif : pendant l’une ou l’autre opération il n’apparaît ni sur les objets, ni dans les formulaires, ni dans les filtres, ni dans aucune des deux API. Cela exige un worker en marche, sinon il reste indéfiniment dans cet état.

Utilisez un custom field quand vous ajoutez un fait sur la chose elle-même. Utilisez un config context quand la valeur doit ruisseler. Utilisez la section suivante quand la chose dont vous avez besoin n’existe pas.

Deux mécanismes différents vivent sous ce titre et ils résolvent des problèmes différents. L’un est pour vos propres champs. L’autre réécrit ceux de NetBox.

Un choice set, pour votre propre champ de sélection

Un custom field de type « selection » tire ses options d’un choice set, un objet que vous gérez dans l’interface comme n’importe quoi d’autre. Ainsi « support tier » devient une vraie liste déroulante plutôt qu’un texte libre que quelqu’un écrira de trois façons.

Choice set de custom field NetBox nommé Support tier, décrit comme what the customer pays for, listant trois choix\u00a0: bronze pour next business day, silver pour 8 hours, et gold pour 4 hours 24x7

C’est imposé, y compris via l’API :

PATCH {"custom_fields": {"support_tier": "platinum"}}
  {"__all__": ["Invalid value for custom field 'support_tier':
                Invalid choice (platinum) for choice set Support tier."]}

Les choice sets sont partagés, donc un seul set peut alimenter le même champ sur plusieurs modèles, et changer la liste en un endroit la change partout.

FIELD_CHOICES, pour les champs propres à NetBox

C’est celui dont les gens ignorent l’existence. Plusieurs des champs de sélection intégrés de NetBox peuvent être étendus ou remplacés depuis configuration.py, dont le statut d’équipement, le statut de site, le statut de baie, le statut de lien opérateur et bien d’autres.12

Ajoutez un signe plus pour compléter ce qui est livré. Omettez-le pour remplacer la liste entièrement.

FIELD_CHOICES = {
    # add to what NetBox ships with
    'dcim.Device.status+': (
        ('burn-in', 'Burn-in', 'cyan'),
        {'value': 'awaiting-rma', 'label': 'Awaiting RMA', 'color': 'orange',
         'description': 'Faulty, with the vendor'},
    ),
    # replace the stock list outright
    'dcim.Site.status': (
        ('surveyed', 'Surveyed', 'purple'),
        ('building', 'Building out', 'orange'),
        ('active', 'Active', 'green'),
        ('closing', 'Closing', 'red'),
    ),
}

J’ai mis exactement cela sur cette instance. Le statut d’équipement est revenu avec les sept valeurs d’origine et mes deux :

offline, active, planned, staged, failed, inventory, decommissioning,
burn-in, awaiting-rma

et le statut de site est revenu avec seulement les miennes, la liste d’origine disparue :

surveyed, building, active, closing

Un choix peut être un simple tuple de valeur, libellé et couleur, ou un dictionnaire qui accepte aussi une description affichée en sous-titre dans le formulaire. Et ils se comportent partout comme des valeurs natives, car pour le reste de NetBox ce sont des valeurs natives :

Liste d’équipements NetBox montrant mcr1-hv-06 portant un badge de statut Burn-in cyan aux côtés des autres équipements au statut d’origine Active, avec le même style que n’importe quel statut intégré

Ce badge est mon statut, dans ma couleur, qui se trie et se filtre comme n’importe quel autre.

Trois choses à savoir avant de l’utiliser.

Remplacer supprime les valeurs d’origine du menu, pas de la base. Tout objet qui en détient déjà une la garde, mais la valeur n’est plus proposée et ne sera plus un choix valide la prochaine fois que quelqu’un éditera cet objet. Si vous remplacez une liste, vérifiez que rien ne repose sur une valeur que vous venez de retirer.

Cela vit dans le fichier de configuration, donc il faut un redémarrage et ce n’est pas quelque chose qu’un utilisateur de l’interface peut changer. Pour un prestataire, c’est le bon sens de la chose : l’ensemble des statuts que votre activité reconnaît est une décision de gouvernance, pas une décision d’un mardi après-midi.

Étendez avant de remplacer. Les valeurs d’origine sont ce que chaque plugin, script et intégration s’attend à voir. Ajouter ne coûte rien. Remplacer est une décision que vous possédez pour toujours.

Les custom objects

Voici une vraie lacune. NetBox modélise les clusters, les machines virtuelles et les disques virtuels. Il ne modélise pas le datastore sur lequel ces disques vivent réellement, et pour quiconque fait tourner Proxmox ou VMware c’est l’objet qui relie le stockage que vous avez acheté à la charge qui l’utilise.

Le plugin Custom Objects vous laisse définir un nouveau type d’objet depuis l’interface ou l’API, sans écrire de code. Donc :

Page Custom Object Type de NetBox pour Datastore, version 1.0.0, décrit comme shared storage a cluster puts VM disks on, avec un onglet Fields indiquant 7 et un panneau Fields listant Name en Text, Cluster en Object pointant vers Virtualization > Cluster, Provisioned by en Multiple objects pointant vers DCIM > Device, Backing en Text, Capacity (GB) en Integer, Thin provisioned en Boolean, et Customer en Object pointant vers Tenancy > Tenant

Sept champs, dont deux sont tout le sujet. cluster est une référence objet unique vers un vrai cluster NetBox, réglée sur protect pour que personne ne puisse supprimer un cluster sous son stockage. provisioned_by est une référence multi-objets vers les équipements qui le servent réellement.

Cela vous donne un objet de première classe avec sa propre entrée de navigation, sa vue en liste, ses filtres, son import et son export :

Liste Datastores de NetBox montrant quatre lignes\u00a0: ds-nvme-01 sur le cluster MCR1-PVE provisionné par mcr1-hv-01, 02 et 03, adossé à un pool Ceph RBD NVMe de 40960 GB et thin provisioned\u00a0; ds-nvme-02\u00a0; ds-archive-01 sur un pool HDD avec WAL NVMe de 196608 GB et non thin provisioned\u00a0; et ds-ravenscroft-01 de 8192 GB rattaché à Ravenscroft Legal

Et comme les références sont réelles, la relation apparaît aussi depuis l’autre bout. Ouvrez le cluster et les datastores y sont listés.

Page cluster de NetBox pour MCR1-PVE, un cluster Proxmox VE dont la portée est Manchester DC1, avec un onglet Custom Objects montrant les datastores qui le référencent

Un custom object type hérite de l’essentiel de ce qui fait d’un objet NetBox un objet NetBox : vues en liste et en détail, une entrée de navigation, des endpoints REST, la recherche plein texte, la journalisation des changements, le journal, les tags, les favoris, l’import et l’export, les event rules et les notifications. Rien de tout cela n’a eu à être écrit.

Ce n’est pas non plus un blob JSON qui se fait passer pour une table. Le plugin émet du vrai DDL, et ce qui apparaît dans PostgreSQL est une vraie table avec de vraies contraintes :

                    Table "public.custom_objects_2"
     Column      |   Type   | Nullable |     Default
-----------------+----------+----------+------------------
 id              | bigint   | not null | identity
 name            | varchar  |          |
 cluster_id      | bigint   |          |
 backing         | varchar  |          |
 capacity_gb     | bigint   |          |
 thin_provisioned| boolean  |          |
Indexes:
    "custom_objects_2_name_key" UNIQUE CONSTRAINT, btree (name)
Foreign-key constraints:
    ... FOREIGN KEY (cluster_id) REFERENCES virtualization_cluster(id) ON DELETE RESTRICT

Le protect que j’ai demandé est devenu ON DELETE RESTRICT, et ça marche : supprimer ce cluster revient en 409 en nommant ce qui en dépend.

Deux choses à savoir avant de vous y fier

La validation est sur le formulaire, pas sur l’API. C’est celle qui piégerait une automatisation. J’ai déclaré required, une expression régulière et des bornes numériques sur les champs d’un custom object type, puis j’ai écrit dessus par l’API REST :

Ce que j’ai déclaréCe que j’ai envoyéRésultat
validation_regexune valeur qui ne correspond à rien201 Created
required: truele champ entièrement omis201 Created
validation_minimum: 1un nombre négatif201 Created
unique: trueun doublon400, rejeté
on_delete_behavior: protectsupprimer l’objet référencé409, rejeté

Les deux qui ont tenu sont les deux qui sont devenues des contraintes en base. Le reste n’existe que sur le formulaire web, donc une personne qui tape est contrainte et une synchronisation nocturne ne l’est pas. Comparez avec le custom field du cœur plus haut, où la même expression régulière était imposée par l’API. Jusqu’à ce que cela change, mettez tout ce dont dépend une facture derrière une vraie contrainte.

Supprimer un type supprime une table. Supprimer un champ supprime une colonne. C’est du DDL depuis un formulaire web, exécuté par quiconque a la permission, et la documentation le dit telle quelle.13 Restreignez qui peut les supprimer.

Quand écrire un vrai plugin à la place

Si l’objet compte, si de l’automatisation écrit dessus, et si vous seriez contrarié d’y trouver n’importe quoi, écrivez le modèle vous-même. Un plugin NetBox minimal, c’est un PluginConfig, un modèle, un serializer, un viewset, une table, un formulaire, quelques vues et une carte d’URL, et cela tient en moins de deux cents lignes majoritairement déclaratives. Dériver PrimaryModel vous donne gratuitement les tags, les custom fields, la journalisation des changements, le journal, les gabarits d’export et la propriété, et chaque contrainte que vous posez sur le modèle est imposée partout, car la machinerie de serializer de NetBox l’exécute.

Il existe un gabarit cookiecutter et un tutoriel de plugin complet maintenus par la communauté. Partez de là plutôt que d’un répertoire vide.

Et c’est la licence que vous choisissez, ce que personne ne peut changer ensuite.

Validation personnalisée et classes de validation

Tout ce qui précède porte sur l’enregistrement de ce qui est là. Ceci porte sur le refus d’enregistrer ce qui ne devrait pas l’être.

NetBox valide chaque objet avant de l’écrire, et vous pouvez ajouter vos propres règles par-dessus. Il y a trois mécanismes, ils vivent tous dans configuration.py, et entre eux ils couvrent presque tout ce dont un standard maison a besoin.

Un : des règles simples, sans code

Un validateur peut être une simple correspondance entre des noms de champs et des conditions. Pas de Python, et c’est portable d’une installation à l’autre parce que ce ne sont que des données.

CUSTOM_VALIDATORS = {
    'dcim.site': (
        {'description': {'required': True}},
    ),
    'dcim.device': (
        {'name': {'regex': r'^[a-z0-9]+-[a-z]+-([0-9]{2}|[a-z])$'}},
    ),
}

Les conditions disponibles sont min, max, min_length, max_length, regex, required, prohibited, eq et neq. Vous pouvez atteindre un objet lié par un chemin pointé, donc region.name sur un site est permis, et vous pouvez filtrer sur request.user.username même si la documentation vous dit, à juste titre, d’utiliser plutôt les permissions pour cela.

Ces deux règles se déclenchent immédiatement :

POST a site with no description
  {"__all__": ["Custom validation failed for description:
                ['This field must not be empty.']"]}

POST a device named "Server1"
  {"__all__": ["Custom validation failed for name: ['Enter a valid value.']"]}

La seconde est un standard de nommage, imposé. Pas écrit sur une page de wiki, pas dans la tête de quelqu’un, pas une chose pour laquelle le nouveau se fait reprendre en revue trois semaines plus tard. La base ne l’accepte pas.

Deux : une classe de validateur, quand la règle est une phrase

Les règles simples vérifient un champ contre une constante. Les vraies règles maison sont généralement conditionnelles : ceci ne compte que quand cela. Pour celles-là vous dérivez CustomValidator, surchargez validate() et appelez fail().

J’en ai mis trois dans un module en /opt/netbox/netbox/house_rules.py :

from extras.validators import CustomValidator


class BillableKitNamesItsCustomer(CustomValidator):
    """Anything in a role we sell has to say whose it is."""
    BILLABLE_ROLES = {'hypervisor'}

    def validate(self, instance, request):
        role = getattr(instance, 'role', None)
        if role and role.slug in self.BILLABLE_ROLES and not instance.tenant:
            self.fail(
                f"A {role} is billable kit, so it must name the customer it belongs to.",
                field='tenant',
            )


class RackedDeviceNeedsAPosition(CustomValidator):
    """A device in a rack with no rack unit is a device nobody can find."""
    def validate(self, instance, request):
        if instance.rack and instance.position is None:
            if instance.device_type and instance.device_type.u_height:
                self.fail(
                    "A device in a rack needs a rack unit. Somebody has to find it.",
                    field='position',
                )

et je les ai câblées par chemin pointé :

CUSTOM_VALIDATORS = {
    'dcim.device': (
        {'name': {'regex': r'^[a-z0-9]+-[a-z]+-([0-9]{2}|[a-z])$'}},
        'house_rules.BillableKitNamesItsCustomer',
        'house_rules.RackedDeviceNeedsAPosition',
    ),
    'ipam.prefix': (
        'house_rules.CustomerPrefixNeedsATenant',
    ),
}

Notez qu’un modèle prend un tuple de validateurs, règles simples et classes librement mélangées, et qu’ils s’exécutent tous. Même un validateur unique doit être passé comme itérable, et c’est facilement cinq minutes de perdues.

Ensuite les règles font ce qu’elles disent :

POST a hypervisor with no tenant
  {"tenant": ["A Hypervisor is billable kit, so it must name
              the customer it belongs to."]}

POST a device into a rack with no position
  {"position": ["A device in a rack needs a rack unit.
                Somebody has to find it."]}

POST a prefix with role "customer" and no tenant
  {"tenant": ["A customer prefix must be assigned to a tenant."]}

POST a leaf switch with no tenant
  accepted, because a leaf switch is not in BILLABLE_ROLES

Parce que fail() prend un field, le message atterrit sur la bonne case du formulaire plutôt qu’en haut de la page, et c’est la différence entre une règle que les gens apprennent et une règle que les gens prennent mal.

C’est aussi la réponse à la lacune de la section sur les custom objects. La validation là-bas n’existait que sur le formulaire web. Un CustomValidator s’exécute dans la couche modèle, donc il s’applique à l’interface, à l’API REST, aux mutations GraphQL, aux imports en masse et à tout ce que fait un script. Il y a un seul endroit où écrire la règle et aucun moyen de la contourner.

Trois : les protection rules, pour la suppression

CUSTOM_VALIDATORS garde les écritures. PROTECTION_RULES garde les suppressions, et il prend exactement les deux mêmes formes.

PROTECTION_RULES = {
    'dcim.device': (
        {'status': {'eq': 'offline'}},
    ),
}

Cela dit qu’un équipement ne peut être supprimé que lorsqu’il est hors ligne. Ce qui produit :

DELETE an active device
  {"detail": "Deletion is prevented by a protection rule:
              [\"Custom validation failed for status:
                ['Ensure this value is equal to offline.']\"]"}

set it to offline, then DELETE
  204 No Content

Deux frappes de clavier de friction entre quelqu’un et un équipement en service, et c’est l’assurance la moins chère de toute l’application. Faites du processus de décommissionnement la chose qui déverrouille le bouton de suppression.

Celle qui vous attrapera

Les données existantes ne sont pas vérifiées avant que vous les touchiez à nouveau. Ajouter une règle ne revient pas en arrière valider ce qui est déjà là. Elle reste tranquille jusqu’à ce que quelqu’un enregistre un objet qui la viole, et alors cette personne reçoit une erreur à propos d’une décision à laquelle elle n’a rien eu à voir.

Je l’ai vu arriver. mcr1-hv-06 a été créé avant que j’écrive quoi que ce soit de tout ceci, en hyperviseur sans tenant. Il était là, parfaitement tranquille. Puis j’ai édité sa description :

PATCH {"description": "touching it to trigger revalidation"}
  {"tenant": ["A Hypervisor is billable kit, so it must name
              the customer it belongs to."]}

L’édition n’avait rien à voir avec le tenant. La règle s’est déclenchée quand même, car la validation porte sur l’objet entier.

C’est un comportement correct et c’est aussi comment une nouvelle règle se transforme en ticket de support. Avant d’en activer une, lancez une requête sur les objets qui échoueraient et corrigez-les d’abord. L’API rend cela facile : la règle est un filtre, donc demandez les équipements ayant ce rôle et aucun tenant, et vous avez votre liste.

Activez la règle ensuite. Alors elle n’attrape plus que les nouvelles erreurs, et c’est à cela qu’elle sert.

Le piloter depuis Ansible

Une source de vérité que personne ne lit pourrit. La collection netbox.netbox est la façon dont la plupart des gens empêchent cela, et elle fonctionne dans les deux sens : NetBox dit à Ansible ce qui existe, et Ansible dit à NetBox ce qu’il a construit.

Elle est en version 3.23.0, sous licence GPL-3.0, et a été téléchargée depuis Galaxy plus de 13,4 millions de fois. Elle porte 91 modules et un plugin d’inventaire.14 Tout ce qui suit a été exécuté contre la même instance, depuis un conteneur ne contenant rien d’autre qu’ansible-core 2.21.4, pynetbox 7.8.0 et la collection.

L’inventaire est une requête, pas un fichier

C’est la moitié qui se rentabilise dès le premier après-midi. nb_inventory construit votre inventaire Ansible directement depuis NetBox :

plugin: netbox.netbox.nb_inventory
api_endpoint: http://netbox:8080
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
config_context: false
group_by:
  - sites
  - device_roles
  - tenants
  - racks
device_query_filters:
  - has_primary_ip: true

Cela produit ceci, sans que personne n’entretienne une liste d’hôtes :

@sites_manchester-dc1:      @device_roles_hypervisor:
  |--mcr1-core-01             |--mcr1-hv-01
  |--mcr1-core-02             |--mcr1-hv-02
  |--mcr1-hv-01               |--mcr1-hv-03
  |--mcr1-hv-02               |--mcr1-hv-04
  ...                         |--mcr1-hv-05
@racks_MCR1-A07:            @tenants_ravenscroft-legal:
  |--mcr1-core-01             |--mcr1-hv-01
  |--mcr1-core-02             |--mcr1-hv-02
  ...                       @tenants_padgate-foods:
@device_roles_core-router:    |--mcr1-hv-03
  |--mcr1-core-01             |--mcr1-hv-04
  |--mcr1-core-02           @tenants_hartley-components:
                              |--mcr1-hv-05

Regardez la colonne de droite. Parce que les tenants sont renseignés sur les équipements, vous obtenez gratuitement un groupe par client, donc --limit tenants_ravenscroft-legal exécute un play contre exactement le matériel d’un seul client. Ajoutez un équipement dans NetBox et il est dans le groupe à l’exécution suivante. Décommissionnez-en un et il disparaît. Personne n’édite quoi que ce soit.

Chaque hôte arrive porteur de ce que NetBox sait de lui :

ansible_host     "2001:db8:20:20::11"
primary_ip4      "10.20.20.11"
primary_ip6      "2001:db8:20:20::11"
device_roles     ["hypervisor"]
sites            ["manchester-dc1"]
racks            ["MCR1-A07"]
tenants          ["ravenscroft-legal"]
device_types     ["sys-1029u-tn10rt"]
manufacturers    ["supermicro"]
status           {"label": "Active", "value": "active"}

Vingt-deux clés en tout, et l’une d’elles vaut la peine d’être remarquée avant qu’elle ne vous surprenne. ansible_host est l’adresse IPv6, parce que cet équipement a une primaire v6 renseignée. Je l’ai vérifié contre deux autres qui n’ont que de l’v4 et ils sont revenus en v4, donc la règle est que le plugin préfère l’v6 là où une primaire v6 existe. Ce qui est correct, et c’est aussi le genre de chose qu’on préfère découvrir maintenant plutôt qu’en se demandant pourquoi un play se connecte par un chemin auquel on n’avait pas pensé.

Écrire en retour

Les modules sont l’autre sens, et celui qu’il faut avoir est l’attribution d’adresses IP, car NetBox sait ce qui est libre et votre playbook non.

- name: Rack the device
  netbox.netbox.netbox_device:
    netbox_url: "{{ nb_url }}"
    netbox_token: "{{ nb_token }}"
    data:
      name: mcr1-hv-07
      device_type: SYS-1029U-TN10RT
      device_role: Hypervisor
      site: Manchester DC1
      rack: MCR1-A07
      position: 14
      face: Front
      tenant: Hartley Components
    state: present

- name: Let NetBox pick the next free address out of the customer prefix
  netbox.netbox.netbox_ip_address:
    netbox_url: "{{ nb_url }}"
    netbox_token: "{{ nb_token }}"
    data:
      prefix: 10.20.22.0/24
      tenant: Hartley Components
      assigned_object:
        device: mcr1-hv-07
        name: eno1
    state: new

Remarquez ce qui n’y est pas. Aucune adresse IP. Vous nommez le préfixe et NetBox vous rend la prochaine libre :

TASK [Let NetBox pick the next free address out of the customer prefix] ****
changed: [localhost]

"device   : mcr1-hv-07  (created)"
"interface: eno1  (created)"
"address  : 10.20.22.1/24  (allocated)"

Et c’est là dans l’interface une seconde plus tard, câblé à rien encore mais racké, rattaché et adressé :

Onglet interfaces de NetBox pour mcr1-hv-07, un équipement créé par le playbook, montrant une interface eno1 de type SFP28 25GE décrite to leaf-01 et portant 10.20.22.1/24

Le piège dans ce playbook

Exécutez-le une seconde fois sans changer une ligne et voici ce qui arrive :

"device   : mcr1-hv-07  (already correct)"
"interface: eno1  (already correct)"
"address  : 10.20.22.2/24  (allocated)"

L’équipement et l’interface sont idempotents. L’adresse ne l’est pas, et ce n’est pas un bug. state: present signifie « fais que ça ressemble à ceci ». state: new signifie « donne-m’en une nouvelle », à chaque fois, et c’est exactement ce qu’il a fait : deux adresses sur une interface après deux exécutions.

C’est très bien quand vous provisionnez réellement quelque chose de neuf, et cela mangera discrètement un préfixe si vous le mettez dans une tâche qui tourne chaque nuit. Attribuez une fois et consignez le résultat, ou utilisez state: present avec l’adresse que vous détenez déjà. Le module fait ce que vous avez demandé. La question est de savoir si vous avez demandé ce que vous vouliez dire.

Ce n’est pas seulement Ansible

La collection attire l’attention parce qu’Ansible est là où la plupart des équipes réseau se trouvent déjà, mais l’API est le produit et bien des choses la parlent.

ChoseCe que c’estLicence
pynetboxLe client Python que la collection utilise elle-mêmeApache 2.0
terraform-provider-netboxGérer les objets NetBox comme des ressources Terraform, activement maintenu par e-breuningerMPL 2.0
nornir_netboxNetBox comme inventaire Nornir, pour ceux qui font du Python plutôt que du YAMLApache 2.0
Prometheus SDSert à Prometheus ses cibles de scrape depuis NetBoxMIT
go-netboxUn client Go, même s’il n’a pas été touché depuis mai 2025voir le dépôt
DiodeLe pipeline d’ingestion de NetBox Labs pour pousser des données découvertesNetBox Limited Use

L’entrée Terraform est l’intéressante pour quiconque gère déjà son infrastructure ainsi, car elle permet à un seul plan de créer la ressource cloud et l’enregistrement NetBox qui la documente, plutôt que de laisser la seconde moitié à la mémoire de quelqu’un.15

Diode porte la même licence que Branching et Custom Objects, donc la même lecture s’impose avant de bâtir dessus.

Ce que l’après-midi achète vraiment

Tout ce qui précède m’a pris une journée sur une seule machine, et la majeure partie de cette journée est passée à semer des données pour que les écrans aient quelque chose dedans. L’installation, c’est vingt minutes dans les deux cas. Les décisions sont la partie qui compte et elles se prennent toutes dans la première heure : les tenants avant tout, les types d’équipement avant les équipements, les chiffres sur les types plutôt que sur le matériel.

Ce que vous obtenez pour cela n’est pas de la documentation. Ce n’est pas une distinction que l’on fait avant d’avoir eu les deux : la documentation est une chose qu’on écrit puis qu’on arrête d’entretenir. Ce que vous obtenez est une base qui refuse de contenir une contradiction : elle ne vous laissera pas mettre deux choses dans une même unité de baie, ni une baie dans une location qui appartient à un autre site, ni un équipement sur un type qui n’existe pas. Chacun de ces refus est une dispute que vous n’aurez pas dans six mois.

Et elle vous dira des choses que personne ne lui a demandées. Cette armoire est remplie à 28,6 % de matériel et à 90,7 % d’électricité. Personne n’a cherché à trouver ça. C’est tombé d’une puissance mise une fois sur un type d’équipement, et c’est la différence entre racker la prochaine commande là et l’apprendre à la dure.

La réserve honnête est la même que pour toute source de vérité. Elle ne vaut que ce qu’il vous coûte de la tenir juste, et la seule version qui survit à un trimestre chargé est celle où quelque chose casse visiblement quand les données sont fausses. Câblez votre supervision, votre provisionnement ou vos règles de pare-feu pour qu’ils y lisent, et une entrée fausse cesse d’être un problème de documentation dont quelqu’un s’occupera. Elle devient une panne à neuf heures et demie un mardi, avec un nom dessus. Cela ressemble à un coût. C’est tout le mécanisme.

Personne ne vous en remerciera non plus. Un enregistrement juste se manifeste comme la migration qui a pris quinze jours au lieu d’un trimestre, l’audit qui a pris un après-midi, le client qui a eu une réponse droite au téléphone. Rien de cela n’apparaît sur un rapport à côté de votre nom.

Faites-le quand même. L’alternative est une entreprise incapable de se décrire, et une entreprise incapable de se décrire n’est pas dirigée. Elle est remémorée, par de moins en moins de gens chaque année.


  1. NetBox, Introduction — « Today, the open source project is stewarded by NetBox Labs and a team of volunteer maintainers », et l’origine chez DigitalOcean en 2015, passé en open source en juin 2016. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. NetBox, LICENSE.txt — Apache License 2.0, copyright DigitalOcean, LLC. Origine et prise en charge d’après l’introduction. ↩︎ ↩︎

  3. Notes de version de Nautobot v1.0.0 — « a divergent fork of NetBox 2.10 », publiée le 26 avril 2021, et la liste de ce qu’elle a ajouté par rapport à NetBox 2.10. ↩︎ ↩︎

  4. Network to Code, « Why Did Network to Code Fork NetBox? », 25 février 2021 — le raisonnement sur les SLA et le support au long cours, la divergence de vision, et « the NetBox project team suggested that we should consider forking ». ↩︎

  5. NetBox Labs a été fondée en 2023 en spin-out de NS1 après son acquisition par IBM, cofondée par le mainteneur principal de NetBox Jeremy Stretch, et a annoncé une Série A de 20 M$ en avril 2023 : NetBox Labs, « Let’s Go: Announcing NetBox Labs » et l’annonce de la Série A. ↩︎

  6. NetBox Labs, NetBox Enterprise — l’édition commerciale autogérée, avec « 24/7 expert assistance from the NetBox Labs team » ; NetBox Cloud est l’offre hébergée. Les termes de SLA précis ne sont pas publiés sur cette page. ↩︎

  7. Nombres d’étoiles et de forks, dates de publication et licences des deux projets relevés via l’API GitHub le 30 septembre 2026 : netbox-community/netbox et nautobot/nautobot. Les versions parallèles 3.2.x et 2.4.x de Nautobot sont toutes deux datées du 28 septembre 2026. ↩︎

  8. netbox-community/netbox-docker — la pile de conteneurs de la communauté, Apache 2.0. ↩︎

  9. NetBox, Installation — le tableau des versions prises en charge, et les notes de version v4.7 pour les minimums PostgreSQL et Redis relevés. Version et édition relevées dans netbox/release.yaml. ↩︎

  10. NetBox Limited Use License 1.0 — portée par netbox-custom-objects et, à l’identique, par netbox-branching. Les deux clauses citées sont littérales. ↩︎

  11. NetBox, HTTP Server installation, « What’s Next? » — « Some of the most popular plugins include » NetBox Branching, NetBox Custom Objects, NetBox DNS et NetBox BGP, sans aucune mention des termes de licence. ↩︎

  12. NetBox, Data Validation configuration — FIELD_CHOICES, et le suffixe signe plus qui étend au lieu de remplacer : « To replace the available choices, specify the app, model, and field name separated by dots … To extend the available choices, append a plus sign ». ↩︎

  13. Documentation de netboxlabs/netbox-custom-objects — « Deleting a Custom Object Type drops an entire database table and should be done with caution. » ↩︎

  14. netbox.netbox sur Ansible Galaxy et netbox-community/ansible_modules — version 3.23.0, GPL-3.0, 91 modules plus le plugin d’inventaire nb_inventory. Nombre de téléchargements relevé sur Galaxy le 30 septembre 2026. Tout ce qui est montré a été exécuté avec ansible-core 2.21.4 et pynetbox 7.8.0. ↩︎

  15. Licences, activité et nombres d’étoiles relevés sur chaque projet le 30 septembre 2026 : pynetbox (Apache 2.0), terraform-provider-netbox (MPL 2.0, v6.0.0-rc.1 sur le registre Terraform), nornir_netbox (Apache 2.0), netbox-plugin-prometheus-sd (MIT), go-netbox (dernier push le 9 mai 2025) et Diode, qui porte la même NetBox Limited Use License 1.0 que Branching et Custom Objects. ↩︎