I’m going to try to keep this post as concise as possible and there is a lot to cover, so I’ll jump right in.

My goal is to push configurations to my lab devices using Nornir.  However, in order to understand Nornir you should have knowledge of Netmiko & NAPALM.  So I’m going to quickly cover those tools first, then go over Nornir.

Netmiko

The Netmiko library was developed by Kirk Byers and you can view it on GitHub (https://github.com/ktbyers/netmiko).  It was created as a python library that simplifies SSH connections to devices.  It’s based on Paramiko, which I am not going to cover here.

With Netmiko, you can create python scripts to connect to your devices and run your commands.  There is a lot to Netmiko, so I recommend you go through the documentation, but I’ll show you a quick script of how it can work:

from netmiko import ConnectHandler

#define device
router = {
    'device_type': 'cisco_ios',
    'host': '10.0.2.101'
    'username': 'ccie'
    'password': 'ccie'
}

#Establish SSH connection to the device
net_connect = ConnectHandler(**router)
#Run Show Command
output = net_connect.send_command('show ip int brief | ex unassigned')
print(output)

As you can see, I define a single device and run “show ip interface brief | exclude unassigned” and print the output.  Now imagine you need to run this command on 1000 devices and get the output, you can easily do this with Netmiko.  Again, this script is VERY BASIC.  Netmiko can do a lot more, but that’s not the point of this post, so I just wanted to give you the basics before moving on.

I would recommend that you head over to Kirk’s website and read this post:  https://pynet.twb-tech.com/blog/automation/netmiko.html

NAPALM

NAPALM stands for Network Automation and Programmability Abstraction Layer with Multivendor support  and was created by David Barroso.  Ignoring the ridiculous name, NAPALM is a very powerful python library that offers a lot of operations to manage network devices.

The great thing about NAPALM is that the syntax is the same regardless of the vendor you are using.  This is different than Netmiko, where you still need to put in the CLI commands for each device.

It’s two main functions are configuration management and retrieving information from network devices.  So let’s go over each.

Configuration Management in NAPALM

NAPALM’s approach to management is called “declarative configuration management.”  This means that you declare what you want the configuration to be, which is different than worrying about the current configuration and then figuring out how to get it from there to where you want it to be.  This can be done by using features from each product OS, for example config replace on Cisco IOS and candidate configurations with Juniper.

With NAPALM you can do a complete configuration replace or a merge.  NAPALM also allows you to rollback changes and do configuration diffs to help with config drift in your network.

Here’s an simple example of doing a configuration replace:

from napalm import get_network_driver

driver = get_network_driver('ios')
device = driver('10.0.2.101', 'ccie_user', 'ccie_pw')

device.open()
device.load_replace_candidate(filename='new_config.cfg')
print(device.compare_config())

#if you are happy with the changes:
device.commit_config()
#if you don't want to push changes:
device.discard_config()

device.close()

Getting Device Information with NAPALM

Another major thing you can do with NAPALM is get information from network devices.  Again, the nice thing about this is that the data is uniform regardless of device vendor or platform.

NAPALM supports a bunch of methods called “getters” that we can use.  The support matrix for all of the NAPALM getters is here.

Here is an example of getting the running configuration from a network device:

from napalm import get_network_driver

driver = get_network_driver('ios')
device = driver('10.0.2.101', 'ccie_user', 'ccie_pw')

device.open()
config = device.get_config()
print(config['running'])

Again, a very simple example.  However, this is very powerful when you considering doing this in a programmatic fashion and across thousands of devices in a multi-vendor environment.

I also want to point out that you can use NAPALM in other tools including Ansible, Salt and StackStorm.

Just like with Netmiko, I’d recommend reading the NAPALM documentation and going through their examples.  I’ve barely scratched the surface of this tool.

Nornir Overview

Nornir is a pure python automation framework.  It has similarities to Ansible.  However, where Ansible playbooks are written in YAML, Nornir uses python.  The advantage here is that with a programming language like python we can use all of the tools that come along with it.  This means that we can do much more debugging and troubleshooting that aren’t available with YAML.

The other advantage of using Nornir comes from complex tasks.  Ansible is powerful, but at a certain point you start programming with YAML, which can be a pain.  If your task starts using complex logic it makes more sense to use python.  That doesn’t mean you can’t do it in Ansible, I just think it makes more sense to do complex logic in Nornir instead.

You’ll start to see a lot of similarities between Ansible & Nornir, but that’s because the Nornir developers (David Barroso) took some of the best parts of Ansible and integrated it into Nornir.

The Nornir Architecture

  • Configuration file – Adjust parameters and define inventory.
  • Inventory files – hosts and groups files to define the inventory.
    • We can also use other inventory plugins like Netbox.
  • Variables – You can assign variables to hosts, groups and users.
  • Runbook – Ties everything together.  This is where you run tasks on your inventory.
    • Although similar to an Ansible playbook, it is different because there is an arbitrary flow, where in Ansible you have to run tasks within plays within a playbook.

Nornir – Netbox Inventory

Instead of defining static inventory files, I’m going to create a dynamic inventory with Netbox.  In order to do this we will need to edit our configuration file (config.yaml) to point to our Netbox instance.  Here’s what my config file looks like:

---
core:
  num_workers: 100

inventory:
  plugin: nornir.plugins.inventory.netbox.NBInventory
  options:
    nb_url: 'http://192.168.1.106:8000'
    nb_token: '0123456789abcdef0123456789abcdef01234567'
    ssl_verify: False

The first thing we see is num_workers set to 100.  This means we’ll run our nornir tasks against 100 devices simultaneously.

Then we define our netbox inventory using the Netbox plugin with Nornir.  (You can find a list of the other supported inventory types in the documentation here.)  We define the URL, our API token, then set SSL verification to false.

Once we have this, we can filter our inventory in our runbooks to run tasks against specific hosts or groups.  In order to do this you first have to initialize Nornir with the config.yaml file we created:

from nornir import InitNornir

nr = InitNornir(config_file="config.yaml")

This creates a nornir object that I named “nr” with the InitNornir function.  Now we can filter our inventory with that object.  I’ll create a couple objects from different filters:

routers = nr.filter(role="router")
switches = nr.filter(role="switch")
lab_devices = nr.filter(site="ccie_lab")
ios_devices = nr.filter(platform="ios")

These variables are now dictionary like objects in python that we can access, change, and run tasks against.

We can also access an individual host like this:

R1 = nr.inventory.hosts["R1"]

Nornir Tasks

Now that we can access our inventory, we can run tasks against them.  So what are tasks?  They are essentially python functions that are run on groups of hosts.  You can build your own tasks or use the built-in Nornir tasks.

You can see the built-in tasks here.  If you drill into the Networking tasks you’ll see why I did that quick overview of Netmiko and NAPALM at the beginning of this post.  Nornir is much easier to understand when you realize it’s a wrapper for other tools like those two.

You can execute a built in task like this:

from nornir.plugins.tasks import networking
from nornir.plugins.functions.text import print_result

routers = nr.filter(role="router")
result = routers.run(task=networking.napalm.get,
                    getters=['config'])

print_result(result)

This simple task uses the config NAPALM getter and gets the config for all of the hosts in the routers group.  It then prints out the configs with the nornir print_results plugin.

You can see how you would be confused if we didn’t go over NAPALM first, but again, that’s the key with Nornir.  It allows us to use NAPALM on a large group of hosts without defining them like we did when only using NAPALM.

We can also write our own tasks in Nornir.  It’s actually quite easy as they look similar to python functions.  Here’s an example:

def hello(task):
    print(f"Hello, my name is {task.host.name}"

routers.run(task=hello)

This is a simple task I created that will print out that statement for each host in the routes group.  Easy right?  We can also run tasks from within tasks if we need to.  Here’s an example of that:

def get_configs(task):
    r = task.run(task=networking.napalm.get,
                getters=['config'])
    print(f"Here is the config for {task.host.name}")
    print(r.result)

routers.run(task=get_configs)

This is useful for a few reasons.  First is that our runbooks will look a lot cleaner if we move all of our tasks to separate files and make function calls to the tasks.  Second, and probably more importantly, this will allow us to reuse our code in the future.

Nornir Runbooks

So now that we know how to initialize Nornir, get an inventory, and run tasks, what is the runbook?  Well, the runbook is where we want to bring it all together.  We’ve basically already seen what they look like, but here is an example of a runbook that uses our get_configs task we just created:

from nornir import InitNornir
from nornir.plugins.functions.text import print_result
from get_configs import get_configs    #this is the task we just made!

nr = InitNornir(config_file="config.yaml")
routers = nr.filter(role="router")

configs = routers.run(task=get_configs)

print_result(configs)

Pretty simple once you break it down.  I’m going to use Nornir to push workbook configurations to my lab devices, but this post is already long so I’ll just make that part 5 in this series.  Instead I’ll throw in one more topic:

Nornir vs Ansible

Which is better?  To be honest, both and neither.  I think both are powerful tools that you should have in your toolbox.  Ansible is really easy and can get you started from zero very quickly if you have no programming experience.  YAML playbooks are easy to read and Ansible has a ton of plugins to do various things beyond networking.

That being said, when it comes to more complex projects I’m going to pick Nornir every time.  Troubleshooting YAML is a pain compared to python and if you have some python skills you are going to love the flexibility that Nornir provides.

For an example of this, check out my next post where I reconfigure 1000’s of switch interfaces for standard VLANs with Nornir.

For my next post in this series I’ll use Nornir to push CCIE workbook files to my devices to speed up my lab setups.