Skip to content

IPAM — NetBox

NetBox is the single source of truth for the network model — devices, interfaces, IPs, cables, VLANs and L2VPN. Almost everything else is derived from it by the Ansible automation; the rare non-derivable settings come from NetBox custom fields and config context.

Direction of truth

Git + the automation are authoritative for intent; the devices are made to match; and their live state is scraped back into NetBox as as-deployed documentation and for drift cross-check. NetBox is never in the config critical path and is never written by the converge step.

The ros_role custom field

The intent classifier on each interface. Values include:

loopback, core-link, uplink, venue-handoff, ebgp-peer, starlink-wan, guest-trunk, vpls-trunk / vpls-circuit, pop-trunk / pop-handoff, radio-mgmt, lan-trunk / access, peer-link / mlag-host / single-port.

Optional per-interface overrides: ospf_cost, ospf/ldp/bfd/mpls_enabled, bgp_in_filter / out_filter, uplink_primary, vpls_service, circuit_id, backup_path, guest_vpls, mlag_id, pvid.

Device-level fields: venue_id, bgp_asn (on eBGP-peer devices — 65502 Starlink, 65510 edge FW), core_as_override (blank ⇒ 65500), ros_version / ros_firmware (written by the fact-sync playbook), oob_ip.

netbox-setup.yml creates all of these idempotently.

Derivation, not duplication

A unit-tested filter plugin (plugins/filter/netbox_topology.py) walks the cable graph to derive most configuration. "Where did this config come from?" resolves in this precedence:

cable graph  →  interface custom fields (ros_role)  →  config context  →  registry.yml

For example, OSPF cost is derived from interface speed (100G→1, 40G→2, 25G→4, 10G→10, 1G→100), with wireless forced to the policy cost 500 and 4000 meaning "drain for planned work". A /31 needs only the local IP set — the far end is derived by arithmetic, which kills the classic "two ends disagree" bug.

Validation

scripts/netbox_lint.py validates the model before any apply: platform/role, primary IP, link IP + cable presence, hand-off far-device venue_id, ebgp-peer far-device bgp_asn (assembly fails without it), radio-mgmt completeness, and derivable OSPF cost.

DNS from IPAM

Every IP carries a dns_name (<iface>.<device>.infra.pubinvest.co.uk) and is published to PowerDNS forward + reverse by powerdns-sync.yml. OOB names derive from the device name (<device>.oob.pubinvest.co.uk, forward-only via oob-sync.yml). The two zones are deliberately disjoint, so forward/reverse intentionally disagree for OOB IPs.

Terraform note

An earlier design used Terraform (network.yaml registry, routeros provider) with git as the source of truth and NetBox downstream. The current implementation uses Ansible for convergence (see Network automation); the registry concept survives as inventory/group_vars/all/registry.yml, which reshapes raw NetBox objects into the variables the roles consume.