Data Center automation โ management, fault finding & deployment
Operate spine-leaf fabrics the same way across every vendor. This guide walks through day-2 management, telemetry-driven fault finding and safe deployment for Cisco NX-OS/ACI, Arista EOS, Juniper QFX, Nokia SR Linux and Cumulus Linux.
Setup, key commands & run examples
Describe the fabric once. A normalized inventory (roles, ASNs, links, loopbacks) lets every workflow target Cisco, Arista, Juniper, Nokia and Cumulus from the same source of truth.
# inventory/fabric.yml
fabric:
asn_base: 65000
underlay: ospf # or ebgp
overlay: evpn-vxlan
devices:
spine1: { role: spine, vendor: arista, mgmt: 10.0.0.11 }
spine2: { role: spine, vendor: cisco, mgmt: 10.0.0.12 }
leaf1: { role: leaf, vendor: juniper, mgmt: 10.0.0.21, vtep: 10.255.0.21 }
leaf2: { role: leaf, vendor: nokia, mgmt: 10.0.0.22, vtep: 10.255.0.22 }
leaf3: { role: leaf, vendor: cumulus, mgmt: 10.0.0.23, vtep: 10.255.0.23 }Use NAPALM/Netmiko drivers keyed off the inventory vendor so one script collects facts and pushes config to any platform in the fabric.
import napalm
DRIVER = {"cisco": "nxos_ssh", "arista": "eos", "juniper": "junos"}
def connect(dev):
drv = napalm.get_network_driver(DRIVER[dev["vendor"]])
return drv(dev["mgmt"], "admin", "secret")
for name, dev in fabric_leaves.items():
with connect(dev) as conn:
facts = conn.get_facts()
print(name, facts["model"], facts["os_version"])Pull structured state (BGP/EVPN peers, interfaces, VXLAN tunnels) across vendors and assert health. Flag down peers, flapping links and missing VTEPs automatically.
def check_evpn(conn, name):
bgp = conn.get_bgp_neighbors()
for vrf, data in bgp.items():
for ip, peer in data["peers"].items():
if not peer["is_up"]:
print(f"[FAULT] {name} EVPN peer {ip} DOWN in {vrf}")
def check_interfaces(conn, name):
for intf, st in conn.get_interfaces().items():
if intf.startswith("Ethernet") and not st["is_up"] and st["is_enabled"]:
print(f"[FAULT] {name} {intf} enabled but down")Render per-vendor config from the intent model, stage it as a candidate, diff it, and only commit when the diff is expected. Roll back on validation failure.
with connect(dev) as conn:
conn.load_merge_candidate(config=rendered_config) # stage
diff = conn.compare_config() # preview
print(diff)
if diff and approved(diff):
conn.commit_config() # apply
else:
conn.discard_config() # abort
# if post-checks fail: conn.rollback()Best practices
One intent model, many renderers
Keep a single vendor-neutral description of the fabric and generate per-platform config from it โ never hand-edit device CLI per box.
Diff before every commit
Stage config as a candidate and review compare_config() (or gate it in CI) so no fabric change is applied blind.
Assert health, don't eyeball it
Codify fault finding: EVPN peers up, all VTEPs reachable, no error counters climbing, MLAG/EVPN-ESI consistent on both leaves of a pair.
Deploy leaf-pairs, not whole rows
Roll changes one MLAG/ESI pair at a time and verify before moving on, so a bad change never takes out a redundant pair together.
Hands-on exercises
Guided practice tasks โ copy the starter snippet, run it against a lab device, and extend it to meet the goal.
Goal: Loop over every device in fabric.yml, connect with the right driver per vendor, and print role, model and OS version in a table.
import yaml, napalm
fabric = yaml.safe_load(open("inventory/fabric.yml"))
DRIVER = {"cisco": "nxos_ssh", "arista": "eos", "juniper": "junos"}
for name, dev in fabric["devices"].items():
# TODO: connect via DRIVER[dev["vendor"]] and print get_facts()
...Goal: Across all leaves, collect BGP/EVPN neighbors and print any peer that is not up, with device name and VRF.
for name, dev in leaves.items():
with connect(dev) as conn:
bgp = conn.get_bgp_neighbors()
# TODO: report peers where peer["is_up"] is False
...Goal: Add a new L2VNI to one leaf pair: stage config, print the diff, and only commit when the diff contains the expected VLAN id.
for dev in [leaf1, leaf2]:
with connect(dev) as conn:
conn.load_merge_candidate(config=render_l2vni(vlan=120, vni=10120))
diff = conn.compare_config()
# TODO: if "120" in diff: conn.commit_config() else conn.discard_config()
...Per-vendor lessons
Drill into management, fault finding and deployment for each vendor.
Cisco NX-OS / ACI
Arista EOS
Juniper QFX (Junos)
Nokia SR Linux
Cumulus Linux (NVUE)
Aruba CX (AOS-CX)
Curated videos & guides
Checklist โ pitfalls, gotchas & verification
Common pitfalls
- โขPushing config box-by-box from CLI instead of rendering from one intent model โ config drift is guaranteed.
- โขDeploying both leaves of an MLAG/ESI pair at once and dropping the redundant path.
- โขAssuming every getter/driver behaves identically โ NX-OS, EOS and JunOS differ in candidate/commit semantics.
Gotchas to watch
- โขcompare_config() returns an empty string (not None) when there is no change โ test truthiness.
- โขACI is intent/controller-driven (APIC REST), not CLI โ treat it differently from NX-OS leaf switches.
- โขVXLAN/EVPN faults often hide in the underlay โ check OSPF/eBGP and MTU before blaming the overlay.
Verify it works
- โขAfter deploy, re-read EVPN neighbors and confirm every peer returned to up.
- โขConfirm all expected VTEPs appear in the overlay and the L2/L3 VNIs are programmed on both pair members.
- โขDiff running-config against intent post-change to prove zero unexpected drift.
Treat the fabric as one system: a vendor-neutral inventory + intent model lets the same management, fault-finding and deployment workflows run everywhere. Pair these patterns with Nornir or Ansible for fleet-wide execution.