๐ŸŒ Your AVD fabric, live in Kubernetes

netadopt and function-avd, powered by AVD

An extensible data model that defines Arista's Unified Cloud Network architecture as "code"
Powered by avd.arista.com and crossplane.io

๐Ÿ“ Where this sits


Your AVD repository inventory ยท group_vars ยท playbook
โ–ผ netadopt
Kubernetes objects Fabric ยท FabricInput
โ–ผ function-avd runs AVD
Every switch Device โ†’ eAPI

๐Ÿ” netadopt reads the repository

$ git clone --depth 1 --branch v6.4.0 https://github.com/aristanetworks/avd
$ cd avd/ansible_collections/arista/avd/examples
$ uvx "netadopt[avd]" avd report single-dc-l3ls --playbook build.yml

Inventory   named by ansible.cfg   8 hosts, 8 groups
Variables   8 files                group_vars 8, host_vars 0
Playbook    build.yml              1 play

#   Play                                     Hosts    Roles
0   Build Configurations and Documentation   FABRIC   eos_designs, eos_cli_config_gen

Carried
  Fabric         single-dc-l3ls, from play 0
  FabricInputs   7

AVD's own single-dc-l3ls example, as Ansible reads it.

๐Ÿงฉ The repository, as objects

$ uvx "netadopt[avd]" avd emit single-dc-l3ls --playbook build.yml > fabric.yaml
kind: Fabric
metadata: {name: single-dc-l3ls}
spec:
  inputs: [single-dc-l3ls-network-services, ...]   # seven, one per group_vars directory
---
kind: FabricInput
metadata: {name: single-dc-l3ls-network-services}
spec:
  appliesTo: {group: NETWORK_SERVICES}
  design:                                    # group_vars/NETWORK_SERVICES/, unchanged
    tenants:
    - name: TENANT1

๐Ÿงช A lab from the same repository

$ uvx "netadopt[avd]" avd lab single-dc-l3ls --playbook build.yml --namespace dc1 \
    --extra-vars-out lab-vars.yml --ceos-image ceos:4.36.1F \
    --ceos-cpu 800m --ceos-memory 1500Mi > values.yaml
$ uvx "netadopt[avd]" avd emit single-dc-l3ls --playbook build.yml \
    -e @lab-vars.yml > fabric.yaml
$ uvx netclab up --namespace dc1 --values values.yaml --crossplane v2.4.2 \
    --configuration xpkg.upbound.io/netclab/configuration-avd:v0.2.2 \
    --manifest runtime.yaml --manifest providerconfig.yaml --manifest fabric.yaml
  • One cEOS per switch in the inventory, cabled as AVD cables them
  • lab-vars.yml points each switch at its cEOS pod

โœ… Eight switches, eight Devices

$ kubectl -n dc1 get devices
NAME                        SYNCED   READY   COMPOSITION   AGE
single-dc-l3ls-dc1-leaf1a   True     True    device-avd    17s
single-dc-l3ls-dc1-leaf1b   True     True    device-avd    17s
single-dc-l3ls-dc1-leaf1c   True     True    device-avd    17s
single-dc-l3ls-dc1-leaf2a   True     True    device-avd    17s
single-dc-l3ls-dc1-leaf2b   True     True    device-avd    17s
single-dc-l3ls-dc1-leaf2c   True     True    device-avd    17s
single-dc-l3ls-dc1-spine1   True     True    device-avd    17s
single-dc-l3ls-dc1-spine2   True     True    device-avd    17s

One Device per switch, each holding the configuration AVD built for it.

๐Ÿ” Built, and running

$ kubectl -n dc1 get devices single-dc-l3ls-dc1-leaf1a \
    -o jsonpath='{.status.configHash}{"\n"}{.status.deployed}'
sha256:6471751dc97b16d9
{"configHash":"sha256:6471751dc97b16d9","digest":"e31f4f334660655755ef8b069042c8485c80c360"}
  • configHash โ€” the configuration AVD built
  • deployed โ€” the configuration the switch runs

The same hash: the switch runs what the model says.

โœ๏ธ The change

$ kubectl -n dc1 patch fabricinputs.avd.netclab.dev single-dc-l3ls-network-services \
    --type=json -p '[{"op": "add", "path": "/spec/design/tenants/0/vrfs/0/svis/-",
      "value": {"id": 13, "name": "VRF10_VLAN13", "enabled": true,
                "ip_address_virtual": "10.10.13.1/24"}}]'

One SVI, in a tenant VRF โ€” the same edit as in group_vars.

On dc1-leaf1a within a minute โ€” 34 s in this run.

๐Ÿ“ฅ What reached dc1-leaf1a

$ kubectl -n dc1 exec dc1-leaf1a -- Cli -p 15 -c "show running-config"   # the VLAN 13 lines
vlan 13
   name VRF10_VLAN13
interface Port-Channel8
   switchport trunk allowed vlan 11-13,21-22,3401-3402
interface Vlan13
   description VRF10_VLAN13
   vrf VRF10
   ip address virtual 10.10.13.1/24
interface Vxlan1
   vxlan vlan 13 vni 10013
router bgp 65101
   vlan 13
      rd 10.255.0.3:10013
      route-target both 10013:10013

๐Ÿง  Computed, not written


  • vni 10013 โ€” the VXLAN VNI
  • rd 10.255.0.3:10013 โ€” the route distinguisher, built from the leaf's own loopback
  • route-target both 10013:10013 โ€” the EVPN route-target
  • switchport trunk allowed vlan 11-13,โ€ฆ โ€” the trunk list, widened rather than replaced

None of it is in the patch. AVD derived all of it from the design.

๐ŸŽฏ And where it did not land


$ kubectl -n dc1 exec dc1-spine1 -- Cli -p 15 -c "show vlan 13"
% VLAN 13 not found in current VLAN database at line 1

dc1-spine1 is in the same fabric. Nothing was excluded by hand: a tenant SVI belongs on leaves, so the model put it on leaves.

โ†ฉ๏ธ A change by hand goes back

$ kubectl -n dc1 exec dc1-leaf1a -- Cli -p 15 -c $'configure\nno interface Vlan13\nend'

Within about a minute interface Vlan13 is back.

The switch runs what the model says. To change it, change the model.

โ™ป๏ธ Delete it, the config stays


$ kubectl -n dc1 delete -f fabric.yaml
$ kubectl -n dc1 exec dc1-leaf1a -- Cli -p 15 -c "show vlan 13"
VLAN  Name                             Status    Ports
----- -------------------------------- --------- -------------------------------
13    VRF10_VLAN13                     active    Cpu, Po3, Po8, Vx1

The Fabric, its Devices and their Requests are gone. The configuration stays: a push replaces a switch's whole configuration, so reverting it on delete would wipe the switch.

๐ŸŽฏ Try it

https://netclab.dev

Read a repository, and bring up its lab:
https://pypi.org/project/netadopt/
https://pypi.org/project/netclab/

Run it in Kubernetes:
https://github.com/netclab/function-avd
https://marketplace.upbound.io/configurations/netclab/configuration-avd