Alles in dit stuk is uitgevoerd tegen een NetBox 4.7.2 op mijn eigen bureau, met een echt bestand erin geladen: één site, één kast, veertien apparaten, gekabeld, van stroom voorzien en geadresseerd over drie klanten. Elke schermafbeelding is die instantie, en elke foutmelding is er een die ik echt heb gekregen.

Het gaat in de volgorde waarin je het zou tegenkomen. Wat het ding is, waarom je er een zou willen, de fork waar je binnen een week zoeken over hoort, hoe je er een opzet, de volgorde waarin je het moet vullen, en dan wat je eruit terugkrijgt. Het aanpas- en uitbreidwerk staat aan het eind, omdat niets daarvan betekenis heeft voordat je de vorm hebt gezien van wat je uitbreidt.

Wat NetBox is

NetBox is een database met een heel specifieke mening over waar een netwerk uit bestaat, en een webapplicatie daarboven. Het is een Django-applicatie op PostgreSQL, het is open source onder Apache 2.0 sinds DigitalOcean het in juni 2016 vrijgaf, en het project wordt vandaag beheerd door NetBox Labs samen met een team vrijwillige maintainers.12

Daaronder zijn het 149 modellen over tien applicaties, bereikbaar via 146 REST-endpoints en één GraphQL-endpoint. Geteld op de instantie die ik hiervoor heb gebouwd, niet afgelezen van een featurepagina:

ApplicatieModellenApplicatieModellen
dcim56virtualization7
extras23tenancy6
ipam18wireless3
circuits11users7
vpn10core8

Zesenvijftig daarvan zijn DCIM, de fysieke laag: sites, locaties, racks, apparaattypes, apparaten, en elk soort poort, bay en kabelafsluiting die een apparaat kan hebben. Achttien zijn IPAM. De rest dekt circuits, tunnels en IKE-policies, virtuele machines en clusters, draadloze verbindingen, tenancy, en de machinerie die het geheel uitbreidbaar maakt.

Het aantal is niet het punt. De joins zijn dat, en de snelste manier om dat te zien is het scherm waar NetBox het bekendst om is.

Begin bij de kast, want dat is het scherm dat het ding verkoopt.

NetBox rack-detailpagina voor MCR1-A07, links regio North West, site Manchester DC1, locatie Hall 2, status Active en 28,6% ruimtebenutting, rechts de voor- en achterelevatie met patchpanelen op U41 en U42, twee routers op U38 en U39, twee switches op U35 en U36 en zes hypervisors tussen U15 en U20, elk gekleurd naar rol

Die elevatie wordt uit de data getekend, niet geüpload. Elk apparaat staat er omdat iets zegt dat het die rack-units bezet, met die kant naar voren, en de kleuren komen van de rol die je het gegeven hebt. De ruimtebenutting leest 28,6% omdat NetBox het heeft uitgerekend. Niemand onderhoudt dat getal.

Er volgen twee dingen uit die meer waard zijn dan het plaatje.

Je kunt vragen welke units vrij zijn en een antwoord krijgen waar je mee verder kunt. Je kunt ook units reserveren voordat er iets in is gebouwd, en dat is het verschil tussen ruimte verkopen die je hebt en ruimte verkopen waarvan je denkt dat je die hebt.

Wat het bewust niet doet

Een product dat weet wat het niet is, is zeldzamer dan een product dat alles slecht doet, en NetBox’ eigen documentatie is er onomwonden over. Het levert geen netwerkmonitoring, geen DNS-dienst, geen RADIUS, geen configuratiebeheer en geen facilitair beheer.1

Belangrijker nog: het bevat de gewenste toestand van je netwerk, niet de operationele toestand, en de documentatie zegt dat geautomatiseerde import van de levende netwerktoestand “strongly discouraged” is, omdat elk record eerst door een mens gecontroleerd moet worden.1

Dat is de beslissing waarop al het andere rust, en het is de beslissing waar mensen over in discussie gaan. Het argument gaat zo: een source of truth zou toch de waarheid moeten zijn, dus ontdek het netwerk en laad het in. Het antwoord is dat een ontdekt netwerk je vertelt wat er is, en wat er is omvat elke fout die ooit iemand heeft gemaakt. Een switchpoort die in 2021 in het verkeerde VLAN is gelaten, is een feit. Het is geen bedoeling.

NetBox bevat de bedoeling. Je monitoring bevat de werkelijkheid. Het interessante getal is het verschil daartussen, en een verschil kun je niet uit één invoer berekenen.

Het andere uitgangspunt staat er net zo helder: als je kunt kiezen tussen een relatief eenvoudige tachtigprocentoplossing en een veel complexere complete, neem dan de eenvoudige.1 Dat voel je de eerste keer dat je iets wilt modelleren wat het niet modelleert, en er staat tegen het eind een hele reeks secties over wat je dan doet.

NetBox doetNetBox doet niet
Vastleggen wat er zou moeten staanOpvragen wat er staat
Het bedoelde VLAN voor een poort bevattenJe vertellen dat de poort eruit ligt
Zeggen van welke klant een prefix isHet hem factureren
De configuratie van een apparaat uit een template renderenDie naar het apparaat pushen
Het circuit, de provider en de commit bijhoudenHet circuit monitoren
Zeggen in welk rack een apparaat staat, en in welke UDe kast opendoen

Lees die rechterkolom als een lijst van gereedschap dat je nog steeds nodig hebt. Lees hem verkeerd en je gaat proberen NetBox al dat gereedschap te laten zijn, en zo wordt een source of truth nog een systeem waar niemand op vertrouwt.

Waarom je er een nodig hebt

Vraag een managed service provider waar het gezaghebbende record van het netwerk van een klant staat, en je krijgt een antwoord. Vraag het twee van hun engineers apart en je krijgt er twee.

Eén wijst naar een spreadsheet. Eén wijst naar een schema dat het laatst is opgeslagen door iemand die in 2023 is weggegaan. Nog iemand zegt dat de firewallconfiguratie de documentatie is, wat tenminste eerlijk is, want een configuratie beschrijft wel degelijk wat een kast doet. Hij beschrijft alleen niet waarom, of wie erom heeft gevraagd, of welke van de vier klanten achter die kast voor de regel betaalt.

Het falen is nooit de dag waarop je merkt dat het record verkeerd is. Het is de dag waarop iemand het nodig heeft.

Het momentWat je moet leverenWat het kost als je dat niet kunt
Een verlengingsgesprekEen post-voor-post overzicht van wat het maandbedrag kooptDe aanbieding van de concurrent is uitgesplitst, want die is gaan tellen
Een engineer neemt ontslagAlles wat hij wist, opgeschrevenZes maanden uitzoeken, één ticket per keer
Een klant vertrektEen beschrijving van zijn eigen bestandDrie weken om die samen te stellen, en een referentie die hij eerlijk zal geven
Een auditor stelt een scopevraagWelke systemen persoonsgegevens bevatten en waar die fysiek staanEen toezichthouder iets vertellen wat later niet waar blijkt
Een migratie moet geprijsd wordenEen telling van wat er echt staatJe biedt op een schatting en eet het verschil op

Niets daarvan is exotisch. Dat is dinsdag.

Wat je eruit haalt is geen documentatie. Documentatie is iets wat je schrijft en daarna niet meer onderhoudt. Wat je krijgt is een database die weigert een tegenspraak te bevatten, en die vragen beantwoordt die niemand vooraf had bedacht. Verderop in dit stuk staat een kast die 28,6 procent vol blijkt te zitten met apparatuur en 90,7 procent met stroom. Niemand was dat gaan zoeken. Het viel eruit omdat er één keer een wattage op een apparaattype is ingevuld.

De eerlijke kanttekening hoort erbij, en de laatste sectie gaat daarover. Een record is alleen waard wat het je kost om het juist te houden. Maar het alternatief is een bedrijf dat zichzelf niet kan beschrijven, en de eerste die dat ontdekt is meestal een klant.

De andere: Nautobot

Hier loop je binnen ongeveer een week zoeken tegenaan, dus het is nuttig te weten wat er is gebeurd.

In 2021 forkte Network to Code NetBox en noemde het resultaat Nautobot. Geen zachte fork en geen distributie: een harde fork die al vijf jaar uiteenloopt. De eigen v1.0-releasenotes beschrijven het als “a divergent fork of NetBox 2.10”, de repository is aangemaakt op 19 februari 2021 en v1.0.0 kwam op 26 april 2021.3

De genoemde redenen staan op hun eigen blog en zijn in hun woorden beter te lezen dan in de mijne. Drie dingen dreven het. Ze wilden enterprise-support verkopen: “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.” Ze wilden dat de source of truth in het midden van een automatiseringsplatform zat in plaats van documentatie te dienen. En “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

Bij die eerste reden moet de datum, want hij is niet meer waar. In februari 2021 was er geen bedrijf achter NetBox dat je iets kon verkopen. NetBox Labs is pas in 2023 opgericht, als spin-out uit NS1 nadat IBM het had overgenomen, mede-opgericht door NetBox’ eigen lead maintainer.5

En het is geen derde partij die een bedrijf op het project van iemand anders heeft gebouwd. NetBox Labs is de beheerder van NetBox: de documentatie van het project zelf zegt “the open source project is stewarded by NetBox Labs and a team of volunteer maintainers”.1 Ze verkopen NetBox Enterprise voor zelfbeheerde installaties, hosten het voor je als NetBox Cloud, en bieden support rond de klok.6

“you cannot buy support for NetBox” was dus een eerlijke uitspraak toen Network to Code forkte, en is nu geen eerlijke uitspraak meer. Achter beide projecten staat een commercieel bedrijf dat iets ondertekent, en in het geval van NetBox is dat bedrijf degene die het project beheert.

De regel die de meeste mensen missen is de volgende, en die is de reden dat dit geen vies verhaal is: “the NetBox project team suggested that we should consider forking.”

Beschaafder wordt een fork nauwelijks. Twee groepen wilden iets anders, zeiden dat over een lange periode, en gingen uit elkaar in plaats van te vechten over één codebase. Beide helften zijn nog steeds Apache 2.0. Niemand heeft iets meegenomen waar hij geen recht op had.

Waar de fork echt voor was

De releasenotes van Nautobot 1.0 noemen wat het toevoegde ten opzichte van NetBox 2.10, en die lijst vertelt het argument beter dan welk blogstuk ook:3

Wat Nautobot in 2021 toevoegdeWaar NetBox nu staat
GraphQL-ondersteuningNetBox heeft het
Git-integratie als databronNetBox heeft het, als gesynchroniseerde databronnen
Single sign-onNetBox heeft het
SecretsNetBox heeft het via een plugin
Scripts en reports samengevoegd tot JobsNetBox haalt scripts in 4.7 naar een plugin
Custom fields op alle modellenNetBox heeft brede ondersteuning voor custom fields
Data-validation-plugin-APINetBox heeft custom validation rules
Aanpasbare statussen, als databaseobjectenNetBox’ statussen zijn nog steeds een Python choice set
Door gebruikers gedefinieerde relaties tussen modellenNetBox heeft geen equivalent
UUID-primaire sleutelsNetBox gebruikt integer-sleutels
Verbeteringen aan de plugin-APINetBox’ plugin-framework is sindsdien flink gegroeid

De bovenste helft is grotendeels naar elkaar toe gegroeid. Meerdere dingen die Nautobot in 2021 uitbracht, kwamen daarna in NetBox terecht, en dat is wat meestal gebeurt als twee projecten dezelfde problemen in het openbaar oplossen.

De onderste drie zijn niet naar elkaar toe gegroeid, en ze zijn architectonisch in plaats van cosmetisch. Ik heb vandaag beide codebases nagekeken in plaats van op de aantekeningen uit 2021 te vertrouwen.

Nautobots Status is een databasemodel, in de eigen broncode beschreven als een “Model for database-backend enum choice objects”, dus een status is een regel die iemand in de UI kan toevoegen. NetBox’ statussen komen uit een Python choice set, en daarom voegt de sectie verderop er een toe door configuration.py aan te passen en te herstarten. Nautobot heeft Relationship- en RelationshipAssociation-modellen, dus je kunt een relatie tussen twee bestaande objecttypes definiëren zonder code te schrijven. NetBox’ antwoord op dat probleem is een plugin, en dat is de laatste reeks secties in dit stuk. En Nautobots primaire sleutels zijn UUID’s, waar die van NetBox integers zijn.

Ze hebben ook gedaan waarvoor ze forkten. Vandaag publiceert Nautobot 3.2.x en 2.4.x op dezelfde dag, wat een echte langetermijnonderhoudslijn naast de huidige is, en dat was een van de drie genoemde redenen.

Welke van de twee

Eerst de eerlijke cijfers. NetBox heeft 21.625 sterren en 3.133 forks; Nautobot heeft 1.617 en 422.7 Naar beide is in de laatste twee dagen gepusht, beide zijn Apache 2.0, en achter beide staat nu een commercieel bedrijf dat support en hosting verkoopt.

Dat gat is geen oordeel over kwaliteit. Het weerspiegelt vijf jaar voorsprong en het feit dat de meeste mensen die een source of truth nodig hebben NetBox het eerst vinden. Maar het beslist wel over wat meestal meer uitmaakt dan features: hoeveel plugins, integraties, Ansible-modules, forumantwoorden en collega’s je vindt voor degene die je kiest.

Dus: wil je een inventaris en een source of truth die andere systemen lezen, en wil je het grootste ecosysteem en het makkelijkste aannemen, dan is het antwoord NetBox, en daar gaat de rest van dit stuk over. Is jouw reden voor een source of truth specifiek om er automatisering uit te besturen, of wil je statussen en relaties laten definiëren door je team in plaats van door een configuratiebestand en een herstart, ga dan eerst goed naar Nautobot kijken voordat je beslist.

Laat niemand je er een van de twee verkopen op support alleen. Beide kampen hebben dat nu gedekt, en de verschillen die er over vijf jaar nog zijn, zijn die uit de tabel hierboven.

Wat je niet moet doen is er een kiezen omdat iemand je heeft verteld dat de ander dood is. Geen van beide is dat, en beide brachten deze week nog releases uit.

Er een aan de praat krijgen

Twee routes, en ik heb ze hier beide gelopen. Containers als je het in twintig minuten werkend wilt hebben, pakketten op een host als het dragend wordt.

De container-stack

De community onderhoudt netbox-docker, en dat is het snelle antwoord: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

Dat geeft je de applicatie, PostgreSQL, Redis en een achtergrondworker, aan elkaar geknoopt. Ik heb hetzelfde met de hand onder podman gebouwd om de onderdelen te zien, en de onderdelen zijn het waard om te kennen, want twee ervan pakken mensen:

ContainerDoet watAls je hem weglaat
netboxDe Django-applicatie achter gunicornniets werkt
postgresDe database, 15 of nieuwerniets werkt
redis / valkeyTwee databases: één voor taken, één voor cachingniets werkt
netbox-workerrqworker, die de takenwachtrij leegwerktwebhooks vuren nooit, achtergrondtaken lopen nooit, en niets waarschuwt je
netbox-housekeepingHet periodieke opruimenchangelog-records verlopen nooit

Die vierde regel is de belangrijke. Zonder worker ziet alles er gezond uit. Event rules stapelen op en blijven liggen.

Twee dingen beten me bij de eerste run, en geen van beide staat in een foutmelding waar je op zou zoeken.

De eerste migratie duurt lang. Geen minuut. Op deze machine waren het er meerdere, omdat NetBox 4.7 django-mptt vervangt door PostgreSQL ltree en onderweg elke hiërarchische tabel herbouwt. De container zit daar gewoon migraties toe te passen. Laat hem met rust.

Zonder API_TOKEN_PEPPERS kun je geen v2-API-token aanmaken, en het enige teken is een waarschuwing in het log:

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

Zet er minstens één, van minstens vijftig tekens, voordat je gaat zoeken waarom de API je afwijst.

Op een host, uit de pakketten

De gedocumenteerde route is getest op Ubuntu 24.04. Ik heb hem van begin tot eind op een schone gelopen, en het kwam hierop uit:

ComponentWat 24.04 me gafWat NetBox 4.7 nodig heeft
PostgreSQL16.1515 of nieuwer
Redis7.0.156.0 of nieuwer
Python3.12.33.12, 3.13 of 3.14
Django6.1.1komt met NetBox
NetBoxv4.7.2

Die minima zijn die van NetBox zelf, en 4.7 heeft zowel de PostgreSQL- als de Redis-ondergrens verhoogd.9

De hele klus is vijf stappen.

# 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

Die vijf waarden zijn ALLOWED_HOSTS, DATABASES, REDIS, SECRET_KEY en API_TOKEN_PEPPERS. Genereer de laatste twee apart en gebruik niet de een voor de ander. Laat de sleutelgenerator dus twee keer lopen en plak de resultaten op verschillende regels.

upgrade.sh bouwt de virtuele omgeving, installeert elke Python-afhankelijkheid, voert de migraties uit, bouwt de documentatie voor offline gebruik en verzamelt de statische bestanden. Als het op een verse machine klaar is, print het een waarschuwing die alarmerend lijkt en dat niet is:

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.)

Dan een superuser, en dat loopt zo:

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

Voor iets echts zet je gunicorn ervoor in plaats van runserver. De configuratie en de unit-bestanden staan al in de repository, en dat is het detail dat je moet kennen omdat mensen hun eigen schrijven:

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 draait gunicorn op 127.0.0.1:8001, netbox-rq.service draait de worker, en nginx of Apache gaat ervoor om TLS af te handelen en /static te serveren. Twee diensten, en de tweede is dezelfde worker die de container-stack nodig heeft.

De volgorde waarin je het moet vullen

Een verse NetBox is een lege database met meningen, en het eerste uur ermee gaat meestal op aan uitzoeken welke dat zijn. Je gaat een apparaat toevoegen, en het laat je niet.

NetBox-formulier Add a new device, met Name, Device role gemarkeerd met een rood sterretje, Description en Tags onder het kopje Device, dan Device type ook met een rood sterretje onder Hardware, gevolgd door Serial number, Asset tag, Cooling method en Airflow

Die rode sterretjes zijn de hele les. NetBox legt niets vast voordat de dingen bestaan waar het aan hangt, en het doet niet moeilijk: een apparaat zonder type is een regel die geen van de vragen kan beantwoorden waar een apparaat voor is.

In plaats van te gokken heb ik dus het model gevraagd welke foreign keys echt verplicht zijn, en daarna geprobeerd elke regel via de API te breken om te zien wat er terugkomt.

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."]}

Elk ervan weigerde, en zei precies wat er ontbrak. Hier dezelfde informatie als lijst van voorwaarden:

Om aan te makenEerst nodigOptioneel, maar je wilt het eerst
Tenantnietseen tenantgroep
Region, Site groupnietseen ouder van dezelfde soort, ze nesten
Sitenietseen Region, een Site group, een Tenant
Locationeen Siteeen bovenliggende Location, een Tenant
Rack typeeen Manufacturer
Rackeen Siteeen Location, Rack group, Rack role, Rack type, Tenant
Device typeeen Manufacturer
Device rolenietseen bovenliggende Device role, ze nesten
Deviceeen Device role, een Device type, een Siteeen rack en positie, een Platform, een Tenant
Interface, en elk ander componenteen Device
Cabletwee dingen om op af te sluiteneen Tenant
Aggregateeen RIReen Tenant
VLANnietseen VLAN group, een Role, een Tenant
Prefixnietseen Site, een VLAN, een Role, een VRF, een Tenant
IP addressnietseen Interface om het aan toe te wijzen, een Tenant
Circuiteen Provider en een Circuit typeeen Tenant
Circuit terminationeen Circuiteen Site om op te landen
Clustereen Cluster typeeen Cluster group, een Site, een Tenant
Virtual machineeen Site, een Cluster of een Deviceeen Platform, een Tenant
VM interfaceeen Virtual machine

De tweede kolom is die wat je kost. Prefixes, IP-adressen, VLAN’s en tenants vereisen helemaal niets, dus niets houdt je tegen om ze op dag één in elke volgorde aan te maken. Of ze ergens goed voor zijn is een andere vraag, want een adres zonder interface erachter is een regel in een lijst, en een apparaat zonder tenant is een apparaat dat je later nog eens gaat bewerken.

De volgorde om echt in te werken

Dat geeft een reeks. Werk hem af en niets weigert je ooit iets.

Eerst de dingen waar al het andere aan hangt. Niets hiervan is opwindend en alles ervan is goedkoop om fout te doen.

  1. Tenantgroepen, dan tenants. Doe deze voor al het andere. Achtentwintig modellen nemen een tenant, waaronder Site, Location en Rack, dus als de klanten nog niet bestaan kun je ze niet onderweg meestempelen en ga je later in bulk bewerken.
  2. Regio’s en site groups. Beide optioneel, beide nesten in zichzelf, en ze staan los van elkaar. Regio’s zijn voor geografie, site groups voor functie, en je kunt er een, beide of geen gebruiken.
  3. Sites. De wortel van bijna alles. Een site vereist niets, en daarom is het het eerste wat je echt kunt aanmaken.
  4. Locaties. Die hebben een site nodig, en ze nesten, dus een hal met rijen met pods is één model drie diep.
  5. Rack roles en rack groups. Beide optioneel. Rack groups zijn vlak en staan naast locaties als tweede as, wat handig is voor rijen en pods.
  6. Manufacturers, dan rack types. Een rack type heeft een manufacturer nodig. Laat rack types weg als je de kasten zelf niet modelleert.
  7. Racks. Die hebben een site nodig. Geef je er ook een locatie bij, dan moet die locatie bij die site horen, en NetBox controleert dat.

Dan de hardwarecatalogus, niet de hardware. Voordat je één apparaat kunt toevoegen heb je manufacturers, device types, device roles en, in de praktijk, platforms nodig.

  1. Manufacturers. Je hebt er misschien al een paar uit stap 6, want rack types hebben ze ook nodig. Module types eveneens.
  2. Device types, die elk een manufacturer nodig hebben.
  3. Device roles, die nesten, en platforms.

Dit is de stap die mensen overslaan, en het is de stap die bepaalt hoeveel typewerk de rest van de klus kost. Een device type draagt zijn eigen interfaces, poorten en bays als templates, dus elk apparaat dat je ervan aanmaakt komt met de juiste componenten er al op. Doe het type één keer goed en veertig ervan inracken zijn veertig namen.

Platform is de buitenbeen in die lijst, want een apparaat vereist er strikt genomen geen. Doe het nu toch. Het is wat later het config-template en de NAPALM-driver draagt, en teruggaan om het op een heel bestand te zetten is dezelfde avond die je aan tenants had besteed.

Dan de apparatuur zelf.

  1. Apparaten. Rol, type en site zijn alle drie verplicht. Rack en positie zijn optioneel, en als je ze geeft moeten ze consistent zijn met de site.
  2. Componenten, als het device type ze niet al heeft geleverd.
  3. Kabels, tussen de componenten.

Dan de adressering, want een adres wil een interface om op te leven en de interface bestaat pas na stap 12.

  1. RIR’s, dan aggregates.
  2. Prefix- en VLAN-rollen, VRF’s, VLAN groups en dan VLAN’s.
  3. Prefixes, dan de losse IP-adressen.

Dan de commerciële laag.

  1. Providers, provider accounts en circuit types.
  2. Circuits, en sluit ze dan af op sites.

En het virtuele bestand, dat het fysieke spiegelt.

  1. Cluster types en cluster groups, dan clusters.
  2. Virtuele machines, die een site, een cluster of een apparaat nodig hebben, en dan hun interfaces.

Stap 1 tot 10 zijn een middag en voelen als administratie. Er is niets glamoureus aan. Het is ook de middag die bepaalt of stap 11 een ochtend of veertien dagen kost.

De regels die later bijten

Verplichte velden zijn de makkelijke helft, want die falen direct en vertellen je waarom. Waar mensen op stuklopen zijn de consistentieregels, en die vuren pas als je genoeg data hebt om jezelf tegen te spreken:

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)"]}

Die laatste is degene waar ik naar zou wijzen als iemand vroeg waarom je je hier druk om maakt. NetBox weet dat het apparaat 1U is, weet wat er in de kast staat, en laat je geen twee dingen op dezelfde plek vastleggen. Jouw spreadsheet laat je dat de hele middag doen zonder een woord te zeggen, en je komt het erachter als iemand met een doos in zijn handen in de hal staat.

Niets hiervan is instelbaar en niets hiervan zou dat moeten zijn. Dat is het verschil tussen een record en een wens.

In gebruik: wat elk scherm je geeft

Met het bestand geladen is dit wat je er echt uit terugkrijgt. Alles hieronder is diezelfde instantie: één kast, veertien apparaten, gekabeld en geadresseerd over drie klanten.

Een apparaat is zijn componenten

Klik in een van die hypervisors en het interessante tabblad is niet de samenvatting, het zijn de interfaces.

NetBox interfaces-tabblad voor apparaat mcr1-hv-01 met drie interfaces: eno1, SFP28 25GE, beschreven to leaf-01, met IP-adres 10.20.20.11/24, kabel MCR1-DAC-0001A naar mcr1-leaf-01 Ethernet1; eno2 SFP28 25GE to leaf-02 met kabel MCR1-DAC-0001B naar mcr1-leaf-02 Ethernet1; en ipmi 1000BASE-T beschreven BMC met 10.20.10.101/24

Lees één regel dwars. De interface, zijn snelheid, wat je erover hebt geschreven, het adres erop, het label op de kabel, en de poort aan de andere kant. Dat is één query, en het is het antwoord op de vraag die iedereen echt stelt, namelijk “wat hangt hier aan”.

Let op waar het adres zit. Het zit op eno1, niet op de server. Dat klinkt als muggenziften, precies tot je een kast hebt met een managementinterface, twee data-interfaces en een loopback, en iemand vraagt welk adres ervoor antwoordt. Een model dat adressen aan apparaten hangt kan het je niet vertellen. Dit kan het wel, en het kan ook het volkomen gewone geval van vier adressen op één interface bevatten.

De kabel volgen

Kabels sluiten af op componenten, niet op apparaten. Een interface aan de ene kant, een interface aan de andere, of een front port, een rear port, een stroomuitgang, een circuitafsluiting. Dat is het detail waarop de hele functie rust, en het is waarom de interfacelijst hierboven de andere kant in een eigen kolom kon afdrukken zonder dat het haar is verteld.

Modelleer een kabel van apparaat naar apparaat en je hebt een plaatje getekend. Sluit hem af op de poorten en NetBox kan hem aflopen.

NetBox-kabelspoor voor interface eno1, getekend als verticaal diagram: mcr1-hv-01, een Supermicro SYS-1029U-TN10RT in Manchester DC1 / Hall 2 / MCR1-A07 (A07) / Front / U20.0, zijn interface eno1, kabel MCR1-DAC-0001A gemarkeerd als Connected, dan Ethernet1 op mcr1-leaf-01, een Arista DCS-7050SX3-48YC8 op Front / U36.0. Trace Completed, in totaal 1 segment

Hier één hop, want het is een direct-attach-kabel. Zet patchpanelen in het midden en het loopt ze af, paneel voor paneel, en vertelt je wat er aan het eind van een traject door drie kasten hangt. Dat is de klus waar anders een zaklamp en iemand die het andere eind van een toongenerator vasthoudt aan te pas komt.

Het spoor drukt ook de volledige locatie van elk uiteinde af. Site, hal, kast, kant, rack-unit. Als je ooit aan de telefoon hebt geprobeerd een remote-hands-engineer uit te leggen naar welke kast hij moet kijken, is die regel de hele waarde.

Adressen, als boom in plaats van als tabblad

IPAM is de helft waarvoor mensen komen.

NetBox prefixlijst met 10.20.0.0/16 als Container met 5 children bij 1,6% benutting, beschreven Manchester DC1, en daaronder één niveau ingesprongen vijf actieve child-prefixes: 10.20.10.0/24 op VLAN mgmt (100) met rol Management, 10.20.20.0/24 met tenant Ravenscroft Legal op VLAN ravenscroft-prod (200), 10.20.21.0/24 voor Padgate Foods, 10.20.22.0/24 voor Hartley Components, en 10.20.30.0/29 op VLAN transit (110) bij 16,7% benutting

De inspringing wordt uit de adressen zelf berekend. Je vertelt NetBox niet dat 10.20.20.0/24 in 10.20.0.0/16 zit, het rekent dat uit, en het blijft dat uitrekenen als iemand er volgend jaar een /26 middenin zet.

Elke regel draagt de dingen waarop je echt filtert: het VLAN waar hij naar wijst, de rol, en de klant van wie hij is. De benutting wordt ook berekend.

Open er een en je krijgt de adressen erin, en de gaten.

NetBox IP Addresses-tabblad voor prefix 10.20.20.0/24, met een groene regel “10 IPs available”, dan 10.20.20.11/24 en 10.20.20.12/24 beide actief en met tenant Ravenscroft Legal, dan een groene regel “242 IPs available”

Die groene regels zijn de vrije ruimte, in lijn met de gebruikte ruimte getoond. Er is een API-aanroep die je het volgende vrije adres uit een prefix teruggeeft, en dat is het ene stuk IPAM-automatisering dat zich direct terugbetaalt, want het is wat mensen anders doen door naar een spreadsheet te turen en te hopen.

Een interface neemt zoveel adressen als je wilt, uit beide families, en je benoemt er een van elk als het primaire van het apparaat.

NetBox IP-adreslijst gefilterd op interface eno1 op mcr1-hv-01, met vier adressen: 10.20.20.11/24 gemarkeerd als primair, 10.20.20.201/24 een serviceadres, 2001:db8:20:20::11/64 primair v6, en 2001:db8:20:20::201/64 een serviceadres, alle met tenant Ravenscroft Legal

Twee IPv4 en twee IPv6 op één poort, wat een gewone dinsdag is en iets wat een apparaatgericht model helemaal niet kan uitdrukken.

Voer adressen in met het masker van het netwerk waarop ze liggen, niet als /32. NetBox accepteert 10.20.20.11/32 en zet het onder het juiste prefix, want de insluiting wordt uit het hostadres bepaald. Wat het niet doet is je achteraf overrulen: het masker wordt precies zo opgeslagen als het is getypt en zo teruggegeven aan alles wat het leest, dus een /32 op een LAN-adres rendert een /32 in de apparaatconfiguratie. De plek waar dat bijt is drie maanden later in een config-template, niet vandaag in het formulier.

Eén installatie, drie klanten

De meeste objecten in NetBox kunnen aan een tenant worden toegewezen. Een onderneming gebruikt dat voor bedrijfsonderdelen. Verkoop je managed services, dan maak je er een per klant.

NetBox tenantpagina voor Ravenscroft Legal in de groep Customers, met een paneel Related Objects met Circuits 2, Devices 2, IP Addresses 2, Prefixes 1 en VLANs 1, en tabbladen bovenaan voor Custom Objects, Contacts, Journal en Changelog

Dat paneel rechts is het antwoord op “wat heeft deze klant”, en het heeft zichzelf samengesteld. Geen rapport, geen spreadsheet, geen navraag bij de engineer die het heeft gebouwd.

Het is de moeite om precies te zijn over wat tenancy betekent, want het op dag één fout doen is een jaar ontwarren later. Een tenant betekent dat het object aan die klant is toegewijd. Een router die alleen hem bedient krijgt zijn tenant. Een firewall die vier van hen bedient hoort bij geen van hen, dus die krijgt er geen, en de relatie gaat ergens anders heen. Daarover verderop meer, want dat is het punt waar de meeste mensen merken dat ze iets van zichzelf moeten toevoegen.

Zet de cijfers op het apparaattype

Dit is de stap die een inventaris scheidt van iets wat vragen beantwoordt, en hij kost ongeveer tien minuten per apparaattype.

Een device type kan zijn gewicht dragen en, via zijn power port-templates, zijn stroomafname. Zet die één keer op het type en elk apparaat dat je er ooit van aanmaakt erft ze. Laat ze weg en NetBox vertelt je vrolijk dat een kast 28,6% vol zit en niets anders.

NetBox device type-pagina voor de Supermicro SYS-1029U-TN10RT, met hoogte 1U, full depth aangevinkt, gewicht 19,10 kg en cooling method Air, met een Power Ports-tabblad met aantal 2 en een paneel Related Objects met 6 devices

Ik heb alle vijf types hier een gewicht gegeven, elk twee power port-templates met een maximale en een toegewezen afname, het PDU-type een inlet en twaalf uitgangen, daarna een power panel en twee feeds naar de kast aangemaakt en het geheel gekabeld: elke PSU1 van elk apparaat naar PDU A, elke PSU2 naar PDU B, en de inlet van elke PDU naar zijn feed.

NetBox power feed MCR1-A07-A, type Primary, status Active, verbonden met mcr1-pdu-a INPUT, met een benutting van toegewezen 5340VA van 5888VA als rode balk op 90,7 procent, met elektrische kenmerken AC-voeding, 230 volt, 32 ampère, eenfasig en 80 procent maximale benutting

Dan verandert de rackpagina compleet.

NetBox rackpagina voor MCR1-A07, nu met cooling capability Hybrid, cooling capacity 15,00 kW, ruimtebenutting 28,6 procent in het groen en strombenutting 90,7 procent in het rood, naast de voor- en achterelevatie

Ruimtebenutting 28,6%. Strombenutting 90,7%.

Die kast zit voor een derde vol met apparatuur en is bijna door zijn stroom heen, en dat is een feit over je bestand dat geen spreadsheet ooit uit zichzelf aandraagt. Het is ook het feit dat bepaalt of de volgende order daar wordt ingeracked of ergens anders, en het viel uit data die je één keer hebt ingevoerd, op de types.

Twee dingen die het op nul laten staan

Ik had eerst 0,0%, twee keer, en beide oorzaken zijn het waard om te kennen want geen van beide levert een fout op.

De uitgangen moeten naar de inlet verwijzen. Een stroomuitgang op een PDU heeft een veld power_port dat naar de bovenliggende poort op hetzelfde apparaat wijst. Laat het leeg en de keten is verbroken: NetBox heeft geen manier om te weten dat die twaalf uitgangen door die inlet worden gevoed, dus niets aggregeert.

Laat de afnamevelden van de inlet leeg. Dit is de contra-intuïtieve. NetBox berekent de afname van een power port uit wat eraan hangt alleen als zijn eigen twee afnamevelden leeg zijn:

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, ...}

Ik had behulpzaam maximum_draw: 7400 op de PDU-inlet gezet, want daar is de PDU op gespecificeerd. NetBox geloofde me dus, nam allocated_draw als niet gezet, en rapporteerde nul. Maak beide leeg en het rekent het uit:

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

De regel is dus: zet echte cijfers op de bladeren, en laat de tussenliggende poorten leeg zodat NetBox ze kan optellen. Een administratief ingestelde waarde wint altijd van de berekende, wat correct gedrag is en precies het verkeerde om op een PDU te doen.

Koeling, wat nieuw is

Versie 4.7 heeft koeling aan DCIM toegevoegd, en het kwam met de helft die duur wordt. Een rack draagt een cooling capability van lucht, hybride of vloeistof en een capaciteit in kilowatt; een device type draagt een cooling method. Daarboven zitten cooling sources voor de koelmachines en CRAC-units, cooling feeds die een lus naar een rack voorstellen, en intake- en outflow-componenten op de apparaten zelf voor koudeplaten en verdelers.

De kast hierboven leest Hybrid, 15,00 kW, omdat ik dat aan het rack heb verteld. Neem je dit jaar vloeistofgekoelde apparatuur in ontvangst, dan is dat een model voor wat je nu in een spreadsheet bijhoudt.

Eén installatie, veel klanten

Tenancy is de reden dat een provider één NetBox kan draaien in plaats van een per klant, en het is de moeite te begrijpen wat het doet en, belangrijker, wat het niet doet.

Achtentwintig van NetBox’ modellen dragen een tenant-veld. Sites, locaties, racks en rackreserveringen. Apparaten, kabels en virtual device contexts. Prefixes, IP-adressen, ranges, aggregates, VLAN’s, VLAN groups, VRF’s, route targets, ASN’s. Circuits en circuit groups. Clusters en virtuele machines. Tunnels, L2VPN’s, wireless LAN’s en links. Power feeds en cooling feeds.

Dat is elk factureerbaar zelfstandig naamwoord. Zet het consequent en “wat heeft deze klant” is geen onderzoek meer.

Maar een tenant is een label, geen slot. Het zegt dat het object aan die klant is toegewijd. Het houdt niemand die kan inloggen tegen om het geheel te lezen, wat binnen één bedrijf prima is en helemaal niets meer waard is zodra een klant een account heeft.

Er volgen twee dingen uit, en het tweede is wat mensen fout doen.

Een tenant betekent toegewijd. Een router die maar één klant bedient krijgt zijn tenant. Een firewall die er vier bedient hoort bij geen van hen, dus die krijgt niets, en je legt de relatie vast op wat je werkelijk hebt verkocht. Een tenant op gedeelde apparatuur forceren maakt elk rapport dat op tenancy is gebouwd stilletjes verkeerd.

En toegang is een compleet eigen mechanisme.

In de permissions zit de isolatie

NetBox’ object permissions nemen een JSON-beperking, en de beperking is een Django-ORM-filter. Hij versmalt de queryset voordat er iets uit wordt gebouwd, dus elke weergave, export, API-aanroep en zoekopdracht wordt ermee versmald.

NetBox permission-detailpagina voor Ravenscroft read-only, met Enabled aangevinkt, Actions met View aangevinkt en Add, Change, Delete, Render configuration en Synchronize data alle doorgestreept, Object Types met Circuits circuit, DCIM device en IPAM prefix, één toegewezen gebruiker ravenscroft-ro, en een Constraints-paneel met de JSON tenant__slug gezet op ravenscroft-legal

Drie velden doen het werk. De objecttypes waarvoor hij geldt, de acties die hij toekent, en die beperking onderaan. Al het andere is boekhouding.

Hier is de apparatenlijst als beheerder.

NetBox apparatenlijst als gebruiker admin, Results 14, met mcr1-core-01 en 02, mcr1-hv-01 tot 06, mcr1-leaf-01 en 02, mcr1-pdu-a en b, en mcr1-pp-01 en 02, met kolommen voor status, tenant, site, locatie, rack, rol, fabrikant, type en IP-adres

En hier is dezelfde URL, dezelfde installatie, ingelogd als de klant.

Dezelfde NetBox-apparatenlijst ingelogd als ravenscroft-ro, Results 2, met alleen mcr1-hv-01 en mcr1-hv-02, beide met tenant Ravenscroft Legal, en de linkernavigatie teruggebracht tot Devices, IPAM, Circuits, Plugins en Admin

Twee regels in plaats van veertien, en kijk naar het menu links. Het is ingeklapt tot de vier dingen die dat account mag aanraken. Niemand heeft dat ingesteld. De navigatie wordt uit dezelfde permissions gebouwd, dus een klant ziet nooit een link naar iets wat hem zou weigeren.

De twee manieren waarop het nee zegt

Dit is het detail dat je moet kennen, want de twee weigeringen betekenen iets anders en beide zijn bedoeld.

Het token van de klant vraagt omHet krijgt
/api/dcim/devices/200, één regel van twee
/api/dcim/devices/1/, zijn eigen200
/api/dcim/devices/2/, dat van iemand anders404
/api/tenancy/tenants/403
/api/dcim/sites/, nooit toegekend403
helemaal geen token403

404 betekent dat het type het jouwe is maar die regel niet. De beperking heeft hem uit de queryset gehaald, dus voor het verzoek bestaat hij niet. Een 403 daar zou bevestigen dat hij bestaat, en een nieuwsgierige klant toestaan je bestand te tellen door de ID’s af te lopen.

403 betekent dat het type nooit het jouwe was. Tenants en sites zijn nooit toegekend, dus die weigeren meteen, en de klant kan niet opsommen wie er nog op het platform zit.

Laat ze dan schrijven

Alleen-lezen is het makkelijke geval. De echte vraag is of je een klant met bewerkrechten die rechten kunt toevertrouwen, dus heb ik change onder dezelfde beperking toegekend en ben ik de uitweg gaan zoeken.

PogingResultaat
Zijn eigen apparaat bewerken200, opgeslagen
Het apparaat van een andere klant bewerken404
Zijn eigen apparaat bewerken en naar de tenant van de andere klant verplaatsen403
Zijn eigen apparaat verwijderen403, delete is nooit toegekend

De derde regel is de belangrijke. Je eigen apparaat aan de tenant van iemand anders toewijzen is de voor de hand liggende ontsnapping, want het object zit bij aankomst van het verzoek binnen je beperking en erna erbuiten. NetBox beoordeelt de beperking tegen de toestand waarin het object zou achterblijven, dus het weigert. Ik heb het apparaat teruggelezen in plaats van op de statuscode te vertrouwen, en de tenant was niet verschoven.

Dat is het gat dat zelfgebouwde multi-tenancy meestal open laat, en het wordt normaal gevonden door een klant en niet door een test.

Variabelen die moeten overerven

Hier is het onderscheid dat mensen fout doen, en het goed doen scheelt veel bewerken.

Een custom field is een waarde op één object. Je zet hem per object, en daar blijft hij. Goed voor een feit over het ding zelf: een asset tag, een supportcontractnummer, een ingebruiknamedatum.

Een config context is een waarde die aan een eigenschap hangt, en die alles wat op die eigenschap past overerft. Goed voor een variabele die moet doorsijpelen: je NTP-servers, je syslog-bestemmingen, je SNMP-community, je DNS-domein, je management-VLAN, je backupvenster.

Betrap je jezelf erop hetzelfde custom field op veertig apparaten op dezelfde waarde te zetten, dan wilde je een config context.

NetBox config contexts-lijst met North West base op gewicht 1000 toegewezen aan de regio North West, en Manchester DC1 syslog op gewicht 2000 toegewezen aan de site Manchester DC1, beide actief

Een context is willekeurige JSON, en hij kan aan een regio, site group, site, locatie, device type, rol, platform, cluster, cluster type, cluster group, tenantgroep, tenant of tag worden gehangen. Tenant staat in die lijst, en voor een provider betekent dat dat een feit dat overal voor één klant geldt hem volgt naar elk apparaat dat je ooit voor hem toevoegt.

Het samenvoegen gaat per sleutel, en het gewicht bepaalt wie elke afzonderlijke wint.

NetBox Config Context-tabblad voor mcr1-core-01, links Rendered Context met domain, ntp_servers, snmp_community en syslog_servers, rechts Source Contexts met North West base op gewicht 1000 met alle vier sleutels en Manchester DC1 syslog op gewicht 2000 met alleen syslog_servers

Lees het rechterpaneel tegen het linker. De regio levert vier sleutels op gewicht 1000. De site levert één sleutel op gewicht 2000. De gerenderde context houdt het domein, de NTP-servers en de community van de regio onaangeroerd, en neemt de syslog-server van de site, want dat is de enige sleutel waar iets om heeft gestreden.

Je schrijft de uitzondering, niet een verse kopie van alles met de uitzondering erin. Dat is de hele waarde, en het is waarom dit schaalt waar een variabelenbestand per apparaat dat niet doet.

Lokale context wint van alles erboven

De overervingsstapel heeft een top, en dat is het object zelf. Local context data op een apparaat wint van elke source context die erop van toepassing is, wat de gewichten ook zijn.

Ik heb dit op mcr1-core-01 gezet:

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

en zijn gerenderde context werd:

{
  "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"]
}

NetBox Config Context-tabblad voor mcr1-core-01 met Local Context nu gevuld met syslog_servers 10.20.10.99 en een note, het paneel meldt dat de lokale config context alle source contexts overschrijft, en de Rendered Context toont de lokale syslog-server naast het overgeërfde domein, de NTP-servers en de community

De lokale syslog-server versloeg zowel de site-override op gewicht 2000 als de regio op gewicht 1000. Alles waarover hij niets zei is alsnog overgeërfd, dus het domein, de NTP-servers en de community kwamen onaangeroerd door, en de nieuwe sleutel werd simpelweg toegevoegd.

Dat is de nooduitgang voor die ene kast die echt anders is, en het paneel vertelt je ronduit dat het alle source contexts overschrijft. Betrap je jezelf erop het op veel apparaten te gebruiken in plaats van op één, dan heb je een eigenschap gevonden die die apparaten delen en wilde je een context die daarop is afgebakend.

Drie valkuilen

Een context zonder afbakening is globaal. Maak er een en vergeet hem aan iets toe te wijzen, en hij geldt voor elk apparaat en elke virtuele machine die je hebt. Dat is gedocumenteerd gedrag en soms wat je wilt. Het is ook geruisloos.

Sleutels mogen geen streepjes hebben als je ze eenvoudig wilt bereiken. Contextdata is JSON, dus ntp-servers is volkomen legaal, maar Jinja-variabelenamen zijn geen JSON-sleutels. {{ ntp-servers }} wordt als aftrekking geparseerd en werpt UndefinedError: 'ntp' is undefined. Schrijf {{ ntp_servers }} tegen data met streepjes en je krijgt een lege string, HTTP 200, en nergens een waarschuwing. Gebruik underscores in de sleutels en het voor de hand liggende template werkt.

Een profiel kan de typefouten stoppen. Een config context-profiel groepeert verwante contexts en dwingt bij het opslaan een JSON-schema op hun data af. Ik heb er een een schema gegeven dat syslog_servers als array van IPv4-strings eist, en daarna de twee fouten gemaakt die mensen echt maken:

{"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

Geweigerd waar iemand ze maakte, in plaats van vierhonderd apparaten later.

De configuratie renderen

Contextdata plus een Jinja-template geeft je een configuratiebestand. Het apparaat zit in scope als device, zijn samengevoegde context zit in scope als gewone variabelen, en je kunt zijn componenten aflopen.

NetBox Render Config-tabblad voor mcr1-core-01, met het config template Junos base en de gerenderde Junos-configuratie: host-name mcr1-core-01, domain-name mcr1.example.net, een location-regel met Manchester DC1 / MCR1-A07 / U39, twee NTP-servers, de enkele syslog-host 10.20.10.99, de SNMP-community n0rthwest, en elke interface met zijn beschrijving en adresfamilie

Alles op die pagina kwam ergens anders vandaan, en dat is het punt:

RegelWaar hij vandaan kwam
host-name mcr1-core-01van het apparaat
domain-name mcr1.example.netvan de regionale config context
location "Manchester DC1 / MCR1-A07 / U39"van de site, het rack en de positie erin
server 172.16.10.22van de regionale context, gewicht 1000
host 10.20.10.99 any noticevan de eigen lokale context van het apparaat, die zowel de site als de regio verslaat
family inet address 10.20.10.11/24van het adres op die interface, met het masker zoals getypt

Het template wordt opgelost via het apparaat, dan de rol, dan het platform, en het verzoek mislukt als geen van de drie er een heeft. Je wijst dus één keer een template aan een platform toe, en elk apparaat dat die software draait rendert eruit, tenzij zijn rol of het apparaat zelf iets anders zegt. Daar is een platform voor: het besturingssysteem of de softwarefamilie van de fabrikant, niet de hardware.

NetBox rendert. Het pusht niet. De uitvoer op de kast krijgen is de taak van jouw automatisering, en op de dag dat een templatefout anders vierhonderd apparaten had herconfigureerd, ben je blij dat dat twee verschillende systemen zijn.

Plugins

Een plugin is een Django-applicatie die naast NetBox wordt geïnstalleerd. Hij kan modellen toevoegen, pagina’s toevoegen, beide API’s uitbreiden, inhoud in bestaande templates injecteren, navigatie toevoegen, achtergrondtakenwachtrijen toevoegen en verdere Django-apps laden. Er is heel weinig wat hij niet kan, want eronder is het gewoon Django.

Er een installeren is vier commando’s en een herstart:

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

Op de container-stack gaan dezelfde pakketten in plugin_requirements.txt en bouw je de image opnieuw. In beide gevallen: pin de versies en controleer eerst de compatibiliteitsmatrix, want een plugin die een NetBox-release niet heeft bijgehouden weigert de hele applicatie te starten in plaats van zichzelf uit te schakelen.

NetBox-pagina installed plugins met Custom Objects versie 0.7.0 van NetBox Labs, Topology views versie 4.7.0 van Mattijs Vanhaverbeke, en qrcode versie 1.0.0 van Nikolay Yuzefovich

De gepubliceerde catalogus noemt er 31. Dit zijn de plugins die het waard zijn te kennen, met de licentie waaronder elk werkelijk wordt uitgebracht:

PluginWat hij doetLicentie
DNSZones, records en nameservers als source of truthMIT
BGPSessies, communities en routing-policiesApache 2.0
Topology ViewsGrafische topologiekaarten, gebouwd uit je kabelsApache 2.0
FloorplanGrafische site- en locatiekaartenLGPL 3.0
QR CodeCodes op racks, apparaten en kabels, voor assetlabelsApache 2.0
ACLsAccess lists en regelsApache 2.0
Prometheus SDLevert Prometheus zijn hostlijst rechtstreeks uit NetBoxMIT
DocumentsDocumenten gekoppeld aan circuits en apparatenApache 2.0
LifecycleHardware end of life, licenties en contractenApache 2.0
ContractContracten en facturenMIT
Reorder RackRack-units verslepenApache 2.0
BranchingGeïsoleerde, samenvoegbare branches van je dataNetBox Limited Use
Custom ObjectsNieuwe objecttypes, in de UI gedefinieerdNetBox Limited Use

Prometheus SD is de eerlijke vorm van het hele idee. NetBox weet wat er bestaat, dus laat NetBox het aan de monitoring vertellen, en stop met het onderhouden van een tweede hostlijst die uit elkaar loopt.

Eén ding om te weten voordat je op de laatste twee regels bouwt. NetBox zelf is Apache 2.0 en is dat sinds DigitalOcean het in 2016 vrijgaf, en het project wordt vandaag beheerd door NetBox Labs samen met een team vrijwillige maintainers.21 NetBox Branching en NetBox Custom Objects zijn dat niet: ze worden uitgebracht onder de NetBox Limited Use License 1.0, die gebruik toestaat “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”, en die niet het recht geeft de software te gebruiken “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”. Heb je NetBox Community van GitHub geïnstalleerd en draai je het namens klanten, lees dan zelf de voorwaarden voordat je er een schema achter zet.10 NetBox’ eigen installatiehandleiding beveelt beide plugins aan zonder het te vermelden.11

Custom fields

Een custom field voegt een attribuut toe aan een bestaand model. Waarden worden als JSON naast elk object opgeslagen, dus er is geen migratie en geen herstart, en er zijn dertien types waaronder object- en multi-objectverwijzingen naar andere NetBox-records.

NetBox apparaatpagina voor mcr1-core-01 met een paneel Custom Fields met een groep Asset met Warranty expires 2029-06-30 en Support contract JNPR-448120, naast het paneel Device Type met Juniper MX204 en een paneel Dimensions met een totaalgewicht van 9,5 kilogram

Twee velden daar, gegroepeerd onder een kopje “Asset” dat ik heb gekozen. Let op het paneel Dimensions rechts ervan: 9,5 kg, die niemand op dit apparaat heeft getypt. Ze kwamen van het apparaattype.

Custom fields worden gevalideerd, en het is de moeite te weten dat ze dat worden, want het is een echt verschil met de route hieronder. Ik heb het contractveld een reguliere expressie gegeven en de API dwong die af:

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}$'"]}

Twee dingen zijn in 4.7 veranderd die op schaal uitmaken. Een veld aanmaken met een standaardwaarde, of een veld verwijderen, moet de opgeslagen data van elk object waarvoor het geldt herschrijven, dus op een grote tabel wordt dat werk aan een achtergrondtaak gegeven en meldt het veld “provisioning” of “deleting” terwijl die loopt. Een veld is alleen live zolang het actief is: tijdens een van die twee operaties verschijnt het niet op objecten, niet in formulieren, niet in filters en in geen van beide API’s. Daar moet een worker voor draaien, anders blijft het onbeperkt in die toestand.

Gebruik een custom field als je een feit over het ding zelf toevoegt. Gebruik een config context als de waarde moet doorsijpelen. Gebruik de volgende sectie als het ding dat je nodig hebt niet bestaat.

Eigen keuzemenu’s, en overschrijven wat wordt meegeleverd

Twee verschillende mechanismen wonen onder dit kopje en ze lossen verschillende problemen op. Het ene is voor je eigen velden. Het andere herschrijft die van NetBox.

Een choice set, voor je eigen keuzeveld

Een custom field van het type “selection” haalt zijn opties uit een choice set, een object dat je in de UI beheert zoals al het andere. Zo wordt “support tier” een echte dropdown in plaats van vrije tekst die iemand op drie manieren spelt.

NetBox custom field choice set met de naam Support tier, beschreven als what the customer pays for, met drie keuzes: bronze voor next business day, silver voor 8 hours, en gold voor 4 hours 24x7

Het wordt afgedwongen, ook via de API:

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

Choice sets worden gedeeld, dus één set kan hetzelfde veld op meerdere modellen voeden, en de lijst op één plek wijzigen wijzigt hem overal.

FIELD_CHOICES, voor NetBox’ eigen velden

Dit is degene waarvan mensen niet weten dat hij bestaat. Meerdere ingebouwde keuzevelden van NetBox kunnen vanuit configuration.py worden uitgebreid of vervangen, waaronder apparaatstatus, sitestatus, rackstatus, circuitstatus en nog veel meer.12

Zet een plus erachter om toe te voegen aan wat wordt meegeleverd. Laat hem weg om de lijst helemaal te vervangen.

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'),
    ),
}

Precies dat heb ik op deze instantie gezet. De apparaatstatus kwam terug met de zeven standaardwaarden én mijn twee:

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

en de sitestatus kwam terug met alleen de mijne, de standaardlijst verdwenen:

surveyed, building, active, closing

Een keuze kan een eenvoudige tuple van waarde, label en kleur zijn, of een dictionary die ook een beschrijving neemt die als ondertitel in het formulier wordt getoond. En ze gedragen zich overal als native waarden, want voor de rest van NetBox zijn ze native waarden:

NetBox apparatenlijst met mcr1-hv-06 dat een cyaan Burn-in-statusbadge draagt naast de andere apparaten op de standaardstatus Active, met dezelfde opmaak als elke ingebouwde status

Die badge is mijn status, in mijn kleur, die sorteert en filtert als elke andere.

Drie dingen om te weten voordat je het gebruikt.

Vervangen verwijdert de standaardwaarden uit het menu, niet uit de database. Elk object dat er al een van heeft houdt hem, maar de waarde wordt niet meer aangeboden en is geen geldige keuze meer als iemand dat object de volgende keer bewerkt. Vervang je een lijst, controleer dan dat er niets op een waarde zit die je net hebt weggehaald.

Het staat in het configuratiebestand, dus het vraagt een herstart en het is niets wat een UI-gebruiker kan wijzigen. Voor een provider is dat de juiste kant op: welke statussen jouw bedrijf erkent is een governancebeslissing, geen beslissing op een dinsdagmiddag.

Breid uit voordat je vervangt. De standaardwaarden zijn wat elke plugin, elk script en elke integratie verwacht te zien. Toevoegen kost niets. Vervangen is een beslissing die je voor altijd bezit.

Custom objects

Hier is een echt gat. NetBox modelleert clusters, virtuele machines en virtuele disks. Het modelleert niet de datastore waar die disks echt op staan, en voor iedereen die Proxmox of VMware draait is dat het object dat de opslag die je hebt gekocht verbindt met de last die hem gebruikt.

De Custom Objects-plugin laat je een nieuw objecttype vanuit de UI of de API definiëren, zonder code te schrijven. Dus:

NetBox-pagina Custom Object Type voor Datastore, versie 1.0.0, beschreven als shared storage a cluster puts VM disks on, met een Fields-tabblad met 7 en een Fields-paneel met Name als Text, Cluster als Object dat naar Virtualization > Cluster wijst, Provisioned by als Multiple objects dat naar DCIM > Device wijst, Backing als Text, Capacity (GB) als Integer, Thin provisioned als Boolean, en Customer als Object dat naar Tenancy > Tenant wijst

Zeven velden, waarvan twee het hele punt zijn. cluster is een enkelvoudige objectverwijzing naar een echt NetBox-cluster, op protect gezet zodat niemand een cluster onder zijn opslag weg kan verwijderen. provisioned_by is een multi-objectverwijzing naar de apparaten die hem werkelijk leveren.

Dat geeft je een eersteklas object met zijn eigen navigatie-item, lijstweergave, filters, import en export:

NetBox Datastores-lijst met vier regels: ds-nvme-01 op cluster MCR1-PVE geleverd door mcr1-hv-01, 02 en 03, gedragen door een Ceph RBD NVMe-pool van 40960 GB en thin provisioned; ds-nvme-02; ds-archive-01 op een HDD-pool met NVMe-WAL van 196608 GB en niet thin provisioned; en ds-ravenscroft-01 van 8192 GB met tenant Ravenscroft Legal

En omdat de verwijzingen echt zijn, laat de relatie zich ook van de andere kant zien. Open het cluster en de datastores staan ertegen vermeld.

NetBox clusterpagina voor MCR1-PVE, een Proxmox VE-cluster met scope Manchester DC1, met een Custom Objects-tabblad dat de datastores toont die ernaar verwijzen

Een custom object type erft het meeste van wat een NetBox-object een NetBox-object maakt: lijst- en detailweergaven, een navigatie-item, REST-endpoints, full-text zoeken, change logging, journaling, tags, bookmarks, import en export, event rules en meldingen. Niets daarvan hoefde geschreven te worden.

Het is ook geen JSON-blob die zich voordoet als tabel. De plugin zet echte DDL af, en wat in PostgreSQL verschijnt is een echte tabel met echte constraints:

                    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

De protect waar ik om vroeg werd ON DELETE RESTRICT, en het werkt: dat cluster verwijderen komt terug als 409 met de naam van wat ervan afhangt.

Twee dingen om te weten voordat je erop vertrouwt

De validatie zit op het formulier, niet op de API. Dit is degene die een automatisering zou pakken. Ik heb required, een reguliere expressie en numerieke grenzen op de velden van een custom object type gedeclareerd, en er daarna via de REST-API naartoe geschreven:

Wat ik declareerdeWat ik stuurdeResultaat
validation_regexeen waarde die op niets past201 Created
required: truehet veld helemaal weggelaten201 Created
validation_minimum: 1een negatief getal201 Created
unique: trueeen duplicaat400, geweigerd
on_delete_behavior: protecthet verwezen object verwijderen409, geweigerd

De twee die hielden zijn de twee die databaseconstraints werden. De rest bestaat alleen op het webformulier, dus een mens die typt wordt beperkt en een nachtelijke sync niet. Vergelijk dat met het core custom field hierboven, waar dezelfde reguliere expressie via de API werd afgedwongen. Tot dat verandert: zet alles waar een factuur van afhangt achter een echt constraint.

Een type verwijderen laat een tabel vallen. Een veld verwijderen laat een kolom vallen. Dat is DDL vanuit een webformulier, uitgevoerd door wie de permissie ook heeft, en de documentatie zegt precies dat.13 Beperk wie deze mag verwijderen.

Wanneer je in plaats daarvan een echte plugin schrijft

Als het object belangrijk is, als automatisering erop schrijft, en als je het erg zou vinden er rommel in te vinden, schrijf het model dan zelf. Een minimale NetBox-plugin is een PluginConfig, een model, een serializer, een viewset, een tabel, een formulier, een paar views en een URL-map, en het komt op onder tweehonderd regels van overwegend declaraties. PrimaryModel subclassen geeft je gratis tags, custom fields, change logging, journaling, export-templates en eigenaarschap, en elk constraint dat je op het model zet wordt overal afgedwongen, want NetBox’ eigen serializer-machinerie voert het uit.

Er is een cookiecutter-template en een volledige plugin-tutorial, door de community onderhouden. Begin daarmee in plaats van met een lege map.

En het is de licentie die jij kiest, wat niemand later kan veranderen.

Custom validation en validatieklassen

Alles hierboven gaat over vastleggen wat er is. Dit gaat over weigeren vast te leggen wat er niet zou moeten zijn.

NetBox valideert elk object voordat het het schrijft, en je kunt er je eigen regels bovenop leggen. Er zijn drie mechanismen, ze wonen allemaal in configuration.py, en samen dekken ze bijna alles wat een huisstandaard nodig heeft.

Eén: eenvoudige regels, geen code

Een validator kan een eenvoudige toewijzing van veldnamen aan voorwaarden zijn. Geen Python, en het is overdraagbaar tussen installaties omdat het alleen data is.

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

De beschikbare voorwaarden zijn min, max, min_length, max_length, regex, required, prohibited, eq en neq. Je kunt met een puntpad in een gerelateerd object reiken, dus region.name op een site mag, en je kunt op request.user.username matchen, al zegt de documentatie je volkomen terecht daarvoor liever permissions te gebruiken.

Beide regels vuren direct:

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.']"]}

Die tweede is een naamgevingsstandaard, afgedwongen. Niet op een wikipagina geschreven, niet in iemands hoofd, geen ding waarover de nieuwe drie weken later in een review wordt aangesproken. De database neemt het niet aan.

Twee: een validatorklasse, als de regel een zin is

Eenvoudige regels toetsen één veld tegen een constante. Echte huisregels zijn meestal voorwaardelijk: dit doet alleen mee als dat. Daarvoor subclass je CustomValidator, overschrijf je validate() en roep je fail() aan.

Ik heb er drie in een module op /opt/netbox/netbox/house_rules.py gezet:

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',
                )

en ze via een puntpad aangesloten:

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',
    ),
}

Merk op dat een model een tuple van validators neemt, eenvoudige regels en klassen vrij gemengd, en dat ze allemaal lopen. Zelfs een enkele validator moet als iterable worden doorgegeven, en dat zijn makkelijk vijf verloren minuten.

Dan doen de regels wat ze zeggen:

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

Omdat fail() een field neemt, landt de melding op het juiste vakje in het formulier in plaats van bovenaan de pagina, en dat is het verschil tussen een regel die mensen leren en een regel die mensen kwalijk nemen.

Dat is ook het antwoord op het gat in de sectie over custom objects. De validatie daar bestond alleen op het webformulier. Een CustomValidator loopt in de modellaag, dus hij geldt voor de UI, de REST-API, GraphQL-mutaties, bulk-imports en alles wat een script doet. Er is één plek waar de regel wordt geschreven en geen manier eromheen.

Drie: protection rules, voor verwijderen

CUSTOM_VALIDATORS waakt over schrijfacties. PROTECTION_RULES waakt over verwijderacties, en neemt precies dezelfde twee vormen.

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

Dat zegt dat een apparaat alleen verwijderd kan worden als het offline is. Wat dit oplevert:

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

Twee toetsaanslagen wrijving tussen iemand en een live apparaat, en het is de goedkoopste verzekering in de hele applicatie. Maak het uitfaseringsproces tot het ding dat de verwijderknop ontgrendelt.

Degene die je gaat pakken

Bestaande data wordt niet gecontroleerd tot je hem de volgende keer aanraakt. Een regel toevoegen gaat niet terug om te valideren wat er al staat. Hij ligt stil tot iemand een object opslaat dat hem breekt, en dan krijgt die persoon een fout over een beslissing waar hij niets mee te maken had.

Ik heb het zien gebeuren. mcr1-hv-06 is aangemaakt voordat ik hier iets van had geschreven, als hypervisor zonder tenant. Hij stond daar volkomen tevreden. Toen bewerkte ik zijn beschrijving:

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

De bewerking had niets met de tenant te maken. De regel vuurde toch, want de validatie loopt over het hele object.

Dat is correct gedrag en het is ook hoe een nieuwe regel in een supportticket verandert. Voordat je er een aanzet, vraag de objecten op die eraan zouden falen en repareer die eerst. De API maakt dat makkelijk: de regel is een filter, dus vraag de apparaten op met die rol en zonder tenant, en je hebt je lijst.

Zet de regel daarna aan. Dan pakt hij alleen nog nieuwe fouten, en daar is hij voor.

Het besturen vanuit Ansible

Een source of truth die niemand leest verrot. De collection netbox.netbox is hoe de meeste mensen dat voorkomen, en hij werkt in beide richtingen: NetBox vertelt Ansible wat er bestaat, en Ansible vertelt NetBox wat het heeft gebouwd.

Hij staat op versie 3.23.0, is gelicentieerd onder GPL-3.0, en is meer dan 13,4 miljoen keer van Galaxy gehaald. Hij draagt 91 modules en één inventory-plugin.14 Alles hieronder is uitgevoerd tegen diezelfde instantie, uit een container met niets anders erin dan ansible-core 2.21.4, pynetbox 7.8.0 en de collection.

De inventaris is een query, geen bestand

Dit is de helft die zich op de eerste middag terugbetaalt. nb_inventory bouwt je Ansible-inventaris rechtstreeks uit 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

Dat levert dit op, zonder dat iemand een hostlijst onderhoudt:

@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

Kijk naar de rechterkolom. Omdat tenancy op de apparaten is gezet, krijg je gratis een groep per klant, dus --limit tenants_ravenscroft-legal voert een play uit tegen precies de apparatuur van één klant. Voeg een apparaat toe in NetBox en het zit bij de volgende run in de groep. Faseer er een uit en het is weg. Niemand bewerkt iets.

Elke host komt aan met wat NetBox over hem weet:

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"}

Tweeëntwintig sleutels in totaal, en één ervan is het waard op te merken voordat hij je verrast. ansible_host is het IPv6-adres, omdat dat apparaat een primair v6 heeft staan. Ik heb het nagekeken tegen twee andere die alleen v4 hebben en die kwamen terug met v4, dus de regel is dat de plugin v6 verkiest waar een primair v6 bestaat. Wat correct is, en ook het soort ding dat je liever nu ontdekt dan terwijl je je afvraagt waarom een play verbindt over een pad waar je niet aan had gedacht.

Terugschrijven

De modules zijn de andere richting, en degene die je wil hebben is IP-uitgifte, want NetBox weet wat vrij is en jouw playbook niet.

- 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

Let op wat er niet in staat. Geen IP-adres. Je noemt het prefix en NetBox geeft het volgende vrije terug:

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)"

En een seconde later staat het in de UI, nog aan niets gekabeld maar ingeracked, met tenant en geadresseerd:

NetBox interfaces-tabblad voor mcr1-hv-07, een apparaat dat door het playbook is aangemaakt, met één interface eno1 van het type SFP28 25GE beschreven to leaf-01 en met 10.20.22.1/24

De valkuil in dat playbook

Laat het een tweede keer lopen zonder één regel te wijzigen en dit gebeurt:

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

Het apparaat en de interface zijn idempotent. Het adres niet, en dat is geen bug. state: present betekent “maak dat het er zo uitziet”. state: new betekent “geef me een nieuwe”, elke keer weer, en dat is precies wat het deed: twee adressen op één interface na twee runs.

Dat is prima als je echt iets nieuws uitrolt, en het eet stilletjes een prefix op als je het in een taak zet die elke nacht loopt. Geef één keer uit en leg het resultaat vast, of gebruik state: present met het adres dat je al hebt. De module doet wat je vroeg. De vraag is of je vroeg wat je bedoelde.

Het is niet alleen Ansible

De collection krijgt de aandacht omdat Ansible is waar de meeste netwerkteams al zitten, maar de API is het product en genoeg dingen spreken hem.

DingWat het isLicentie
pynetboxDe Python-client die de collection zelf gebruiktApache 2.0
terraform-provider-netboxNetBox-objecten als Terraform-resources beheren, actief onderhouden door e-breuningerMPL 2.0
nornir_netboxNetBox als Nornir-inventaris, voor wie Python doet in plaats van YAMLApache 2.0
Prometheus SDLevert Prometheus zijn scrape-targets uit NetBoxMIT
go-netboxEen Go-client, al is er sinds mei 2025 niet aan geraaktzie repository
DiodeNetBox Labs’ eigen ingestion-pijplijn om ontdekte data naar binnen te duwenNetBox Limited Use

Die Terraform-regel is de interessante voor wie zijn infrastructuur al zo beheert, want hij laat één plan de cloudresource en het NetBox-record dat hem documenteert aanmaken, in plaats van de tweede helft aan iemands geheugen te laten.15

Diode draagt dezelfde licentie als Branching en Custom Objects, dus dezelfde lezing geldt voordat je erop bouwt.

Wat die middag echt koopt

Alles hierboven kostte me een dag op één machine, en het grootste deel van die dag ging op aan data zaaien zodat de schermen iets bevatten. De installatie is twintig minuten, welke route je ook neemt. De beslissingen zijn het deel dat uitmaakt en ze worden allemaal in het eerste uur genomen: tenants voor alles, apparaattypes voor apparaten, de cijfers op de types in plaats van op de apparatuur.

Wat je daarvoor krijgt is geen documentatie. Dat onderscheid maakt niemand voordat hij beide heeft gehad: documentatie is iets wat je schrijft en daarna niet meer onderhoudt. Wat je krijgt is een database die weigert een tegenspraak te bevatten: hij laat je geen twee dingen in één rack-unit zetten, of een rack in een locatie die bij een andere site hoort, of een apparaat op een type dat niet bestaat. Elk van die weigeringen is een discussie die je over zes maanden niet hebt.

En het vertelt je dingen die niemand heeft gevraagd. Die kast zit 28,6% vol met apparatuur en 90,7% vol met stroom. Niemand was dat gaan zoeken. Het viel eruit omdat er één keer een wattage op een apparaattype is gezet, en het is het verschil tussen de volgende order daar inracken en het op de harde manier ontdekken.

De eerlijke kanttekening is dezelfde die elke source of truth heeft. Hij is alleen waard wat het je kost om hem juist te houden, en de enige versie hiervan die een druk kwartaal overleeft is die waarin er zichtbaar iets stukgaat als de data verkeerd is. Knoop je monitoring, je provisioning of je firewallregels eraan vast zodat ze eruit lezen, en een verkeerde invoer is geen documentatieprobleem meer waar iemand ooit aan toekomt. Het wordt een storing om half tien op een dinsdag, met een naam eraan. Dat klinkt als een kostenpost. Het is het hele mechanisme.

Niemand gaat je er ook voor bedanken. Een juist record laat zich zien als de migratie die veertien dagen duurde in plaats van een kwartaal, de audit die een middag duurde, de klant die aan de telefoon een recht antwoord kreeg. Niets daarvan verschijnt in een rapport naast je naam.

Doe het toch. Het alternatief is een bedrijf dat zichzelf niet kan beschrijven, en een bedrijf dat zichzelf niet kan beschrijven wordt niet geleid. Het wordt herinnerd, door elk jaar minder mensen.


  1. NetBox, Introduction — “Today, the open source project is stewarded by NetBox Labs and a team of volunteer maintainers”, en de herkomst bij DigitalOcean in 2015, open source sinds juni 2016. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. NetBox, LICENSE.txt — Apache License 2.0, copyright DigitalOcean, LLC. Herkomst en beheer uit de inleiding. ↩︎ ↩︎

  3. Releasenotes van Nautobot v1.0.0 — “a divergent fork of NetBox 2.10”, gepubliceerd op 26 april 2021, en de lijst van wat het toevoegde ten opzichte van NetBox 2.10. ↩︎ ↩︎

  4. Network to Code, “Why Did Network to Code Fork NetBox?”, 25 februari 2021 — de redenering over SLA’s en langetermijnondersteuning, de uiteenlopende visie, en “the NetBox project team suggested that we should consider forking”. ↩︎

  5. NetBox Labs is in 2023 opgericht als spin-out uit NS1 na de overname door IBM, mede-opgericht door NetBox’ lead maintainer Jeremy Stretch, en kondigde in april 2023 een Series A van 20 miljoen dollar aan: NetBox Labs, “Let’s Go: Announcing NetBox Labs” en de aankondiging van de Series A. ↩︎

  6. NetBox Labs, NetBox Enterprise — de zelfbeheerde commerciële editie, met “24/7 expert assistance from the NetBox Labs team”; NetBox Cloud is het gehoste aanbod. Concrete SLA-voorwaarden staan niet op die pagina. ↩︎

  7. Aantallen sterren en forks, releasedata en licenties van beide projecten op 30 september 2026 via de GitHub-API afgelezen: netbox-community/netbox en nautobot/nautobot. Nautobots parallelle releases 3.2.x en 2.4.x zijn beide gedateerd 28 september 2026. ↩︎

  8. netbox-community/netbox-docker — de container-stack van de community, Apache 2.0. ↩︎

  9. NetBox, Installation — de tabel met ondersteunde versies, en de releasenotes van v4.7 voor de verhoogde minima van PostgreSQL en Redis. Versie en editie afgelezen uit netbox/release.yaml. ↩︎

  10. NetBox Limited Use License 1.0 — gedragen door netbox-custom-objects en, identiek, door netbox-branching. Beide geciteerde clausules zijn letterlijk. ↩︎

  11. NetBox, HTTP Server installation, “What’s Next?” — “Some of the most popular plugins include” NetBox Branching, NetBox Custom Objects, NetBox DNS en NetBox BGP, zonder enige vermelding van licentievoorwaarden. ↩︎

  12. NetBox, Data Validation configuration — FIELD_CHOICES, en het plus-achtervoegsel dat uitbreidt in plaats van vervangt: “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. Documentatie van netboxlabs/netbox-custom-objects — “Deleting a Custom Object Type drops an entire database table and should be done with caution.” ↩︎

  14. netbox.netbox op Ansible Galaxy en netbox-community/ansible_modules — versie 3.23.0, GPL-3.0, 91 modules plus de inventory-plugin nb_inventory. Downloadaantal op 30 september 2026 op Galaxy afgelezen. Alles wat is getoond liep met ansible-core 2.21.4 en pynetbox 7.8.0. ↩︎

  15. Licenties, activiteit en aantallen sterren op 30 september 2026 bij elk project afgelezen: pynetbox (Apache 2.0), terraform-provider-netbox (MPL 2.0, v6.0.0-rc.1 in het Terraform-register), nornir_netbox (Apache 2.0), netbox-plugin-prometheus-sd (MIT), go-netbox (laatste push 9 mei 2025) en Diode, dat dezelfde NetBox Limited Use License 1.0 draagt als Branching en Custom Objects. ↩︎