How to build a NetBox-driven Ansible pipeline to provision BGP at scale in datacenter networks
Provisioning BGP across hundreds (or thousands) of devices is mostly a data problem: you need a consistent source of truth, strong modeling conventions, deterministic rendering, safe deployment mechanics, and repeatable verification. This tutorial walks through an advanced, production-oriented pipeline where netbox is the authoritative inventory and intent store, and Ansible is the execution engine.
Architecture overview
At a high level, the pipeline looks like this:
- NetBox: source of truth for sites/tenants, device roles, RIR/ASNs, IPAM prefixes, peer definitions, and policy metadata.
- Ansible:
- Pulls data from NetBox (dynamic inventory and API queries)
- Renders intended BGP configuration per device (Jinja2)
- Pushes config using network-native modules (napalm, netconf, eAPI/HTTPAPI, or vendor collections)
- Validates control-plane and data-plane outcomes
- CI/CD:
- Runs lint/unit tests and “render-only” diffs
- Requires approvals for production changes
- Supports canaries and rollback workflows
- Telemetry/Verification:
- BGP session status and route checks (device facts + route lookups)
- Optional active probes (ping/HTTP) for critical prefixes
Design NetBox data models for BGP: tenants, autonomous-systems, route-targets, prefixes, and role of devices — common pitfalls and versioning strategy
Model the intent, not the CLI
Treat NetBox as the place to store intent (who peers with whom, where, with what policy), not raw configuration. A reliable approach is to model BGP in layers:
- Who owns what: tenants, sites, VRFs
- Identity: ASNs (global + per-tenant), router IDs
- Addressing: loopbacks, P2P links, peering subnets, anycast VIPs
- Policy metadata: communities/route-targets, import/export intent, allowed prefix sets
- Relationships: BGP sessions (neighbors), with scope (global/VRF), and attributes
Core NetBox objects to use
NetBox has strong primitives for DC networks; BGP-specific needs typically require a combination of built-ins plus custom fields (or plugins if you standardize on them).
Recommended baseline objects:
- Tenants: represent administrative boundaries (business unit/customer)
- Sites / Regions: physical topology grouping
- Device Roles: e.g. spine, leaf, border-leaf, dcgw, rr
- Devices: authoritative list of nodes, platform, primary IPs
- Interfaces + IP addresses:
- loopbacks (lo0 / Loopback0)
- underlay P2P interfaces
- Prefixes:
- underlay pools (P2P)
- loopback pools
- VRF prefixes
- RIR + ASN objects (NetBox supports ASNs):
- use for local AS, tenant AS, and external peer AS
- VRFs:
- essential for EVPN and multi-tenancy
- Route Targets (RTs):
- NetBox core doesn’t always cover all RT workflows; many teams store RTs in:
- VRF import_targets/export_targets (if available in your NetBox version/plugins), or
- custom fields on VRF (e.g., rt_import, rt_export) with lists
BGP neighbor/session modeling options
NetBox core doesn’t universally provide a “BGP neighbor” object in all versions. Two common strategies:
Option A: Custom object (preferred if you can standardize)
- Use a plugin or custom model to represent:
- local device
- remote endpoint (device or external peer)
- address family (ipv4/ipv6/evpn)
- VRF/global scope
- authentication, timers, BFD, allowas-in, send-community, etc.
Option B: Use built-ins + relationships
- Store neighbor definitions as:
- relationships between devices and “peer” objects (external peer modeled as a Device with role external-peer or as a “virtual device”)
- plus custom fields on device/interface/VRF for per-neighbor settings
Whichever you choose, keep the rendering input consistent and validated.
Practical data conventions (that prevent pain later)
- Device role drives behavior: spines act as RRs, leaves peer to spines, border devices peer externally.
- Local AS placement:
- store per-site or per-tenant AS in NetBox (e.g.,
site.custom_fields.local_asn) - avoid “hardcoding ASNs in playbooks”
- Router ID:
- derive from Loopback0 address in NetBox
- enforce exactly one router-id source of truth
- Prefixes and allowed advertisements:
- model which prefixes belong to which VRF/tenant/site in NetBox
- use tags like
advertise-bgp,aggregate,no-export
Versioning strategy for intent and schema
You need two kinds of versioning:
- Schema/versioning of the model:
- Maintain custom fields and relationships as code (migration scripts or documented step-by-step changes).
-
Use a “schema version” custom field on NetBox (or in repo) to keep pipelines compatible.
-
Intent/versioning of configuration:
- Don’t let “NetBox state” change without review.
- Use a staging NetBox (or a “proposed” workflow) and promote changes to production NetBox after approval.
- At minimum, export NetBox data snapshots for each change window (API dump) and store in artifact storage.
Idempotent Ansible patterns for BGP: templating, Jinja techniques, check-mode-safe tasks, and handling device-specific quirks (Cisco, Arista, Juniper)
Ansible collection choices
Use vendor-supported modules when possible:
- Arista EOS:
arista.eos.*(eAPI/HTTPAPI) - Cisco IOS-XE/NX-OS:
cisco.ios.*,cisco.nxos.* - Juniper Junos:
junipernetworks.junos.*(NETCONF)
For multi-vendor normalization, consider:
- ansible.netcommon.* for common patterns
- NAPALM modules for some workflows (tradeoffs: abstraction vs feature parity)
Dynamic inventory from NetBox
Use the NetBox inventory plugin (netbox.netbox.nb_inventory) and group by role/site/tenant.
Key patterns:
- Group devices by device_role and site to apply role-specific BGP behaviors.
- Pull custom fields into hostvars so templates have all required inputs.
Render-first: generate intended config deterministically
A reliable advanced pattern:
1. Gather NetBox-derived facts (ASNs, loopbacks, peers, VRFs)
2. Render a device-specific “intended config” file
3. Compare to live (or to candidate) and apply delta
4. Verify sessions/routes
Jinja techniques that matter at scale
- Use | sort(attribute='...') to keep output stable.
- Use default(omit) carefully; avoid rendering empty stanzas.
- Avoid whitespace-driven diffs: normalize indentation and newlines.
- Create reusable macros for neighbors/policies per vendor.
Check-mode safe tasks
Check mode is only useful if:
- templates are deterministic
- tasks don’t fail when they cannot commit
- you can still produce a diff artifact
For network devices, prefer modules that support:
- candidate config
- diff
- commit check
Handling vendor quirks (high-level)
Cisco
- IOS-XE vs NX-OS syntax differences (especially address-families and route-maps)
- Some platforms require explicit neighbor x update-source Loopback0
- BGP peer-group / template behavior differs
Arista
- EOS is consistent and automation-friendly
- Pay attention to service routing protocols model (multi-agent vs ribd) differences if relevant
- eAPI transport and config session semantics are strong—use them
Juniper
- Commit model is transactional (good), but requires correct candidate construction
- Policy statements and prefix-lists are first-class; lean into them
- Be explicit about family inet unicast / family evpn structure
Example: NetBox → structured vars → vendor config push
Below is a simplified pipeline excerpt showing:
- Pulling neighbors from NetBox (as preprocessed vars)
- Rendering config
- Pushing config with vendor modules
- Using check-mode friendly diff behavior
---
- name: NetBox-driven BGP rollout
hosts: dc_fabric
gather_facts: false
vars:
intended_dir: "intended/{{ inventory_hostname }}"
tasks:
- name: Ensure intended config dir exists
ansible.builtin.file:
path: "{{ intended_dir }}"
state: directory
# In practice, populate bgp_intent via:
# - netbox inventory plugin hostvars (custom_fields)
# - additional nb_lookup queries (netbox.netbox.nb_lookup)
# - preprocessing role that normalizes peers/VRFs per device role
- name: Render intended BGP config (EOS example)
ansible.builtin.template:
src: "templates/eos/bgp.j2"
dest: "{{ intended_dir }}/bgp.cfg"
when: ansible_network_os == "arista.eos.eos"
- name: Load and apply BGP config (EOS) with diff
arista.eos.eos_config:
src: "{{ intended_dir }}/bgp.cfg"
replace: line
diff: true
when: ansible_network_os == "arista.eos.eos"
- name: Commit candidate (Junos) with commit check in check mode
junipernetworks.junos.junos_config:
src: "{{ intended_dir }}/bgp.set"
update: merge
check_commit: true
confirm: 5
when: ansible_network_os == "junipernetworks.junos.junos"
Notes on idempotency:
- replace: line (EOS) is often safer for incremental changes; for strict drift control, use full replace with guardrails.
- Junos check_commit: true makes check mode meaningful (you’ll catch syntax/policy errors early).
- For Cisco, prefer platform-specific modules (cisco.ios.ios_config, cisco.nxos.nxos_config) and be explicit about parents/sections to avoid unintended merges.
Secure automation: credential handling, transaction-based commits, partial rollbacks, and testing with network device simulators
Credential handling
Recommended practices:
- Use Ansible Vault (or an external secret manager) for device credentials.
- Use per-environment credentials (lab/staging/prod).
- Prefer short-lived tokens where possible (eAPI tokens, AAA integrations).
- Never store credentials in NetBox custom fields.
Concrete checklist:
- [ ] Secrets stored in Vault/secret manager
- [ ] CI injects secrets at runtime (not in repo)
- [ ] Principle of least privilege for automation users
- [ ] Audit logging enabled on devices and NetBox API
Transaction-based commits and safe failure modes
Junos
- Transactional commit is native; use:
- commit check
- commit confirmed for safety during risky changes
EOS
- Use config sessions; ensure your module/plugin uses them.
- Save config only after verification gates pass (or never automatically save, depending on policy).
Cisco
- IOS/NX-OS do not behave like transactional databases.
- Use:
- pre-change checkpoints when available (e.g., NX-OS checkpoint/rollback)
- or archive configs + rollback procedures
- staged changes with canary nodes first
Partial rollbacks
Rollbacks should be explicit and tested:
- Store last-known-good intended configs as artifacts per device.
- If verification fails, automatically:
- revert candidate (Junos)
- load previous checkpoint (NX-OS) or re-apply last-known-good config snippet
- stop the pipeline and open an incident workflow
Testing with device simulators / virtual labs
Before touching production:
- Use containerized EOS (cEOS), Junos vMX/vQFX, or vendor simulators when possible.
- Use topology orchestrators (e.g., containerlab) to:
- validate neighbor bring-up
- validate EVPN route-target import/export behavior
- run regression tests against template changes
Minimal lab goals:
- [ ] at least 1 spine/leaf pair per vendor you support
- [ ] iBGP underlay + EVPN overlay (if used)
- [ ] external eBGP peer simulation for border cases
Validation and verification: automated BGP session checks, route propagation tests, and post-change probes (control-plane and data-plane)
Control-plane validation (BGP sessions)
Validation should be automated, per-device and per-neighbor:
- Session state: Established
- AFI/SAFI negotiated correctly (IPv4 unicast, IPv6, EVPN)
- Prefix counts within expected thresholds (min/max)
- BFD state (if enabled)
- Timer/policy sanity checks (optional but valuable)
Implementation patterns:
- Use device facts modules:
- EOS: arista.eos.eos_command: show bgp summary
- Junos: junos_command: show bgp summary
- Cisco: ios_command/nxos_command
- Parse output (TextFSM/Genie) or prefer JSON outputs where supported.
Route propagation tests (functional intent)
Sessions up is necessary but not sufficient. Add checks like:
- “Leaf A advertises loopback and VTEP; spine sees it”
- “VRF X imports RT Y and receives expected prefixes”
- “External peer receives only allowed aggregates”
Practical checks:
- Verify presence/absence of key routes in RIB/FIB
- Verify community tagging on a sample of routes
- Verify next-hop correctness (e.g., EVPN type-5, underlay reachability)
Post-change probes (data-plane)
Use active probes to validate the user-visible path:
- Ping critical VIPs across VRFs
- Synthetic TCP checks to load balancers or service endpoints
- MTU/PMTUD probes if you frequently touch underlay
A common pattern:
- Control-plane checks gate “proceed”
- Data-plane checks gate “finalize/save config”
CI/CD and operationalizing the pipeline: staging NetBox sync, change approvals, canary deployments, and runbooks for incidents
Staging NetBox sync model
Avoid making production NetBox the playground. Mature workflows use:
- NetBox-dev/staging: where changes are built and validated
- Promotion:
- export a change set (API diff, or scripted comparison)
- apply to production NetBox after approval
- Ensure both environments share the same custom fields/schema version.
Change approvals and artifacts
Your CI pipeline should produce artifacts that humans can review:
- rendered intended configs per device
- diffs versus previous intended configs
- a summarized “BGP intent diff”: sessions added/removed, AS changes, RT changes
Approval gates:
- Require review for:
- AS number changes
- neighbor removals
- RT import/export modifications
- max-prefix or policy relaxations
Canary deployments
Canary strategy depends on your topology:
- Deploy to one rack/pod (or one spine/leaf pair) first
- Verify:
- all sessions up
- route propagation unchanged (or as intended)
- no unexpected prefix spikes
- Roll forward progressively, with hold points
Runbooks for incidents (make failure boring)
Write runbooks aligned to pipeline steps:
If BGP sessions fail to establish
- Check:
- IP reachability between loopbacks or peering IPs
- ASN mismatch
- authentication mismatch
- address-family mismatch
- ACLs/control-plane policing
- Automated action:
- stop further rollout
- revert last device change (transaction/rollback)
- attach “show” outputs as artifacts
If routes are missing/unexpected
- Check:
- RT import/export correctness
- route-policy/prefix-list logic
- max-prefix triggered
- next-hop-self/update-source errors
- Automated action:
- run route-table diff checks across canary vs baseline devices
If blast radius grows
- Use pipeline kill switch:
- stop at canary
- block production NetBox promotion
- require incident commander approval to continue
Common pitfalls
- Overloading NetBox with raw config: storing CLI snippets as strings becomes unmaintainable and breaks validation.
- Non-deterministic templates: unordered lists cause config churn; always sort neighbors/prefixes/RTs.
- Mixing underlay and overlay intent: keep separate models/vars for underlay iBGP/eBGP vs EVPN overlay to prevent accidental coupling.
- No schema governance: adding custom fields ad hoc breaks pipelines; treat schema changes like code changes.
- Assuming “session up” means success: without route and data-plane checks, you’ll miss policy regressions.
- Unsafe vendor abstractions: “one template for all vendors” often hides critical differences (AFI/SAFI, policy semantics, commit behavior).
- Lack of rollback artifacts: if you can’t re-apply last-known-good quickly, automation increases outage duration instead of reducing it.
Key takeaways
- NetBox is most valuable when it stores intent and relationships, not vendor syntax.
- Idempotency comes from deterministic rendering + predictable application mechanics, not from “re-running the playbook and hoping.”
- Security and safety require transactional thinking: commits, checkpoints, staged rollouts, and explicit rollback plans.
- Verification must include both control-plane and data-plane to catch policy and propagation issues.
- Operationalizing the pipeline (staging NetBox, approvals, canaries, runbooks) is what turns automation into a reliable production system.