Spine-leaf data center fabric on ContainerLab. eBGP underlay, EVPN/VXLAN overlay, distributed anycast gateway, BFD-driven sub-second convergence, Prometheus + Grafana telemetry.
Two parallel config trees: configs/ (traditional numbered /30 P2P, RFC 7938) and configs-unnumbered/ (BGP unnumbered over IPv6 link-local with RFC 5549 v4-over-v6 next-hop).
AS 4200000001 AS 4200000002
spine1 spine2
/ \ / \
/ \ / \
/ \ / \
AS 4200000003 AS 4200000004
leaf1 leaf2
│ │
┌───┴───┐ ┌───┴───┐
VTEP SVI 192.168.10.254/24 VTEP
10.0.0.3 (anycast 44:38:39:ff:00:01) 10.0.0.4
│ │
h1 (netshoot) h2 (netshoot)
192.168.10.1/24 192.168.10.2/24
gw 192.168.10.254 gw 192.168.10.254
- Per-leaf 4-byte ASN from
4200000000+range — RFC 7938 alignment, eliminates AS-path prepend gymnastics for ECMP analytics - Spines are 4200000001 / 4200000002, leaves are 4200000003 / 4200000004
- eBGP everywhere, no iBGP/RR (the route-server pattern is interesting but adds state without value at this scale)
- Distributed anycast gateway: both leaves share IP
192.168.10.254and MAC44:38:39:ff:00:01(NVIDIA Cumulus reference convention) — either leaf answers ARP locally, MAC mobility works seamlessly via EVPN Type-2 +advertise-default-gw
- Underlay: eBGP per-leaf-ASN with BFD (150ms/150ms × 3 detect), max-prefix limits (1000 / 80% warn / restart 5), route-maps with bogon filter inbound + own-fabric scope outbound
- Overlay: eBGP EVPN — spines re-advertise EVPN routes between leaves via standard eBGP behaviour; leaves run
advertise-all-vni+advertise-default-gw - Data Plane: Kernel VXLAN (VNI 10) bridged to host-facing ports, distributed anycast gateway via shared SVI IP+MAC on both leaves
- Convergence target: BFD-detected link-down → forwarding restored in < 500ms (3 × 150ms BFD detect interval + BGP withdraw + FIB program)
- MAC mobility: host migration leaf1 → leaf2 detected and reprogrammed in < 1s via EVPN Type-2 sequence-number
- Telemetry: frr_exporter → Prometheus → Grafana
| Tree | Underlay addressing | Use case |
|---|---|---|
configs/ |
/30 IPv4 P2P + IPv4 loopbacks |
Traditional. Easier to debug with tcpdump, every link has a sensible IPv4 SRC. |
configs-unnumbered/ |
IPv6 link-local + RFC 5549 next-hop | Modern. Zero underlay IPAM, plug-and-play leaf insertion, matches Cumulus / SONiC defaults. See configs-unnumbered/README.md. |
| Tool | Role |
|---|---|
| ContainerLab | Topology orchestration |
| FRRouting | BGP + EVPN + BFD control plane |
| BGP | eBGP underlay, EVPN overlay routing |
| EVPN | Overlay control plane (Route Type 2/3 + anycast gateway) |
| VXLAN | Data plane encapsulation (VNI 10) |
| BFD | Sub-second link-failure detection |
| Prometheus | Metrics collection (frr_exporter) |
| Grafana | BGP state + EVPN VNI + BFD session dashboards |
| GitLab CI/CD | YAML lint on every push |
# Deploy the lab (4 switches + 2 hosts)
make deploy
# Validate BGP, BFD, EVPN VNI, and h1↔h2 reachability via anycast gw
make validate
# Start monitoring stack (Prometheus + Grafana at localhost:3000)
make monitor
# Destroy the lab
make destroytopology.clab.yml— ContainerLab topologyconfigs/— Numbered /30 P2P configs per nodeconfigs-unnumbered/— BGP unnumbered variant (IPv6 link-local + RFC 5549)docs/adr/— Architecture decision recordsmonitoring/— Prometheus config + Grafana dashboardsscripts/validate.py— BGP/BFD/EVPN state + dataplane validationdocker-compose.monitoring.yml— Prometheus + Grafana stack