Apr 17, 2023

Using Nautobot to create multi vendor EVPN VxLAN network

Challenges with EVPN network configurations:

Configuring an EVPN (Ethernet VPN) network involves a lot of complex parameters such as VNI (Virtual Network Identifier), route targets, ESI (Ethernet Segment Identifier), BGP ASN (Autonomous System Number), and BGP adjacency configurations. Attempting to manage this manually can result in human errors and inconsistencies.
This is an excellent use case for using automation to build the configuration instead of manual work.

 

Not all automations are equal.

When it comes to automation, there is an option to use static files (usually YAML files) to manage the intended configuration.
However, this approach doesn’t include any UI and doesn’t validate the data (such as duplicate VLANs or overlapping subnets).

 

So what can be done about this ?

To manage these parameters at scale, we need an SSOT (Single Source of Truth) application that validates the data, stores it in a database, exposes an API and UI, and allows easy querying of the data.

 

Once the information from the SSOT is available, we run it through a templating engine to generate the configuration.
There are two approaches to configuring network devices: pushing only the delta of changes or pushing the entire configuration.
The first option can be problematic as it requires understanding the required changes and running them in the correct order.

 

Our approach is to create the entire configuration and replace the device configuration with it. This way, we can ensure that the device configuration matches the intended configuration. If someone manually changes the configuration, we will override it.
Below we will present the an open source based tool set we use to perform all the tasks described above, with some specific features that deserve special attention.

 

The Tools:
Nautobot

Nautobot, developed by Network.toCode(), is a fork of Netbox. It serves as a Network Source of Truth, which means it manages all kinds of network information, including:

In an ideal world, the Single Source of Truth (SSOT) is used to manage everything and create the configuration of our network.
This is not always the case, specifically with brownfield deployments.
Some NautoBot features discussed below help to overcome some of these limitations.
Below are some screenshots from NautoBot UI.

 

 

 

Nautobot offers a range of customization options, including apps (plugins), custom fields, and custom relationships.

 

GraphQL
GraphQL is a data query language that was developed by Meta (Facebook) in 2012.
It allows developers to request exactly the data they need, retrieve multiple resources with a single request, and filter the query to retrieve only relevant information.Nautobot supports GraphQL, which means that its database can be queried using GraphQL queries.

This is a very useful feature for config generation.
For example, the following query filters data based on the site name, and retrieves all VRFs with their route targets and RD, as well as all VLANs with their prefixes and VRF assignments.

 

query ($site_name: [String]) {

vrfs(rd__isw: $site_name ) {
name
rd
import_targets {
name
exporting_vrfs {
name
}
}
export_targets {
name
}
}
vlans(site: $site_name) {
name
vid
prefixes {
prefix
vrf {
name
}
}
}
}

 

 

Config Context
In some cases, the database schema does not include fields for certain types of information, such as system general settings (such as users, NTP, and syslog), interface configurations (including access/trunk status and associated VLANs), and BGP sessions per VRF (although there is an app for this).
To manage this information, we can use config context, which stores the information in YAML format.

To track changes to these configurations over time, we can store them in a Git repository.
Nautobot allows us to assign config context to specific sections of the database, such as sites or devices.

 

 


_metadata:
name: interface_roles_fra
weight: 1000
description: Interface roles for fra site config context
is_active: true
sites:
– slug: fra
regions:
– slug: europe
roles:
– slug: leaf
– slug: s-spine

role:
MX204:
mode: trunk
vlans:
– ‘131’
– ‘141’
– ‘151’
ESXI:
mode: trunk
vlans:
– ‘111’
– ‘112’
DB:
mode: trunk
vlans:
– ‘131’
RS:
mode: trunk
vlans:
– ‘201’
– ‘202’
SERVER_PROVISIONING:
mode: access
vlans:
– ‘111’

 

 

Jinja Templates
Jinja is a templating engine for Python that allows us to use variables, loops, conditions, and filters to build complex configurations based on data provided by GraphQL.
Depending on the platform, we can use different templates based on the device vendor (Juniper Junos and Arista EoS in our case).
To build EVPN configuration, we are using four templates per device:

  1. Common: for common device configuration.
  2. Underlay: to build the IP network used as transport for the EVPN services.
  3. VRFs
  4. Access (to build the access ports, including ESI LAG)

In the following example (part of the access template for Junos), we are creating an access interface.

It also includes a complex filter to compute the EVPN ESI based on a Nautobot custom relationship between the LAG interface and the ESI neighbor. This filter takes a custom field called “device ID” from both devices, sorts them, and adds the LAG ID, all in hexadecimal format and separated by colons. This ensures that we create a unique ESI ID in the correct format.

 

interfaces {
{% for interface in interfaces[host.name] %}
{% if (interface.name | regex_search(‘^ae\d+$’) or interface.name | regex_search(‘^\w\w-0\/0\/\d+(:\d$|$)’) )
and interface.ip_addresses[0] not in interface.ip_addresses and interface.cf_interface_role != “UPLINK”
and interface.cf_interface_role != none %}
{{ interface.name }} {
{% if interface.status.name == “Decommissioning” %}
disable;
{% endif %}
{% if interface.description != “” %}
description “{{ interface.description }}”;
{% endif %}
{% if interface.mtu != none and interface.lag == none %}
mtu {{ interface.mtu }};
{% elif interface.lag == none %}
mtu 9216;
{% endif %}
{% if interface.name | regex_search(‘^ae\d+$’) and interface.rel_interface_to_device != none %}
esi {
{% set switches_id = [ interface.rel_interface_to_device.cf_dev_id,
host.data.pynautobot_dictionary.custom_fields.dev_id ] %}
00:02:02:03:04:05:06{% for switch_id in switches_id | sort %}{{ ‘:%02x’ | format(switch_id) }}{% endfor %}{{ ‘:%02x’ |
format(interface.name.split(‘ae’)[-1] | int ) }};
all-active;
}

 

 

Glue it all together

Nornir is used to build the configuration. It is a Python framework that includes many plugins, such as Nautobot, Jinja, and Napalm.
Unlike Ansible, Nornir is pure Python, which means that we don’t have to learn a new domain-specific language (DSL).
Additionally, Nornir is very fast because it uses multi-threading to work in parallel on multiple devices.
Based on these tools, I have built a Python script that performs the following tasks:

 

Get the list of devices from Nautobot.
Query the information from Nautobot via GraphQL.
Use Jinja templates to generate configuration files and store the output in temporary files.
Combine the temporary files into a single configuration file.
Store the files in a Git repository for review.

 

Push the configuration to the device
The last step is to push the configuration to the device.
We only do this after reviewing the configuration changes on GIT.

 

NAPALM
NAPALM is a Python library that implements a set of functions to interact with different network device operating systems using a unified API.
It has a Nornir plugin, which allows us to run multiple threads and makes it fast. It also offers other options such as:

 

Nitzan Tzelniker
Solutions Architect at Oasis Communication Technologies

 

LinkedIn

 

Under Attack?
Broken Network System?

Leave your details below and we’ll get back to you shortly